From 1e2e877302505e5feebf40dcbbf4d6051bdb508b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 8 Oct 2016 09:58:28 -0700 Subject: [PATCH 001/147] Version bump to 0.31.0.dev0 --- homeassistant/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index cba668d88cf..6203d99ec20 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,8 +1,8 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 30 -PATCH_VERSION = '0' +MINOR_VERSION = 31 +PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2) From bbbb4441ea9e7004c4a23a8aa4bcbf48892527ce Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Sat, 8 Oct 2016 20:26:01 +0200 Subject: [PATCH 002/147] Add discovery support for Netatmo weather Station (#3714) * Add discovery support for Netatmo Weather station Only The weather information are currently supported (No battery or wifi status supported) Signed-off-by: Hugo D. (jabesq) * Add unique_id for netatmo sensors to avoid duplicate Signed-off-by: Hugo D. (jabesq) * Update requirements_all.txt and PEP8 fixes Signed-off-by: Hugo D. (jabesq) --- homeassistant/components/netatmo.py | 2 +- homeassistant/components/sensor/netatmo.py | 28 ++++++++++++++++------ requirements_all.txt | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index 07f90d0879e..f385940c01d 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -16,7 +16,7 @@ import homeassistant.helpers.config_validation as cv REQUIREMENTS = [ 'https://github.com/jabesq/netatmo-api-python/archive/' - 'v0.5.0.zip#lnetatmo==0.5.0'] + 'v0.6.0.zip#lnetatmo==0.6.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index be8f2e7d76d..e59bb4a5553 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -55,7 +55,7 @@ MODULE_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_STATION): cv.string, - vol.Required(CONF_MODULES): MODULE_SCHEMA, + vol.Optional(CONF_MODULES): MODULE_SCHEMA, }) @@ -65,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): data = NetAtmoData(netatmo.NETATMO_AUTH, config.get(CONF_STATION, None)) dev = [] - try: + if CONF_MODULES in config: # Iterate each module for module_name, monitored_conditions in config[CONF_MODULES].items(): # Test if module exist """ @@ -75,8 +75,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Only create sensor for monitored """ for variable in monitored_conditions: dev.append(NetAtmoSensor(data, module_name, variable)) - except KeyError: - pass + else: + for module_name in data.get_module_names(): + for variable in data.station_data.monitoredConditions(module_name): + dev.append(NetAtmoSensor(data, module_name, variable)) add_devices(dev) @@ -94,6 +96,11 @@ class NetAtmoSensor(Entity): self.type = sensor_type self._state = None self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + module_id = self.netatmo_data.\ + station_data.moduleByName(module=module_name)['_id'] + self._unique_id = "Netatmo Sensor {0} - {1} ({2})".format(self._name, + module_id, + self.type) self.update() @property @@ -101,6 +108,11 @@ class NetAtmoSensor(Entity): """Return the name of the sensor.""" return self._name + @property + def unique_id(self): + """Return the unique ID for this sensor.""" + return self._unique_id + @property def icon(self): """Icon to use in the frontend, if any.""" @@ -222,6 +234,7 @@ class NetAtmoData(object): """Initialize the data object.""" self.auth = auth self.data = None + self.station_data = None self.station = station def get_module_names(self): @@ -233,9 +246,10 @@ class NetAtmoData(object): def update(self): """Call the Netatmo API to update the data.""" import lnetatmo - dev_list = lnetatmo.DeviceList(self.auth) + self.station_data = lnetatmo.DeviceList(self.auth) if self.station is not None: - self.data = dev_list.lastData(station=self.station, exclude=3600) + self.data = self.station_data.lastData(station=self.station, + exclude=3600) else: - self.data = dev_list.lastData(exclude=3600) + self.data = self.station_data.lastData(exclude=3600) diff --git a/requirements_all.txt b/requirements_all.txt index 8a65b76f5cc..03ecb280bd7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ https://github.com/danieljkemp/onkyo-eiscp/archive/python3.zip#onkyo-eiscp==0.9. https://github.com/gadgetreactor/pyHS100/archive/ef85f939fd5b07064a0f34dfa673fa7d6140bd95.zip#pyHS100==0.1.2 # homeassistant.components.netatmo -https://github.com/jabesq/netatmo-api-python/archive/v0.5.0.zip#lnetatmo==0.5.0 +https://github.com/jabesq/netatmo-api-python/archive/v0.6.0.zip#lnetatmo==0.6.0 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 From 1b26b5ad14ac428565cff5e7976b4ecfb63d83ec Mon Sep 17 00:00:00 2001 From: wokar Date: Sat, 8 Oct 2016 20:26:14 +0200 Subject: [PATCH 003/147] add include configuration to logbook (#3739) --- homeassistant/components/logbook.py | 45 +++++++++++++-- tests/components/test_logbook.py | 85 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 9100c098413..557c59a33ec 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -29,15 +29,22 @@ DEPENDENCIES = ['recorder', 'frontend'] _LOGGER = logging.getLogger(__name__) CONF_EXCLUDE = 'exclude' +CONF_INCLUDE = 'include' CONF_ENTITIES = 'entities' CONF_DOMAINS = 'domains' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ CONF_EXCLUDE: vol.Schema({ - vol.Optional(CONF_ENTITIES, default=[]): cv.ensure_list, - vol.Optional(CONF_DOMAINS, default=[]): cv.ensure_list + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, + [cv.string]) }), + CONF_INCLUDE: vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, + [cv.string]) + }) }), }, extra=vol.ALLOW_EXTRA) @@ -267,12 +274,19 @@ def humanify(events): def _exclude_events(events, config): """Get lists of excluded entities and platforms.""" + # pylint: disable=too-many-branches excluded_entities = [] excluded_domains = [] + included_entities = [] + included_domains = [] exclude = config[DOMAIN].get(CONF_EXCLUDE) if exclude: excluded_entities = exclude[CONF_ENTITIES] excluded_domains = exclude[CONF_DOMAINS] + include = config[DOMAIN].get(CONF_INCLUDE) + if include: + included_entities = include[CONF_ENTITIES] + included_domains = include[CONF_DOMAINS] filtered_events = [] for event in events: @@ -288,11 +302,32 @@ def _exclude_events(events, config): continue domain = to_state.domain - # check if logbook entry is excluded for this domain - if domain in excluded_domains: + entity_id = to_state.entity_id + # filter if only excluded is configured for this domain + if excluded_domains and domain in excluded_domains and \ + not included_domains: + if (included_entities and entity_id not in included_entities) \ + or not included_entities: + continue + # filter if only included is configured for this domain + elif not excluded_domains and included_domains and \ + domain not in included_domains: + if (included_entities and entity_id not in included_entities) \ + or not included_entities: + continue + # filter if included and excluded is configured for this domain + elif excluded_domains and included_domains and \ + (domain not in included_domains or + domain in excluded_domains): + if (included_entities and entity_id not in included_entities) \ + or not included_entities or domain in excluded_domains: + continue + # filter if only included is configured for this entity + elif not excluded_domains and not included_domains and \ + included_entities and entity_id not in included_entities: continue # check if logbook entry is excluded for this entity - if to_state.entity_id in excluded_entities: + if entity_id in excluded_entities: continue filtered_events.append(event) return filtered_events diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 9e8ab09a5a6..a98273b6521 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -186,6 +186,91 @@ class TestComponentLogbook(unittest.TestCase): self.assert_entry(entries[1], pointB, 'blu', domain='sensor', entity_id=entity_id2) + def test_include_events_entity(self): + """Test if events are filtered if entity is included in config.""" + entity_id = 'sensor.bla' + entity_id2 = 'sensor.blu' + pointA = dt_util.utcnow() + pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) + + eventA = self.create_state_changed_event(pointA, entity_id, 10) + eventB = self.create_state_changed_event(pointB, entity_id2, 20) + + config = logbook.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + logbook.DOMAIN: {logbook.CONF_INCLUDE: { + logbook.CONF_ENTITIES: [entity_id2, ]}}}) + events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), + eventA, eventB), config) + entries = list(logbook.humanify(events)) + + self.assertEqual(2, len(entries)) + self.assert_entry( + entries[0], name='Home Assistant', message='stopped', + domain=ha.DOMAIN) + self.assert_entry( + entries[1], pointB, 'blu', domain='sensor', entity_id=entity_id2) + + def test_include_events_domain(self): + """Test if events are filtered if domain is included in config.""" + entity_id = 'switch.bla' + entity_id2 = 'sensor.blu' + pointA = dt_util.utcnow() + pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) + + eventA = self.create_state_changed_event(pointA, entity_id, 10) + eventB = self.create_state_changed_event(pointB, entity_id2, 20) + + config = logbook.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + logbook.DOMAIN: {logbook.CONF_INCLUDE: { + logbook.CONF_DOMAINS: ['sensor', ]}}}) + events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_START), + eventA, eventB), config) + entries = list(logbook.humanify(events)) + + self.assertEqual(2, len(entries)) + self.assert_entry(entries[0], name='Home Assistant', message='started', + domain=ha.DOMAIN) + self.assert_entry(entries[1], pointB, 'blu', domain='sensor', + entity_id=entity_id2) + + def test_include_exclude_events(self): + """Test if events are filtered if include and exclude is configured.""" + entity_id = 'switch.bla' + entity_id2 = 'sensor.blu' + entity_id3 = 'sensor.bli' + pointA = dt_util.utcnow() + pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) + + eventA1 = self.create_state_changed_event(pointA, entity_id, 10) + eventA2 = self.create_state_changed_event(pointA, entity_id2, 10) + eventA3 = self.create_state_changed_event(pointA, entity_id3, 10) + eventB1 = self.create_state_changed_event(pointB, entity_id, 20) + eventB2 = self.create_state_changed_event(pointB, entity_id2, 20) + + config = logbook.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + logbook.DOMAIN: { + logbook.CONF_INCLUDE: { + logbook.CONF_DOMAINS: ['sensor', ], + logbook.CONF_ENTITIES: ['switch.bla', ]}, + logbook.CONF_EXCLUDE: { + logbook.CONF_DOMAINS: ['switch', ], + logbook.CONF_ENTITIES: ['sensor.bli', ]}}}) + events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_START), + eventA1, eventA2, eventA3, + eventB1, eventB2), config) + entries = list(logbook.humanify(events)) + + self.assertEqual(3, len(entries)) + self.assert_entry(entries[0], name='Home Assistant', message='started', + domain=ha.DOMAIN) + self.assert_entry(entries[1], pointA, 'blu', domain='sensor', + entity_id=entity_id2) + self.assert_entry(entries[2], pointB, 'blu', domain='sensor', + entity_id=entity_id2) + def test_exclude_auto_groups(self): """Test if events of automatically generated groups are filtered.""" entity_id = 'switch.bla' From 7b40a641eceef014daee5a4a631dade08566c913 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sat, 8 Oct 2016 20:27:35 +0200 Subject: [PATCH 004/147] Continue on invalid platforms and new setup_component unit tests (#3736) --- homeassistant/bootstrap.py | 8 +- tests/common.py | 46 ++- .../alarm_control_panel/test_mqtt.py | 52 +-- tests/components/automation/test_init.py | 63 ++-- .../components/binary_sensor/test_sleepiq.py | 8 +- .../components/binary_sensor/test_template.py | 79 ++--- tests/components/binary_sensor/test_trend.py | 59 ++-- tests/components/camera/test_local_file.py | 25 +- .../climate/test_generic_thermostat.py | 7 +- .../components/device_tracker/test_asuswrt.py | 32 +- tests/components/device_tracker/test_init.py | 9 +- tests/components/garage_door/test_mqtt.py | 101 +++--- tests/components/light/test_mqtt.py | 211 ++++++------ tests/components/light/test_mqtt_json.py | 70 ++-- tests/components/notify/test_group.py | 21 +- tests/components/sensor/test_darksky.py | 10 +- tests/components/sensor/test_sleepiq.py | 4 +- tests/components/sensor/test_template.py | 128 +++---- tests/components/switch/test_flux.py | 34 +- tests/components/switch/test_template.py | 312 +++++++++--------- .../thermostat/test_heat_control.py | 11 +- tests/scripts/test_check_config.py | 50 +-- tests/test_bootstrap.py | 174 ++++++---- 23 files changed, 842 insertions(+), 672 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 21c56f55bad..57adcd74fa4 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -38,6 +38,7 @@ def setup_component(hass: core.HomeAssistant, domain: str, config: Optional[Dict]=None) -> bool: """Setup a component and all its dependencies.""" if domain in hass.config.components: + _LOGGER.debug('Component %s already set up.', domain) return True _ensure_loader_prepared(hass) @@ -53,6 +54,7 @@ def setup_component(hass: core.HomeAssistant, domain: str, for component in components: if not _setup_component(hass, component, config): + _LOGGER.error('Component %s failed to setup', component) return False return True @@ -158,7 +160,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, p_validated = component.PLATFORM_SCHEMA(p_config) except vol.Invalid as ex: log_exception(ex, domain, config) - return None + continue # Not all platform components follow same pattern for platforms # So if p_name is None we are not going to validate platform @@ -171,7 +173,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, p_name) if platform is None: - return None + continue # Validate platform specific schema if hasattr(platform, 'PLATFORM_SCHEMA'): @@ -180,7 +182,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, except vol.Invalid as ex: log_exception(ex, '{}.{}'.format(domain, p_name), p_validated) - return None + continue platforms.append(p_validated) diff --git a/tests/common.py b/tests/common.py index 891bd3534a3..9dc98d2f4b4 100644 --- a/tests/common.py +++ b/tests/common.py @@ -7,9 +7,10 @@ from unittest.mock import patch from io import StringIO import logging import threading +from contextlib import contextmanager from homeassistant import core as ha, loader -from homeassistant.bootstrap import setup_component +from homeassistant.bootstrap import setup_component, prepare_setup_component from homeassistant.helpers.entity import ToggleEntity from homeassistant.util.unit_system import METRIC_SYSTEM import homeassistant.util.dt as date_util @@ -58,6 +59,8 @@ def get_test_home_assistant(num_threads=None): stop_event = threading.Event() def run_loop(): + """Run event loop.""" + # pylint: disable=protected-access loop._thread_ident = threading.get_ident() loop.run_forever() loop.close() @@ -70,6 +73,7 @@ def get_test_home_assistant(num_threads=None): @asyncio.coroutine def fake_stop(): + """Fake stop.""" yield None @patch.object(ha, 'async_create_timer') @@ -84,6 +88,7 @@ def get_test_home_assistant(num_threads=None): hass.block_till_done() def stop_hass(): + """Stop hass.""" orig_stop() stop_event.wait() @@ -112,6 +117,7 @@ def mock_service(hass, domain, service): """ calls = [] + # pylint: disable=unnecessary-lambda hass.services.register(domain, service, lambda call: calls.append(call)) return calls @@ -315,3 +321,41 @@ def patch_yaml_files(files_dict, endswith=True): raise FileNotFoundError('File not found: {}'.format(fname)) return patch.object(yaml, 'open', mock_open_f, create=True) + + +@contextmanager +def assert_setup_component(count, domain=None): + """Collect valid configuration from setup_component. + + - count: The amount of valid platforms that should be setup + - domain: The domain to count is optional. It can be automatically + determined most of the time + + Use as a context manager aroung bootstrap.setup_component + with assert_setup_component(0) as result_config: + setup_component(hass, start_config, domain) + # using result_config is optional + """ + config = {} + + def mock_psc(hass, config_input, domain): + """Mock the prepare_setup_component to capture config.""" + res = prepare_setup_component(hass, config_input, domain) + config[domain] = None if res is None else res.get(domain) + _LOGGER.debug('Configuration for %s, Validated: %s, Original %s', + domain, config[domain], config_input.get(domain)) + return res + + assert isinstance(config, dict) + with patch('homeassistant.bootstrap.prepare_setup_component', mock_psc): + yield config + + if domain is None: + assert len(config) == 1, ('assert_setup_component requires DOMAIN: {}' + .format(list(config.keys()))) + domain = list(config.keys())[0] + + res = config.get(domain) + res_len = 0 if res is None else len(res) + assert res_len == count, 'setup_component failed, expected {} got {}: {}' \ + .format(count, res_len, res) diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index e4e120cec19..871bc6afd76 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -1,14 +1,15 @@ """The tests the MQTT alarm control panel component.""" import unittest -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN) from homeassistant.components import alarm_control_panel from tests.common import ( - mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, + assert_setup_component) CODE = 'HELLO_CODE' @@ -16,7 +17,9 @@ CODE = 'HELLO_CODE' class TestAlarmControlPanelMQTT(unittest.TestCase): """Test the manual alarm module.""" - def setUp(self): # pylint: disable=invalid-name + # pylint: disable=invalid-name + + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) @@ -28,27 +31,30 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_fail_setup_without_state_topic(self): """Test for failing with no state topic.""" self.hass.config.components = ['mqtt'] - assert not _setup_component(self.hass, alarm_control_panel.DOMAIN, { - alarm_control_panel.DOMAIN: { - 'platform': 'mqtt', - 'command_topic': 'alarm/command' - } - }) + with assert_setup_component(0) as config: + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { + alarm_control_panel.DOMAIN: { + 'platform': 'mqtt', + 'command_topic': 'alarm/command' + } + }) + assert not config[alarm_control_panel.DOMAIN] def test_fail_setup_without_command_topic(self): """Test failing with no command topic.""" self.hass.config.components = ['mqtt'] - assert not _setup_component(self.hass, alarm_control_panel.DOMAIN, { - alarm_control_panel.DOMAIN: { - 'platform': 'mqtt', - 'state_topic': 'alarm/state' - } - }) + with assert_setup_component(0): + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { + alarm_control_panel.DOMAIN: { + 'platform': 'mqtt', + 'state_topic': 'alarm/state' + } + }) def test_update_state_via_state_topic(self): """Test updating with via state topic.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -72,7 +78,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_ignore_update_state_if_unknown_via_state_topic(self): """Test ignoring updates via state topic.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -93,7 +99,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_home_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -110,7 +116,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_home_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -128,7 +134,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_away_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -145,7 +151,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_away_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -163,7 +169,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_disarm_publishes_mqtt(self): """Test publishing of MQTT messages while disarmed.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -180,7 +186,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_disarm_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, alarm_control_panel.DOMAIN, { + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', 'name': 'test', diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 0a601452393..ec128f77756 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -8,19 +8,22 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestAutomation(unittest.TestCase): """Test the event automation.""" - def setUp(self): # pylint: disable=invalid-name + # pylint: disable=invalid-name + + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.components.append('group') self.calls = [] def record_call(service): + """Record call.""" self.calls.append(service) self.hass.services.register('test', 'automation', record_call) @@ -31,18 +34,19 @@ class TestAutomation(unittest.TestCase): def test_service_data_not_a_dict(self): """Test service data not dict.""" - assert not setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'data': 100, + with assert_setup_component(0): + assert not setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'data': 100, + } } - } - }) + }) def test_service_specify_data(self): """Test service data.""" @@ -70,7 +74,7 @@ class TestAutomation(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() assert len(self.calls) == 1 - assert 'event - test_event' == self.calls[0].data['some'] + assert self.calls[0].data['some'] == 'event - test_event' state = self.hass.states.get('automation.hello') assert state is not None assert state.attributes.get('last_triggered') == time @@ -444,21 +448,22 @@ class TestAutomation(unittest.TestCase): }) def test_reload_config_when_invalid_config(self, mock_load_yaml): """Test the reload config service handling invalid config.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'event': '{{ trigger.event.event_type }}' + with assert_setup_component(1): + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'event': '{{ trigger.event.event_type }}' + } } } - } - }) + }) assert self.hass.states.get('automation.hello') is not None self.hass.bus.fire('test_event') @@ -470,11 +475,11 @@ class TestAutomation(unittest.TestCase): automation.reload(self.hass) self.hass.block_till_done() - assert self.hass.states.get('automation.hello') is not None + assert self.hass.states.get('automation.hello') is None self.hass.bus.fire('test_event') self.hass.block_till_done() - assert len(self.calls) == 2 + assert len(self.calls) == 1 def test_reload_config_handles_load_fails(self): """Test the reload config service.""" diff --git a/tests/components/binary_sensor/test_sleepiq.py b/tests/components/binary_sensor/test_sleepiq.py index d220578ca9d..94a51832d56 100644 --- a/tests/components/binary_sensor/test_sleepiq.py +++ b/tests/components/binary_sensor/test_sleepiq.py @@ -4,10 +4,11 @@ from unittest.mock import MagicMock import requests_mock -from homeassistant import core as ha +from homeassistant.bootstrap import setup_component from homeassistant.components.binary_sensor import sleepiq from tests.components.test_sleepiq import mock_responses +from tests.common import get_test_home_assistant class TestSleepIQBinarySensorSetup(unittest.TestCase): @@ -22,7 +23,7 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.username = 'foo' self.password = 'bar' self.config = { @@ -35,6 +36,9 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase): """Test for successfully setting up the SleepIQ platform.""" mock_responses(mock) + setup_component(self.hass, 'sleepiq', { + 'sleepiq': self.config}) + sleepiq.setup_platform(self.hass, self.config, self.add_devices, diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index c9e4bf6138b..3a46cb8c3b0 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -9,12 +9,15 @@ from homeassistant.components.binary_sensor import template from homeassistant.exceptions import TemplateError from homeassistant.helpers import template as template_hlpr -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestBinarySensorTemplate(unittest.TestCase): """Test for Binary sensor template platform.""" + hass = None + # pylint: disable=invalid-name + def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() @@ -47,53 +50,53 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_setup_no_sensors(self): """"Test setup with no sensors.""" - result = bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template' - } - }) - self.assertFalse(result) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template' + } + }) def test_setup_invalid_device(self): """"Test the setup with invalid devices.""" - result = bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'foo bar': {}, - }, - } - }) - self.assertFalse(result) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'foo bar': {}, + }, + } + }) def test_setup_invalid_sensor_class(self): """"Test setup with invalid sensor class.""" - result = bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test': { - 'value_template': '{{ foo }}', - 'sensor_class': 'foobarnotreal', + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test': { + 'value_template': '{{ foo }}', + 'sensor_class': 'foobarnotreal', + }, }, - }, - } - }) - self.assertFalse(result) + } + }) def test_setup_invalid_missing_template(self): """"Test setup with invalid and missing template.""" - result = bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test': { - 'sensor_class': 'motion', - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test': { + 'sensor_class': 'motion', + }, + } } - } - }) - self.assertFalse(result) + }) def test_attributes(self): """"Test the attributes.""" @@ -107,7 +110,9 @@ class TestBinarySensorTemplate(unittest.TestCase): vs.update() self.assertFalse(vs.is_on) + # pylint: disable=protected-access vs._template = template_hlpr.Template("{{ 2 > 1 }}", self.hass) + vs.update() self.assertTrue(vs.is_on) diff --git a/tests/components/binary_sensor/test_trend.py b/tests/components/binary_sensor/test_trend.py index 475e445175b..8b522db4a58 100644 --- a/tests/components/binary_sensor/test_trend.py +++ b/tests/components/binary_sensor/test_trend.py @@ -1,12 +1,14 @@ """The test for the Trend sensor platform.""" import homeassistant.bootstrap as bootstrap -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestTrendBinarySensor: """Test the Trend sensor.""" + hass = None + def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() @@ -189,41 +191,46 @@ class TestTrendBinarySensor: state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' - def test_invalid_name_does_not_create(self): + def test_invalid_name_does_not_create(self): \ + # pylint: disable=invalid-name """Test invalid name.""" - assert not bootstrap.setup_component(self.hass, 'binary_sensor', { - 'binary_sensor': { - 'platform': 'template', - 'sensors': { - 'test INVALID sensor': { - 'entity_id': - "sensor.test_state" + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { + 'platform': 'template', + 'sensors': { + 'test INVALID sensor': { + 'entity_id': + "sensor.test_state" + } } } - } - }) + }) assert self.hass.states.all() == [] - def test_invalid_sensor_does_not_create(self): + def test_invalid_sensor_does_not_create(self): \ + # pylint: disable=invalid-name """Test invalid sensor.""" - assert not bootstrap.setup_component(self.hass, 'binary_sensor', { - 'binary_sensor': { - 'platform': 'template', - 'sensors': { - 'test_trend_sensor': { - 'not_entity_id': - "sensor.test_state" + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { + 'platform': 'template', + 'sensors': { + 'test_trend_sensor': { + 'not_entity_id': + "sensor.test_state" + } } } - } - }) + }) assert self.hass.states.all() == [] def test_no_sensors_does_not_create(self): """Test no sensors.""" - assert not bootstrap.setup_component(self.hass, 'binary_sensor', { - 'binary_sensor': { - 'platform': 'trend' - } - }) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { + 'platform': 'trend' + } + }) assert self.hass.states.all() == [] diff --git a/tests/components/camera/test_local_file.py b/tests/components/camera/test_local_file.py index 546152b0d8a..c29aa4b6da0 100644 --- a/tests/components/camera/test_local_file.py +++ b/tests/components/camera/test_local_file.py @@ -8,7 +8,7 @@ from werkzeug.test import EnvironBuilder from homeassistant.bootstrap import setup_component from homeassistant.components.http import request_class -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestLocalCamera(unittest.TestCase): @@ -28,21 +28,21 @@ class TestLocalCamera(unittest.TestCase): """Test that it loads image from disk.""" self.hass.wsgi = mock.MagicMock() - with NamedTemporaryFile() as fp: - fp.write('hello'.encode('utf-8')) - fp.flush() + with NamedTemporaryFile() as fptr: + fptr.write('hello'.encode('utf-8')) + fptr.flush() assert setup_component(self.hass, 'camera', { 'camera': { 'name': 'config_test', 'platform': 'local_file', - 'file_path': fp.name, + 'file_path': fptr.name, }}) image_view = self.hass.wsgi.mock_calls[0][1][0] builder = EnvironBuilder(method='GET') - Request = request_class() + Request = request_class() # pylint: disable=invalid-name request = Request(builder.get_environ()) request.authenticated = True resp = image_view.get(request, 'camera.config_test') @@ -54,16 +54,17 @@ class TestLocalCamera(unittest.TestCase): """Test local file will not setup when file is not readable.""" self.hass.wsgi = mock.MagicMock() - with NamedTemporaryFile() as fp: - fp.write('hello'.encode('utf-8')) - fp.flush() + with NamedTemporaryFile() as fptr: + fptr.write('hello'.encode('utf-8')) + fptr.flush() - with mock.patch('os.access', return_value=False): - assert not setup_component(self.hass, 'camera', { + with mock.patch('os.access', return_value=False), \ + assert_setup_component(0): + assert setup_component(self.hass, 'camera', { 'camera': { 'name': 'config_test', 'platform': 'local_file', - 'file_path': fp.name, + 'file_path': fptr.name, }}) assert [] == self.hass.states.all() diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 313bfd6035f..070ca31f8df 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -16,7 +16,7 @@ from homeassistant.const import ( from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.components import climate -from tests.common import get_test_home_assistant +from tests.common import assert_setup_component, get_test_home_assistant ENTITY = 'climate.test' @@ -44,8 +44,9 @@ class TestSetupClimateGenericThermostat(unittest.TestCase): 'name': 'test', 'target_sensor': ENT_SENSOR } - self.assertFalse(setup_component(self.hass, 'climate', { - 'climate': config})) + with assert_setup_component(0): + setup_component(self.hass, 'climate', { + 'climate': config}) def test_valid_conf(self): """Test set up genreic_thermostat with valid config values.""" diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index a4d5ee64b32..9ea3ae4dec6 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -5,14 +5,15 @@ from unittest import mock import voluptuous as vol -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.components import device_tracker from homeassistant.components.device_tracker.asuswrt import ( CONF_PROTOCOL, CONF_MODE, CONF_PUB_KEY, PLATFORM_SCHEMA, DOMAIN) from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) -from tests.common import get_test_home_assistant, get_test_config_dir +from tests.common import ( + get_test_home_assistant, get_test_config_dir, assert_setup_component) FAKEFILE = None @@ -32,6 +33,7 @@ def teardown_module(): class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): """Tests for the ASUSWRT device tracker platform.""" + hass = None def setup_method(self, _): @@ -49,12 +51,13 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): def test_password_or_pub_key_required(self): \ # pylint: disable=invalid-name """Test creating an AsusWRT scanner without a pass or pubkey.""" - self.assertFalse(_setup_component( - self.hass, DOMAIN, {DOMAIN: { - CONF_PLATFORM: 'asuswrt', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user' - }})) + with assert_setup_component(0): + assert setup_component( + self.hass, DOMAIN, {DOMAIN: { + CONF_PLATFORM: 'asuswrt', + CONF_HOST: 'fake_host', + CONF_USERNAME: 'fake_user' + }}) @mock.patch( 'homeassistant.components.device_tracker.asuswrt.AsusWrtDeviceScanner', @@ -70,7 +73,10 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): CONF_PASSWORD: 'fake_pass' } } - self.assertIsNotNone(_setup_component(self.hass, DOMAIN, conf_dict)) + + with assert_setup_component(1): + assert setup_component(self.hass, DOMAIN, conf_dict) + conf_dict[DOMAIN][CONF_MODE] = 'router' conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' asuswrt_mock.assert_called_once_with(conf_dict[DOMAIN]) @@ -90,7 +96,8 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): } } - self.assertIsNotNone(_setup_component(self.hass, DOMAIN, conf_dict)) + with assert_setup_component(1): + assert setup_component(self.hass, DOMAIN, conf_dict) conf_dict[DOMAIN][CONF_MODE] = 'router' conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' @@ -163,6 +170,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): update_mock.start() self.addCleanup(update_mock.stop) - self.assertFalse(_setup_component(self.hass, DOMAIN, - {DOMAIN: conf_dict})) + with assert_setup_component(0): + assert setup_component(self.hass, DOMAIN, + {DOMAIN: conf_dict}) ssh.login.assert_not_called() diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index f195712285a..80967543f0d 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -17,7 +17,7 @@ from homeassistant.exceptions import HomeAssistantError from tests.common import ( get_test_home_assistant, fire_time_changed, fire_service_discovered, - patch_yaml_files) + patch_yaml_files, assert_setup_component) TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}} @@ -336,6 +336,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): @patch('homeassistant.components.device_tracker.log_exception') def test_config_failure(self, mock_ex): """Test that the device tracker see failures.""" - assert not setup_component(self.hass, device_tracker.DOMAIN, - {device_tracker.DOMAIN: { - device_tracker.CONF_CONSIDER_HOME: -1}}) + with assert_setup_component(0, device_tracker.DOMAIN): + setup_component(self.hass, device_tracker.DOMAIN, + {device_tracker.DOMAIN: { + device_tracker.CONF_CONSIDER_HOME: -1}}) diff --git a/tests/components/garage_door/test_mqtt.py b/tests/components/garage_door/test_mqtt.py index f2f5e61d1fb..c46befe6f1b 100644 --- a/tests/components/garage_door/test_mqtt.py +++ b/tests/components/garage_door/test_mqtt.py @@ -1,18 +1,21 @@ """The tests for the MQTT Garge door platform.""" import unittest -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import STATE_OPEN, STATE_CLOSED, ATTR_ASSUMED_STATE import homeassistant.components.garage_door as garage_door from tests.common import ( - mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, + assert_setup_component) class TestGarageDoorMQTT(unittest.TestCase): """Test the MQTT Garage door.""" - def setUp(self): # pylint: disable=invalid-name + # pylint: disable=invalid-name + + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) @@ -24,29 +27,31 @@ class TestGarageDoorMQTT(unittest.TestCase): def test_fail_setup_if_no_command_topic(self): """Test if command fails with command topic.""" self.hass.config.components = ['mqtt'] - assert not _setup_component(self.hass, garage_door.DOMAIN, { - garage_door.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': '/home/garage_door/door' - } - }) + with assert_setup_component(0): + assert setup_component(self.hass, garage_door.DOMAIN, { + garage_door.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': '/home/garage_door/door' + } + }) self.assertIsNone(self.hass.states.get('garage_door.test')) def test_controlling_state_via_topic(self): """Test the controlling state via topic.""" - assert _setup_component(self.hass, garage_door.DOMAIN, { - garage_door.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'state_open': 1, - 'state_closed': 0, - 'service_open': 1, - 'service_close': 0 - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, garage_door.DOMAIN, { + garage_door.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'state_open': 1, + 'state_closed': 0, + 'service_open': 1, + 'service_close': 0 + } + }) state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_CLOSED, state.state) @@ -66,18 +71,19 @@ class TestGarageDoorMQTT(unittest.TestCase): def test_sending_mqtt_commands_and_optimistic(self): """Test the sending MQTT commands in optimistic mode.""" - assert _setup_component(self.hass, garage_door.DOMAIN, { - garage_door.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'command-topic', - 'state_open': 'beer state open', - 'state_closed': 'beer state closed', - 'service_open': 'beer open', - 'service_close': 'beer close', - 'qos': '2' - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, garage_door.DOMAIN, { + garage_door.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'command-topic', + 'state_open': 'beer state open', + 'state_closed': 'beer state closed', + 'service_open': 'beer open', + 'service_close': 'beer close', + 'qos': '2' + } + }) state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_CLOSED, state.state) @@ -101,19 +107,20 @@ class TestGarageDoorMQTT(unittest.TestCase): def test_controlling_state_via_topic_and_json_message(self): """Test the controlling state via topic and JSON message.""" - assert _setup_component(self.hass, garage_door.DOMAIN, { - garage_door.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'state_open': 'beer open', - 'state_closed': 'beer closed', - 'service_open': 'beer service open', - 'service_close': 'beer service close', - 'value_template': '{{ value_json.val }}' - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, garage_door.DOMAIN, { + garage_door.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'state_open': 'beer open', + 'state_closed': 'beer closed', + 'service_open': 'beer service open', + 'service_close': 'beer service close', + 'value_template': '{{ value_json.val }}' + } + }) state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_CLOSED, state.state) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 375a4a45905..667f2342603 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -75,17 +75,20 @@ light: """ import unittest -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE import homeassistant.components.light as light from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) + assert_setup_component, get_test_home_assistant, mock_mqtt_component, + fire_mqtt_message) class TestLightMQTT(unittest.TestCase): """Test the MQTT light.""" - def setUp(self): # pylint: disable=invalid-name + # pylint: disable=invalid-name + + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) @@ -97,25 +100,28 @@ class TestLightMQTT(unittest.TestCase): def test_fail_setup_if_no_command_topic(self): """Test if command fails with command topic.""" self.hass.config.components = ['mqtt'] - assert not _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - } - }) + with assert_setup_component(0): + assert setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + } + }) self.assertIsNone(self.hass.states.get('light.test')) - def test_no_color_or_brightness_or_color_temp_if_no_topics(self): + def test_no_color_or_brightness_or_color_temp_if_no_topics(self): \ + # pylint: disable=invalid-name """Test if there is no color and brightness if no topic.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_light_rgb/status', - 'command_topic': 'test_light_rgb/set', - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + } + }) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -132,26 +138,28 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get('brightness')) self.assertIsNone(state.attributes.get('color_temp')) - def test_controlling_state_via_topic(self): + def test_controlling_state_via_topic(self): \ + # pylint: disable=invalid-name """Test the controlling of the state via topic.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_state_topic': 'test_light_rgb/color_temp/status', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'qos': '0', + 'payload_on': 1, + 'payload_off': 0 + }} + self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_light_rgb/status', - 'command_topic': 'test_light_rgb/set', - 'brightness_state_topic': 'test_light_rgb/brightness/status', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'rgb_state_topic': 'test_light_rgb/rgb/status', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'color_temp_state_topic': 'test_light_rgb/color_temp/status', - 'color_temp_command_topic': 'test_light_rgb/color_temp/set', - 'qos': '0', - 'payload_on': 1, - 'payload_off': 0 - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -206,20 +214,21 @@ class TestLightMQTT(unittest.TestCase): def test_controlling_scale(self): """Test the controlling scale.""" self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_scale/status', - 'command_topic': 'test_scale/set', - 'brightness_state_topic': 'test_scale/brightness/status', - 'brightness_command_topic': 'test_scale/brightness/set', - 'brightness_scale': '99', - 'qos': 0, - 'payload_on': 'on', - 'payload_off': 'off' - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_scale/status', + 'command_topic': 'test_scale/set', + 'brightness_state_topic': 'test_scale/brightness/status', + 'brightness_command_topic': 'test_scale/brightness/set', + 'brightness_scale': '99', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + }) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -250,24 +259,26 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(255, light_state.attributes['brightness']) - def test_controlling_state_via_topic_with_templates(self): + def test_controlling_state_via_topic_with_templates(self): \ + # pylint: disable=invalid-name """Test the setting og the state with a template.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'color_temp_state_topic': 'test_light_rgb/color_temp/status', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'state_value_template': '{{ value_json.hello }}', + 'brightness_value_template': '{{ value_json.hello }}', + 'color_temp_value_template': '{{ value_json.hello }}', + 'rgb_value_template': '{{ value_json.hello | join(",") }}', + }} + self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_light_rgb/status', - 'command_topic': 'test_light_rgb/set', - 'brightness_state_topic': 'test_light_rgb/brightness/status', - 'color_temp_state_topic': 'test_light_rgb/color_temp/status', - 'rgb_state_topic': 'test_light_rgb/rgb/status', - 'state_value_template': '{{ value_json.hello }}', - 'brightness_value_template': '{{ value_json.hello }}', - 'color_temp_value_template': '{{ value_json.hello }}', - 'rgb_value_template': '{{ value_json.hello | join(",") }}', - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -290,22 +301,24 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual([1, 2, 3], state.attributes.get('rgb_color')) self.assertEqual(300, state.attributes.get('color_temp')) - def test_sending_mqtt_commands_and_optimistic(self): + def test_sending_mqtt_commands_and_optimistic(self): \ + # pylint: disable=invalid-name """Test the sending of command in optimistic mode.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'qos': 2, + 'payload_on': 'on', + 'payload_off': 'off' + }} + self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'color_temp_command_topic': 'test_light_rgb/color_temp/set', - 'qos': 2, - 'payload_on': 'on', - 'payload_off': 'off' - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -352,16 +365,17 @@ class TestLightMQTT(unittest.TestCase): def test_show_brightness_if_only_command_topic(self): """Test the brightness if only a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status', + }} + self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status', - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -376,16 +390,17 @@ class TestLightMQTT(unittest.TestCase): def test_show_color_temp_only_if_command_topic(self): """Test the color temp only if a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'color_temp_command_topic': 'test_light_rgb/brightness/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status' + }} + self.hass.config.components = ['mqtt'] - assert _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'color_temp_command_topic': 'test_light_rgb/brightness/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status' - } - }) + with assert_setup_component(1): + assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 6ea01dccccd..6fc4a00097d 100755 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -30,11 +30,12 @@ light: import json import unittest -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import _setup_component, setup_component from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE import homeassistant.components.light as light from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) + get_test_home_assistant, mock_mqtt_component, fire_mqtt_message, + assert_setup_component) class TestLightMQTTJSON(unittest.TestCase): @@ -49,18 +50,21 @@ class TestLightMQTTJSON(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - def test_fail_setup_if_no_command_topic(self): + def test_fail_setup_if_no_command_topic(self): \ + # pylint: disable=invalid-name """Test if setup fails with no command topic.""" self.hass.config.components = ['mqtt'] - assert not _setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - } - }) + with assert_setup_component(0): + assert setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + } + }) self.assertIsNone(self.hass.states.get('light.test')) - def test_no_color_or_brightness_if_no_config(self): + def test_no_color_or_brightness_if_no_config(self): \ + # pylint: disable=invalid-name """Test if there is no color and brightness if they aren't defined.""" self.hass.config.components = ['mqtt'] assert _setup_component(self.hass, light.DOMAIN, { @@ -85,7 +89,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertIsNone(state.attributes.get('rgb_color')) self.assertIsNone(state.attributes.get('brightness')) - def test_controlling_state_via_topic(self): + def test_controlling_state_via_topic(self): \ + # pylint: disable=invalid-name """Test the controlling of the state via topic.""" self.hass.config.components = ['mqtt'] assert _setup_component(self.hass, light.DOMAIN, { @@ -108,10 +113,9 @@ class TestLightMQTTJSON(unittest.TestCase): # Turn on the light, full white fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"color":{"r":255,"g":255,"b":255},' + - '"brightness":255}' - ) + '{"state":"ON",' + '"color":{"r":255,"g":255,"b":255},' + '"brightness":255}') self.hass.block_till_done() state = self.hass.states.get('light.test') @@ -127,9 +131,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"brightness":100}' - ) + '{"state":"ON",' + '"brightness":100}') self.hass.block_till_done() light_state = self.hass.states.get('light.test') @@ -138,16 +141,16 @@ class TestLightMQTTJSON(unittest.TestCase): light_state.attributes['brightness']) fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"color":{"r":125,"g":125,"b":125}}' - ) + '{"state":"ON",' + '"color":{"r":125,"g":125,"b":125}}') self.hass.block_till_done() light_state = self.hass.states.get('light.test') self.assertEqual([125, 125, 125], light_state.attributes.get('rgb_color')) - def test_sending_mqtt_commands_and_optimistic(self): + def test_sending_mqtt_commands_and_optimistic(self): \ + # pylint: disable=invalid-name """Test the sending of command in optimistic mode.""" self.hass.config.components = ['mqtt'] assert _setup_component(self.hass, light.DOMAIN, { @@ -202,7 +205,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual((75, 75, 75), state.attributes['rgb_color']) self.assertEqual(50, state.attributes['brightness']) - def test_flash_short_and_long(self): + def test_flash_short_and_long(self): \ + # pylint: disable=invalid-name """Test for flash length being sent when included.""" self.hass.config.components = ['mqtt'] assert _setup_component(self.hass, light.DOMAIN, { @@ -285,7 +289,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(10, message_json["transition"]) self.assertEqual("OFF", message_json["state"]) - def test_invalid_color_and_brightness_values(self): + def test_invalid_color_and_brightness_values(self): \ + # pylint: disable=invalid-name """Test that invalid color/brightness values are ignored.""" self.hass.config.components = ['mqtt'] assert _setup_component(self.hass, light.DOMAIN, { @@ -308,10 +313,9 @@ class TestLightMQTTJSON(unittest.TestCase): # Turn on the light fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"color":{"r":255,"g":255,"b":255},' + - '"brightness": 255}' - ) + '{"state":"ON",' + '"color":{"r":255,"g":255,"b":255},' + '"brightness": 255}') self.hass.block_till_done() state = self.hass.states.get('light.test') @@ -321,9 +325,8 @@ class TestLightMQTTJSON(unittest.TestCase): # Bad color values fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"color":{"r":"bad","g":"val","b":"test"}}' - ) + '{"state":"ON",' + '"color":{"r":"bad","g":"val","b":"test"}}') self.hass.block_till_done() # Color should not have changed @@ -333,9 +336,8 @@ class TestLightMQTTJSON(unittest.TestCase): # Bad brightness values fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' + - '"brightness": "badValue"}' - ) + '{"state":"ON",' + '"brightness": "badValue"}') self.hass.block_till_done() # Brightness should not have changed diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py index e1c6d9f5bd4..4a318a2d3b8 100644 --- a/tests/components/notify/test_group.py +++ b/tests/components/notify/test_group.py @@ -5,7 +5,7 @@ from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import group -from tests.common import get_test_home_assistant +from tests.common import assert_setup_component, get_test_home_assistant class TestNotifyGroup(unittest.TestCase): @@ -15,15 +15,16 @@ class TestNotifyGroup(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.events = [] - self.assertTrue(setup_component(self.hass, notify.DOMAIN, { - 'notify': [{ - 'name': 'demo1', - 'platform': 'demo' - }, { - 'name': 'demo2', - 'platform': 'demo' - }] - })) + with assert_setup_component(2): + setup_component(self.hass, notify.DOMAIN, { + 'notify': [{ + 'name': 'demo1', + 'platform': 'demo' + }, { + 'name': 'demo2', + 'platform': 'demo' + }] + }) self.service = group.get_service(self.hass, {'services': [ {'service': 'demo1'}, diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index f44f7385e5b..09ced049b58 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -57,12 +57,12 @@ class TestDarkSkySetup(unittest.TestCase): @requests_mock.Mocker() @patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast) - def test_setup(self, m, mock_get_forecast): + def test_setup(self, mock_req, mock_get_forecast): """Test for successfully setting up the forecast.io platform.""" - uri = ('https://api.darksky.net\/forecast\/(\w+)\/' - '(-?\d+\.?\d*),(-?\d+\.?\d*)') - m.get(re.compile(uri), - text=load_fixture('darksky.json')) + uri = (r'https://api.(darksky.net|forecast.io)\/forecast\/(\w+)\/' + r'(-?\d+\.?\d*),(-?\d+\.?\d*)') + mock_req.get(re.compile(uri), + text=load_fixture('darksky.json')) darksky.setup_platform(self.hass, self.config, MagicMock()) self.assertTrue(mock_get_forecast.called) self.assertEqual(mock_get_forecast.call_count, 1) diff --git a/tests/components/sensor/test_sleepiq.py b/tests/components/sensor/test_sleepiq.py index 2ec250f50c2..b0c937c4025 100644 --- a/tests/components/sensor/test_sleepiq.py +++ b/tests/components/sensor/test_sleepiq.py @@ -4,10 +4,10 @@ from unittest.mock import MagicMock import requests_mock -from homeassistant import core as ha from homeassistant.components.sensor import sleepiq from tests.components.test_sleepiq import mock_responses +from tests.common import get_test_home_assistant class TestSleepIQSensorSetup(unittest.TestCase): @@ -22,7 +22,7 @@ class TestSleepIQSensorSetup(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.username = 'foo' self.password = 'bar' self.config = { diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index b80f8032bf1..58f0fb84ac7 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -1,12 +1,15 @@ """The test for the Template sensor platform.""" -import homeassistant.bootstrap as bootstrap +from homeassistant.bootstrap import setup_component -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestTemplateSensor: """Test the Template sensor.""" + hass = None + # pylint: disable=invalid-name + def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() @@ -17,17 +20,18 @@ class TestTemplateSensor: def test_template(self): """Test template.""" - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test_template_sensor': { - 'value_template': - "It {{ states.sensor.test_state.state }}." + with assert_setup_component(1): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test_template_sensor': { + 'value_template': + "It {{ states.sensor.test_state.state }}." + } } } - } - }) + }) state = self.hass.states.get('sensor.test_template_sensor') assert state.state == 'It .' @@ -39,83 +43,89 @@ class TestTemplateSensor: def test_template_syntax_error(self): """Test templating syntax error.""" - assert not bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test_template_sensor': { - 'value_template': - "{% if rubbish %}" + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test_template_sensor': { + 'value_template': + "{% if rubbish %}" + } } } - } - }) + }) assert self.hass.states.all() == [] def test_template_attribute_missing(self): """Test missing attribute template.""" - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test_template_sensor': { - 'value_template': - "It {{ states.sensor.test_state.attributes.missing }}." + with assert_setup_component(1): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test_template_sensor': { + 'value_template': 'It {{ states.sensor.test_state' + '.attributes.missing }}.' + } } } - } - }) + }) state = self.hass.states.get('sensor.test_template_sensor') assert state.state == 'unknown' def test_invalid_name_does_not_create(self): """Test invalid name.""" - assert not bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test INVALID sensor': { - 'value_template': - "{{ states.sensor.test_state.state }}" + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test INVALID sensor': { + 'value_template': + "{{ states.sensor.test_state.state }}" + } } } - } - }) + }) assert self.hass.states.all() == [] def test_invalid_sensor_does_not_create(self): """Test invalid sensor.""" - assert not bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test_template_sensor': 'invalid' + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test_template_sensor': 'invalid' + } } - } - }) + }) assert self.hass.states.all() == [] def test_no_sensors_does_not_create(self): """Test no sensors.""" - assert not bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template' - } - }) + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template' + } + }) assert self.hass.states.all() == [] def test_missing_template_does_not_create(self): """Test missing template.""" - assert not bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { - 'platform': 'template', - 'sensors': { - 'test_template_sensor': { - 'not_value_template': - "{{ states.sensor.test_state.state }}" + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'test_template_sensor': { + 'not_value_template': + "{{ states.sensor.test_state.state }}" + } } } - } - }) + }) assert self.hass.states.all() == [] diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index 01b5d797222..1ee865ef3ac 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -1,6 +1,6 @@ """The tests for the Flux switch platform.""" -import unittest from datetime import timedelta +import unittest from unittest.mock import patch from homeassistant.bootstrap import setup_component @@ -8,8 +8,10 @@ from homeassistant.components import switch, light from homeassistant.const import CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON import homeassistant.loader as loader import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant -from tests.common import fire_time_changed, mock_service + +from tests.common import ( + assert_setup_component, get_test_home_assistant, fire_time_changed, + mock_service) class TestSwitchFlux(unittest.TestCase): @@ -50,21 +52,23 @@ class TestSwitchFlux(unittest.TestCase): def test_valid_config_no_name(self): """Test configuration.""" - assert setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'flux', - 'lights': ['light.desk', 'light.lamp'] - } - }) + with assert_setup_component(1, 'switch'): + assert setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'flux', + 'lights': ['light.desk', 'light.lamp'] + } + }) def test_invalid_config_no_lights(self): """Test configuration.""" - assert not setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'flux', - 'name': 'flux' - } - }) + with assert_setup_component(0, 'switch'): + assert setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'flux', + 'name': 'flux' + } + }) def test_flux_when_switch_is_off(self): """Test the flux switch when it is off.""" diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py index e13b0f7392b..af91c9a565b 100644 --- a/tests/components/switch/test_template.py +++ b/tests/components/switch/test_template.py @@ -6,12 +6,16 @@ from homeassistant.const import ( STATE_ON, STATE_OFF) -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, assert_setup_component class TestTemplateSwitch: """Test the Template switch.""" + hass = None + calls = None + # pylint: disable=invalid-name + def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() @@ -29,25 +33,26 @@ class TestTemplateSwitch: def test_template_state_text(self): """"Test the state text of a template.""" - assert bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{{ states.switch.test_state.state }}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(1): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{{ states.switch.test_state.state }}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) state = self.hass.states.set('switch.test_state', STATE_ON) self.hass.block_till_done() @@ -63,188 +68,197 @@ class TestTemplateSwitch: def test_template_state_boolean_on(self): """Test the setting of the state with boolean on.""" - assert bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{{ 1 == 1 }}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(1): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{{ 1 == 1 }}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_ON def test_template_state_boolean_off(self): """Test the setting of the state with off.""" - assert bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{{ 1 == 2 }}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(1): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{{ 1 == 2 }}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_OFF def test_template_syntax_error(self): """Test templating syntax error.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{% if rubbish %}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{% if rubbish %}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) assert self.hass.states.all() == [] def test_invalid_name_does_not_create(self): """Test invalid name.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test INVALID switch': { - 'value_template': - "{{ rubbish }", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test INVALID switch': { + 'value_template': + "{{ rubbish }", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) assert self.hass.states.all() == [] def test_invalid_switch_does_not_create(self): """Test invalid switch.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': 'Invalid' + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': 'Invalid' + } } - } - }) + }) assert self.hass.states.all() == [] def test_no_switches_does_not_create(self): """Test if there are no switches no creation.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template' - } - }) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template' + } + }) assert self.hass.states.all() == [] def test_missing_template_does_not_create(self): """Test missing template.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'not_value_template': - "{{ states.switch.test_state.state }}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'not_value_template': + "{{ states.switch.test_state.state }}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) assert self.hass.states.all() == [] def test_missing_on_does_not_create(self): """Test missing on.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{{ states.switch.test_state.state }}", - 'not_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'turn_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{{ states.switch.test_state.state }}", + 'not_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'turn_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) assert self.hass.states.all() == [] def test_missing_off_does_not_create(self): """Test missing off.""" - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'template', - 'switches': { - 'test_template_switch': { - 'value_template': - "{{ states.switch.test_state.state }}", - 'turn_on': { - 'service': 'switch.turn_on', - 'entity_id': 'switch.test_state' - }, - 'not_off': { - 'service': 'switch.turn_off', - 'entity_id': 'switch.test_state' - }, + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'template', + 'switches': { + 'test_template_switch': { + 'value_template': + "{{ states.switch.test_state.state }}", + 'turn_on': { + 'service': 'switch.turn_on', + 'entity_id': 'switch.test_state' + }, + 'not_off': { + 'service': 'switch.turn_off', + 'entity_id': 'switch.test_state' + }, + } } } - } - }) + }) assert self.hass.states.all() == [] def test_on_action(self): diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index 475e9c70046..300bfd6cc4a 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -4,7 +4,7 @@ import unittest from unittest import mock -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, SERVICE_TURN_OFF, @@ -16,7 +16,7 @@ from homeassistant.const import ( from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.components import thermostat -from tests.common import get_test_home_assistant +from tests.common import assert_setup_component, get_test_home_assistant ENTITY = 'thermostat.test' @@ -44,12 +44,13 @@ class TestSetupThermostatHeatControl(unittest.TestCase): 'name': 'test', 'target_sensor': ENT_SENSOR } - self.assertFalse(_setup_component(self.hass, 'thermostat', { - 'thermostat': config})) + with assert_setup_component(0): + setup_component(self.hass, 'thermostat', { + 'thermostat': config}) def test_valid_conf(self): """Test set up heat_control with valid config values.""" - self.assertTrue(_setup_component(self.hass, 'thermostat', + self.assertTrue(setup_component(self.hass, 'thermostat', {'thermostat': { 'platform': 'heat_control', 'name': 'test', diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index e31c46b40a8..056bb074afa 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -89,14 +89,18 @@ class TestCheckConfig(unittest.TestCase): with patch_yaml_files(files): res = check_config.check(get_test_config_dir('platform.yaml')) change_yaml_files(res) - self.assertDictEqual({ - 'components': {'mqtt': {'keepalive': 60, 'port': 1883, - 'protocol': '3.1.1'}}, - 'except': {'light.mqtt_json': {'platform': 'mqtt_json'}}, - 'secret_cache': {}, - 'secrets': {}, - 'yaml_files': ['.../platform.yaml'] - }, res) + self.assertDictEqual( + {'mqtt': {'keepalive': 60, 'port': 1883, 'protocol': '3.1.1'}, + 'light': []}, + res['components'] + ) + self.assertDictEqual( + {'light.mqtt_json': {'platform': 'mqtt_json'}}, + res['except'] + ) + self.assertDictEqual({}, res['secret_cache']) + self.assertDictEqual({}, res['secrets']) + self.assertListEqual(['.../platform.yaml'], res['yaml_files']) def test_component_platform_not_found(self, mock_get_loop): """Test errors if component or platform not found.""" @@ -107,25 +111,23 @@ class TestCheckConfig(unittest.TestCase): with patch_yaml_files(files): res = check_config.check(get_test_config_dir('badcomponent.yaml')) change_yaml_files(res) - self.assertDictEqual({ - 'components': {}, - 'except': {check_config.ERROR_STR: - ['Component not found: beer']}, - 'secret_cache': {}, - 'secrets': {}, - 'yaml_files': ['.../badcomponent.yaml'] - }, res) + self.assertDictEqual({}, res['components']) + self.assertDictEqual({check_config.ERROR_STR: + ['Component not found: beer']}, + res['except']) + self.assertDictEqual({}, res['secret_cache']) + self.assertDictEqual({}, res['secrets']) + self.assertListEqual(['.../badcomponent.yaml'], res['yaml_files']) res = check_config.check(get_test_config_dir('badplatform.yaml')) change_yaml_files(res) - self.assertDictEqual({ - 'components': {}, - 'except': {check_config.ERROR_STR: - ['Platform not found: light.beer']}, - 'secret_cache': {}, - 'secrets': {}, - 'yaml_files': ['.../badplatform.yaml'] - }, res) + self.assertDictEqual({'light': []}, res['components']) + self.assertDictEqual({check_config.ERROR_STR: + ['Platform not found: light.beer']}, + res['except']) + self.assertDictEqual({}, res['secret_cache']) + self.assertDictEqual({}, res['secrets']) + self.assertListEqual(['.../badplatform.yaml'], res['yaml_files']) def test_secrets(self, mock_get_loop): """Test secrets config checking method.""" diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index b74a1de0d35..0f675d7f012 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -3,6 +3,7 @@ import tempfile from unittest import mock import threading +import logging import voluptuous as vol @@ -10,14 +11,22 @@ from homeassistant import bootstrap, loader import homeassistant.util.dt as dt_util from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from tests.common import get_test_home_assistant, MockModule, MockPlatform +from tests.common import \ + get_test_home_assistant, MockModule, MockPlatform, assert_setup_component ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE +_LOGGER = logging.getLogger(__name__) + class TestBootstrap: """Test the bootstrap utils.""" + hass = None + backup_cache = None + + # pylint: disable=invalid-name, no-self-use + def setup_method(self, method): """Setup the test.""" self.backup_cache = loader._COMPONENT_CACHE @@ -110,59 +119,72 @@ class TestBootstrap: loader.set_component( 'platform_conf.whatever', MockPlatform('whatever')) - assert not bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': { - 'hello': 'world', - 'invalid': 'extra', - } - }) - - assert not bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': { - 'platform': 'whatever', - 'hello': 'world', - }, - - 'platform_conf 2': { - 'invalid': True - } - }) - - assert not bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': { - 'platform': 'not_existing', - 'hello': 'world', - } - }) - - assert bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': { - 'platform': 'whatever', - 'hello': 'world', - } - }) + with assert_setup_component(0): + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': { + 'hello': 'world', + 'invalid': 'extra', + } + }) self.hass.config.components.remove('platform_conf') - assert bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': [{ - 'platform': 'whatever', - 'hello': 'world', - }] - }) + with assert_setup_component(1): + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': { + 'platform': 'whatever', + 'hello': 'world', + }, + 'platform_conf 2': { + 'invalid': True + } + }) self.hass.config.components.remove('platform_conf') - # Any falsey paltform config will be ignored (None, {}, etc) - assert bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': None - }) + with assert_setup_component(0): + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': { + 'platform': 'not_existing', + 'hello': 'world', + } + }) self.hass.config.components.remove('platform_conf') - assert bootstrap._setup_component(self.hass, 'platform_conf', { - 'platform_conf': {} - }) + with assert_setup_component(1): + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': { + 'platform': 'whatever', + 'hello': 'world', + } + }) + + self.hass.config.components.remove('platform_conf') + + with assert_setup_component(1): + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': [{ + 'platform': 'whatever', + 'hello': 'world', + }] + }) + + self.hass.config.components.remove('platform_conf') + + # Any falsey platform config will be ignored (None, {}, etc) + with assert_setup_component(0) as config: + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': None + }) + assert 'platform_conf' in self.hass.config.components + assert not config['platform_conf'] # empty + + assert bootstrap._setup_component(self.hass, 'platform_conf', { + 'platform_conf': {} + }) + assert 'platform_conf' in self.hass.config.components + assert not config['platform_conf'] # empty def test_component_not_found(self): """setup_component should not crash if component doesn't exist.""" @@ -170,7 +192,6 @@ class TestBootstrap: def test_component_not_double_initialized(self): """Test we do not setup a component twice.""" - mock_setup = mock.MagicMock(return_value=True) loader.set_component('comp', MockModule('comp', setup=mock_setup)) @@ -195,15 +216,13 @@ class TestBootstrap: assert 'comp' not in self.hass.config.components def test_component_not_setup_twice_if_loaded_during_other_setup(self): - """ - Test component that gets setup while waiting for lock is not setup - twice. - """ + """Test component setup while waiting for lock is not setup twice.""" loader.set_component('comp', MockModule('comp')) result = [] def setup_component(): + """Setup the component.""" result.append(bootstrap.setup_component(self.hass, 'comp')) with bootstrap._SETUP_LOCK: @@ -258,7 +277,6 @@ class TestBootstrap: def test_component_setup_with_validation_and_dependency(self): """Test all config is passed to dependencies.""" - def config_check_setup(hass, config): """Setup method that tests config is passed in.""" if config.get('comp_a', {}).get('valid', False): @@ -283,36 +301,48 @@ class TestBootstrap: def test_platform_specific_config_validation(self): """Test platform that specifies config.""" - platform_schema = PLATFORM_SCHEMA.extend({ 'valid': True, }, extra=vol.PREVENT_EXTRA) + mock_setup = mock.MagicMock() + loader.set_component( 'switch.platform_a', - MockPlatform(platform_schema=platform_schema)) + MockPlatform(platform_schema=platform_schema, + setup_platform=mock_setup)) - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'platform_a', - 'invalid': True - } - }) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'platform_a', + 'invalid': True + } + }) + assert mock_setup.call_count == 0 - assert not bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'platform_a', - 'valid': True, - 'invalid_extra': True, - } - }) + self.hass.config.components.remove('switch') - assert bootstrap.setup_component(self.hass, 'switch', { - 'switch': { - 'platform': 'platform_a', - 'valid': True - } - }) + with assert_setup_component(0): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'platform_a', + 'valid': True, + 'invalid_extra': True, + } + }) + assert mock_setup.call_count == 0 + + self.hass.config.components.remove('switch') + + with assert_setup_component(1): + assert bootstrap.setup_component(self.hass, 'switch', { + 'switch': { + 'platform': 'platform_a', + 'valid': True + } + }) + assert mock_setup.call_count == 1 def test_disable_component_if_invalid_return(self): """Test disabling component if invalid return.""" From 8f8bba4ad7f74f6764e898ad9438d558aaf855a0 Mon Sep 17 00:00:00 2001 From: Willems Davy Date: Sat, 8 Oct 2016 20:38:58 +0200 Subject: [PATCH 005/147] Haveibeenpwned sensor platform (#3618) * Initial version of "haveibeenpwned" sensor component * 2 flake8 fixes * remove debugging error message * Increase scan_interval as well as throttle to make sure that during initial startup of hass the request happens with 5 seconds delays and after startup with 15 minutes delays. Scan_interval is increased also to not call update as often * update .coveragerc * remove (ssl) verify=False * - use dict to keep the request values with email as key - use track_point_in_time system to make sure data updates initially at 5 seconds between each call until all sensor's email have a result in the dict. * fix a pylint error that happend on the py35 tests --- .coveragerc | 1 + .../components/sensor/haveibeenpwned.py | 181 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 homeassistant/components/sensor/haveibeenpwned.py diff --git a/.coveragerc b/.coveragerc index 80bc6905c21..045e8f77588 100644 --- a/.coveragerc +++ b/.coveragerc @@ -235,6 +235,7 @@ omit = homeassistant/components/sensor/google_travel_time.py homeassistant/components/sensor/gpsd.py homeassistant/components/sensor/gtfs.py + homeassistant/components/sensor/haveibeenpwned.py homeassistant/components/sensor/hp_ilo.py homeassistant/components/sensor/imap.py homeassistant/components/sensor/imap_email_content.py diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/sensor/haveibeenpwned.py new file mode 100644 index 00000000000..f317ef14565 --- /dev/null +++ b/homeassistant/components/sensor/haveibeenpwned.py @@ -0,0 +1,181 @@ +""" +Support for haveibeenpwned (email breaches) sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.haveibeenpwned/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol +import requests + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (STATE_UNKNOWN, CONF_EMAIL) +from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import track_point_in_time + +_LOGGER = logging.getLogger(__name__) + +DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S" +USER_AGENT = "Home Assistant HaveIBeenPwned Sensor Component" + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) +MIN_TIME_BETWEEN_FORCED_UPDATES = timedelta(seconds=5) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string]), +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the RESTful sensor.""" + emails = config.get(CONF_EMAIL) + data = HaveIBeenPwnedData(emails) + + devices = [] + for email in emails: + devices.append(HaveIBeenPwnedSensor(data, hass, email)) + + add_devices(devices) + + # To make sure we get initial data for the sensors + # ignoring the normal throttle of 15 minutes but using + # an update throttle of 5 seconds + for sensor in devices: + sensor.update_nothrottle() + + +class HaveIBeenPwnedSensor(Entity): + """Implementation of HaveIBeenPwnedSensor.""" + + def __init__(self, data, hass, email): + """Initialize the HaveIBeenPwnedSensor sensor.""" + self._state = STATE_UNKNOWN + self._data = data + self._hass = hass + self._email = email + self._unit_of_measurement = "Breaches" + + @property + def name(self): + """Return the name of the sensor.""" + return "Breaches {}".format(self._email) + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def state_attributes(self): + """Return the atrributes of the sensor.""" + val = {} + if self._email not in self._data.data: + return val + + for idx, value in enumerate(self._data.data[self._email]): + tmpname = "breach {}".format(idx+1) + tmpvalue = "{} {}".format( + value["Title"], + dt_util.as_local(dt_util.parse_datetime( + value["AddedDate"])).strftime(DATE_STR_FORMAT)) + val[tmpname] = tmpvalue + + return val + + def update_nothrottle(self, dummy=None): + """Update sensor without throttle.""" + self._data.update_no_throttle() + + # Schedule a forced update 5 seconds in the future if the + # update above returned no data for this sensors email. + # this is mainly to make sure that we don't + # get http error "too many requests" and to have initial + # data after hass startup once we have the data it will + # update as normal using update + if self._email not in self._data.data: + track_point_in_time(self._hass, + self.update_nothrottle, + dt_util.now() + + MIN_TIME_BETWEEN_FORCED_UPDATES) + return + + if self._email in self._data.data: + self._state = len(self._data.data[self._email]) + self.update_ha_state() + + def update(self): + """Update data and see if it contains data for our email.""" + self._data.update() + + if self._email in self._data.data: + self._state = len(self._data.data[self._email]) + + +class HaveIBeenPwnedData(object): + """Class for handling the data retrieval.""" + + def __init__(self, emails): + """Initialize the data object.""" + self._email_count = len(emails) + self._current_index = 0 + self.data = {} + self._email = emails[0] + self._emails = emails + + def set_next_email(self): + """Set the next email to be looked up.""" + self._current_index = (self._current_index + 1) % self._email_count + self._email = self._emails[self._current_index] + + def update_no_throttle(self): + """Get the data for a specific email.""" + self.update(no_throttle=True) + + @Throttle(MIN_TIME_BETWEEN_UPDATES, MIN_TIME_BETWEEN_FORCED_UPDATES) + def update(self, **kwargs): + """Get the latest data for current email from REST service.""" + try: + url = "https://haveibeenpwned.com/api/v2/breachedaccount/{}". \ + format(self._email) + + _LOGGER.info("Checking for breaches for email %s", self._email) + + req = requests.get(url, headers={"User-agent": USER_AGENT}, + allow_redirects=True, timeout=5) + + except requests.exceptions.RequestException: + _LOGGER.error("failed fetching HaveIBeenPwned Data for '%s'", + self._email) + return + + if req.status_code == 200: + self.data[self._email] = sorted(req.json(), + key=lambda k: k["AddedDate"], + reverse=True) + + # only goto next email if we had data so that + # the forced updates try this current email again + self.set_next_email() + + elif req.status_code == 404: + self.data[self._email] = [] + + # only goto next email if we had data so that + # the forced updates try this current email again + self.set_next_email() + + else: + _LOGGER.error("failed fetching HaveIBeenPwned Data for '%s'" + "(HTTP Status_code = %d)", self._email, + req.status_code) From 7882b19dc5b53f6815d1e267c84168aa4f798200 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Sat, 8 Oct 2016 15:57:40 -0400 Subject: [PATCH 006/147] Fixed issue #3760, handle X10 unit numbers greater than 9. (#3763) --- homeassistant/components/light/x10.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/x10.py b/homeassistant/components/light/x10.py index 499bd7513a6..30ede3eac18 100644 --- a/homeassistant/components/light/x10.py +++ b/homeassistant/components/light/x10.py @@ -41,7 +41,7 @@ def get_status(): def get_unit_status(code): """Get on/off status for given unit.""" - unit = int(code[1]) + unit = int(code[1:]) return get_status()[16 - int(unit)] == '1' From 6419d273eafa9ec9b1ef009936459a6e20939e99 Mon Sep 17 00:00:00 2001 From: Roi Dayan Date: Sat, 8 Oct 2016 23:03:32 +0300 Subject: [PATCH 007/147] Fix command line cover template (#3754) The command line cover value template is optional so we need to check it's not none before assigning hass to it. Fixes #3649 Signed-off-by: Roi Dayan --- homeassistant/components/cover/command_line.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index d70d43463cf..27e1c810cf4 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -38,7 +38,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for device_name, device_config in devices.items(): value_template = device_config.get(CONF_VALUE_TEMPLATE) - value_template.hass = hass + if value_template is not None: + value_template.hass = hass covers.append( CommandCover( From 4d9bac6f9cab428cc52e0337704f19b4effccb3c Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sat, 8 Oct 2016 23:40:50 +0200 Subject: [PATCH 008/147] Coerce device IDs from known_devices to be slugs (#3764) * Slugify & consider_home test fix [due to load valid PR] * undo schema change * Fix slugify error --- .../components/device_tracker/__init__.py | 8 +++++--- homeassistant/helpers/config_validation.py | 14 ++++++++++++-- tests/components/device_tracker/test_init.py | 19 +++++++++++++++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index a05b57cff33..3fa8361a44d 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -46,6 +46,7 @@ CONF_TRACK_NEW = 'track_new_devices' DEFAULT_TRACK_NEW = True CONF_CONSIDER_HOME = 'consider_home' +DEFAULT_CONSIDER_HOME = 180 CONF_SCAN_INTERVAL = 'interval_seconds' DEFAULT_SCAN_INTERVAL = 12 @@ -119,8 +120,9 @@ def setup(hass: HomeAssistantType, config: ConfigType): return False else: conf = conf[0] if len(conf) > 0 else {} - consider_home = conf[CONF_CONSIDER_HOME] - track_new = conf[CONF_TRACK_NEW] + consider_home = conf.get(CONF_CONSIDER_HOME, + timedelta(seconds=DEFAULT_CONSIDER_HOME)) + track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) devices = load_config(yaml_path, hass, consider_home) @@ -415,7 +417,7 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): for dev_id, device in devices.items(): try: device = dev_schema(device) - device['dev_id'] = cv.slug(dev_id) + device['dev_id'] = cv.slugify(dev_id) except vol.Invalid as exp: log_exception(exp, dev_id, devices) else: diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 7236debbe88..1d368a37d3c 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -17,7 +17,7 @@ from homeassistant.const import ( from homeassistant.core import valid_entity_id from homeassistant.exceptions import TemplateError import homeassistant.util.dt as dt_util -from homeassistant.util import slugify +from homeassistant.util import slugify as util_slugify from homeassistant.helpers import template as template_helper # pylint: disable=invalid-name @@ -218,12 +218,22 @@ def slug(value): if value is None: raise vol.Invalid('Slug should not be None') value = str(value) - slg = slugify(value) + slg = util_slugify(value) if value == slg: return value raise vol.Invalid('invalid slug {} (try {})'.format(value, slg)) +def slugify(value): + """Coerce a value to a slug.""" + if value is None: + raise vol.Invalid('Slug should not be None') + slg = util_slugify(str(value)) + if len(slg) > 0: + return slg + raise vol.Invalid('Unable to slugify {}'.format(value)) + + def string(value: Any) -> str: """Coerce value to string, except for None.""" if value is not None: diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 80967543f0d..8b904ca6e8e 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -60,12 +60,27 @@ class TestComponentsDeviceTracker(unittest.TestCase): """Test when known devices contains invalid data.""" files = {'empty.yaml': '', 'nodict.yaml': '100', - 'allok.yaml': 'my_device:\n name: Device'} + 'badkey.yaml': '@:\n name: Device', + 'noname.yaml': 'my_device:\n', + 'allok.yaml': 'My Device:\n name: Device', + 'oneok.yaml': ('My Device!:\n name: Device\n' + 'bad_device:\n nme: Device')} args = {'hass': self.hass, 'consider_home': timedelta(seconds=60)} with patch_yaml_files(files): assert device_tracker.load_config('empty.yaml', **args) == [] assert device_tracker.load_config('nodict.yaml', **args) == [] - assert len(device_tracker.load_config('allok.yaml', **args)) == 1 + assert device_tracker.load_config('noname.yaml', **args) == [] + assert device_tracker.load_config('badkey.yaml', **args) == [] + + res = device_tracker.load_config('allok.yaml', **args) + assert len(res) == 1 + assert res[0].name == 'Device' + assert res[0].dev_id == 'my_device' + + res = device_tracker.load_config('oneok.yaml', **args) + assert len(res) == 1 + assert res[0].name == 'Device' + assert res[0].dev_id == 'my_device' def test_reading_yaml_config(self): """Test the rendering of the YAML configuration.""" From dc95b284874c82eeccce50909aedf3304d83d61b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 9 Oct 2016 16:40:54 +0200 Subject: [PATCH 009/147] Use voluptuous for Russound RNET (#3689) * Migrate to voluptuous * Remove wrong default --- .../components/media_player/russound_rnet.py | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/media_player/russound_rnet.py b/homeassistant/components/media_player/russound_rnet.py index a0405f3f531..91aecb57a10 100644 --- a/homeassistant/components/media_player/russound_rnet.py +++ b/homeassistant/components/media_player/russound_rnet.py @@ -6,23 +6,42 @@ https://home-assistant.io/components/media_player.russound_rnet/ """ import logging +import voluptuous as vol + from homeassistant.components.media_player import ( SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOURCE, MediaPlayerDevice) + SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.const import ( - CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON) + CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, CONF_NAME) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = [ 'https://github.com/laf/russound/archive/0.1.6.zip' '#russound==0.1.6'] -ZONES = 'zones' -SOURCES = 'sources' +_LOGGER = logging.getLogger(__name__) + +CONF_ZONES = 'zones' +CONF_SOURCES = 'sources' SUPPORT_RUSSOUND = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE -_LOGGER = logging.getLogger(__name__) +ZONE_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, +}) + +SOURCE_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_ZONES): vol.Schema({cv.positive_int: ZONE_SCHEMA}), + vol.Required(CONF_SOURCES): vol.All(cv.ensure_list, [SOURCE_SCHEMA]), +}) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -32,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): keypad = config.get('keypad', '70') if host is None or port is None: - _LOGGER.error('Invalid config. Expected %s and %s', + _LOGGER.error("Invalid config. Expected %s and %s", CONF_HOST, CONF_PORT) return False @@ -42,13 +61,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): russ.connect(keypad) sources = [] - for source in config[SOURCES]: + for source in config[CONF_SOURCES]: sources.append(source['name']) if russ.is_connected(): - for zone_id, extra in config[ZONES].items(): - add_devices([RussoundRNETDevice(hass, russ, sources, zone_id, - extra)]) + for zone_id, extra in config[CONF_ZONES].items(): + add_devices([RussoundRNETDevice( + hass, russ, sources, zone_id, extra)]) else: _LOGGER.error('Not connected to %s:%s', host, port) From 154eacef6c39c0fcf3235fe8e3990a6857ef8872 Mon Sep 17 00:00:00 2001 From: hexa- Date: Sun, 9 Oct 2016 17:13:30 +0200 Subject: [PATCH 010/147] Http: Change approved_ips from string to cidr validation (#3532) [BREAKING CHANGE] * Change approved_ips from string to cidr validation Relabel to trusted_networks, better reflecting its expected inputs, everything that ipaddress.ip_networks recognizes as an ip network is possible: - 127.0.0.1 (single ipv4 addresses) - 192.168.0.0/24 (ipv4 networks) - ::1 (single ipv6 addresses) - 2001:DB8::/48 (ipv6 networks) * Add support for the X-Forwarded-For header --- homeassistant/components/emulated_hue.py | 2 +- homeassistant/components/frontend/__init__.py | 10 +++- homeassistant/components/http.py | 39 +++++++++++---- tests/components/test_http.py | 50 ++++++++++++++----- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/emulated_hue.py b/homeassistant/components/emulated_hue.py index 88a5ff58fe5..a63117fc31b 100644 --- a/homeassistant/components/emulated_hue.py +++ b/homeassistant/components/emulated_hue.py @@ -77,7 +77,7 @@ def setup(hass, yaml_config): ssl_certificate=None, ssl_key=None, cors_origins=[], - approved_ips=[] + trusted_networks=[] ) server.register_view(DescriptionXmlView(hass, config)) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index ab967fb114f..2d9abe8fe33 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -211,8 +211,14 @@ class IndexView(HomeAssistantView): panel_url = PANELS[panel]['url'] if panel != 'states' else '' - # auto login if no password was set - no_auth = 'false' if self.hass.config.api.api_password else 'true' + no_auth = 'true' + if self.hass.config.api.api_password: + # require password if set + no_auth = 'false' + if self.hass.wsgi.is_trusted_ip( + self.hass.wsgi.get_real_ip(request)): + # bypass for trusted networks + no_auth = 'true' icons_url = '/static/mdi-{}.html'.format(FINGERPRINTS['mdi.html']) template = self.templates.get_template('index.html') diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 73c8079023f..5aa68297bf5 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -11,6 +11,7 @@ import mimetypes import threading import re import ssl +from ipaddress import ip_address, ip_network import voluptuous as vol @@ -30,13 +31,13 @@ DOMAIN = 'http' REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') CONF_API_PASSWORD = 'api_password' -CONF_APPROVED_IPS = 'approved_ips' CONF_SERVER_HOST = 'server_host' CONF_SERVER_PORT = 'server_port' CONF_DEVELOPMENT = 'development' CONF_SSL_CERTIFICATE = 'ssl_certificate' CONF_SSL_KEY = 'ssl_key' CONF_CORS_ORIGINS = 'cors_allowed_origins' +CONF_TRUSTED_NETWORKS = 'trusted_networks' DATA_API_PASSWORD = 'api_password' NOTIFICATION_ID_LOGIN = 'http-login' @@ -76,7 +77,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_KEY): cv.isfile, vol.Optional(CONF_CORS_ORIGINS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_APPROVED_IPS): vol.All(cv.ensure_list, [cv.string]) + vol.Optional(CONF_TRUSTED_NETWORKS): + vol.All(cv.ensure_list, [ip_network]) }), }, extra=vol.ALLOW_EXTRA) @@ -113,7 +115,9 @@ def setup(hass, config): ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_key = conf.get(CONF_SSL_KEY) cors_origins = conf.get(CONF_CORS_ORIGINS, []) - approved_ips = conf.get(CONF_APPROVED_IPS, []) + trusted_networks = [ + ip_network(trusted_network) + for trusted_network in conf.get(CONF_TRUSTED_NETWORKS, [])] server = HomeAssistantWSGI( hass, @@ -124,7 +128,7 @@ def setup(hass, config): ssl_certificate=ssl_certificate, ssl_key=ssl_key, cors_origins=cors_origins, - approved_ips=approved_ips + trusted_networks=trusted_networks ) def start_wsgi_server(event): @@ -257,7 +261,7 @@ class HomeAssistantWSGI(object): def __init__(self, hass, development, api_password, ssl_certificate, ssl_key, server_host, server_port, cors_origins, - approved_ips): + trusted_networks): """Initilalize the WSGI Home Assistant server.""" from werkzeug.wrappers import Response @@ -276,7 +280,7 @@ class HomeAssistantWSGI(object): self.server_host = server_host self.server_port = server_port self.cors_origins = cors_origins - self.approved_ips = approved_ips + self.trusted_networks = trusted_networks self.event_forwarder = None self.server = None @@ -431,6 +435,19 @@ class HomeAssistantWSGI(object): environ['PATH_INFO'] = '{}.{}'.format(*fingerprinted.groups()) return app(environ, start_response) + @staticmethod + def get_real_ip(request): + """Return the clients correct ip address, even in proxied setups.""" + if request.access_route: + return request.access_route[-1] + else: + return request.remote_addr + + def is_trusted_ip(self, remote_addr): + """Match an ip address against trusted CIDR networks.""" + return any(ip_address(remote_addr) in trusted_network + for trusted_network in self.hass.wsgi.trusted_networks) + class HomeAssistantView(object): """Base view for all views.""" @@ -471,13 +488,15 @@ class HomeAssistantView(object): except AttributeError: raise MethodNotAllowed + remote_addr = HomeAssistantWSGI.get_real_ip(request) + # Auth code verbose on purpose authenticated = False if self.hass.wsgi.api_password is None: authenticated = True - elif request.remote_addr in self.hass.wsgi.approved_ips: + elif self.hass.wsgi.is_trusted_ip(remote_addr): authenticated = True elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), @@ -491,17 +510,17 @@ class HomeAssistantView(object): if self.requires_auth and not authenticated: _LOGGER.warning('Login attempt or request with an invalid ' - 'password from %s', request.remote_addr) + 'password from %s', remote_addr) persistent_notification.create( self.hass, - 'Invalid password used from {}'.format(request.remote_addr), + 'Invalid password used from {}'.format(remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise Unauthorized() request.authenticated = authenticated _LOGGER.info('Serving %s to %s (auth: %s)', - request.path, request.remote_addr, authenticated) + request.path, remote_addr, authenticated) result = handler(request, **values) diff --git a/tests/components/test_http.py b/tests/components/test_http.py index e4e0fafd7c7..feb6efaf9fd 100644 --- a/tests/components/test_http.py +++ b/tests/components/test_http.py @@ -2,6 +2,8 @@ # pylint: disable=protected-access,too-many-public-methods import logging import time +from ipaddress import ip_network +from unittest.mock import patch import requests @@ -18,6 +20,11 @@ HA_HEADERS = { const.HTTP_HEADER_HA_AUTH: API_PASSWORD, const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON, } +# dont' add 127.0.0.1/::1 as trusted, as it may interfere with other test cases +TRUSTED_NETWORKS = ["192.0.2.0/24", + "2001:DB8:ABCD::/48", + '100.64.0.1', + 'FD01:DB8::1'] CORS_ORIGINS = [HTTP_BASE_URL, HTTP_BASE] @@ -46,6 +53,10 @@ def setUpModule(): # pylint: disable=invalid-name bootstrap.setup_component(hass, 'api') + hass.wsgi.trusted_networks = [ + ip_network(trusted_network) + for trusted_network in TRUSTED_NETWORKS] + hass.start() time.sleep(0.05) @@ -72,14 +83,21 @@ class TestHttp: assert req.status_code == 401 - def test_access_denied_with_ip_no_in_approved_ips(self, caplog): - """Test access deniend with ip not in approved ip.""" - hass.wsgi.approved_ips = ['134.4.56.1'] + def test_access_denied_with_untrusted_ip(self, caplog): + """Test access with an untrusted ip address.""" - req = requests.get(_url(const.URL_API), - params={'api_password': ''}) + for remote_addr in ['198.51.100.1', + '2001:DB8:FA1::1', + '127.0.0.1', + '::1']: + with patch('homeassistant.components.http.' + 'HomeAssistantWSGI.get_real_ip', + return_value=remote_addr): + req = requests.get(_url(const.URL_API), + params={'api_password': ''}) - assert req.status_code == 401 + assert req.status_code == 401, \ + "{} shouldn't be trusted".format(remote_addr) def test_access_with_password_in_header(self, caplog): """Test access with password in URL.""" @@ -121,14 +139,20 @@ class TestHttp: # assert const.URL_API in logs assert API_PASSWORD not in logs - def test_access_with_ip_in_approved_ips(self, caplog): - """Test access with approved ip.""" - hass.wsgi.approved_ips = ['127.0.0.1', '134.4.56.1'] + def test_access_with_trusted_ip(self, caplog): + """Test access with trusted addresses.""" + for remote_addr in ['100.64.0.1', + '192.0.2.100', + 'FD01:DB8::1', + '2001:DB8:ABCD::1']: + with patch('homeassistant.components.http.' + 'HomeAssistantWSGI.get_real_ip', + return_value=remote_addr): + req = requests.get(_url(const.URL_API), + params={'api_password': ''}) - req = requests.get(_url(const.URL_API), - params={'api_password': ''}) - - assert req.status_code == 200 + assert req.status_code == 200, \ + "{} should be trusted".format(remote_addr) def test_cors_allowed_with_password_in_url(self): """Test cross origin resource sharing with password in url.""" From 1d0df636156d5ebe2cfa65716ad97a2e013663b0 Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Sun, 9 Oct 2016 17:45:12 +0200 Subject: [PATCH 011/147] Netatmo binary sensor (#3455) * Basic support for Netatmo welcome binary sensors Signed-off-by: Hugo D. (jabesq) * Bug fixes and optimization for Netatmo devices Signed-off-by: Hugo D. (jabesq) * pylint fixes * Bug Fixing and optimization for Netatmo devices Signed-off-by: Hugo D. (jabesq) * Add unique_id for cameras to avoid duplicate And global config to disable discovery for netatmo devices Signed-off-by: Hugo D. (jabesq) --- .../components/binary_sensor/netatmo.py | 128 ++++++++++++++++++ homeassistant/components/camera/netatmo.py | 46 ++----- homeassistant/components/netatmo.py | 43 +++++- 3 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 homeassistant/components/binary_sensor/netatmo.py diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py new file mode 100644 index 00000000000..7a7f865494a --- /dev/null +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -0,0 +1,128 @@ +""" +Support for the Netatmo binary sensors. + +The binary sensors based on events seen by the NetatmoCamera + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.netatmo/ +""" +import logging +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.netatmo import WelcomeData +from homeassistant.loader import get_component +from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.helpers import config_validation as cv + +DEPENDENCIES = ["netatmo"] + +_LOGGER = logging.getLogger(__name__) + + +# These are the available sensors mapped to binary_sensor class +SENSOR_TYPES = { + "Someone known": "motion", + "Someone unknown": "motion", + "Motion": "motion", +} + +CONF_HOME = 'home' +CONF_CAMERAS = 'cameras' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HOME): cv.string, + vol.Optional(CONF_CAMERAS, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES.keys()): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup access to Netatmo binary sensor.""" + netatmo = get_component('netatmo') + home = config.get(CONF_HOME, None) + + import lnetatmo + try: + data = WelcomeData(netatmo.NETATMO_AUTH, home) + except lnetatmo.NoDevice: + return None + + if data.get_camera_names() == []: + return None + + sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES) + + for camera_name in data.get_camera_names(): + if CONF_CAMERAS in config: + if config[CONF_CAMERAS] != [] and \ + camera_name not in config[CONF_CAMERAS]: + continue + for variable in sensors: + add_devices([WelcomeBinarySensor(data, camera_name, home, + variable)]) + + +class WelcomeBinarySensor(BinarySensorDevice): + """Represent a single binary sensor in a Netatmo Welcome device.""" + + def __init__(self, data, camera_name, home, sensor): + """Setup for access to the Netatmo camera events.""" + self._data = data + self._camera_name = camera_name + self._home = home + if home: + self._name = home + ' / ' + camera_name + else: + self._name = camera_name + self._sensor_name = sensor + self._name += ' ' + sensor + camera_id = data.welcomedata.cameraByName(camera=camera_name, + home=home)['id'] + self._unique_id = "Welcome_binary_sensor {0} - {1}".format(self._name, + camera_id) + self.update() + + @property + def name(self): + """The name of the Netatmo device and this sensor.""" + return self._name + + @property + def unique_id(self): + """Return the unique ID for this sensor.""" + return self._unique_id + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return SENSOR_TYPES.get(self._sensor_name) + + @property + def is_on(self): + """Return true if binary sensor is on.""" + return self._state + + def update(self): + """Request an update from the Netatmo API.""" + self._data.update() + self._data.welcomedata.updateEvent(home=self._data.home) + + if self._sensor_name == "Someone known": + self._state =\ + self._data.welcomedata.someoneKnownSeen(self._home, + self._camera_name) + elif self._sensor_name == "Someone unknown": + self._state =\ + self._data.welcomedata.someoneUnknownSeen(self._home, + self._camera_name) + elif self._sensor_name == "Motion": + self._state =\ + self._data.welcomedata.motionDetected(self._home, + self._camera_name) + else: + return None diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 9069a5c6c28..259feb41a1c 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -5,12 +5,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.netatmo/ """ import logging -from datetime import timedelta import requests import voluptuous as vol -from homeassistant.util import Throttle +from homeassistant.components.netatmo import WelcomeData from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) from homeassistant.loader import get_component from homeassistant.helpers import config_validation as cv @@ -22,8 +21,6 @@ _LOGGER = logging.getLogger(__name__) CONF_HOME = 'home' CONF_CAMERAS = 'cameras' -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOME): cv.string, vol.Optional(CONF_CAMERAS, default=[]): @@ -43,8 +40,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return None for camera_name in data.get_camera_names(): - if config[CONF_CAMERAS] != []: - if camera_name not in config[CONF_CAMERAS]: + if CONF_CAMERAS in config: + if config[CONF_CAMERAS] != [] and \ + camera_name not in config[CONF_CAMERAS]: continue add_devices([WelcomeCamera(data, camera_name, home)]) @@ -61,6 +59,10 @@ class WelcomeCamera(Camera): self._name = home + ' / ' + camera_name else: self._name = camera_name + camera_id = data.welcomedata.cameraByName(camera=camera_name, + home=home)['id'] + self._unique_id = "Welcome_camera {0} - {1}".format(self._name, + camera_id) self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls( camera=camera_name ) @@ -87,31 +89,7 @@ class WelcomeCamera(Camera): """Return the name of this Netatmo Welcome device.""" return self._name - -class WelcomeData(object): - """Get the latest data from NetAtmo.""" - - def __init__(self, auth, home=None): - """Initialize the data object.""" - self.auth = auth - self.welcomedata = None - self.camera_names = [] - self.home = home - - def get_camera_names(self): - """Return all module available on the API as a list.""" - self.update() - if not self.home: - for home in self.welcomedata.cameras: - for camera in self.welcomedata.cameras[home].values(): - self.camera_names.append(camera['name']) - else: - for camera in self.welcomedata.cameras[self.home].values(): - self.camera_names.append(camera['name']) - return self.camera_names - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Call the NetAtmo API to update the data.""" - import lnetatmo - self.welcomedata = lnetatmo.WelcomeData(self.auth) + @property + def unique_id(self): + """Return the unique ID for this sensor.""" + return self._unique_id diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index f385940c01d..81feba85d63 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -5,14 +5,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/netatmo/ """ import logging +from datetime import timedelta from urllib.error import HTTPError import voluptuous as vol from homeassistant.const import ( - CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME) + CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, CONF_DISCOVERY) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle REQUIREMENTS = [ 'https://github.com/jabesq/netatmo-api-python/archive/' @@ -25,6 +27,9 @@ CONF_SECRET_KEY = 'secret_key' DOMAIN = 'netatmo' NETATMO_AUTH = None +DEFAULT_DISCOVERY = True + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -32,6 +37,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_SECRET_KEY): cv.string, vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) @@ -50,7 +56,38 @@ def setup(hass, config): _LOGGER.error("Unable to connect to Netatmo API") return False - for component in 'camera', 'sensor': - discovery.load_platform(hass, component, DOMAIN, {}, config) + if config[DOMAIN][CONF_DISCOVERY]: + for component in 'camera', 'sensor', 'binary_sensor': + discovery.load_platform(hass, component, DOMAIN, {}, config) return True + + +class WelcomeData(object): + """Get the latest data from Netatmo.""" + + def __init__(self, auth, home=None): + """Initialize the data object.""" + self.auth = auth + self.welcomedata = None + self.camera_names = [] + self.home = home + + def get_camera_names(self): + """Return all module available on the API as a list.""" + self.camera_names = [] + self.update() + if not self.home: + for home in self.welcomedata.cameras: + for camera in self.welcomedata.cameras[home].values(): + self.camera_names.append(camera['name']) + else: + for camera in self.welcomedata.cameras[self.home].values(): + self.camera_names.append(camera['name']) + return self.camera_names + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Call the Netatmo API to update the data.""" + import lnetatmo + self.welcomedata = lnetatmo.WelcomeData(self.auth) From 63d9ea66431a903993640ff2e9a3687d55aa464d Mon Sep 17 00:00:00 2001 From: Erik Eriksson Date: Sun, 9 Oct 2016 18:15:58 +0200 Subject: [PATCH 012/147] slugify (#3777) --- homeassistant/components/device_tracker/volvooncall.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/volvooncall.py b/homeassistant/components/device_tracker/volvooncall.py index 8f5cf5fff7a..746bbea6ccf 100644 --- a/homeassistant/components/device_tracker/volvooncall.py +++ b/homeassistant/components/device_tracker/volvooncall.py @@ -14,6 +14,7 @@ import requests import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow +from homeassistant.util import slugify from homeassistant.const import ( CONF_PASSWORD, CONF_SCAN_INTERVAL, @@ -87,7 +88,7 @@ def setup_scanner(hass, config, see): vehicle_url = rel["vehicle"] + '/' attributes = query("attributes", vehicle_url) - dev_id = "volvo_" + attributes["registrationNumber"] + dev_id = "volvo_" + slugify(attributes["registrationNumber"]) host_name = "%s %s/%s" % (attributes["registrationNumber"], attributes["vehicleType"], attributes["modelYear"]) From 9d5c20b629f1388bf2ae02a9d1ba8cc450045bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Sun, 9 Oct 2016 21:47:35 +0200 Subject: [PATCH 013/147] vsure0.11.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 e8eabddc440..732677a0b5f 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -16,7 +16,7 @@ from homeassistant.helpers import discovery from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['vsure==0.10.3'] +REQUIREMENTS = ['vsure==0.11.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 03ecb280bd7..8f056813fcf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -500,7 +500,7 @@ urllib3 uvcclient==0.9.0 # homeassistant.components.verisure -vsure==0.10.3 +vsure==0.11.0 # homeassistant.components.sensor.vasttrafik vtjp==0.1.11 From 76a1a54369fb93e504205baed84b49abd8ae9538 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 10 Oct 2016 13:46:23 +0200 Subject: [PATCH 014/147] Hotfix device name with autodiscovery (#3791) --- homeassistant/components/homematic.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/homematic.py b/homeassistant/components/homematic.py index b09f53ae748..6e4727f8188 100644 --- a/homeassistant/components/homematic.py +++ b/homeassistant/components/homematic.py @@ -353,7 +353,8 @@ def _get_devices(device_type, keys): name = _create_ha_name( name=device.NAME, channel=channel, - param=param + param=param, + count=len(channels) ) device_dict = { CONF_PLATFORM: "homematic", @@ -377,22 +378,22 @@ def _get_devices(device_type, keys): return device_arr -def _create_ha_name(name, channel, param): +def _create_ha_name(name, channel, param, count): """Generate a unique object name.""" # HMDevice is a simple device - if channel == 1 and param is None: + if count == 1 and param is None: return name # Has multiple elements/channels - if channel > 1 and param is None: + if count > 1 and param is None: return "{} {}".format(name, channel) # With multiple param first elements - if channel == 1 and param is not None: + if count == 1 and param is not None: return "{} {}".format(name, param) # Multiple param on object with multiple elements - if channel > 1 and param is not None: + if count > 1 and param is not None: return "{} {} {}".format(name, channel, param) @@ -600,12 +601,6 @@ class HMDevice(Entity): if self._state: self._state = self._state.upper() - # Generate name - if not self._name: - self._name = _create_ha_name(name=self._address, - channel=self._channel, - param=self._state) - @property def should_poll(self): """Return false. Homematic states are pushed by the XML RPC Server.""" From df58f718abc4ffe718c29acdedc88e12f891628c Mon Sep 17 00:00:00 2001 From: Ellis Percival Date: Mon, 10 Oct 2016 15:53:18 +0100 Subject: [PATCH 015/147] Make 'pin' optional for zigbee device config. (#3799) --- homeassistant/components/zigbee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py index 4b4da350199..8a9271745c9 100644 --- a/homeassistant/components/zigbee.py +++ b/homeassistant/components/zigbee.py @@ -55,7 +55,7 @@ CONFIG_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_PIN): cv.positive_int, + vol.Optional(CONF_PIN): cv.positive_int, vol.Optional(CONF_ADDRESS): cv.string, }, extra=vol.ALLOW_EXTRA) From 552265bc310107eeee43093b49553727c1a4bade Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 10 Oct 2016 19:38:20 +0200 Subject: [PATCH 016/147] No longer use old name (#3792) --- homeassistant/components/sensor/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py index 0c413ab9263..6e75c105ec3 100644 --- a/homeassistant/components/sensor/statistics.py +++ b/homeassistant/components/sensor/statistics.py @@ -74,7 +74,7 @@ class StatisticsSensor(Entity): self.min = self.max = self.total = self.count = 0 self.update() - def calculate_sensor_state_listener(entity, old_state, new_state): + def stats_sensor_state_listener(entity, old_state, new_state): """Called when the sensor changes state.""" self._unit_of_measurement = new_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) @@ -87,7 +87,7 @@ class StatisticsSensor(Entity): self.update_ha_state(True) - track_state_change(hass, entity_id, calculate_sensor_state_listener) + track_state_change(hass, entity_id, stats_sensor_state_listener) @property def name(self): From 3b331eac563c55f2e5cc531a79f65baa9cbe68f2 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 10 Oct 2016 19:38:32 +0200 Subject: [PATCH 017/147] Update docstrings (sensor.pi_hole, sensor.haveibeenpwned) (#3793) * Fix docstrings * Update docstrings --- .../components/sensor/haveibeenpwned.py | 22 +++++++++---------- homeassistant/components/sensor/pi_hole.py | 6 ++--- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/sensor/haveibeenpwned.py index f317ef14565..36330f9bba9 100644 --- a/homeassistant/components/sensor/haveibeenpwned.py +++ b/homeassistant/components/sensor/haveibeenpwned.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the RESTful sensor.""" + """Set up the HaveIBeenPwnedSensor sensor.""" emails = config.get(CONF_EMAIL) data = HaveIBeenPwnedData(emails) @@ -43,15 +43,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devices) - # To make sure we get initial data for the sensors - # ignoring the normal throttle of 15 minutes but using - # an update throttle of 5 seconds + # To make sure we get initial data for the sensors ignoring the normal + # throttle of 15 minutes but using an update throttle of 5 seconds for sensor in devices: sensor.update_nothrottle() class HaveIBeenPwnedSensor(Entity): - """Implementation of HaveIBeenPwnedSensor.""" + """Implementation of a HaveIBeenPwnedSensor.""" def __init__(self, data, hass, email): """Initialize the HaveIBeenPwnedSensor sensor.""" @@ -77,7 +76,7 @@ class HaveIBeenPwnedSensor(Entity): return self._state @property - def state_attributes(self): + def device_state_attributes(self): """Return the atrributes of the sensor.""" val = {} if self._email not in self._data.data: @@ -97,12 +96,11 @@ class HaveIBeenPwnedSensor(Entity): """Update sensor without throttle.""" self._data.update_no_throttle() - # Schedule a forced update 5 seconds in the future if the - # update above returned no data for this sensors email. - # this is mainly to make sure that we don't - # get http error "too many requests" and to have initial - # data after hass startup once we have the data it will - # update as normal using update + # Schedule a forced update 5 seconds in the future if the update above + # returned no data for this sensors email. This is mainly to make sure + # that we don't get HTTP Error "too many requests" and to have initial + # data after hass startup once we have the data it will update as + # normal using update if self._email not in self._data.data: track_point_in_time(self._hass, self.update_nothrottle, diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py index a578a6bb119..a9a4c11e67f 100644 --- a/homeassistant/components/sensor/pi_hole.py +++ b/homeassistant/components/sensor/pi_hole.py @@ -89,8 +89,8 @@ class PiHoleSensor(Entity): # pylint: disable=no-member @property - def state_attributes(self): - """Return the state attributes of the GPS.""" + def device_state_attributes(self): + """Return the state attributes of the Pi-Hole.""" return { ATTR_BLOCKED_DOMAINS: self._state.get('domains_being_blocked'), ATTR_PERCENTAGE_TODAY: self._state.get('ads_percentage_today'), @@ -98,7 +98,7 @@ class PiHoleSensor(Entity): } def update(self): - """Get the latest data from REST API and updates the state.""" + """Get the latest data from the Pi-Hole API and updates the state.""" try: self.rest.update() self._state = json.loads(self.rest.data) From b19ec21e88dd4bb77b52313a154c030f89b1ab1d Mon Sep 17 00:00:00 2001 From: sander76 Date: Tue, 11 Oct 2016 07:30:27 +0200 Subject: [PATCH 018/147] Changing import as powerview api did change. (#3780) --- homeassistant/components/scene/hunterdouglas_powerview.py | 7 ++++--- requirements_all.txt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py index 9fc36cf1cff..0ae44d878f8 100644 --- a/homeassistant/components/scene/hunterdouglas_powerview.py +++ b/homeassistant/components/scene/hunterdouglas_powerview.py @@ -11,8 +11,9 @@ from homeassistant.helpers.entity import generate_entity_id _LOGGER = logging.getLogger(__name__) REQUIREMENTS = [ - 'https://github.com/sander76/powerviewApi/' - 'archive/cc6f75dd39160d4aaf46cb2ed9220136b924bcb4.zip#powerviewApi==0.2'] + 'https://github.com/sander76/powerviewApi/archive' + '/246e782d60d5c0addcc98d7899a0186f9d5640b0.zip#powerviewApi==0.3.15' +] HUB_ADDRESS = 'address' @@ -20,7 +21,7 @@ HUB_ADDRESS = 'address' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the powerview scenes stored in a Powerview hub.""" - import powerview + from powerview_api import powerview hub_address = config.get(HUB_ADDRESS) diff --git a/requirements_all.txt b/requirements_all.txt index 8f056813fcf..9efd5d2e755 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -205,7 +205,7 @@ https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f https://github.com/robbiet480/pygtfs/archive/00546724e4bbcb3053110d844ca44e2246267dd8.zip#pygtfs==0.1.3 # homeassistant.components.scene.hunterdouglas_powerview -https://github.com/sander76/powerviewApi/archive/cc6f75dd39160d4aaf46cb2ed9220136b924bcb4.zip#powerviewApi==0.2 +https://github.com/sander76/powerviewApi/archive/246e782d60d5c0addcc98d7899a0186f9d5640b0.zip#powerviewApi==0.3.15 # homeassistant.components.mysensors https://github.com/theolind/pymysensors/archive/8ce98b7fb56f7921a808eb66845ce8b2c455c81e.zip#pymysensors==0.7.1 From 9b98d470c2b42dd776eca165d20cefdb6e9ceba0 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 10 Oct 2016 22:31:15 -0700 Subject: [PATCH 019/147] Wrap found target in list (#3809) * Wrap found target in list * Fix test_messages_to_targets_route --- homeassistant/components/notify/__init__.py | 2 +- tests/components/notify/test_demo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index e56dfa5586d..c779c78d15e 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -100,7 +100,7 @@ def setup(hass, config): kwargs[ATTR_TITLE] = title.render() if targets.get(call.service) is not None: - kwargs[ATTR_TARGET] = targets[call.service] + kwargs[ATTR_TARGET] = [targets[call.service]] else: kwargs[ATTR_TARGET] = call.data.get(ATTR_TARGET) diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index b996b479ef9..0d41f0606e5 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -152,7 +152,7 @@ data_template: assert { 'message': 'my message', - 'target': 'test target id', + 'target': ['test target id'], 'title': 'my title', 'data': {'hello': 'world'} } == data From c8ca66b671d77638f040a0563cf325c5de810c5f Mon Sep 17 00:00:00 2001 From: phardy Date: Tue, 11 Oct 2016 16:36:20 +1100 Subject: [PATCH 020/147] Stop GTFS component from overwriting friendly_name (#3798) * Prevent update from clobbering configured name. * linted * Fixing awkward line breaking. --- homeassistant/components/sensor/gtfs.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 2a5f4aa0de6..5fcf46832db 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -185,7 +185,8 @@ class GTFSDepartureSensor(Entity): self._pygtfs = pygtfs self.origin = origin self.destination = destination - self._name = name + self._custom_name = name + self._name = '' self._unit_of_measurement = 'min' self._state = 0 self._attributes = {} @@ -233,9 +234,10 @@ class GTFSDepartureSensor(Entity): trip = self._departure['trip'] name = '{} {} to {} next departure' - self._name = name.format(agency.agency_name, - origin_station.stop_id, - destination_station.stop_id) + self._name = (self._custom_name or + name.format(agency.agency_name, + origin_station.stop_id, + destination_station.stop_id)) # Build attributes self._attributes = {} From 87d9cdd78ffe870f7845660e971e5c581ffaf55c Mon Sep 17 00:00:00 2001 From: Teemu Mikkonen Date: Tue, 11 Oct 2016 09:17:27 +0300 Subject: [PATCH 021/147] Fix for html5 notification tag problem. Fixes #3774 (#3790) --- homeassistant/components/notify/html5.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py index a868552d051..7173538032c 100644 --- a/homeassistant/components/notify/html5.py +++ b/homeassistant/components/notify/html5.py @@ -344,12 +344,15 @@ class HTML5NotificationService(BaseNotificationService): # Pick out fields that should go into the notification directly vs # into the notification data dictionary. - for key, val in data.copy().items(): + data_tmp = {} + + for key, val in data.items(): if key in HTML5_SHOWNOTIFICATION_PARAMETERS: payload[key] = val - del data[key] + else: + data_tmp[key] = val - payload[ATTR_DATA] = data + payload[ATTR_DATA] = data_tmp if (payload[ATTR_DATA].get(ATTR_URL) is None and payload.get(ATTR_ACTIONS) is None): From 63580f9e03446b39956c12a3bfe45a37ced5b5d9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 08:23:32 +0200 Subject: [PATCH 022/147] Upgrade pyowm to 2.5.0 (#3806) --- homeassistant/components/sensor/openweathermap.py | 8 ++++---- requirements_all.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index e7936cc0535..9dcb354a125 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pyowm==2.4.0'] +REQUIREMENTS = ['pyowm==2.5.0'] _LOGGER = logging.getLogger(__name__) @@ -174,12 +174,12 @@ class WeatherData(object): """Get the latest data from OpenWeatherMap.""" obs = self.owm.weather_at_coords(self.latitude, self.longitude) if obs is None: - _LOGGER.warning('Failed to fetch data from OWM') + _LOGGER.warning("Failed to fetch data from OpenWeatherMap") return self.data = obs.get_weather() if self.forecast == 1: - obs = self.owm.three_hours_forecast_at_coords(self.latitude, - self.longitude) + obs = self.owm.three_hours_forecast_at_coords( + self.latitude, self.longitude) self.fc_data = obs.get_forecast() diff --git a/requirements_all.txt b/requirements_all.txt index 9efd5d2e755..ccb75612948 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -361,7 +361,7 @@ pynetio==0.1.6 pynx584==0.2 # homeassistant.components.sensor.openweathermap -pyowm==2.4.0 +pyowm==2.5.0 # homeassistant.components.switch.acer_projector pyserial==3.1.1 From 002660fd9ec14cb6b4eedf9e1ad6c21787a27110 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 08:24:10 +0200 Subject: [PATCH 023/147] Upgrade dnspython3 to 1.15.0 (#3804) --- homeassistant/components/notify/xmpp.py | 6 ++---- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index f292ceccd26..cbe6da89d81 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -14,10 +14,11 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_PASSWORD, CONF_SENDER, CONF_RECIPIENT REQUIREMENTS = ['sleekxmpp==1.3.1', - 'dnspython3==1.14.0', + 'dnspython3==1.15.0', 'pyasn1==0.1.9', 'pyasn1-modules==0.0.8'] +_LOGGER = logging.getLogger(__name__) CONF_TLS = 'tls' @@ -29,9 +30,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -_LOGGER = logging.getLogger(__name__) - - def get_service(hass, config): """Get the Jabber (XMPP) notification service.""" return XmppNotificationService( diff --git a/requirements_all.txt b/requirements_all.txt index ccb75612948..1b8bb9b3437 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -70,7 +70,7 @@ colorlog>2.1,<3 directpy==0.1 # homeassistant.components.notify.xmpp -dnspython3==1.14.0 +dnspython3==1.15.0 # homeassistant.components.dweet # homeassistant.components.sensor.dweet From 656ee5243574b778258a3dc0210b688f93a63517 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 08:27:53 +0200 Subject: [PATCH 024/147] Upgrade cherrypy to 8.1.2 (#3805) --- homeassistant/components/http.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 5aa68297bf5..97009b69d1c 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -28,7 +28,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components import persistent_notification DOMAIN = 'http' -REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') +REQUIREMENTS = ('cherrypy==8.1.2', 'static3==0.7.0', 'Werkzeug==0.11.11') CONF_API_PASSWORD = 'api_password' CONF_SERVER_HOST = 'server_host' diff --git a/requirements_all.txt b/requirements_all.txt index 1b8bb9b3437..ac73fd89855 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -58,7 +58,7 @@ boto3==1.3.1 # homeassistant.components.emulated_hue # homeassistant.components.http -cherrypy==8.1.0 +cherrypy==8.1.2 # homeassistant.components.sensor.coinmarketcap coinmarketcap==2.0.1 From a2503e4d13a05e7646211ec5fef2aca0f3b5023c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 08:29:43 +0200 Subject: [PATCH 025/147] Upgrade sqlalchemy to 1.1.1 (#3796) --- homeassistant/components/recorder/__init__.py | 21 +++++++++---------- requirements_all.txt | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 7f836a1363d..d1983e8f28f 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -26,15 +26,15 @@ from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.typing import ConfigType, QueryType import homeassistant.util.dt as dt_util -DOMAIN = "recorder" +DOMAIN = 'recorder' -REQUIREMENTS = ['sqlalchemy==1.0.15'] +REQUIREMENTS = ['sqlalchemy==1.1.1'] -DEFAULT_URL = "sqlite:///{hass_config_path}" -DEFAULT_DB_FILE = "home-assistant_v2.db" +DEFAULT_URL = 'sqlite:///{hass_config_path}' +DEFAULT_DB_FILE = 'home-assistant_v2.db' -CONF_DB_URL = "db_url" -CONF_PURGE_DAYS = "purge_days" +CONF_DB_URL = 'db_url' +CONF_PURGE_DAYS = 'purge_days' RETRIES = 3 CONNECT_RETRY_WAIT = 10 @@ -56,8 +56,8 @@ _LOGGER = logging.getLogger(__name__) Session = None # pylint: disable=no-member -def execute(q: QueryType) \ - -> List[Any]: # pylint: disable=invalid-sequence-index +# pylint: disable=invalid-sequence-index +def execute(q: QueryType) -> List[Any]: """Query the database and convert the objects to HA native form. This method also retries a few times in the case of stale connections. @@ -101,7 +101,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: global _INSTANCE # pylint: disable=global-statement if _INSTANCE is not None: - _LOGGER.error('Only a single instance allowed.') + _LOGGER.error("Only a single instance allowed") return False purge_days = config.get(DOMAIN, {}).get(CONF_PURGE_DAYS) @@ -155,8 +155,7 @@ class Recorder(threading.Thread): """A threaded recorder class.""" # pylint: disable=too-many-instance-attributes - def __init__(self, hass: HomeAssistant, purge_days: int, uri: str) \ - -> None: + def __init__(self, hass: HomeAssistant, purge_days: int, uri: str) -> None: """Initialize the recorder.""" threading.Thread.__init__(self) diff --git a/requirements_all.txt b/requirements_all.txt index ac73fd89855..964138e9de9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -458,7 +458,7 @@ speedtest-cli==0.3.4 # homeassistant.components.recorder # homeassistant.scripts.db_migrator -sqlalchemy==1.0.15 +sqlalchemy==1.1.1 # homeassistant.components.emulated_hue # homeassistant.components.http From 711526e57439e7a8f9a36c83489781c6ebe4ae6f Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Mon, 10 Oct 2016 23:36:38 -0700 Subject: [PATCH 026/147] Cache condition in helpers.Script (#3797) * Cache condition in helpers.Script The caching is a simple in-memory, per-instance dictionary. We use __str__ to format cache keys. This naive implementation has some disadvantages (e.g., we won't be able to cache two conditions that contain references to distinct-but-equivalent object instances and we don't have any control over the size of the condition cache), but for most simple use-cases the approach should be good enough. Resolves #3629 * Fix docstring style --- homeassistant/helpers/script.py | 10 ++++-- tests/helpers/test_script.py | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index cb4a1fbbe04..a5869915d46 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -51,6 +51,7 @@ class Script(): in self.sequence) self._async_unsub_delay_listener = None self._template_cache = {} + self._config_cache = {} @property def is_running(self) -> bool: @@ -153,9 +154,14 @@ class Script(): def _async_check_condition(self, action, variables): """Test if condition is matching.""" + config_cache_key = frozenset((k, str(v)) for k, v in action.items()) + config = self._config_cache.get(config_cache_key) + if not config: + config = condition.async_from_config(action, False) + self._config_cache[config_cache_key] = config + self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION]) - check = condition.async_from_config(action, False)( - self.hass, variables) + check = config(self.hass, variables) self._log("Test condition {}: {}".format(self.last_action, check)) return check diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 9a868bd8d8a..b4febc83048 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1,6 +1,7 @@ """The tests for the Script component.""" # pylint: disable=too-many-public-methods,protected-access from datetime import timedelta +from unittest import mock import unittest # Otherwise can't test just this file (import order issue) @@ -279,3 +280,62 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() self.hass.block_till_done() assert len(events) == 3 + + @mock.patch('homeassistant.helpers.script.condition.async_from_config') + def test_condition_created_once(self, async_from_config): + """Test that the conditions do not get created multiple times.""" + event = 'test_event' + events = [] + + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + self.hass.bus.listen(event, record_event) + + self.hass.states.set('test.entity', 'hello') + + script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state == "hello" }}', + }, + {'event': event}, + ])) + + script_obj.run() + script_obj.run() + self.hass.block_till_done() + assert async_from_config.call_count == 1 + assert len(script_obj._config_cache) == 1 + + def test_all_conditions_cached(self): + """Test that multiple conditions get cached.""" + event = 'test_event' + events = [] + + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + self.hass.bus.listen(event, record_event) + + self.hass.states.set('test.entity', 'hello') + + script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state == "hello" }}', + }, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state != "hello" }}', + }, + {'event': event}, + ])) + + script_obj.run() + self.hass.block_till_done() + assert len(script_obj._config_cache) == 2 From 941fccd3fc7dc53f386a85e8c074febf6d5c11dd Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 11 Oct 2016 08:44:41 +0200 Subject: [PATCH 027/147] Update python-lirc to 1.2.3 (#3784) * Upgrade to latest python-lirc 1.2.3 * update requirements_all.txt --- homeassistant/components/lirc.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lirc.py b/homeassistant/components/lirc.py index 06a5f288c77..ac4807b26af 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc.py @@ -14,7 +14,7 @@ import voluptuous as vol from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) -REQUIREMENTS = ['python-lirc==1.2.1'] +REQUIREMENTS = ['python-lirc==1.2.3'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 964138e9de9..52c63ad8607 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -380,7 +380,7 @@ python-forecastio==1.3.5 python-hpilo==3.8 # homeassistant.components.lirc -# python-lirc==1.2.1 +# python-lirc==1.2.3 # homeassistant.components.media_player.mpd python-mpd2==0.5.5 From 8c9d1d9af1a474b6a664da78d81a05649d7a0b45 Mon Sep 17 00:00:00 2001 From: Ferry van Zeelst Date: Tue, 11 Oct 2016 08:57:14 +0200 Subject: [PATCH 028/147] Added additional checks which hides functions which are not supported by Nest (#3751) * Added additional checks which hides functions which are not support (like fans / humidity / cooling) * Fixed pylint and flake8 errors (not test file available) * Fixed pydocstyle error * Refactored Code and Comments as described in pull-request * Added additional comment and requesting retest * Upgraded to python-nest 2.11 which contains previously hidden functions --- homeassistant/components/climate/nest.py | 36 ++++++++++++++++++------ homeassistant/components/nest.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index 36be2ca25f5..c4582b839f3 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -40,8 +40,19 @@ class NestThermostat(ClimateDevice): self.structure = structure self.device = device self._fan_list = [STATE_ON, STATE_AUTO] - self._operation_list = [STATE_HEAT, STATE_COOL, STATE_AUTO, - STATE_OFF] + + # Not all nest devices support cooling and heating remove unused + self._operation_list = [STATE_OFF] + + # Add supported nest thermostat features + if self.device.can_heat: + self._operation_list.append(STATE_HEAT) + + if self.device.can_cool: + self._operation_list.append(STATE_COOL) + + if self.device.can_heat and self.device.can_cool: + self._operation_list.append(STATE_AUTO) @property def name(self): @@ -64,11 +75,15 @@ class NestThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - # Move these to Thermostat Device and make them global - return { - "humidity": self.device.humidity, - "target_humidity": self.device.target_humidity, - } + if self.device.has_humidifier or self.device.has_dehumidifier: + # Move these to Thermostat Device and make them global + return { + "humidity": self.device.humidity, + "target_humidity": self.device.target_humidity, + } + else: + # No way to control humidity not show setting + return {} @property def current_temperature(self): @@ -164,7 +179,12 @@ class NestThermostat(ClimateDevice): @property def current_fan_mode(self): """Return whether the fan is on.""" - return STATE_ON if self.device.fan else STATE_AUTO + if self.device.has_fan: + # Return whether the fan is on + return STATE_ON if self.device.fan else STATE_AUTO + else: + # No Fan available so disable slider + return None @property def fan_list(self): diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index a3d86a725aa..b8aa1d1c70a 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -14,7 +14,7 @@ from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, CONF_STRUCTURE) _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['python-nest==2.10.0'] +REQUIREMENTS = ['python-nest==2.11.0'] DOMAIN = 'nest' diff --git a/requirements_all.txt b/requirements_all.txt index 52c63ad8607..980fb226d7b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -389,7 +389,7 @@ python-mpd2==0.5.5 python-mystrom==0.3.6 # homeassistant.components.nest -python-nest==2.10.0 +python-nest==2.11.0 # homeassistant.components.device_tracker.nmap_tracker python-nmap==0.6.1 From 7c2cb6cffd30070309324acda0c23c573b7d711a Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 11 Oct 2016 00:00:29 -0700 Subject: [PATCH 029/147] Separate climate platform and presentation units (#3755) * Separate platform and presentation units in climate * Fix unit tests Maybe * Fix unit tests some more Maybe * Rename _platform_unit_of_measurement to temperature_unit * Fix tests for renamed attribute --- homeassistant/components/climate/__init__.py | 15 ++++++++++----- homeassistant/components/climate/demo.py | 2 +- homeassistant/components/climate/ecobee.py | 2 +- homeassistant/components/climate/eq3btsmart.py | 2 +- .../components/climate/generic_thermostat.py | 2 +- homeassistant/components/climate/heatmiser.py | 2 +- homeassistant/components/climate/homematic.py | 2 +- homeassistant/components/climate/honeywell.py | 4 ++-- homeassistant/components/climate/knx.py | 2 +- homeassistant/components/climate/mysensors.py | 2 +- homeassistant/components/climate/nest.py | 2 +- homeassistant/components/climate/proliphix.py | 2 +- homeassistant/components/climate/radiotherm.py | 2 +- homeassistant/components/climate/vera.py | 2 +- homeassistant/components/climate/zwave.py | 2 +- tests/components/climate/test_honeywell.py | 8 ++++---- 16 files changed, 29 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index e8047093cc8..6c6d493084c 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -253,7 +253,7 @@ def setup(hass, config): kwargs[value] = convert_temperature( temp, hass.config.units.temperature_unit, - climate.unit_of_measurement + climate.temperature_unit ) else: kwargs[value] = temp @@ -422,7 +422,12 @@ class ClimateDevice(Entity): @property def unit_of_measurement(self): - """Return the unit of measurement.""" + """The unit of measurement to display.""" + return self.hass.config.units.temperature_unit + + @property + def temperature_unit(self): + """The unit of measurement used by the platform.""" raise NotImplementedError @property @@ -556,10 +561,10 @@ class ClimateDevice(Entity): if temp is None or not isinstance(temp, Number): return temp - value = convert_temperature(temp, self.unit_of_measurement, - self.hass.config.units.temperature_unit) + value = convert_temperature(temp, self.temperature_unit, + self.unit_of_measurement) - if self.hass.config.units.temperature_unit is TEMP_CELSIUS: + if self.unit_of_measurement is TEMP_CELSIUS: decimal_count = 1 else: # Users of fahrenheit generally expect integer units. diff --git a/homeassistant/components/climate/demo.py b/homeassistant/components/climate/demo.py index 51346e62269..0104d9d01af 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/climate/demo.py @@ -59,7 +59,7 @@ class DemoClimate(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return self._unit_of_measurement diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index 10e56490c84..4b414935136 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -105,7 +105,7 @@ class Thermostat(ClimateDevice): return self.thermostat['name'] @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" if self.thermostat['settings']['useCelsius']: return TEMP_CELSIUS diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 646bf7f2aa8..5389585d8f1 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -46,7 +46,7 @@ class EQ3BTSmartThermostat(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement that is used.""" return TEMP_CELSIUS diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 97ca7fe012f..c5c38d624f5 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -100,7 +100,7 @@ class GenericThermostat(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return self._unit diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py index 941f211c411..06fac09013c 100644 --- a/homeassistant/components/climate/heatmiser.py +++ b/homeassistant/components/climate/heatmiser.py @@ -77,7 +77,7 @@ class HeatmiserV3Thermostat(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement which this thermostat uses.""" return TEMP_CELSIUS diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index c9901c40aea..7113779eb57 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -41,7 +41,7 @@ class HMThermostat(homematic.HMDevice, ClimateDevice): """Representation of a Homematic thermostat.""" @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement that is used.""" return TEMP_CELSIUS diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index fb7b2887344..3af4f62246d 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -120,7 +120,7 @@ class RoundThermostat(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return TEMP_CELSIUS @@ -217,7 +217,7 @@ class HoneywellUSThermostat(ClimateDevice): return self._device.name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return (TEMP_CELSIUS if self._device.temperature_unit == 'C' else TEMP_FAHRENHEIT) diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index 5ea932ab8f5..ef7445c35fd 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -63,7 +63,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): return True @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return self._unit_of_measurement diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/climate/mysensors.py index 9b997954889..c93a69ac0b9 100755 --- a/homeassistant/components/climate/mysensors.py +++ b/homeassistant/components/climate/mysensors.py @@ -47,7 +47,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): return self.gateway.optimistic @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return (TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index c4582b839f3..fb7e4b7ec11 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -68,7 +68,7 @@ class NestThermostat(ClimateDevice): return location.capitalize() + '(' + name + ')' @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return TEMP_CELSIUS diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index da5f5918d7c..521ef7d58e6 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -69,7 +69,7 @@ class ProliphixThermostat(ClimateDevice): } @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return TEMP_FAHRENHEIT diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 6af0e96045c..74778682540 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -81,7 +81,7 @@ class RadioThermostat(ClimateDevice): return self._name @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return TEMP_FAHRENHEIT diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index 26d81e2b510..447d2e4f720 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -93,7 +93,7 @@ class VeraThermostat(VeraDevice, ClimateDevice): self._state = self.vera_device.get_hvac_mode() @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" return TEMP_FAHRENHEIT diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 767bed6ff94..3e12d4c6006 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -209,7 +209,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): return self._swing_list @property - def unit_of_measurement(self): + def temperature_unit(self): """Return the unit of measurement.""" if self._unit == 'C': return TEMP_CELSIUS diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py index 470e280faa7..6f4888ef3e5 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/climate/test_honeywell.py @@ -264,13 +264,13 @@ class TestHoneywellRound(unittest.TestCase): def test_attributes(self): """Test the attributes.""" self.assertEqual('House', self.round1.name) - self.assertEqual(TEMP_CELSIUS, self.round1.unit_of_measurement) + self.assertEqual(TEMP_CELSIUS, self.round1.temperature_unit) self.assertEqual(20, self.round1.current_temperature) self.assertEqual(21, self.round1.target_temperature) self.assertFalse(self.round1.is_away_mode_on) self.assertEqual('Hot Water', self.round2.name) - self.assertEqual(TEMP_CELSIUS, self.round2.unit_of_measurement) + self.assertEqual(TEMP_CELSIUS, self.round2.temperature_unit) self.assertEqual(21, self.round2.current_temperature) self.assertEqual(None, self.round2.target_temperature) self.assertFalse(self.round2.is_away_mode_on) @@ -330,9 +330,9 @@ class TestHoneywellUS(unittest.TestCase): def test_unit_of_measurement(self): """Test the unit of measurement.""" - self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.unit_of_measurement) + self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.temperature_unit) self.device.temperature_unit = 'C' - self.assertEqual(TEMP_CELSIUS, self.honeywell.unit_of_measurement) + self.assertEqual(TEMP_CELSIUS, self.honeywell.temperature_unit) def test_target_temp(self): """Test the target temperature.""" From 7cf9ff83bcb96c7f9a2169cd71b9f78d2065029d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 09:26:11 +0200 Subject: [PATCH 030/147] Migrate to voluptuous (#3293) [BREAKING CHANGE] --- homeassistant/components/proximity.py | 132 ++++--- tests/components/test_proximity.py | 488 +++++++++++++------------- 2 files changed, 304 insertions(+), 316 deletions(-) diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index ba0a192398f..fceec21dd5d 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -9,106 +9,92 @@ https://home-assistant.io/components/proximity/ """ import logging +import voluptuous as vol + +from homeassistant.const import ( + CONF_ZONE, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change -from homeassistant.util.location import distance from homeassistant.util.distance import convert -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT - -DEPENDENCIES = ['zone', 'device_tracker'] - -DOMAIN = 'proximity' - -NOT_SET = 'not set' - -# Default tolerance -DEFAULT_TOLERANCE = 1 - -# Default zone -DEFAULT_PROXIMITY_ZONE = 'home' - -# Default distance to zone -DEFAULT_DIST_TO_ZONE = NOT_SET - -# Default direction of travel -DEFAULT_DIR_OF_TRAVEL = NOT_SET - -# Default nearest device -DEFAULT_NEAREST = NOT_SET - -# Entity attributes -ATTR_DIST_FROM = 'dist_to_zone' -ATTR_DIR_OF_TRAVEL = 'dir_of_travel' -ATTR_NEAREST = 'nearest' +from homeassistant.util.location import distance +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +ATTR_DIR_OF_TRAVEL = 'dir_of_travel' +ATTR_DIST_FROM = 'dist_to_zone' +ATTR_NEAREST = 'nearest' -def setup_proximity_component(hass, config): +CONF_IGNORED_ZONES = 'ignored_zones' +CONF_TOLERANCE = 'tolerance' + +DEFAULT_DIR_OF_TRAVEL = 'not set' +DEFAULT_DIST_TO_ZONE = 'not set' +DEFAULT_NEAREST = 'not set' +DEFAULT_PROXIMITY_ZONE = 'home' +DEFAULT_TOLERANCE = 1 +DEPENDENCIES = ['zone', 'device_tracker'] +DOMAIN = 'proximity' + +UNITS = ['km', 'm', 'mi', 'ft'] + +ZONE_SCHEMA = vol.Schema({ + vol.Optional(CONF_ZONE, default=DEFAULT_PROXIMITY_ZONE): cv.string, + vol.Optional(CONF_DEVICES, default=[]): + vol.All(cv.ensure_list, [cv.entity_id]), + vol.Optional(CONF_IGNORED_ZONES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_TOLERANCE, default=DEFAULT_TOLERANCE): cv.positive_int, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.All(cv.string, vol.In(UNITS)), +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + cv.slug: ZONE_SCHEMA, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup_proximity_component(hass, name, config): """Set up individual proximity component.""" - # Get the devices from configuration.yaml. - if 'devices' not in config: - _LOGGER.error('devices not found in config') - return False - - ignored_zones = [] - if 'ignored_zones' in config: - for variable in config['ignored_zones']: - ignored_zones.append(variable) - - proximity_devices = [] - for variable in config['devices']: - proximity_devices.append(variable) - - # Get the direction of travel tolerance from configuration.yaml. - tolerance = config.get('tolerance', DEFAULT_TOLERANCE) - - # Get the zone to monitor proximity to from configuration.yaml. - proximity_zone = config.get('zone', DEFAULT_PROXIMITY_ZONE) - - # Get the unit of measurement from configuration.yaml. - unit_of_measure = config.get(ATTR_UNIT_OF_MEASUREMENT, - hass.config.units.length_unit) - + ignored_zones = config.get(CONF_IGNORED_ZONES) + proximity_devices = config.get(CONF_DEVICES) + tolerance = config.get(CONF_TOLERANCE) + proximity_zone = name + unit_of_measurement = config.get( + CONF_UNIT_OF_MEASUREMENT, hass.config.units.length_unit) zone_id = 'zone.{}'.format(proximity_zone) - state = hass.states.get(zone_id) - zone_friendly_name = (state.name).lower() - proximity = Proximity(hass, zone_friendly_name, DEFAULT_DIST_TO_ZONE, + proximity = Proximity(hass, proximity_zone, DEFAULT_DIST_TO_ZONE, DEFAULT_DIR_OF_TRAVEL, DEFAULT_NEAREST, ignored_zones, proximity_devices, tolerance, - zone_id, unit_of_measure) + zone_id, unit_of_measurement) proximity.entity_id = '{}.{}'.format(DOMAIN, proximity_zone) proximity.update_ha_state() - # Main command to monitor proximity of devices. - track_state_change(hass, proximity_devices, - proximity.check_proximity_state_change) + track_state_change( + hass, proximity_devices, proximity.check_proximity_state_change) return True def setup(hass, config): """Get the zones and offsets from configuration.yaml.""" - result = True - if isinstance(config[DOMAIN], list): - for proximity_config in config[DOMAIN]: - if not setup_proximity_component(hass, proximity_config): - result = False - elif not setup_proximity_component(hass, config[DOMAIN]): - result = False + for zone, proximity_config in config[DOMAIN].items(): + setup_proximity_component(hass, zone, proximity_config) - return result + return True -class Proximity(Entity): # pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-instance-attributes +class Proximity(Entity): """Representation of a Proximity.""" # pylint: disable=too-many-arguments def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel, nearest, ignored_zones, proximity_devices, tolerance, - proximity_zone, unit_of_measure): + proximity_zone, unit_of_measurement): """Initialize the proximity.""" self.hass = hass self.friendly_name = zone_friendly_name @@ -119,7 +105,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes self.proximity_devices = proximity_devices self.tolerance = tolerance self.proximity_zone = proximity_zone - self.unit_of_measure = unit_of_measure + self._unit_of_measurement = unit_of_measurement @property def name(self): @@ -134,7 +120,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return self.unit_of_measure + return self._unit_of_measurement @property def state_attributes(self): @@ -209,7 +195,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes # Add the device and distance to a dictionary. distances_to_zone[device] = round( - convert(dist_to_zone, 'm', self.unit_of_measure), 1) + convert(dist_to_zone, 'm', self.unit_of_measurement), 1) # Loop through each of the distances collected and work out the # closest. diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index cbd36a1fc1f..1a1033ab31d 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -1,13 +1,17 @@ """The tests for the Proximity component.""" -from homeassistant.components import proximity +import unittest +from homeassistant.components import proximity +from homeassistant.components.proximity import DOMAIN + +from homeassistant.bootstrap import setup_component from tests.common import get_test_home_assistant -class TestProximity: +class TestProximity(unittest.TestCase): """Test the Proximity component.""" - def setup_method(self, method): + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.states.set( @@ -27,31 +31,34 @@ class TestProximity: 'radius': 10 }) - def teardown_method(self, method): + def tearDown(self): """Stop everything that was started.""" self.hass.stop() def test_proximities(self): """Test a list of proximities.""" - assert proximity.setup(self.hass, { - 'proximity': [{ - 'zone': 'home', - 'ignored_zones': { - 'work' + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - }, { - 'zone': 'work', - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' - }] - }) + 'work': { + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) proximities = ['home', 'work'] @@ -66,40 +73,46 @@ class TestProximity: state = self.hass.states.get('proximity.' + prox) assert state.state == '0' - def test_proximities_missing_devices(self): - """Test a list of proximities with one missing devices.""" - assert not proximity.setup(self.hass, { - 'proximity': [{ - 'zone': 'home', - 'ignored_zones': { - 'work' + def test_proximities_setup(self): + """Test a list of proximities with missing devices.""" + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - }, { - 'zone': 'work', - 'tolerance': '1' - }] - }) + 'work': { + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) def test_proximity(self): """Test the proximity.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) state = self.hass.states.get('proximity.home') assert state.state == 'not set' @@ -111,75 +124,23 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.state == '0' - def test_no_devices_in_config(self): - """Test for missing devices in configuration.""" - assert not proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'tolerance': '1' - } - }) - - def test_no_tolerance_in_config(self): - """Test for missing tolerance in configuration .""" - assert proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - } - } - }) - - def test_no_ignored_zones_in_config(self): - """Test for ignored zones in configuration.""" - assert proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - } - }) - - def test_no_zone_in_config(self): - """Test for missing zone in configuration.""" - assert proximity.setup(self.hass, { - 'proximity': { - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - } - }) - def test_device_tracker_test1_in_zone(self): """Test for tracker in zone.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'home', @@ -196,19 +157,22 @@ class TestProximity: def test_device_trackers_in_zone(self): """Test for trackers in zone.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'home', @@ -234,18 +198,21 @@ class TestProximity: def test_device_tracker_test1_away(self): """Test for tracker state away.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -254,6 +221,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' @@ -261,17 +229,21 @@ class TestProximity: def test_device_tracker_test1_awayfurther(self): """Test for tracker state away further.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -284,31 +256,6 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - self.hass.states.set( - 'device_tracker.test1', 'not_home', - { - 'friendly_name': 'test1', - 'latitude': 40.1, - 'longitude': 20.1 - }) - self.hass.block_till_done() - state = self.hass.states.get('proximity.home') - assert state.attributes.get('nearest') == 'test1' - assert state.attributes.get('dir_of_travel') == 'away_from' - - def test_device_tracker_test1_awaycloser(self): - """Test for tracker state away closer.""" - assert proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - } - } - }) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -320,32 +267,67 @@ class TestProximity: self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' - assert state.attributes.get('dir_of_travel') == 'unknown' - self.hass.states.set( - 'device_tracker.test1', 'not_home', - { - 'friendly_name': 'test1', - 'latitude': 20.1, - 'longitude': 10.1 - }) - self.hass.block_till_done() - state = self.hass.states.get('proximity.home') - assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'towards' - def test_all_device_trackers_in_ignored_zone(self): - """Test for tracker in ignored zone.""" - assert proximity.setup(self.hass, { + def test_device_tracker_test1_awaycloser(self): + """Test for tracker state away closer.""" + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'away_from' + + def test_all_device_trackers_in_ignored_zone(self): + """Test for tracker in ignored zone.""" + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'work', @@ -360,18 +342,21 @@ class TestProximity: def test_device_tracker_test1_no_coordinates(self): """Test for tracker with no coordinates.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -397,15 +382,18 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1', } } }) @@ -421,6 +409,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( 'device_tracker.test2', 'not_home', { @@ -449,13 +438,14 @@ class TestProximity: self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -471,6 +461,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -499,13 +490,14 @@ class TestProximity: self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -536,15 +528,17 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -566,6 +560,7 @@ class TestProximity: 'longitude': 10.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -574,6 +569,7 @@ class TestProximity: 'longitude': 20.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -582,12 +578,14 @@ class TestProximity: 'longitude': 15.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'work', { 'friendly_name': 'test1' }) self.hass.block_till_done() + state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -596,14 +594,15 @@ class TestProximity: """Test for tracker states.""" assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': 1000 + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': 1000 + } } }) @@ -618,6 +617,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -644,15 +644,17 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) From 7cf2c4817571090d7e9f1ad70745a24054c27684 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 09:27:15 +0200 Subject: [PATCH 031/147] Use voluptuous for FitBit (#3686) * Migrate to voluptuous * Fix default --- homeassistant/components/sensor/fitbit.py | 290 ++++++++++++---------- 1 file changed, 153 insertions(+), 137 deletions(-) diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py index b99a4f320c9..cf11c9bb393 100644 --- a/homeassistant/components/sensor/fitbit.py +++ b/homeassistant/components/sensor/fitbit.py @@ -10,110 +10,125 @@ import logging import datetime import time -from homeassistant.util import Throttle +import voluptuous as vol + +from homeassistant.components.http import HomeAssistantView +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component -from homeassistant.components.http import HomeAssistantView +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["fitbit==0.2.3"] -DEPENDENCIES = ["http"] - -ICON = "mdi:walk" +REQUIREMENTS = ['fitbit==0.2.3'] _CONFIGURING = {} +_LOGGER = logging.getLogger(__name__) + +ATTR_ACCESS_TOKEN = 'access_token' +ATTR_REFRESH_TOKEN = 'refresh_token' +ATTR_CLIENT_ID = 'client_id' +ATTR_CLIENT_SECRET = 'client_secret' +ATTR_LAST_SAVED_AT = 'last_saved_at' + +CONF_MONITORED_RESOURCES = 'monitored_resources' + +DEPENDENCIES = ['http'] + +FITBIT_AUTH_CALLBACK_PATH = '/auth/fitbit/callback' +FITBIT_AUTH_START = '/auth/fitbit' +FITBIT_CONFIG_FILE = 'fitbit.conf' +FITBIT_DEFAULT_RESOURCES = ['activities/steps'] + +ICON = 'mdi:walk' -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=30) -FITBIT_AUTH_START = "/auth/fitbit" -FITBIT_AUTH_CALLBACK_PATH = "/auth/fitbit/callback" - DEFAULT_CONFIG = { - "client_id": "CLIENT_ID_HERE", - "client_secret": "CLIENT_SECRET_HERE" + 'client_id': 'CLIENT_ID_HERE', + 'client_secret': 'CLIENT_SECRET_HERE' } -FITBIT_CONFIG_FILE = "fitbit.conf" - FITBIT_RESOURCES_LIST = { - "activities/activityCalories": "cal", - "activities/calories": "cal", - "activities/caloriesBMR": "cal", - "activities/distance": "", - "activities/elevation": "", - "activities/floors": "floors", - "activities/heart": "bpm", - "activities/minutesFairlyActive": "minutes", - "activities/minutesLightlyActive": "minutes", - "activities/minutesSedentary": "minutes", - "activities/minutesVeryActive": "minutes", - "activities/steps": "steps", - "activities/tracker/activityCalories": "cal", - "activities/tracker/calories": "cal", - "activities/tracker/distance": "", - "activities/tracker/elevation": "", - "activities/tracker/floors": "floors", - "activities/tracker/minutesFairlyActive": "minutes", - "activities/tracker/minutesLightlyActive": "minutes", - "activities/tracker/minutesSedentary": "minutes", - "activities/tracker/minutesVeryActive": "minutes", - "activities/tracker/steps": "steps", - "body/bmi": "BMI", - "body/fat": "%", - "sleep/awakeningsCount": "times awaken", - "sleep/efficiency": "%", - "sleep/minutesAfterWakeup": "minutes", - "sleep/minutesAsleep": "minutes", - "sleep/minutesAwake": "minutes", - "sleep/minutesToFallAsleep": "minutes", - "sleep/startTime": "start time", - "sleep/timeInBed": "time in bed", - "body/weight": "" + 'activities/activityCalories': 'cal', + 'activities/calories': 'cal', + 'activities/caloriesBMR': 'cal', + 'activities/distance': '', + 'activities/elevation': '', + 'activities/floors': 'floors', + 'activities/heart': 'bpm', + 'activities/minutesFairlyActive': 'minutes', + 'activities/minutesLightlyActive': 'minutes', + 'activities/minutesSedentary': 'minutes', + 'activities/minutesVeryActive': 'minutes', + 'activities/steps': 'steps', + 'activities/tracker/activityCalories': 'cal', + 'activities/tracker/calories': 'cal', + 'activities/tracker/distance': '', + 'activities/tracker/elevation': '', + 'activities/tracker/floors': 'floors', + 'activities/tracker/minutesFairlyActive': 'minutes', + 'activities/tracker/minutesLightlyActive': 'minutes', + 'activities/tracker/minutesSedentary': 'minutes', + 'activities/tracker/minutesVeryActive': 'minutes', + 'activities/tracker/steps': 'steps', + 'body/bmi': 'BMI', + 'body/fat': '%', + 'sleep/awakeningsCount': 'times awaken', + 'sleep/efficiency': '%', + 'sleep/minutesAfterWakeup': 'minutes', + 'sleep/minutesAsleep': 'minutes', + 'sleep/minutesAwake': 'minutes', + 'sleep/minutesToFallAsleep': 'minutes', + 'sleep/startTime': 'start time', + 'sleep/timeInBed': 'time in bed', + 'body/weight': '' } -FITBIT_DEFAULT_RESOURCE_LIST = ["activities/steps"] - FITBIT_MEASUREMENTS = { - "en_US": { - "duration": "ms", - "distance": "mi", - "elevation": "ft", - "height": "in", - "weight": "lbs", - "body": "in", - "liquids": "fl. oz.", - "blood glucose": "mg/dL", + 'en_US': { + 'duration': 'ms', + 'distance': 'mi', + 'elevation': 'ft', + 'height': 'in', + 'weight': 'lbs', + 'body': 'in', + 'liquids': 'fl. oz.', + 'blood glucose': 'mg/dL', }, - "en_GB": { - "duration": "milliseconds", - "distance": "kilometers", - "elevation": "meters", - "height": "centimeters", - "weight": "stone", - "body": "centimeters", - "liquids": "milliliters", - "blood glucose": "mmol/L" + 'en_GB': { + 'duration': 'milliseconds', + 'distance': 'kilometers', + 'elevation': 'meters', + 'height': 'centimeters', + 'weight': 'stone', + 'body': 'centimeters', + 'liquids': 'milliliters', + 'blood glucose': 'mmol/L' }, - "metric": { - "duration": "milliseconds", - "distance": "kilometers", - "elevation": "meters", - "height": "centimeters", - "weight": "kilograms", - "body": "centimeters", - "liquids": "milliliters", - "blood glucose": "mmol/L" + 'metric': { + 'duration': 'milliseconds', + 'distance': 'kilometers', + 'elevation': 'meters', + 'height': 'centimeters', + 'weight': 'kilograms', + 'body': 'centimeters', + 'liquids': 'milliliters', + 'blood glucose': 'mmol/L' } } +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES): + vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]), +}) + def config_from_file(filename, config=None): """Small configuration file management function.""" if config: # We"re writing configuration try: - with open(filename, "w") as fdesc: + with open(filename, 'w') as fdesc: fdesc.write(json.dumps(config)) except IOError as error: _LOGGER.error("Saving config file failed: %s", error) @@ -123,7 +138,7 @@ def config_from_file(filename, config=None): # We"re reading config if os.path.isfile(filename): try: - with open(filename, "r") as fdesc: + with open(filename, 'r') as fdesc: return json.loads(fdesc.read()) except IOError as error: _LOGGER.error("Reading config file failed: %s", error) @@ -136,7 +151,7 @@ def config_from_file(filename, config=None): def request_app_setup(hass, config, add_devices, config_path, discovery_info=None): """Assist user with configuring the Fitbit dev application.""" - configurator = get_component("configurator") + configurator = get_component('configurator') # pylint: disable=unused-argument def fitbit_configuration_callback(callback_data): @@ -147,7 +162,7 @@ def request_app_setup(hass, config, add_devices, config_path, if config_file == DEFAULT_CONFIG: error_msg = ("You didn't correctly modify fitbit.conf", " please try again") - configurator.notify_errors(_CONFIGURING["fitbit"], error_msg) + configurator.notify_errors(_CONFIGURING['fitbit'], error_msg) else: setup_platform(hass, config, add_devices, discovery_info) else: @@ -167,8 +182,8 @@ def request_app_setup(hass, config, add_devices, config_path, submit = "I have saved my Client ID and Client Secret into fitbit.conf." - _CONFIGURING["fitbit"] = configurator.request_config( - hass, "Fitbit", fitbit_configuration_callback, + _CONFIGURING['fitbit'] = configurator.request_config( + hass, 'Fitbit', fitbit_configuration_callback, description=description, submit_caption=submit, description_image="/static/images/config_fitbit_app.png" ) @@ -176,10 +191,10 @@ def request_app_setup(hass, config, add_devices, config_path, def request_oauth_completion(hass): """Request user complete Fitbit OAuth2 flow.""" - configurator = get_component("configurator") + configurator = get_component('configurator') if "fitbit" in _CONFIGURING: configurator.notify_errors( - _CONFIGURING["fitbit"], "Failed to register, please try again.") + _CONFIGURING['fitbit'], "Failed to register, please try again.") return @@ -187,12 +202,12 @@ def request_oauth_completion(hass): def fitbit_configuration_callback(callback_data): """The actions to do when our configuration callback is called.""" - start_url = "{}{}".format(hass.config.api.base_url, FITBIT_AUTH_START) + start_url = '{}{}'.format(hass.config.api.base_url, FITBIT_AUTH_START) description = "Please authorize Fitbit by visiting {}".format(start_url) - _CONFIGURING["fitbit"] = configurator.request_config( - hass, "Fitbit", fitbit_configuration_callback, + _CONFIGURING['fitbit'] = configurator.request_config( + hass, 'Fitbit', fitbit_configuration_callback, description=description, submit_caption="I have authorized Fitbit." ) @@ -206,60 +221,61 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if os.path.isfile(config_path): config_file = config_from_file(config_path) if config_file == DEFAULT_CONFIG: - request_app_setup(hass, config, add_devices, config_path, - discovery_info=None) + request_app_setup( + hass, config, add_devices, config_path, discovery_info=None) return False else: config_file = config_from_file(config_path, DEFAULT_CONFIG) - request_app_setup(hass, config, add_devices, config_path, - discovery_info=None) + request_app_setup( + hass, config, add_devices, config_path, discovery_info=None) return False if "fitbit" in _CONFIGURING: - get_component("configurator").request_done(_CONFIGURING.pop("fitbit")) + get_component('configurator').request_done(_CONFIGURING.pop("fitbit")) import fitbit - access_token = config_file.get("access_token") - refresh_token = config_file.get("refresh_token") + access_token = config_file.get(ATTR_ACCESS_TOKEN) + refresh_token = config_file.get(ATTR_REFRESH_TOKEN) if None not in (access_token, refresh_token): - authd_client = fitbit.Fitbit(config_file.get("client_id"), - config_file.get("client_secret"), + authd_client = fitbit.Fitbit(config_file.get(ATTR_CLIENT_ID), + config_file.get(ATTR_CLIENT_SECRET), access_token=access_token, refresh_token=refresh_token) - if int(time.time()) - config_file.get("last_saved_at", 0) > 3600: + if int(time.time()) - config_file.get(ATTR_LAST_SAVED_AT, 0) > 3600: authd_client.client.refresh_token() authd_client.system = authd_client.user_profile_get()["user"]["locale"] if authd_client.system != 'en_GB': if hass.config.units.is_metric: - authd_client.system = "metric" + authd_client.system = 'metric' else: - authd_client.system = "en_US" + authd_client.system = 'en_US' dev = [] - for resource in config.get("monitored_resources", - FITBIT_DEFAULT_RESOURCE_LIST): - dev.append(FitbitSensor(authd_client, config_path, resource, - hass.config.units.is_metric)) + for resource in config.get(CONF_MONITORED_RESOURCES): + dev.append(FitbitSensor( + authd_client, config_path, resource, + hass.config.units.is_metric)) add_devices(dev) else: - oauth = fitbit.api.FitbitOauth2Client(config_file.get("client_id"), - config_file.get("client_secret")) + oauth = fitbit.api.FitbitOauth2Client( + config_file.get(ATTR_CLIENT_ID), + config_file.get(ATTR_CLIENT_SECRET)) - redirect_uri = "{}{}".format(hass.config.api.base_url, + redirect_uri = '{}{}'.format(hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH) fitbit_auth_start_url, _ = oauth.authorize_token_url( redirect_uri=redirect_uri, - scope=["activity", "heartrate", "nutrition", "profile", - "settings", "sleep", "weight"]) + scope=['activity', 'heartrate', 'nutrition', 'profile', + 'settings', 'sleep', 'weight']) hass.wsgi.register_redirect(FITBIT_AUTH_START, fitbit_auth_start_url) - hass.wsgi.register_view(FitbitAuthCallbackView(hass, config, - add_devices, oauth)) + hass.wsgi.register_view(FitbitAuthCallbackView( + hass, config, add_devices, oauth)) request_oauth_completion(hass) @@ -288,12 +304,12 @@ class FitbitAuthCallbackView(HomeAssistantView): response_message = """Fitbit has been successfully authorized! You can close this window now!""" - if data.get("code") is not None: - redirect_uri = "{}{}".format(self.hass.config.api.base_url, - FITBIT_AUTH_CALLBACK_PATH) + if data.get('code') is not None: + redirect_uri = '{}{}'.format( + self.hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH) try: - self.oauth.fetch_access_token(data.get("code"), redirect_uri) + self.oauth.fetch_access_token(data.get('code'), redirect_uri) except MissingTokenError as error: _LOGGER.error("Missing token: %s", error) response_message = """Something went wrong when @@ -315,14 +331,14 @@ class FitbitAuthCallbackView(HomeAssistantView):

{}

""".format(response_message) config_contents = { - "access_token": self.oauth.token["access_token"], - "refresh_token": self.oauth.token["refresh_token"], - "client_id": self.oauth.client_id, - "client_secret": self.oauth.client_secret + ATTR_ACCESS_TOKEN: self.oauth.token['access_token'], + ATTR_REFRESH_TOKEN: self.oauth.token['refresh_token'], + ATTR_CLIENT_ID: self.oauth.client_id, + ATTR_CLIENT_SECRET: self.oauth.client_secret } if not config_from_file(self.hass.config.path(FITBIT_CONFIG_FILE), config_contents): - _LOGGER.error("failed to save config file") + _LOGGER.error("Failed to save config file") setup_platform(self.hass, self.config, self.add_devices) @@ -338,22 +354,22 @@ class FitbitSensor(Entity): self.client = client self.config_path = config_path self.resource_type = resource_type - pretty_resource = self.resource_type.replace("activities/", "") - pretty_resource = pretty_resource.replace("/", " ") + pretty_resource = self.resource_type.replace('activities/', '') + pretty_resource = pretty_resource.replace('/', ' ') pretty_resource = pretty_resource.title() - if pretty_resource == "Body Bmi": - pretty_resource = "BMI" + if pretty_resource == 'Body Bmi': + pretty_resource = 'BMI' self._name = pretty_resource unit_type = FITBIT_RESOURCES_LIST[self.resource_type] if unit_type == "": - split_resource = self.resource_type.split("/") + split_resource = self.resource_type.split('/') try: measurement_system = FITBIT_MEASUREMENTS[self.client.system] except KeyError: if is_metric: - measurement_system = FITBIT_MEASUREMENTS["metric"] + measurement_system = FITBIT_MEASUREMENTS['metric'] else: - measurement_system = FITBIT_MEASUREMENTS["en_US"] + measurement_system = FITBIT_MEASUREMENTS['en_US'] unit_type = measurement_system[split_resource[-1]] self._unit_of_measurement = unit_type self._state = 0 @@ -384,16 +400,16 @@ class FitbitSensor(Entity): def update(self): """Get the latest data from the Fitbit API and update the states.""" container = self.resource_type.replace("/", "-") - response = self.client.time_series(self.resource_type, period="7d") - self._state = response[container][-1].get("value") - if self.resource_type == "activities/heart": - self._state = response[container][-1].get("restingHeartRate") + response = self.client.time_series(self.resource_type, period='7d') + self._state = response[container][-1].get('value') + if self.resource_type == 'activities/heart': + self._state = response[container][-1].get('restingHeartRate') config_contents = { - "access_token": self.client.client.token["access_token"], - "refresh_token": self.client.client.token["refresh_token"], - "client_id": self.client.client.client_id, - "client_secret": self.client.client.client_secret, - "last_saved_at": int(time.time()) + ATTR_ACCESS_TOKEN: self.client.client.token['access_token'], + ATTR_REFRESH_TOKEN: self.client.client.token['refresh_token'], + ATTR_CLIENT_ID: self.client.client.client_id, + ATTR_CLIENT_SECRET: self.client.client.client_secret, + ATTR_LAST_SAVED_AT: int(time.time()) } if not config_from_file(self.config_path, config_contents): - _LOGGER.error("failed to save config file") + _LOGGER.error("Failed to save config file") From 8ded8f572a7ed25e35e0f4689e2e7f56929d1b5c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 09:28:19 +0200 Subject: [PATCH 032/147] Add/adjust attribution of sensor platform (#3719) * Add/adjust attribution * Fix typo --- homeassistant/components/sensor/bom.py | 16 ++++++----- .../components/sensor/coinmarketcap.py | 7 +++-- homeassistant/components/sensor/darksky.py | 10 ++++++- homeassistant/components/sensor/fixer.py | 18 ++++++------ .../components/sensor/openweathermap.py | 10 ++++++- .../sensor/swiss_hydrological_data.py | 28 +++++++++++-------- .../sensor/swiss_public_transport.py | 16 ++++++----- .../components/sensor/wunderground.py | 19 +++++++++---- .../components/sensor/yahoo_finance.py | 13 ++++----- homeassistant/components/sensor/yr.py | 10 +++++-- homeassistant/components/sensor/yweather.py | 8 +++--- homeassistant/const.py | 3 ++ 12 files changed, 101 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/sensor/bom.py index eb1fddeb810..a49ac48ba6f 100644 --- a/homeassistant/components/sensor/bom.py +++ b/homeassistant/components/sensor/bom.py @@ -11,16 +11,17 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, - STATE_UNKNOWN, CONF_NAME) + CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, STATE_UNKNOWN, CONF_NAME, + ATTR_ATTRIBUTION) +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv _RESOURCE = 'http://www.bom.gov.au/fwo/{}/{}.{}.json' _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Data provided by the Australian Bureau of Meteorology" CONF_ZONE_ID = 'zone_id' CONF_WMO_ID = 'wmo_id' @@ -75,7 +76,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the BOM sensor.""" + """Set up the BOM sensor.""" rest = BOMCurrentData( hass, config.get(CONF_ZONE_ID), config.get(CONF_WMO_ID)) @@ -96,7 +97,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BOMCurrentSensor(Entity): - """Implementing the BOM current sensor.""" + """Implementation of a BOM current sensor.""" def __init__(self, rest, condition, stationname): """Initialize the sensor.""" @@ -131,6 +132,7 @@ class BOMCurrentSensor(Entity): attr['Station Name'] = self.rest.data['name'] attr['Last Update'] = datetime.datetime.strptime(str( self.rest.data['local_date_time_full']), '%Y%m%d%H%M%S') + attr[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION return attr @property diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/sensor/coinmarketcap.py index 83adcac7fea..a166ec91d10 100644 --- a/homeassistant/components/sensor/coinmarketcap.py +++ b/homeassistant/components/sensor/coinmarketcap.py @@ -12,9 +12,10 @@ from urllib.error import HTTPError import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['coinmarketcap==2.0.1'] @@ -30,6 +31,7 @@ ATTR_PRICE = 'price_usd' ATTR_SYMBOL = 'symbol' ATTR_TOTAL_SUPPLY = 'total_supply' +CONF_ATTRIBUTION = "Data provided by CoinMarketCap" CONF_CURRENCY = 'currency' DEFAULT_CURRENCY = 'bitcoin' @@ -89,7 +91,7 @@ class CoinMarketCapSensor(Entity): return ICON @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_24H_VOLUME_USD: self._ticker.get('24h_volume_usd'), @@ -99,6 +101,7 @@ class CoinMarketCapSensor(Entity): ATTR_PERCENT_CHANGE_7D: self._ticker.get('percent_change_7d'), ATTR_SYMBOL: self._ticker.get('symbol'), ATTR_TOTAL_SUPPLY: self._ticker.get('total_supply'), + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } # pylint: disable=too-many-branches diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 241ab5f4655..f092959ba1d 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -13,7 +13,7 @@ from requests.exceptions import ConnectionError as ConnectError, \ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS) + CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -22,6 +22,7 @@ REQUIREMENTS = ['python-forecastio==1.3.5'] _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Powered by Dark Sky" CONF_UNITS = 'units' CONF_UPDATE_INTERVAL = 'update_interval' @@ -178,6 +179,13 @@ class DarkSkySensor(Entity): """Icon to use in the frontend, if any.""" return SENSOR_TYPES[self.type][6] + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + } + # pylint: disable=too-many-branches,too-many-statements def update(self): """Get the latest data from Dark Sky and updates the states.""" diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/sensor/fixer.py index 8aa5002fbfa..c8fe3b06c4e 100644 --- a/homeassistant/components/sensor/fixer.py +++ b/homeassistant/components/sensor/fixer.py @@ -10,7 +10,7 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -19,6 +19,11 @@ REQUIREMENTS = ['fixerio==0.1.1'] _LOGGER = logging.getLogger(__name__) +ATTR_BASE = 'Base currency' +ATTR_EXCHANGE_RATE = 'Exchange rate' +ATTR_TARGET = 'Target currency' + +CONF_ATTRIBUTION = "Data provided by the European Central Bank (ECB)" CONF_BASE = 'base' CONF_TARGET = 'target' @@ -29,10 +34,6 @@ ICON = 'mdi:currency' MIN_TIME_BETWEEN_UPDATES = timedelta(days=1) -STATE_ATTR_BASE = 'Base currency' -STATE_ATTR_EXCHANGE_RATE = 'Exchange rate' -STATE_ATTR_TARGET = 'Target currency' - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_TARGET): cv.string, vol.Optional(CONF_BASE, default=DEFAULT_BASE): cv.string, @@ -90,9 +91,10 @@ class ExchangeRateSensor(Entity): """Return the state attributes.""" if self.data.rate is not None: return { - STATE_ATTR_BASE: self.data.rate['base'], - STATE_ATTR_TARGET: self._target, - STATE_ATTR_EXCHANGE_RATE: self.data.rate['rates'][self._target] + ATTR_BASE: self.data.rate['base'], + ATTR_TARGET: self._target, + ATTR_EXCHANGE_RATE: self.data.rate['rates'][self._target], + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } @property diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 9dcb354a125..b59bfa7dab5 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_API_KEY, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, - CONF_MONITORED_CONDITIONS) + CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -21,6 +21,7 @@ REQUIREMENTS = ['pyowm==2.5.0'] _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Data provied by OpenWeatherMap" CONF_FORECAST = 'forecast' DEFAULT_NAME = 'OWM' @@ -113,6 +114,13 @@ class OpenWeatherMapSensor(Entity): """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + } + # pylint: disable=too-many-branches def update(self): """Get the latest data from OWM and updates the states.""" diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/sensor/swiss_hydrological_data.py index b2e95690727..c8e0be68062 100644 --- a/homeassistant/components/sensor/swiss_hydrological_data.py +++ b/homeassistant/components/sensor/swiss_hydrological_data.py @@ -11,7 +11,8 @@ import voluptuous as vol import requests from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (TEMP_CELSIUS, CONF_NAME, STATE_UNKNOWN) +from homeassistant.const import ( + TEMP_CELSIUS, CONF_NAME, STATE_UNKNOWN, ATTR_ATTRIBUTION) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -22,19 +23,23 @@ _LOGGER = logging.getLogger(__name__) _RESOURCE = 'http://www.hydrodata.ch/xml/SMS.xml' CONF_STATION = 'station' +CONF_ATTRIBUTION = "Data provided by the Swiss Federal Office for the " \ + "Environment FOEN" + DEFAULT_NAME = 'Water temperature' + ICON = 'mdi:cup-water' -ATTR_LOCATION = 'Location' -ATTR_UPDATE = 'Update' -ATTR_DISCHARGE = 'Discharge' -ATTR_WATERLEVEL = 'Level' -ATTR_DISCHARGE_MEAN = 'Discharge mean' -ATTR_WATERLEVEL_MEAN = 'Level mean' -ATTR_TEMPERATURE_MEAN = 'Temperature mean' -ATTR_DISCHARGE_MAX = 'Discharge max' -ATTR_WATERLEVEL_MAX = 'Level max' -ATTR_TEMPERATURE_MAX = 'Temperature max' +ATTR_LOCATION = 'location' +ATTR_UPDATE = 'update' +ATTR_DISCHARGE = 'discharge' +ATTR_WATERLEVEL = 'level' +ATTR_DISCHARGE_MEAN = 'discharge_mean' +ATTR_WATERLEVEL_MEAN = 'level_mean' +ATTR_TEMPERATURE_MEAN = 'temperature_mean' +ATTR_DISCHARGE_MAX = 'discharge_max' +ATTR_WATERLEVEL_MAX = 'level_max' +ATTR_TEMPERATURE_MAX = 'temperature_max' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_STATION): vol.Coerce(int), @@ -125,6 +130,7 @@ class SwissHydrologicalDataSensor(Entity): attributes[ATTR_LOCATION] = self.data.measurings['location'] attributes[ATTR_UPDATE] = self.data.measurings['update_time'] + attributes[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION return attributes @property diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index d7d80ac2a3c..823a96cc01f 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -11,7 +11,7 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -20,12 +20,13 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = 'http://transport.opendata.ch/v1/' -ATTR_DEPARTURE_TIME1 = 'Next departure' -ATTR_DEPARTURE_TIME2 = 'Next on departure' -ATTR_REMAINING_TIME = 'Remaining time' -ATTR_START = 'Start' -ATTR_TARGET = 'Destination' +ATTR_DEPARTURE_TIME1 = 'next_departure' +ATTR_DEPARTURE_TIME2 = 'next_on_departure' +ATTR_REMAINING_TIME = 'remaining_time' +ATTR_START = 'start' +ATTR_TARGET = 'destination' +CONF_ATTRIBUTION = "Data provided by transport.opendata.ch" CONF_DESTINATION = 'to' CONF_START = 'from' @@ -96,7 +97,8 @@ class SwissPublicTransportSensor(Entity): ATTR_START: self._from, ATTR_TARGET: self._to, ATTR_REMAINING_TIME: '{}'.format( - ':'.join(str(self._times[2]).split(':')[:2])) + ':'.join(str(self._times[2]).split(':')[:2])), + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } @property diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 623016518ac..ef20e3f2679 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -11,16 +11,17 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS, - STATE_UNKNOWN) + STATE_UNKNOWN, ATTR_ATTRIBUTION) +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv _RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/' _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Data provided by the WUnderground weather service" CONF_PWS_ID = 'pws_id' MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) @@ -108,6 +109,13 @@ class WUndergroundSensor(Entity): else: return STATE_UNKNOWN + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + } + @property def entity_picture(self): """Return the entity picture.""" @@ -123,9 +131,8 @@ class WUndergroundSensor(Entity): """Update current conditions.""" self.rest.update() + # pylint: disable=too-few-public-methods - - class WUndergroundData(object): """Get data from WUnderground.""" diff --git a/homeassistant/components/sensor/yahoo_finance.py b/homeassistant/components/sensor/yahoo_finance.py index 822c50823fc..a389a13656d 100644 --- a/homeassistant/components/sensor/yahoo_finance.py +++ b/homeassistant/components/sensor/yahoo_finance.py @@ -10,7 +10,7 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -19,7 +19,9 @@ REQUIREMENTS = ['yahoo-finance==1.2.1'] _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Stock market information provided by Yahoo! Inc." CONF_SYMBOL = 'symbol' + DEFAULT_SYMBOL = 'YHOO' DEFAULT_NAME = 'Yahoo Stock' @@ -28,8 +30,8 @@ ICON = 'mdi:currency-usd' MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) ATTR_CHANGE = 'Change' -ATTR_OPEN = 'Open' -ATTR_PREV_CLOSE = 'Prev. Close' +ATTR_OPEN = 'open' +ATTR_PREV_CLOSE = 'prev_close' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SYMBOL, default=DEFAULT_SYMBOL): cv.string, @@ -82,10 +84,7 @@ class YahooFinanceSensor(Entity): ATTR_CHANGE: self.data.price_change, ATTR_OPEN: self.data.price_open, ATTR_PREV_CLOSE: self.data.prev_close, - 'About': "Stock market information delivered by Yahoo!" - " Inc. are provided free of charge for use" - " by individuals and non-profit organizations" - " for personal, non-commercial uses." + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } @property diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index d69bd65688a..be7693f6e4f 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -5,13 +5,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.yr/ """ import logging + import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_ELEVATION, CONF_MONITORED_CONDITIONS) + CONF_LATITUDE, CONF_LONGITUDE, CONF_ELEVATION, CONF_MONITORED_CONDITIONS, + ATTR_ATTRIBUTION) from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util @@ -19,6 +21,9 @@ REQUIREMENTS = ['xmltodict==0.10.2'] _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Weather forecast from yr.no, delivered by the Norwegian " \ + "Meteorological Institute and the NRK." + # Sensor types are defined like so: SENSOR_TYPES = { 'symbol': ['Symbol', None], @@ -108,8 +113,7 @@ class YrSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" return { - 'about': "Weather forecast from yr.no, delivered by the" - " Norwegian Meteorological Institute and the NRK" + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } @property diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index f482d8d2e2c..f59913facb8 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -11,7 +11,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - TEMP_CELSIUS, CONF_MONITORED_CONDITIONS, CONF_NAME, STATE_UNKNOWN) + TEMP_CELSIUS, CONF_MONITORED_CONDITIONS, CONF_NAME, STATE_UNKNOWN, + ATTR_ATTRIBUTION) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -20,6 +21,7 @@ REQUIREMENTS = ["yahooweather==0.8"] _LOGGER = logging.getLogger(__name__) +CONF_ATTRIBUTION = "Weather details provided by Yahoo! Inc." CONF_FORECAST = 'forecast' CONF_WOEID = 'woeid' @@ -140,9 +142,7 @@ class YahooWeatherSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" return { - 'about': "Weather forecast delivered by Yahoo! Inc. are provided" - " free of charge for use by individuals and non-profit" - " organizations for personal, non-commercial uses." + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, } def update(self): diff --git a/homeassistant/const.py b/homeassistant/const.py index 6203d99ec20..e2670d2b08f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -173,6 +173,9 @@ STATE_UNLOCKED = 'unlocked' STATE_UNAVAILABLE = 'unavailable' # #### STATE AND EVENT ATTRIBUTES #### +# Attribution +ATTR_ATTRIBUTION = 'attribution' + # Contains current time for a TIME_CHANGED event ATTR_NOW = 'now' From 0568ef025bec97bf638a24fbedf1789be4bfb1ad Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 09:53:24 +0200 Subject: [PATCH 033/147] Use voluptuous for Heatmiser (#3732) --- homeassistant/components/climate/heatmiser.py | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py index 06fac09013c..a6dd01af4ab 100644 --- a/homeassistant/components/climate/heatmiser.py +++ b/homeassistant/components/climate/heatmiser.py @@ -1,56 +1,54 @@ """ Support for the PRT Heatmiser themostats using the V3 protocol. -See https://github.com/andylockran/heatmiserV3 for more info on the -heatmiserV3 module dependency. - For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.heatmiser/ """ import logging -from homeassistant.components.climate import ClimateDevice -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE +import voluptuous as vol -CONF_IPADDRESS = 'ipaddress' -CONF_PORT = 'port' -CONF_TSTATS = 'tstats' +from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.const import ( + TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID) +import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ["heatmiserV3==0.9.1"] +REQUIREMENTS = ['heatmiserV3==0.9.1'] _LOGGER = logging.getLogger(__name__) +CONF_IPADDRESS = 'ipaddress' +CONF_TSTATS = 'tstats' +TSTATS_SCHEMA = vol.Schema({ + vol.Required(CONF_ID): cv.string, + vol.Required(CONF_NAME): cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_IPADDRESS): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_TSTATS, default={}): + vol.Schema({cv.string: TSTATS_SCHEMA}), +}) + + +# pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the heatmiser thermostat.""" from heatmiserV3 import heatmiser, connection - ipaddress = str(config[CONF_IPADDRESS]) - port = str(config[CONF_PORT]) - - if ipaddress is None or port is None: - _LOGGER.error("Missing required configuration items %s or %s", - CONF_IPADDRESS, CONF_PORT) - return False + ipaddress = config.get(CONF_IPADDRESS) + port = str(config.get(CONF_PORT)) + tstats = config.get(CONF_TSTATS) serport = connection.connection(ipaddress, port) serport.open() - tstats = [] - if CONF_TSTATS in config: - tstats = config[CONF_TSTATS] - - if tstats is None: - _LOGGER.error("No thermostats configured.") - return False - - for tstat in tstats: + for thermostat, tstat in tstats.items(): add_devices([ HeatmiserV3Thermostat( - heatmiser, - tstat.get("id"), - tstat.get("name"), - serport) + heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport) ]) return @@ -69,7 +67,7 @@ class HeatmiserV3Thermostat(ClimateDevice): self._id = device self.dcb = None self.update() - self._target_temperature = int(self.dcb.get("roomset")) + self._target_temperature = int(self.dcb.get('roomset')) @property def name(self): @@ -85,9 +83,9 @@ class HeatmiserV3Thermostat(ClimateDevice): def current_temperature(self): """Return the current temperature.""" if self.dcb is not None: - low = self.dcb.get("floortemplow ") - high = self.dcb.get("floortemphigh") - temp = (high*256 + low)/10.0 + low = self.dcb.get('floortemplow ') + high = self.dcb.get('floortemphigh') + temp = (high * 256 + low) / 10.0 self._current_temperature = temp else: self._current_temperature = None From a99f36f519e4e1de1164b6846ef35d27dbd8cc4e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 09:56:57 +0200 Subject: [PATCH 034/147] Migrate to voluptuous (#3737) --- homeassistant/components/sensor/arduino.py | 36 +++++++++++++------ homeassistant/components/switch/arduino.py | 41 ++++++++++++++++------ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py index 203848fbe6e..03307a49768 100644 --- a/homeassistant/components/sensor/arduino.py +++ b/homeassistant/components/sensor/arduino.py @@ -8,28 +8,44 @@ https://home-assistant.io/components/sensor.arduino/ """ import logging +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.components.arduino as arduino -from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv + + +_LOGGER = logging.getLogger(__name__) + +CONF_PINS = 'pins' +CONF_TYPE = 'analog' DEPENDENCIES = ['arduino'] -_LOGGER = logging.getLogger(__name__) + +PIN_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PINS): + vol.Schema({cv.positive_int: PIN_SCHEMA}), +}) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Arduino platform.""" + """Set up the Arduino platform.""" # Verify that the Arduino board is present if arduino.BOARD is None: - _LOGGER.error('A connection has not been made to the Arduino board.') + _LOGGER.error("A connection has not been made to the Arduino board") return False + pins = config.get(CONF_PINS) + sensors = [] - pins = config.get('pins') for pinnum, pin in pins.items(): - if pin.get('name'): - sensors.append(ArduinoSensor(pin.get('name'), - pinnum, - 'analog')) + sensors.append(ArduinoSensor(pin.get(CONF_NAME), pinnum, CONF_TYPE)) add_devices(sensors) @@ -39,7 +55,7 @@ class ArduinoSensor(Entity): def __init__(self, name, pin, pin_type): """Initialize the sensor.""" self._pin = pin - self._name = name or DEVICE_DEFAULT_NAME + self._name = name self.pin_type = pin_type self.direction = 'in' self._value = None diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py index 46e6baf8943..3aa61feffc8 100644 --- a/homeassistant/components/switch/arduino.py +++ b/homeassistant/components/switch/arduino.py @@ -8,27 +8,46 @@ https://home-assistant.io/components/switch.arduino/ """ import logging +import voluptuous as vol + import homeassistant.components.arduino as arduino -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['arduino'] _LOGGER = logging.getLogger(__name__) +CONF_PINS = 'pins' +CONF_TYPE = 'digital' +CONF_NEGATE = 'negate' +CONF_INITIAL = 'initial' + +PIN_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_INITIAL, default=False): cv.boolean, + vol.Optional(CONF_NEGATE, default=False): cv.boolean, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PINS, default={}): + vol.Schema({cv.positive_int: PIN_SCHEMA}), +}) + def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Arduino platform.""" + """Set up the Arduino platform.""" # Verify that Arduino board is present if arduino.BOARD is None: - _LOGGER.error('A connection has not been made to the Arduino board.') + _LOGGER.error("A connection has not been made to the Arduino board") return False + pins = config.get(CONF_PINS) + switches = [] - pins = config.get('pins') for pinnum, pin in pins.items(): - if pin.get('name'): - switches.append(ArduinoSwitch(pinnum, pin)) + switches.append(ArduinoSwitch(pinnum, pin)) add_devices(switches) @@ -38,13 +57,13 @@ class ArduinoSwitch(SwitchDevice): def __init__(self, pin, options): """Initialize the Pin.""" self._pin = pin - self._name = options.get('name') or DEVICE_DEFAULT_NAME - self.pin_type = options.get('type') + self._name = options.get(CONF_NAME) + self.pin_type = CONF_TYPE self.direction = 'out' - self._state = options.get('initial', False) + self._state = options.get(CONF_INITIAL) - if options.get('negate', False): + if options.get(CONF_NEGATE): self.turn_on_handler = arduino.BOARD.set_digital_out_low self.turn_off_handler = arduino.BOARD.set_digital_out_high else: From a8cdf36d5c149647fa992f66ae2bfa7d65ef2088 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 11 Oct 2016 00:58:43 -0700 Subject: [PATCH 035/147] Update recorder callback (#3812) --- homeassistant/components/recorder/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index d1983e8f28f..6feee95be45 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -7,7 +7,6 @@ to query this database. For more details about this component, please refer to the documentation at https://home-assistant.io/components/recorder/ """ -import asyncio import logging import queue import threading @@ -17,7 +16,7 @@ from typing import Any, Union, Optional, List import voluptuous as vol -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.const import (EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) @@ -225,7 +224,7 @@ class Recorder(threading.Thread): self.queue.task_done() - @asyncio.coroutine + @callback def event_listener(self, event): """Listen for new events and put them in the process queue.""" self.queue.put(event) From d4dc2707a1ac5c1fc434ddae577343ff88badc51 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 11:27:31 +0200 Subject: [PATCH 036/147] Use voluptuous for eQ-3 thermostat (#3729) * Migrate to voluptuous * Fix requirement and typo --- .../components/climate/eq3btsmart.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 5389585d8f1..87d9e322405 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -1,24 +1,38 @@ """ -Support for eq3 Bluetooth Smart thermostats. +Support for eQ-3 Bluetooth Smart thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.eq3btsmart/ """ import logging -from homeassistant.components.climate import ClimateDevice -from homeassistant.const import TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE +import voluptuous as vol + +from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE) from homeassistant.util.temperature import convert +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['bluepy_devices==0.2.0'] -CONF_MAC = 'mac' - _LOGGER = logging.getLogger(__name__) +ATTR_MODE = 'mode' +ATTR_MODE_READABLE = 'mode_readable' + +DEVICE_SCHEMA = vol.Schema({ + vol.Required(CONF_MAC): cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICES): + vol.Schema({cv.string: DEVICE_SCHEMA}), +}) + def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the eq3 BLE thermostats.""" + """Setup the eQ-3 BLE thermostats.""" devices = [] for name, device_cfg in config[CONF_DEVICES].items(): @@ -30,14 +44,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes, import-error, abstract-method class EQ3BTSmartThermostat(ClimateDevice): - """Representation of a EQ3 Bluetooth Smart thermostat.""" + """Representation of a eQ-3 Bluetooth Smart thermostat.""" def __init__(self, _mac, _name): """Initialize the thermostat.""" from bluepy_devices.devices import eq3btsmart self._name = _name - self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac) @property @@ -70,8 +83,10 @@ class EQ3BTSmartThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - return {"mode": self._thermostat.mode, - "mode_readable": self._thermostat.mode_readable} + return { + ATTR_MODE: self._thermostat.mode, + ATTR_MODE_READABLE: self._thermostat.mode_readable, + } @property def min_temp(self): From e135691bd6df15f877bd6c14c13ef4418c26d644 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Oct 2016 17:33:22 +0200 Subject: [PATCH 037/147] Migrate to voluptuous (#3817) --- homeassistant/components/switch/rpi_rf.py | 62 ++++++++++++++--------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/switch/rpi_rf.py b/homeassistant/components/switch/rpi_rf.py index b96a1d70dc5..8bff88c48a6 100644 --- a/homeassistant/components/switch/rpi_rf.py +++ b/homeassistant/components/switch/rpi_rf.py @@ -4,51 +4,65 @@ Allows to configure a switch using a 433MHz module via GPIO on a Raspberry Pi. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.rpi_rf/ """ - import logging -from homeassistant.components.switch import SwitchDevice +import voluptuous as vol + +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_NAME, CONF_SWITCHES) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['rpi-rf==0.9.5'] _LOGGER = logging.getLogger(__name__) +CONF_CODE_OFF = 'code_off' +CONF_CODE_ON = 'code_on' +CONF_GPIO = 'gpio' +CONF_PROTOCOL = 'protocol' +CONF_PULSELENGTH = 'pulselength' + +DEFAULT_PROTOCOL = 1 + +SWITCH_SCHEMA = vol.Schema({ + vol.Required(CONF_CODE_OFF): cv.positive_int, + vol.Required(CONF_CODE_ON): cv.positive_int, + vol.Optional(CONF_PULSELENGTH): cv.positive_int, + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_GPIO): cv.positive_int, + vol.Required(CONF_SWITCHES): vol.Schema({cv.string: SWITCH_SCHEMA}), +}) + # pylint: disable=unused-argument, import-error -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Find and return switches controlled by a generic RF device via GPIO.""" import rpi_rf - gpio = config.get('gpio') - if not gpio: - _LOGGER.error("No GPIO specified") - return False - + gpio = config.get(CONF_GPIO) rfdevice = rpi_rf.RFDevice(gpio) + switches = config.get(CONF_SWITCHES) - switches = config.get('switches', {}) devices = [] for dev_name, properties in switches.items(): - if not properties.get('code_on'): - _LOGGER.error("%s: code_on not specified", dev_name) - continue - if not properties.get('code_off'): - _LOGGER.error("%s: code_off not specified", dev_name) - continue - devices.append( RPiRFSwitch( hass, - properties.get('name', dev_name), + properties.get(CONF_NAME, dev_name), rfdevice, - properties.get('protocol', None), - properties.get('pulselength', None), - properties.get('code_on'), - properties.get('code_off'))) + properties.get(CONF_PROTOCOL), + properties.get(CONF_PULSELENGTH), + properties.get(CONF_CODE_ON), + properties.get(CONF_CODE_OFF) + ) + ) if devices: rfdevice.enable_tx() - add_devices_callback(devices) + add_devices(devices) class RPiRFSwitch(SwitchDevice): @@ -84,10 +98,10 @@ class RPiRFSwitch(SwitchDevice): def _send_code(self, code, protocol, pulselength): """Send the code with a specified pulselength.""" - _LOGGER.info('Sending code: %s', code) + _LOGGER.info("Sending code: %s", code) res = self._rfdevice.tx_code(code, protocol, pulselength) if not res: - _LOGGER.error('Sending code %s failed', code) + _LOGGER.error("Sending code %s failed", code) return res def turn_on(self): From d302dbec2d1b30dd713e23c336bd7941ec1c07fd Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Tue, 11 Oct 2016 20:33:41 -0700 Subject: [PATCH 038/147] Notify: Only attach target if in call data (#3831) * Only pass through the target if it has a value * Target will no longer be none --- homeassistant/components/notify/__init__.py | 2 +- tests/components/notify/test_demo.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index c779c78d15e..b30777a0cf2 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -101,7 +101,7 @@ def setup(hass, config): if targets.get(call.service) is not None: kwargs[ATTR_TARGET] = [targets[call.service]] - else: + elif call.data.get(ATTR_TARGET) is not None: kwargs[ATTR_TARGET] = call.data.get(ATTR_TARGET) message.hass = hass diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 0d41f0606e5..a0d9f28fe1a 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -64,7 +64,6 @@ class TestNotifyDemo(unittest.TestCase): data = self.events[0].data assert { 'message': 'my message', - 'target': None, 'title': 'my title', 'data': {'hello': 'world'} } == data @@ -92,7 +91,6 @@ data_template: self.assertTrue(len(self.events) == 1) assert { 'message': 'Test 123 4', - 'target': None, 'data': { 'push': { 'sound': @@ -124,7 +122,6 @@ data_template: assert { 'message': 'Test 123 4', 'title': 'Test', - 'target': None, 'data': { 'push': { 'sound': From 40094cecae0521d868fd30cac6c67856a07ae342 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 12 Oct 2016 06:16:11 +0200 Subject: [PATCH 039/147] Fix slack targets (#3826) --- homeassistant/components/notify/slack.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 75ebb9b26fe..7c0a2b4d118 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -68,7 +68,10 @@ class SlackNotificationService(BaseNotificationService): """Send a message to a user.""" import slacker - targets = kwargs.get(ATTR_TARGET, [self._default_channel]) + if kwargs.get(ATTR_TARGET) is None: + targets = [self._default_channel] + else: + targets = kwargs.get(ATTR_TARGET) data = kwargs.get('data') attachments = data.get('attachments') if data else None From d83de36c327590f2474fe71cd28038b37743f8fd Mon Sep 17 00:00:00 2001 From: Keith Lamprecht Date: Wed, 12 Oct 2016 00:59:34 -0400 Subject: [PATCH 040/147] Restore Optional Target Config Attribute (notify.pushover) (#3769) * Restore Optional Target Config Attribute * Fix Tabs * Change indents to spaces * Make a target fix * Change to simpler not syntax --- homeassistant/components/notify/pushover.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index bd7542c21cd..04aa7627963 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -63,6 +63,9 @@ class PushoverNotificationService(BaseNotificationService): targets = kwargs.get(ATTR_TARGET) + if not isinstance(targets, list): + targets = [targets] + for target in targets: if target is not None: data['device'] = target From 1d7169403b570902f4c84ea080876820a06733a6 Mon Sep 17 00:00:00 2001 From: Jean Regisser Date: Wed, 12 Oct 2016 07:25:17 +0200 Subject: [PATCH 041/147] Add again certifi to Docker image (#3813) Latest versions of certifi (>= 2016.8.31) don't seem to break anything anymore. See home-assistant/home-assistant#2554 --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 14e70a0412c..b42d7edcc89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,7 @@ RUN script/build_python_openzwave && \ ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config COPY requirements_all.txt requirements_all.txt -# certifi breaks Debian based installs -RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \ +RUN pip3 install --no-cache-dir -r requirements_all.txt && \ pip3 install mysqlclient psycopg2 uvloop # Copy source From 10feac11d960d1a6e8883ef8e6b01945adb6777c Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Wed, 12 Oct 2016 11:05:41 +0100 Subject: [PATCH 042/147] Support recursive config inclusions (#3783) --- homeassistant/util/yaml.py | 33 ++++++++----- tests/util/test_yaml.py | 95 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 035a96b657e..cf773bb999f 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -1,8 +1,8 @@ """YAML utility functions.""" -import glob import logging import os import sys +import fnmatch from collections import OrderedDict from typing import Union, List, Dict @@ -61,23 +61,32 @@ def _include_yaml(loader: SafeLineLoader, return load_yaml(fname) +def _find_files(directory, pattern): + """Recursively load files in a directory.""" + for root, _dirs, files in os.walk(directory): + for basename in files: + if fnmatch.fnmatch(basename, pattern): + filename = os.path.join(root, basename) + yield filename + + def _include_dir_named_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> OrderedDict: """Load multiple files from directory as a dictionary.""" mapping = OrderedDict() # type: OrderedDict - files = os.path.join(os.path.dirname(loader.name), node.value, '*.yaml') - for fname in glob.glob(files): + loc = os.path.join(os.path.dirname(loader.name), node.value) + for fname in _find_files(loc, '*.yaml'): filename = os.path.splitext(os.path.basename(fname))[0] mapping[filename] = load_yaml(fname) return mapping def _include_dir_merge_named_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> OrderedDict: """Load multiple files from directory as a merged dictionary.""" mapping = OrderedDict() # type: OrderedDict - files = os.path.join(os.path.dirname(loader.name), node.value, '*.yaml') - for fname in glob.glob(files): + loc = os.path.join(os.path.dirname(loader.name), node.value) + for fname in _find_files(loc, '*.yaml'): if os.path.basename(fname) == _SECRET_YAML: continue loaded_yaml = load_yaml(fname) @@ -89,18 +98,18 @@ def _include_dir_merge_named_yaml(loader: SafeLineLoader, def _include_dir_list_yaml(loader: SafeLineLoader, node: yaml.nodes.Node): """Load multiple files from directory as a list.""" - files = os.path.join(os.path.dirname(loader.name), node.value, '*.yaml') - return [load_yaml(f) for f in glob.glob(files) + loc = os.path.join(os.path.dirname(loader.name), node.value) + return [load_yaml(f) for f in _find_files(loc, '*.yaml') if os.path.basename(f) != _SECRET_YAML] def _include_dir_merge_list_yaml(loader: SafeLineLoader, node: yaml.nodes.Node): """Load multiple files from directory as a merged list.""" - files = os.path.join(os.path.dirname(loader.name), - node.value, '*.yaml') # type: str + loc = os.path.join(os.path.dirname(loader.name), + node.value) # type: str merged_list = [] # type: List - for fname in glob.glob(files): + for fname in _find_files(loc, '*.yaml'): if os.path.basename(fname) == _SECRET_YAML: continue loaded_yaml = load_yaml(fname) diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 6b35e4f844c..b1214c2ff17 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -92,6 +92,27 @@ class TestYaml(unittest.TestCase): doc = yaml.yaml.safe_load(file) assert sorted(doc["key"]) == sorted(["one", "two"]) + def test_include_dir_list_recursive(self): + """Test include dir recursive list yaml.""" + with tempfile.TemporaryDirectory() as include_dir: + file_0 = tempfile.NamedTemporaryFile(dir=include_dir, + suffix=".yaml", delete=False) + file_0.write(b"zero") + file_0.close() + temp_dir = tempfile.TemporaryDirectory(dir=include_dir) + file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_1.write(b"one") + file_1.close() + file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_2.write(b"two") + file_2.close() + conf = "key: !include_dir_list {}".format(include_dir) + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) + def test_include_dir_named(self): """Test include dir named yaml.""" with tempfile.TemporaryDirectory() as include_dir: @@ -111,6 +132,32 @@ class TestYaml(unittest.TestCase): doc = yaml.yaml.safe_load(file) assert doc["key"] == correct + def test_include_dir_named_recursive(self): + """Test include dir named yaml.""" + with tempfile.TemporaryDirectory() as include_dir: + file_0 = tempfile.NamedTemporaryFile(dir=include_dir, + suffix=".yaml", delete=False) + file_0.write(b"zero") + file_0.close() + temp_dir = tempfile.TemporaryDirectory(dir=include_dir) + file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_1.write(b"one") + file_1.close() + file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_2.write(b"two") + file_2.close() + conf = "key: !include_dir_named {}".format(include_dir) + correct = {} + correct[os.path.splitext( + os.path.basename(file_0.name))[0]] = "zero" + correct[os.path.splitext(os.path.basename(file_1.name))[0]] = "one" + correct[os.path.splitext(os.path.basename(file_2.name))[0]] = "two" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc["key"] == correct + def test_include_dir_merge_list(self): """Test include dir merge list yaml.""" with tempfile.TemporaryDirectory() as include_dir: @@ -127,6 +174,28 @@ class TestYaml(unittest.TestCase): doc = yaml.yaml.safe_load(file) assert sorted(doc["key"]) == sorted(["one", "two", "three"]) + def test_include_dir_merge_list_recursive(self): + """Test include dir merge list yaml.""" + with tempfile.TemporaryDirectory() as include_dir: + file_0 = tempfile.NamedTemporaryFile(dir=include_dir, + suffix=".yaml", delete=False) + file_0.write(b"- zero") + file_0.close() + temp_dir = tempfile.TemporaryDirectory(dir=include_dir) + file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_1.write(b"- one") + file_1.close() + file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_2.write(b"- two\n- three") + file_2.close() + conf = "key: !include_dir_merge_list {}".format(include_dir) + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert sorted(doc["key"]) == sorted(["zero", "one", "two", + "three"]) + def test_include_dir_merge_named(self): """Test include dir merge named yaml.""" with tempfile.TemporaryDirectory() as include_dir: @@ -147,6 +216,32 @@ class TestYaml(unittest.TestCase): "key3": "three" } + def test_include_dir_merge_named_recursive(self): + """Test include dir merge named yaml.""" + with tempfile.TemporaryDirectory() as include_dir: + file_0 = tempfile.NamedTemporaryFile(dir=include_dir, + suffix=".yaml", delete=False) + file_0.write(b"key0: zero") + file_0.close() + temp_dir = tempfile.TemporaryDirectory(dir=include_dir) + file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_1.write(b"key1: one") + file_1.close() + file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, + suffix=".yaml", delete=False) + file_2.write(b"key2: two\nkey3: three") + file_2.close() + conf = "key: !include_dir_merge_named {}".format(include_dir) + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc["key"] == { + "key0": "zero", + "key1": "one", + "key2": "two", + "key3": "three" + } + FILES = {} From e1647fb6acccec5eaa13b8e3aaa9cadc8fc17f88 Mon Sep 17 00:00:00 2001 From: jgriff2 Date: Thu, 13 Oct 2016 08:49:58 -0700 Subject: [PATCH 043/147] Add synology ss cameras (#3626) * Add files via upload * Update .coveragerc * test * Update synology camera * Use voluptuous for synology * Use voluptuous for synology * Use voluptuous for synology * Use voluptuous for synology * Conform synology to flake8 * Added Whitelist to synology * Sync to dev branch * Added helper function to synology --- .coveragerc | 1 + homeassistant/components/camera/synology.py | 223 ++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 homeassistant/components/camera/synology.py diff --git a/.coveragerc b/.coveragerc index 045e8f77588..a9b9d4e6df5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -114,6 +114,7 @@ omit = homeassistant/components/camera/foscam.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py + homeassistant/components/camera/synology.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/heatmiser.py homeassistant/components/climate/homematic.py diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py new file mode 100644 index 00000000000..dedf91a0031 --- /dev/null +++ b/homeassistant/components/camera/synology.py @@ -0,0 +1,223 @@ +""" +Support for Synology Surveillance Station Cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.synology/ +""" +import logging + +import voluptuous as vol + +import requests + +from homeassistant.const import ( + CONF_NAME, CONF_USERNAME, CONF_PASSWORD, + CONF_URL, CONF_WHITELIST) +from homeassistant.components.camera import ( + Camera, PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +# pylint: disable=too-many-locals +DEFAULT_NAME = 'Synology Camera' +DEFAULT_STREAM_ID = '0' +TIMEOUT = 5 +CONF_CAMERA_NAME = 'camera_name' +CONF_STREAM_ID = 'stream_id' +CONF_VALID_CERT = 'valid_cert' + +QUERY_CGI = 'query.cgi' +QUERY_API = 'SYNO.API.Info' +AUTH_API = 'SYNO.API.Auth' +CAMERA_API = 'SYNO.SurveillanceStation.Camera' +STREAMING_API = 'SYNO.SurveillanceStation.VideoStream' +SESSION_ID = '0' + +WEBAPI_PATH = '/webapi/' +AUTH_PATH = 'auth.cgi' +CAMERA_PATH = 'camera.cgi' +STREAMING_PATH = 'SurveillanceStation/videoStreaming.cgi' + +SYNO_API_URL = '{0}{1}{2}' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list, + vol.Optional(CONF_VALID_CERT, default=True): cv.boolean, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup a Synology IP Camera.""" + # Determine API to use for authentication + syno_api_url = SYNO_API_URL.format(config.get(CONF_URL), + WEBAPI_PATH, + QUERY_CGI) + query_payload = {'api': QUERY_API, + 'method': 'Query', + 'version': '1', + 'query': 'SYNO.'} + query_req = requests.get(syno_api_url, + params=query_payload, + verify=config.get(CONF_VALID_CERT), + timeout=TIMEOUT) + query_resp = query_req.json() + auth_path = query_resp['data'][AUTH_API]['path'] + camera_api = query_resp['data'][CAMERA_API]['path'] + camera_path = query_resp['data'][CAMERA_API]['path'] + streaming_path = query_resp['data'][STREAMING_API]['path'] + + # Authticate to NAS to get a session id + syno_auth_url = SYNO_API_URL.format(config.get(CONF_URL), + WEBAPI_PATH, + auth_path) + session_id = get_session_id(config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), + syno_auth_url, + config.get(CONF_VALID_CERT)) + + # Use SessionID to get cameras in system + syno_camera_url = SYNO_API_URL.format(config.get(CONF_URL), + WEBAPI_PATH, + camera_api) + camera_payload = {'api': CAMERA_API, + 'method': 'List', + 'version': '1'} + camera_req = requests.get(syno_camera_url, + params=camera_payload, + verify=config.get(CONF_VALID_CERT), + timeout=TIMEOUT, + cookies={'id': session_id}) + camera_resp = camera_req.json() + cameras = camera_resp['data']['cameras'] + for camera in cameras: + if not config.get(CONF_WHITELIST): + camera_id = camera['id'] + snapshot_path = camera['snapshot_path'] + + add_devices([SynologyCamera(config, + camera_id, + camera['name'], + snapshot_path, + streaming_path, + camera_path, + auth_path)]) + + +def get_session_id(username, password, login_url, valid_cert): + """Get a session id.""" + auth_payload = {'api': AUTH_API, + 'method': 'Login', + 'version': '2', + 'account': username, + 'passwd': password, + 'session': 'SurveillanceStation', + 'format': 'sid'} + auth_req = requests.get(login_url, + params=auth_payload, + verify=valid_cert, + timeout=TIMEOUT) + auth_resp = auth_req.json() + return auth_resp['data']['sid'] + + +# pylint: disable=too-many-instance-attributes +class SynologyCamera(Camera): + """An implementation of a Synology NAS based IP camera.""" + +# pylint: disable=too-many-arguments + def __init__(self, config, camera_id, camera_name, + snapshot_path, streaming_path, camera_path, auth_path): + """Initialize a Synology Surveillance Station camera.""" + super().__init__() + self._name = camera_name + self._username = config.get(CONF_USERNAME) + self._password = config.get(CONF_PASSWORD) + self._synology_url = config.get(CONF_URL) + self._api_url = config.get(CONF_URL) + 'webapi/' + self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi' + self._camera_name = config.get(CONF_CAMERA_NAME) + self._stream_id = config.get(CONF_STREAM_ID) + self._valid_cert = config.get(CONF_VALID_CERT) + self._camera_id = camera_id + self._snapshot_path = snapshot_path + self._streaming_path = streaming_path + self._camera_path = camera_path + self._auth_path = auth_path + + self._session_id = get_session_id(self._username, + self._password, + self._login_url, + self._valid_cert) + + def get_sid(self): + """Get a session id.""" + auth_payload = {'api': AUTH_API, + 'method': 'Login', + 'version': '2', + 'account': self._username, + 'passwd': self._password, + 'session': 'SurveillanceStation', + 'format': 'sid'} + auth_req = requests.get(self._login_url, + params=auth_payload, + verify=self._valid_cert, + timeout=TIMEOUT) + auth_resp = auth_req.json() + self._session_id = auth_resp['data']['sid'] + + def camera_image(self): + """Return a still image response from the camera.""" + image_url = SYNO_API_URL.format(self._synology_url, + WEBAPI_PATH, + self._camera_path) + image_payload = {'api': CAMERA_API, + 'method': 'GetSnapshot', + 'version': '1', + 'cameraId': self._camera_id} + try: + response = requests.get(image_url, + params=image_payload, + timeout=TIMEOUT, + verify=self._valid_cert, + cookies={'id': self._session_id}) + except requests.exceptions.RequestException as error: + _LOGGER.error('Error getting camera image: %s', error) + return None + + return response.content + + def camera_stream(self): + """Return a MJPEG stream image response directly from the camera.""" + streaming_url = SYNO_API_URL.format(self._synology_url, + WEBAPI_PATH, + self._streaming_path) + streaming_payload = {'api': STREAMING_API, + 'method': 'Stream', + 'version': '1', + 'cameraId': self._camera_id, + 'format': 'mjpeg'} + response = requests.get(streaming_url, + payload=streaming_payload, + stream=True, + timeout=TIMEOUT, + cookies={'id': self._session_id}) + return response + + def mjpeg_steam(self, response): + """Generate an HTTP MJPEG Stream from the Synology NAS.""" + stream = self.camera_stream() + return response( + stream.iter_content(chunk_size=1024), + mimetype=stream.headers['CONTENT_TYPE_HEADER'], + direct_passthrough=True + ) + + @property + def name(self): + """Return the name of this device.""" + return self._name From e031b8078f59fe3a76dcc6146dd95866cb179962 Mon Sep 17 00:00:00 2001 From: Vittorio Monaco Date: Thu, 13 Oct 2016 17:51:43 +0200 Subject: [PATCH 044/147] Fixes an issue where Chromecast audio groups were not properly discovered (#3630) * Fixes an issue where Chromecast audio groups were not properly discovered * Forgot to commit the main fix * Removes unused variable * Doesn't use a protected API anymore * PR remarks * Fixes tests, adds comment * Restores line as it was in the original commit, rephrases comment * Should fix lint issues * Trailing whitespace * Some more lint --- homeassistant/components/media_player/cast.py | 22 ++++++++++++------- tests/components/media_player/test_cast.py | 16 +++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 8468390c590..cf764fd723e 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -68,12 +68,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None): casts = [] + # get_chromecasts() returns Chromecast objects + # with the correct friendly name for grouped devices + all_chromecasts = pychromecast.get_chromecasts() + for host in hosts: - try: - casts.append(CastDevice(*host)) - KNOWN_HOSTS.append(host) - except pychromecast.ChromecastConnectionError: - pass + found = [device for device in all_chromecasts + if (device.host, device.port) == host] + if found: + try: + casts.append(CastDevice(found[0])) + KNOWN_HOSTS.append(host) + except pychromecast.ChromecastConnectionError: + pass add_devices(casts) @@ -83,10 +90,9 @@ class CastDevice(MediaPlayerDevice): # pylint: disable=abstract-method # pylint: disable=too-many-public-methods - def __init__(self, host, port): + def __init__(self, chromecast): """Initialize the Cast device.""" - import pychromecast - self.cast = pychromecast.Chromecast(host, port) + self.cast = chromecast self.cast.socket_client.receiver_controller.register_status_listener( self) diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index 9930ae678f3..b4d4b15351c 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -6,12 +6,25 @@ from unittest.mock import patch from homeassistant.components.media_player import cast +class FakeChromeCast(object): + def __init__(self, host, port): + self.host = host + self.port = port + + class TestCastMediaPlayer(unittest.TestCase): """Test the media_player module.""" @patch('homeassistant.components.media_player.cast.CastDevice') - def test_filter_duplicates(self, mock_device): + @patch('pychromecast.get_chromecasts') + def test_filter_duplicates(self, mock_get_chromecasts, mock_device): """Test filtering of duplicates.""" + + mock_get_chromecasts.return_value = [ + FakeChromeCast('some_host', cast.DEFAULT_PORT) + ] + + # Test chromecasts as if they were hardcoded in configuration.yaml cast.setup_platform(None, { 'host': 'some_host' }, lambda _: _) @@ -21,6 +34,7 @@ class TestCastMediaPlayer(unittest.TestCase): mock_device.reset_mock() assert not mock_device.called + # Test chromecasts as if they were automatically discovered cast.setup_platform(None, {}, lambda _: _, ('some_host', cast.DEFAULT_PORT)) assert not mock_device.called From aa8622f8e8471890b8b9daeeff2f98c12581ca81 Mon Sep 17 00:00:00 2001 From: wokar Date: Thu, 13 Oct 2016 17:54:45 +0200 Subject: [PATCH 045/147] Added include and exclude functionality to history component (#3674) * added include and exclude functionality to history component * fixed summary lines in test method doc. * cleanup of query filter creation * o improved config validation o move move IGNORE_DOMAINS to Filter.apply() o removed config from Last5StatesView o Filters instance is now created on setup o config values are processed in setup and set to the Filters instance o function _set_filters_in_query() moved to Filters class and renamed to apply() * added unittests for more include/exclude filter combinations * make pylint happy --- homeassistant/components/history.py | 153 ++++++++++++++--- tests/components/test_history.py | 250 +++++++++++++++++++++++++++- 2 files changed, 371 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 9a09f56c474..4cebf637c16 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -7,16 +7,39 @@ https://home-assistant.io/components/history/ from collections import defaultdict from datetime import timedelta from itertools import groupby +import voluptuous as vol +import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components import recorder, script from homeassistant.components.frontend import register_built_in_panel from homeassistant.components.http import HomeAssistantView +from homeassistant.const import ATTR_HIDDEN DOMAIN = 'history' DEPENDENCIES = ['recorder', 'http'] -SIGNIFICANT_DOMAINS = ('thermostat',) +CONF_EXCLUDE = 'exclude' +CONF_INCLUDE = 'include' +CONF_ENTITIES = 'entities' +CONF_DOMAINS = 'domains' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + CONF_EXCLUDE: vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, + [cv.string]) + }), + CONF_INCLUDE: vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, + [cv.string]) + }) + }), +}, extra=vol.ALLOW_EXTRA) + +SIGNIFICANT_DOMAINS = ('thermostat', 'climate') IGNORE_DOMAINS = ('zone', 'scene',) @@ -32,7 +55,8 @@ def last_5_states(entity_id): ).order_by(states.state_id.desc()).limit(5)) -def get_significant_states(start_time, end_time=None, entity_id=None): +def get_significant_states(start_time, end_time=None, entity_id=None, + filters=None): """ Return states changes during UTC period start_time - end_time. @@ -40,25 +64,25 @@ def get_significant_states(start_time, end_time=None, entity_id=None): as well as all states from certain domains (for instance thermostat so that we get current temperature in our graphs). """ + entity_ids = (entity_id.lower(), ) if entity_id is not None else None states = recorder.get_model('States') query = recorder.query('States').filter( (states.domain.in_(SIGNIFICANT_DOMAINS) | (states.last_changed == states.last_updated)) & - ((~states.domain.in_(IGNORE_DOMAINS)) & - (states.last_updated > start_time))) + (states.last_updated > start_time)) + if filters: + query = filters.apply(query, entity_ids) if end_time is not None: query = query.filter(states.last_updated < end_time) - if entity_id is not None: - query = query.filter_by(entity_id=entity_id.lower()) - states = ( state for state in recorder.execute( query.order_by(states.entity_id, states.last_updated)) - if _is_significant(state)) + if (_is_significant(state) and + not state.attributes.get(ATTR_HIDDEN, False))) - return states_to_json(states, start_time, entity_id) + return states_to_json(states, start_time, entity_id, filters) def state_changes_during_period(start_time, end_time=None, entity_id=None): @@ -80,7 +104,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None): return states_to_json(states, start_time, entity_id) -def get_states(utc_point_in_time, entity_ids=None, run=None): +def get_states(utc_point_in_time, entity_ids=None, run=None, filters=None): """Return the states at a specific point in time.""" if run is None: run = recorder.run_information(utc_point_in_time) @@ -96,12 +120,11 @@ def get_states(utc_point_in_time, entity_ids=None, run=None): func.max(states.state_id).label('max_state_id') ).filter( (states.created >= run.start) & - (states.created < utc_point_in_time) - ) - - if entity_ids is not None: - most_recent_state_ids = most_recent_state_ids.filter( - states.entity_id.in_(entity_ids)) + (states.created < utc_point_in_time) & + (~states.domain.in_(IGNORE_DOMAINS))) + if filters: + most_recent_state_ids = filters.apply(most_recent_state_ids, + entity_ids) most_recent_state_ids = most_recent_state_ids.group_by( states.entity_id).subquery() @@ -109,10 +132,12 @@ def get_states(utc_point_in_time, entity_ids=None, run=None): query = recorder.query('States').join(most_recent_state_ids, and_( states.state_id == most_recent_state_ids.c.max_state_id)) - return recorder.execute(query) + for state in recorder.execute(query): + if not state.attributes.get(ATTR_HIDDEN, False): + yield state -def states_to_json(states, start_time, entity_id): +def states_to_json(states, start_time, entity_id, filters=None): """Convert SQL results into JSON friendly data structure. This takes our state list and turns it into a JSON friendly data @@ -127,7 +152,7 @@ def states_to_json(states, start_time, entity_id): entity_ids = [entity_id] if entity_id is not None else None # Get the states at the start time - for state in get_states(start_time, entity_ids): + for state in get_states(start_time, entity_ids, filters=filters): state.last_changed = start_time state.last_updated = start_time result[state.entity_id].append(state) @@ -140,16 +165,25 @@ def states_to_json(states, start_time, entity_id): def get_state(utc_point_in_time, entity_id, run=None): """Return a state at a specific point in time.""" - states = get_states(utc_point_in_time, (entity_id,), run) - + states = list(get_states(utc_point_in_time, (entity_id,), run)) return states[0] if states else None # pylint: disable=unused-argument def setup(hass, config): """Setup the history hooks.""" - hass.wsgi.register_view(Last5StatesView) - hass.wsgi.register_view(HistoryPeriodView) + filters = Filters() + exclude = config[DOMAIN].get(CONF_EXCLUDE) + if exclude: + filters.excluded_entities = exclude[CONF_ENTITIES] + filters.excluded_domains = exclude[CONF_DOMAINS] + include = config[DOMAIN].get(CONF_INCLUDE) + if include: + filters.included_entities = include[CONF_ENTITIES] + filters.included_domains = include[CONF_DOMAINS] + + hass.wsgi.register_view(Last5StatesView(hass)) + hass.wsgi.register_view(HistoryPeriodView(hass, filters)) register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box') return True @@ -161,6 +195,10 @@ class Last5StatesView(HomeAssistantView): url = '/api/history/entity//recent_states' name = 'api:history:entity-recent-states' + def __init__(self, hass): + """Initilalize the history last 5 states view.""" + super().__init__(hass) + def get(self, request, entity_id): """Retrieve last 5 states of entity.""" return self.json(last_5_states(entity_id)) @@ -173,6 +211,11 @@ class HistoryPeriodView(HomeAssistantView): name = 'api:history:view-period' extra_urls = ['/api/history/period/'] + def __init__(self, hass, filters): + """Initilalize the history period view.""" + super().__init__(hass) + self.filters = filters + def get(self, request, datetime=None): """Return history over a period of time.""" one_day = timedelta(days=1) @@ -185,8 +228,68 @@ class HistoryPeriodView(HomeAssistantView): end_time = start_time + one_day entity_id = request.args.get('filter_entity_id') - return self.json( - get_significant_states(start_time, end_time, entity_id).values()) + return self.json(get_significant_states( + start_time, end_time, entity_id, self.filters).values()) + + +# pylint: disable=too-few-public-methods +class Filters(object): + """Container for the configured include and exclude filters.""" + + def __init__(self): + """Initialise the include and exclude filters.""" + self.excluded_entities = [] + self.excluded_domains = [] + self.included_entities = [] + self.included_domains = [] + + def apply(self, query, entity_ids=None): + """Apply the Include/exclude filter on domains and entities on query. + + Following rules apply: + * only the include section is configured - just query the specified + entities or domains. + * only the exclude section is configured - filter the specified + entities and domains from all the entities in the system. + * if include and exclude is defined - select the entities specified in + the include and filter out the ones from the exclude list. + """ + states = recorder.get_model('States') + # specific entities requested - do not in/exclude anything + if entity_ids is not None: + return query.filter(states.entity_id.in_(entity_ids)) + query = query.filter(~states.domain.in_(IGNORE_DOMAINS)) + + filter_query = None + # filter if only excluded domain is configured + if self.excluded_domains and not self.included_domains: + filter_query = ~states.domain.in_(self.excluded_domains) + if self.included_entities: + filter_query &= states.entity_id.in_(self.included_entities) + # filter if only included domain is configured + elif not self.excluded_domains and self.included_domains: + filter_query = states.domain.in_(self.included_domains) + if self.included_entities: + filter_query |= states.entity_id.in_(self.included_entities) + # filter if included and excluded domain is configured + elif self.excluded_domains and self.included_domains: + filter_query = ~states.domain.in_(self.excluded_domains) + if self.included_entities: + filter_query &= (states.domain.in_(self.included_domains) | + states.entity_id.in_(self.included_entities)) + else: + filter_query &= (states.domain.in_(self.included_domains) & + ~states.domain.in_(self.excluded_domains)) + # no domain filter just included entities + elif not self.excluded_domains and not self.included_domains and \ + self.included_entities: + filter_query = states.entity_id.in_(self.included_entities) + if filter_query is not None: + query = query.filter(filter_query) + # finally apply excluded entities filter if configured + if self.excluded_entities: + query = query.filter(~states.entity_id.in_(self.excluded_entities)) + return query def _is_significant(state): diff --git a/tests/components/test_history.py b/tests/components/test_history.py index 80d0b1e9f9d..520afed81d9 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -43,7 +43,15 @@ class TestComponentHistory(unittest.TestCase): def test_setup(self): """Test setup method of history.""" mock_http_component(self.hass) - self.assertTrue(setup_component(self.hass, history.DOMAIN, {})) + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_DOMAINS: ['media_player'], + history.CONF_ENTITIES: ['thermostat.test']}, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['thermostat'], + history.CONF_ENTITIES: ['media_player.test']}}}) + self.assertTrue(setup_component(self.hass, history.DOMAIN, config)) def test_last_5_states(self): """Test retrieving the last 5 states.""" @@ -145,14 +153,236 @@ class TestComponentHistory(unittest.TestCase): def test_get_significant_states(self): """Test that only significant states are returned. - We inject a bunch of state updates from media player, zone and - thermostat. We should get back every thermostat change that + We should get back every thermostat change that includes an attribute change, but only the state updates for media player (attribute changes are not significant and not returned). """ + zero, four, states = self.record_states() + hist = history.get_significant_states( + zero, four, filters=history.Filters()) + assert states == hist + + def test_get_significant_states_entity_id(self): + """Test that only significant states are returned for one entity.""" + zero, four, states = self.record_states() + del states['media_player.test2'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + hist = history.get_significant_states( + zero, four, 'media_player.test', + filters=history.Filters()) + assert states == hist + + def test_get_significant_states_exclude_domain(self): + """Test if significant states are returned when excluding domains. + + We should get back every thermostat change that includes an attribute + change, but no media player changes. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['media_player.test2'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['media_player', ]}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_exclude_entity(self): + """Test if significant states are returned when excluding entities. + + We should get back every thermostat and script changes, but no media + player changes. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_EXCLUDE: { + history.CONF_ENTITIES: ['media_player.test', ]}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_exclude(self): + """Test significant states when excluding entities and domains. + + We should not get back every thermostat and media player test changes. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['thermostat.test'] + del states['thermostat.test2'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['thermostat', ], + history.CONF_ENTITIES: ['media_player.test', ]}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_exclude_include_entity(self): + """Test significant states when excluding domains and include entities. + + We should not get back every thermostat and media player test changes. + """ + zero, four, states = self.record_states() + del states['media_player.test2'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_ENTITIES: ['media_player.test', + 'thermostat.test']}, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['thermostat']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include_domain(self): + """Test if significant states are returned when including domains. + + We should get back every thermostat and script changes, but no media + player changes. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['media_player.test2'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_DOMAINS: ['thermostat', 'script']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include_entity(self): + """Test if significant states are returned when including entities. + + We should only get back changes of the media_player.test entity. + """ + zero, four, states = self.record_states() + del states['media_player.test2'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_ENTITIES: ['media_player.test']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include(self): + """Test significant states when including domains and entities. + + We should only get back changes of the media_player.test entity and the + thermostat domain. + """ + zero, four, states = self.record_states() + del states['media_player.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_DOMAINS: ['thermostat'], + history.CONF_ENTITIES: ['media_player.test']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include_exclude_domain(self): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only the + media_player domain but also exclude it. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['media_player.test2'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_DOMAINS: ['media_player']}, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['media_player']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include_exclude_entity(self): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only + media_player.test but also exclude it. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['media_player.test2'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_ENTITIES: ['media_player.test']}, + history.CONF_EXCLUDE: { + history.CONF_ENTITIES: ['media_player.test']}}}) + self.check_significant_states(zero, four, states, config) + + def test_get_significant_states_include_exclude(self): + """Test if significant states when in/excluding domains and entities. + + We should only get back changes of the media_player.test2 entity. + """ + zero, four, states = self.record_states() + del states['media_player.test'] + del states['thermostat.test'] + del states['thermostat.test2'] + del states['script.can_cancel_this_one'] + + config = history.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + history.DOMAIN: {history.CONF_INCLUDE: { + history.CONF_DOMAINS: ['media_player'], + history.CONF_ENTITIES: ['thermostat.test']}, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ['thermostat'], + history.CONF_ENTITIES: ['media_player.test']}}}) + self.check_significant_states(zero, four, states, config) + + def check_significant_states(self, zero, four, states, config): + """Check if significant states are retrieved.""" + filters = history.Filters() + exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) + if exclude: + filters.excluded_entities = exclude[history.CONF_ENTITIES] + filters.excluded_domains = exclude[history.CONF_DOMAINS] + include = config[history.DOMAIN].get(history.CONF_INCLUDE) + if include: + filters.included_entities = include[history.CONF_ENTITIES] + filters.included_domains = include[history.CONF_DOMAINS] + + hist = history.get_significant_states(zero, four, filters=filters) + assert states == hist + + def record_states(self): + """Record some test states. + + We inject a bunch of state updates from media player, zone and + thermostat. + """ self.init_recorder() mp = 'media_player.test' + mp2 = 'media_player.test2' therm = 'thermostat.test' + therm2 = 'thermostat.test2' zone = 'zone.home' script_nc = 'script.cannot_cancel_this_one' script_c = 'script.can_cancel_this_one' @@ -168,7 +398,7 @@ class TestComponentHistory(unittest.TestCase): three = two + timedelta(seconds=1) four = three + timedelta(seconds=1) - states = {therm: [], mp: [], script_c: []} + states = {therm: [], therm2: [], mp: [], mp2: [], script_c: []} with patch('homeassistant.components.recorder.dt_util.utcnow', return_value=one): states[mp].append( @@ -177,6 +407,9 @@ class TestComponentHistory(unittest.TestCase): states[mp].append( set_state(mp, 'YouTube', attributes={'media_title': str(sentinel.mt2)})) + states[mp2].append( + set_state(mp2, 'YouTube', + attributes={'media_title': str(sentinel.mt2)})) states[therm].append( set_state(therm, 20, attributes={'current_temperature': 19.5})) @@ -192,6 +425,8 @@ class TestComponentHistory(unittest.TestCase): set_state(script_c, 'off', attributes={'can_cancel': True})) states[therm].append( set_state(therm, 21, attributes={'current_temperature': 19.8})) + states[therm2].append( + set_state(therm2, 20, attributes={'current_temperature': 19})) with patch('homeassistant.components.recorder.dt_util.utcnow', return_value=three): @@ -201,6 +436,7 @@ class TestComponentHistory(unittest.TestCase): # Attributes changed even though state is the same states[therm].append( set_state(therm, 21, attributes={'current_temperature': 20})) - - hist = history.get_significant_states(zero, four) - assert states == hist + # state will be skipped since entity is hidden + set_state(therm, 22, attributes={'current_temperature': 21, + 'hidden': True}) + return zero, four, states From 39a446c43c83f4373961a34441f84b7607de6f3f Mon Sep 17 00:00:00 2001 From: Scott Reston Date: Thu, 13 Oct 2016 12:07:10 -0400 Subject: [PATCH 046/147] Proper title, added album and artist for Squeezebox (#3735) * Proper title, added album and artist Title had previously concatenated artist - title. * Made changes suggested by @balloobbot --- .../components/media_player/squeezebox.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index d54226b0566..9df91ceb276 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -126,7 +126,8 @@ class LogitechMediaServer(object): # a (artist): Artist name 'artist' # d (duration): Song duration in seconds 'duration' # K (artwork_url): URL to remote artwork - tags = 'adK' + # l (album): Album, including the server's "(N of M)" + tags = 'adKl' new_status = {} try: telnet = telnetlib.Telnet(self.host, self.port) @@ -236,14 +237,24 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def media_title(self): """Title of current playing media.""" - if 'artist' in self._status and 'title' in self._status: - return '{artist} - {title}'.format( - artist=self._status['artist'], - title=self._status['title'] - ) + if 'title' in self._status: + return self._status['title'] + if 'current_title' in self._status: return self._status['current_title'] + @property + def media_artist(self): + """Artist of current playing media.""" + if 'artist' in self._status: + return self._status['artist'] + + @property + def media_album_name(self): + """Album of current playing media.""" + if 'album' in self._status: + return self._status['album'].rstrip() + @property def supported_media_commands(self): """Flag of media commands that are supported.""" From cb322f72db746827fd87f1b9a49f7c6627e8fff2 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Thu, 13 Oct 2016 18:09:07 +0200 Subject: [PATCH 047/147] Add persistent notifications to bootstrap (#3738) * Add persistent notifications to bootstrap * Rebase, Fix test --- homeassistant/bootstrap.py | 27 ++++++++++++++++++++++----- homeassistant/scripts/check_config.py | 5 +++-- tests/scripts/test_check_config.py | 16 +++++++++------- tests/test_bootstrap.py | 3 ++- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 57adcd74fa4..4d4887a1a52 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -32,6 +32,8 @@ _CURRENT_SETUP = [] ATTR_COMPONENT = 'component' ERROR_LOG_FILENAME = 'home-assistant.log' +_PERSISTENT_PLATFORMS = set() +_PERSISTENT_VALIDATION = set() def setup_component(hass: core.HomeAssistant, domain: str, @@ -149,7 +151,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, try: config = component.CONFIG_SCHEMA(config) except vol.Invalid as ex: - log_exception(ex, domain, config) + log_exception(ex, domain, config, hass) return None elif hasattr(component, 'PLATFORM_SCHEMA'): @@ -159,7 +161,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, try: p_validated = component.PLATFORM_SCHEMA(p_config) except vol.Invalid as ex: - log_exception(ex, domain, config) + log_exception(ex, domain, config, hass) continue # Not all platform components follow same pattern for platforms @@ -181,7 +183,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict, p_validated = platform.PLATFORM_SCHEMA(p_validated) except vol.Invalid as ex: log_exception(ex, '{}.{}'.format(domain, p_name), - p_validated) + p_validated, hass) continue platforms.append(p_validated) @@ -211,6 +213,13 @@ def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, # Not found if platform is None: _LOGGER.error('Unable to find platform %s', platform_path) + + _PERSISTENT_PLATFORMS.add(platform_path) + message = ('Unable to find the following platforms: ' + + ', '.join(list(_PERSISTENT_PLATFORMS)) + + '(please check your configuration)') + persistent_notification.create( + hass, message, 'Invalid platforms', 'platform_errors') return None # Already loaded @@ -257,7 +266,7 @@ def from_config_dict(config: Dict[str, Any], try: conf_util.process_ha_core_config(hass, core_config) except vol.Invalid as ex: - log_exception(ex, 'homeassistant', core_config) + log_exception(ex, 'homeassistant', core_config, hass) return None conf_util.process_ha_config_upgrade(hass) @@ -305,6 +314,7 @@ def from_config_dict(config: Dict[str, Any], hass.loop.run_until_complete( hass.loop.run_in_executor(None, component_setup) ) + return hass @@ -397,9 +407,16 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None: loader.prepare(hass) -def log_exception(ex, domain, config): +def log_exception(ex, domain, config, hass=None): """Generate log exception for config validation.""" message = 'Invalid config for [{}]: '.format(domain) + if hass is not None: + _PERSISTENT_VALIDATION.add(domain) + message = ('The following platforms contain invalid configuration: ' + + ', '.join(list(_PERSISTENT_VALIDATION)) + + ' (please check your configuration)') + persistent_notification.create( + hass, message, 'Invalid config', 'invalid_config') if 'extra keys not allowed' in ex.error_message: message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index b3df02e8b34..f8b6fc6e69b 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -199,9 +199,10 @@ def check(config_path): res['secrets'][node.value] = val return val - def mock_except(ex, domain, config): # pylint: disable=unused-variable + def mock_except(ex, domain, config, # pylint: disable=unused-variable + hass=None): """Mock bootstrap.log_exception.""" - MOCKS['except'][1](ex, domain, config) + MOCKS['except'][1](ex, domain, config, hass) res['except'][domain] = config.get(domain, config) # Patches to skip functions diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 056bb074afa..efe99f86ebd 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -74,13 +74,15 @@ class TestCheckConfig(unittest.TestCase): with patch_yaml_files(files): res = check_config.check(get_test_config_dir('component.yaml')) change_yaml_files(res) - self.assertDictEqual({ - 'components': {}, - 'except': {'http': {'password': 'err123'}}, - 'secret_cache': {}, - 'secrets': {}, - 'yaml_files': ['.../component.yaml'] - }, res) + + self.assertDictEqual({}, res['components']) + self.assertDictEqual( + {'http': {'password': 'err123'}}, + res['except'] + ) + self.assertDictEqual({}, res['secret_cache']) + self.assertDictEqual({}, res['secrets']) + self.assertListEqual(['.../component.yaml'], res['yaml_files']) files = { 'platform.yaml': (BASE_CONFIG + 'mqtt:\n\n' diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 0f675d7f012..d3f3caf795c 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -269,11 +269,12 @@ class TestBootstrap: def test_home_assistant_core_config_validation(self): """Test if we pass in wrong information for HA conf.""" # Extensive HA conf validation testing is done in test_config.py + hass = get_test_home_assistant() assert None is bootstrap.from_config_dict({ 'homeassistant': { 'latitude': 'some string' } - }) + }, hass=hass) def test_component_setup_with_validation_and_dependency(self): """Test all config is passed to dependencies.""" From 8c13d3ed4c9e8e07abb6aca0f2814e118c788199 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 13 Oct 2016 12:09:41 -0400 Subject: [PATCH 048/147] Update zwave lights to increase update delay timer as needed (#3741) Fix permissions --- homeassistant/components/light/zwave.py | 57 +++++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 14d635153fb..5d0b334aeee 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -24,6 +24,22 @@ AEOTEC = 0x86 AEOTEC_ZW098_LED_BULB = 0x62 AEOTEC_ZW098_LED_BULB_LIGHT = (AEOTEC, AEOTEC_ZW098_LED_BULB) +LINEAR = 0x14f +LINEAR_WD500Z_DIMMER = 0x3034 +LINEAR_WD500Z_DIMMER_LIGHT = (LINEAR, LINEAR_WD500Z_DIMMER) + +GE = 0x63 +GE_12724_DIMMER = 0x3031 +GE_12724_DIMMER_LIGHT = (GE, GE_12724_DIMMER) + +DRAGONTECH = 0x184 +DRAGONTECH_PD100_DIMMER = 0x3032 +DRAGONTECH_PD100_DIMMER_LIGHT = (DRAGONTECH, DRAGONTECH_PD100_DIMMER) + +ACT = 0x01 +ACT_ZDP100_DIMMER = 0x3030 +ACT_ZDP100_DIMMER_LIGHT = (ACT, ACT_ZDP100_DIMMER) + COLOR_CHANNEL_WARM_WHITE = 0x01 COLOR_CHANNEL_COLD_WHITE = 0x02 COLOR_CHANNEL_RED = 0x04 @@ -31,9 +47,14 @@ COLOR_CHANNEL_GREEN = 0x08 COLOR_CHANNEL_BLUE = 0x10 WORKAROUND_ZW098 = 'zw098' +WORKAROUND_DELAY = 'alt_delay' DEVICE_MAPPINGS = { - AEOTEC_ZW098_LED_BULB_LIGHT: WORKAROUND_ZW098 + AEOTEC_ZW098_LED_BULB_LIGHT: WORKAROUND_ZW098, + LINEAR_WD500Z_DIMMER_LIGHT: WORKAROUND_DELAY, + GE_12724_DIMMER_LIGHT: WORKAROUND_DELAY, + DRAGONTECH_PD100_DIMMER_LIGHT: WORKAROUND_DELAY, + ACT_ZDP100_DIMMER_LIGHT: WORKAROUND_DELAY } # Generate midpoint color temperatures for bulbs that have limited @@ -95,6 +116,23 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): self._brightness = None self._state = None self.update_properties() + self._alt_delay = None + self._zw098 = None + + # Enable appropriate workaround flags for our device + # Make sure that we have values for the key before converting to int + if (value.node.manufacturer_id.strip() and + value.node.product_id.strip()): + specific_sensor_key = (int(value.node.manufacturer_id, 16), + int(value.node.product_id, 16)) + if specific_sensor_key in DEVICE_MAPPINGS: + if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: + _LOGGER.debug("AEOTEC ZW098 workaround enabled") + self._zw098 = 1 + elif DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_DELAY: + _LOGGER.debug("Dimmer delay workaround enabled for node:" + " %s", value.parent_id) + self._alt_delay = 1 # Used for value change event handling self._refreshing = False @@ -125,7 +163,10 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): if self._timer is not None and self._timer.isAlive(): self._timer.cancel() - self._timer = Timer(2, _refresh_value) + if self._alt_delay: + self._timer = Timer(5, _refresh_value) + else: + self._timer = Timer(2, _refresh_value) self._timer.start() self.update_ha_state() @@ -180,7 +221,6 @@ class ZwaveColorLight(ZwaveDimmer): self._color_channels = None self._rgb = None self._ct = None - self._zw098 = None # Here we attempt to find a zwave color value with the same instance # id as the dimmer value. Currently zwave nodes that change colors @@ -202,17 +242,6 @@ class ZwaveColorLight(ZwaveDimmer): if self._value_color_channels is None: raise ValueError("Color Channels not found.") - # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16)) - - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: - _LOGGER.debug("AEOTEC ZW098 workaround enabled") - self._zw098 = 1 - super().__init__(value) def update_properties(self): From c663d85129cb80c719807c035eb0f8378b778b97 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Thu, 13 Oct 2016 09:14:22 -0700 Subject: [PATCH 049/147] Add Alexa Flash Briefing Skill API support (#3745) * Add Alexa Flash Briefing Skill API support * Set default value for text to empty string as per API docs * Clean up existing Alexa tests * Update configuration parsing and validation * Add tests for the Flash Briefing API * Update test_alexa.py --- homeassistant/components/alexa.py | 110 +++++++- tests/components/test_alexa.py | 418 +++++++++++++++++------------- 2 files changed, 349 insertions(+), 179 deletions(-) diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 94d5b24cbf0..64ff50af323 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -7,16 +7,20 @@ https://home-assistant.io/components/alexa/ import copy import enum import logging +import uuid +from datetime import datetime import voluptuous as vol from homeassistant.const import HTTP_BAD_REQUEST from homeassistant.helpers import template, script, config_validation as cv from homeassistant.components.http import HomeAssistantView +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -API_ENDPOINT = '/api/alexa' +INTENTS_API_ENDPOINT = '/api/alexa' +FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/' CONF_ACTION = 'action' CONF_CARD = 'card' @@ -28,6 +32,23 @@ CONF_TITLE = 'title' CONF_CONTENT = 'content' CONF_TEXT = 'text' +CONF_FLASH_BRIEFINGS = 'flash_briefings' +CONF_UID = 'uid' +CONF_DATE = 'date' +CONF_TITLE = 'title' +CONF_AUDIO = 'audio' +CONF_TEXT = 'text' +CONF_DISPLAY_URL = 'display_url' + +ATTR_UID = 'uid' +ATTR_UPDATE_DATE = 'updateDate' +ATTR_TITLE_TEXT = 'titleText' +ATTR_STREAM_URL = 'streamUrl' +ATTR_MAIN_TEXT = 'mainText' +ATTR_REDIRECTION_URL = 'redirectionURL' + +DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z' + DOMAIN = 'alexa' DEPENDENCIES = ['http'] @@ -61,6 +82,16 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_TEXT): cv.template, } } + }, + CONF_FLASH_BRIEFINGS: { + cv.string: vol.All(cv.ensure_list, [{ + vol.Required(CONF_UID, default=str(uuid.uuid4())): cv.string, + vol.Optional(CONF_DATE, default=datetime.utcnow()): cv.string, + vol.Required(CONF_TITLE): cv.template, + vol.Optional(CONF_AUDIO): cv.template, + vol.Required(CONF_TEXT, default=""): cv.template, + vol.Optional(CONF_DISPLAY_URL): cv.template, + }]), } } }, extra=vol.ALLOW_EXTRA) @@ -68,16 +99,19 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): """Activate Alexa component.""" - hass.wsgi.register_view(AlexaView(hass, - config[DOMAIN].get(CONF_INTENTS, {}))) + intents = config[DOMAIN].get(CONF_INTENTS, {}) + flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {}) + + hass.wsgi.register_view(AlexaIntentsView(hass, intents)) + hass.wsgi.register_view(AlexaFlashBriefingView(hass, flash_briefings)) return True -class AlexaView(HomeAssistantView): +class AlexaIntentsView(HomeAssistantView): """Handle Alexa requests.""" - url = API_ENDPOINT + url = INTENTS_API_ENDPOINT name = 'api:alexa' def __init__(self, hass, intents): @@ -235,3 +269,69 @@ class AlexaResponse(object): 'sessionAttributes': self.session_attributes, 'response': response, } + + +class AlexaFlashBriefingView(HomeAssistantView): + """Handle Alexa Flash Briefing skill requests.""" + + url = FLASH_BRIEFINGS_API_ENDPOINT + name = 'api:alexa:flash_briefings' + + def __init__(self, hass, flash_briefings): + """Initialize Alexa view.""" + super().__init__(hass) + self.flash_briefings = copy.deepcopy(flash_briefings) + template.attach(hass, self.flash_briefings) + + # pylint: disable=too-many-branches + def get(self, request, briefing_id): + """Handle Alexa Flash Briefing request.""" + _LOGGER.debug('Received Alexa flash briefing request for: %s', + briefing_id) + + if self.flash_briefings.get(briefing_id) is None: + err = 'No configured Alexa flash briefing was found for: %s' + _LOGGER.error(err, briefing_id) + return self.Response(status=404) + + briefing = [] + + for item in self.flash_briefings.get(briefing_id, []): + output = {} + if item.get(CONF_TITLE) is not None: + if isinstance(item.get(CONF_TITLE), template.Template): + output[ATTR_TITLE_TEXT] = item[CONF_TITLE].render() + else: + output[ATTR_TITLE_TEXT] = item.get(CONF_TITLE) + + if item.get(CONF_TEXT) is not None: + if isinstance(item.get(CONF_TEXT), template.Template): + output[ATTR_MAIN_TEXT] = item[CONF_TEXT].render() + else: + output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT) + + if item.get(CONF_UID) is not None: + output[ATTR_UID] = item.get(CONF_UID) + + if item.get(CONF_AUDIO) is not None: + if isinstance(item.get(CONF_AUDIO), template.Template): + output[ATTR_STREAM_URL] = item[CONF_AUDIO].render() + else: + output[ATTR_STREAM_URL] = item.get(CONF_AUDIO) + + if item.get(CONF_DISPLAY_URL) is not None: + if isinstance(item.get(CONF_DISPLAY_URL), + template.Template): + output[ATTR_REDIRECTION_URL] = \ + item[CONF_DISPLAY_URL].render() + else: + output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL) + + if isinstance(item[CONF_DATE], str): + item[CONF_DATE] = dt_util.parse_datetime(item[CONF_DATE]) + + output[ATTR_UPDATE_DATE] = item[CONF_DATE].strftime(DATE_FORMAT) + + briefing.append(output) + + return self.json(briefing) diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index a40b401c777..31d5d6eec5c 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access,too-many-public-methods import json import time +import datetime import unittest import requests @@ -13,19 +14,27 @@ from tests.common import get_test_instance_port, get_test_home_assistant API_PASSWORD = "test1234" SERVER_PORT = get_test_instance_port() -API_URL = "http://127.0.0.1:{}{}".format(SERVER_PORT, alexa.API_ENDPOINT) +BASE_API_URL = "http://127.0.0.1:{}".format(SERVER_PORT) +INTENTS_API_URL = "{}{}".format(BASE_API_URL, alexa.INTENTS_API_ENDPOINT) + HA_HEADERS = { const.HTTP_HEADER_HA_AUTH: API_PASSWORD, const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON, } -SESSION_ID = 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000' -APPLICATION_ID = 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' -REQUEST_ID = 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000' +SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" +APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" +REQUEST_ID = "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000" +# pylint: disable=invalid-name hass = None calls = [] +NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3" + +# 2016-10-10T19:51:42+00:00 +STATIC_TIME = datetime.datetime.utcfromtimestamp(1476129102) + def setUpModule(): # pylint: disable=invalid-name """Initialize a Home Assistant server for testing this module.""" @@ -36,23 +45,40 @@ def setUpModule(): # pylint: disable=invalid-name bootstrap.setup_component( hass, http.DOMAIN, {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: SERVER_PORT}}) + http.CONF_SERVER_PORT: SERVER_PORT}}) - hass.services.register('test', 'alexa', lambda call: calls.append(call)) + hass.services.register("test", "alexa", lambda call: calls.append(call)) bootstrap.setup_component(hass, alexa.DOMAIN, { # Key is here to verify we allow other keys in config too - 'homeassistant': {}, - 'alexa': { - 'intents': { - 'WhereAreWeIntent': { - 'speech': { - 'type': 'plaintext', - 'text': + "homeassistant": {}, + "alexa": { + "flash_briefings": { + "weather": [ + {"title": "Weekly forecast", + "text": "This week it will be sunny.", + "date": "2016-10-09T19:51:42.0Z"}, + {"title": "Current conditions", + "text": "Currently it is 80 degrees fahrenheit.", + "date": STATIC_TIME} + ], + "news_audio": { + "title": "NPR", + "audio": NPR_NEWS_MP3_URL, + "display_url": "https://npr.org", + "date": STATIC_TIME, + "uid": "uuid" + } + }, + "intents": { + "WhereAreWeIntent": { + "speech": { + "type": "plaintext", + "text": """ - {%- if is_state('device_tracker.paulus', 'home') - and is_state('device_tracker.anne_therese', - 'home') -%} + {%- if is_state("device_tracker.paulus", "home") + and is_state("device_tracker.anne_therese", + "home") -%} You are both home, you silly {%- else -%} Anne Therese is at {{ @@ -64,23 +90,23 @@ def setUpModule(): # pylint: disable=invalid-name """, } }, - 'GetZodiacHoroscopeIntent': { - 'speech': { - 'type': 'plaintext', - 'text': 'You told us your sign is {{ ZodiacSign }}.', + "GetZodiacHoroscopeIntent": { + "speech": { + "type": "plaintext", + "text": "You told us your sign is {{ ZodiacSign }}.", } }, - 'CallServiceIntent': { - 'speech': { - 'type': 'plaintext', - 'text': 'Service called', + "CallServiceIntent": { + "speech": { + "type": "plaintext", + "text": "Service called", }, - 'action': { - 'service': 'test.alexa', - 'data_template': { - 'hello': '{{ ZodiacSign }}' + "action": { + "service": "test.alexa", + "data_template": { + "hello": "{{ ZodiacSign }}" }, - 'entity_id': 'switch.test', + "entity_id": "switch.test", } } } @@ -96,11 +122,19 @@ def tearDownModule(): # pylint: disable=invalid-name hass.stop() -def _req(data={}): - return requests.post(API_URL, data=json.dumps(data), timeout=5, +def _intent_req(data={}): + return requests.post(INTENTS_API_URL, data=json.dumps(data), timeout=5, headers=HA_HEADERS) +def _flash_briefing_req(briefing_id=None): + url_format = "{}/api/alexa/flash_briefings/{}" + FLASH_BRIEFING_API_URL = url_format.format(BASE_API_URL, + briefing_id) + return requests.get(FLASH_BRIEFING_API_URL, timeout=5, + headers=HA_HEADERS) + + class TestAlexa(unittest.TestCase): """Test Alexa.""" @@ -108,231 +142,267 @@ class TestAlexa(unittest.TestCase): """Stop everything that was started.""" hass.block_till_done() - def test_launch_request(self): + def test_intent_launch_request(self): """Test the launch of a request.""" data = { - 'version': '1.0', - 'session': { - 'new': True, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": True, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': {}, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "attributes": {}, + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'LaunchRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z' + "request": { + "type": "LaunchRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z" } } - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) resp = req.json() - self.assertIn('outputSpeech', resp['response']) + self.assertIn("outputSpeech", resp["response"]) def test_intent_request_with_slots(self): """Test a request with slots.""" data = { - 'version': '1.0', - 'session': { - 'new': False, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": False, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': { - 'supportedHoroscopePeriods': { - 'daily': True, - 'weekly': False, - 'monthly': False + "attributes": { + "supportedHoroscopePeriods": { + "daily": True, + "weekly": False, + "monthly": False } }, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'IntentRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z', - 'intent': { - 'name': 'GetZodiacHoroscopeIntent', - 'slots': { - 'ZodiacSign': { - 'name': 'ZodiacSign', - 'value': 'virgo' + "request": { + "type": "IntentRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + "intent": { + "name": "GetZodiacHoroscopeIntent", + "slots": { + "ZodiacSign": { + "name": "ZodiacSign", + "value": "virgo" } } } } } - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', - {}).get('text') - self.assertEqual('You told us your sign is virgo.', text) + text = req.json().get("response", {}).get("outputSpeech", + {}).get("text") + self.assertEqual("You told us your sign is virgo.", text) def test_intent_request_with_slots_but_no_value(self): """Test a request with slots but no value.""" data = { - 'version': '1.0', - 'session': { - 'new': False, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": False, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': { - 'supportedHoroscopePeriods': { - 'daily': True, - 'weekly': False, - 'monthly': False + "attributes": { + "supportedHoroscopePeriods": { + "daily": True, + "weekly": False, + "monthly": False } }, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'IntentRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z', - 'intent': { - 'name': 'GetZodiacHoroscopeIntent', - 'slots': { - 'ZodiacSign': { - 'name': 'ZodiacSign', + "request": { + "type": "IntentRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + "intent": { + "name": "GetZodiacHoroscopeIntent", + "slots": { + "ZodiacSign": { + "name": "ZodiacSign", } } } } } - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', - {}).get('text') - self.assertEqual('You told us your sign is .', text) + text = req.json().get("response", {}).get("outputSpeech", + {}).get("text") + self.assertEqual("You told us your sign is .", text) def test_intent_request_without_slots(self): """Test a request without slots.""" data = { - 'version': '1.0', - 'session': { - 'new': False, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": False, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': { - 'supportedHoroscopePeriods': { - 'daily': True, - 'weekly': False, - 'monthly': False + "attributes": { + "supportedHoroscopePeriods": { + "daily": True, + "weekly": False, + "monthly": False } }, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'IntentRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z', - 'intent': { - 'name': 'WhereAreWeIntent', + "request": { + "type": "IntentRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + "intent": { + "name": "WhereAreWeIntent", } } } - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', - {}).get('text') + text = req.json().get("response", {}).get("outputSpeech", + {}).get("text") - self.assertEqual('Anne Therese is at unknown and Paulus is at unknown', + self.assertEqual("Anne Therese is at unknown and Paulus is at unknown", text) - hass.states.set('device_tracker.paulus', 'home') - hass.states.set('device_tracker.anne_therese', 'home') + hass.states.set("device_tracker.paulus", "home") + hass.states.set("device_tracker.anne_therese", "home") - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', - {}).get('text') - self.assertEqual('You are both home, you silly', text) + text = req.json().get("response", {}).get("outputSpeech", + {}).get("text") + self.assertEqual("You are both home, you silly", text) def test_intent_request_calling_service(self): """Test a request for calling a service.""" data = { - 'version': '1.0', - 'session': { - 'new': False, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": False, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': {}, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "attributes": {}, + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'IntentRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z', - 'intent': { - 'name': 'CallServiceIntent', - 'slots': { - 'ZodiacSign': { - 'name': 'ZodiacSign', - 'value': 'virgo', + "request": { + "type": "IntentRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + "intent": { + "name": "CallServiceIntent", + "slots": { + "ZodiacSign": { + "name": "ZodiacSign", + "value": "virgo", } } } } } call_count = len(calls) - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) self.assertEqual(call_count + 1, len(calls)) call = calls[-1] - self.assertEqual('test', call.domain) - self.assertEqual('alexa', call.service) - self.assertEqual(['switch.test'], call.data.get('entity_id')) - self.assertEqual('virgo', call.data.get('hello')) + self.assertEqual("test", call.domain) + self.assertEqual("alexa", call.service) + self.assertEqual(["switch.test"], call.data.get("entity_id")) + self.assertEqual("virgo", call.data.get("hello")) - def test_session_ended_request(self): + def test_intent_session_ended_request(self): """Test the request for ending the session.""" data = { - 'version': '1.0', - 'session': { - 'new': False, - 'sessionId': SESSION_ID, - 'application': { - 'applicationId': APPLICATION_ID + "version": "1.0", + "session": { + "new": False, + "sessionId": SESSION_ID, + "application": { + "applicationId": APPLICATION_ID }, - 'attributes': { - 'supportedHoroscopePeriods': { - 'daily': True, - 'weekly': False, - 'monthly': False + "attributes": { + "supportedHoroscopePeriods": { + "daily": True, + "weekly": False, + "monthly": False } }, - 'user': { - 'userId': 'amzn1.account.AM3B00000000000000000000000' + "user": { + "userId": "amzn1.account.AM3B00000000000000000000000" } }, - 'request': { - 'type': 'SessionEndedRequest', - 'requestId': REQUEST_ID, - 'timestamp': '2015-05-13T12:34:56Z', - 'reason': 'USER_INITIATED' + "request": { + "type": "SessionEndedRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + "reason": "USER_INITIATED" } } - req = _req(data) + req = _intent_req(data) self.assertEqual(200, req.status_code) - self.assertEqual('', req.text) + self.assertEqual("", req.text) + + def test_flash_briefing_invalid_id(self): + """Test an invalid Flash Briefing ID.""" + req = _flash_briefing_req() + self.assertEqual(404, req.status_code) + self.assertEqual("", req.text) + + def test_flash_briefing_date_from_str(self): + """Test the response has a valid date parsed from string.""" + req = _flash_briefing_req("weather") + self.assertEqual(200, req.status_code) + self.assertEqual(req.json()[0].get(alexa.ATTR_UPDATE_DATE), + "2016-10-09T19:51:42.0Z") + + def test_flash_briefing_date_from_datetime(self): + """Test the response has a valid date from a datetime object.""" + req = _flash_briefing_req("weather") + self.assertEqual(200, req.status_code) + self.assertEqual(req.json()[1].get(alexa.ATTR_UPDATE_DATE), + '2016-10-10T19:51:42.0Z') + + def test_flash_briefing_valid(self): + """Test the response is valid.""" + data = [{ + "titleText": "NPR", + "redirectionURL": "https://npr.org", + "streamUrl": NPR_NEWS_MP3_URL, + "mainText": "", + "uid": "uuid", + "updateDate": '2016-10-10T19:51:42.0Z' + }] + + req = _flash_briefing_req("news_audio") + self.assertEqual(200, req.status_code) + response = req.json() + self.assertEqual(response, data) From d873a7baf041369a7ffc12a7315d68a54f37f88f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 13 Oct 2016 18:20:49 +0200 Subject: [PATCH 050/147] Use async_render_* and fix config validation (#3847) --- homeassistant/components/binary_sensor/rest.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 3e22150a4fd..36b455f9dfe 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.rest/ """ import logging -import json import voluptuous as vol from requests.auth import HTTPBasicAuth, HTTPDigestAuth @@ -30,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RESOURCE): cv.url, vol.Optional(CONF_AUTHENTICATION): vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), - vol.Optional(CONF_HEADERS): cv.string, + vol.Optional(CONF_HEADERS): {cv.string: cv.string}, vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string, @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): verify_ssl = config.get(CONF_VERIFY_SSL) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - headers = json.loads(config.get(CONF_HEADERS, '{}')) + headers = config.get(CONF_HEADERS) sensor_class = config.get(CONF_SENSOR_CLASS) value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: @@ -70,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): rest.update() if rest.data is None: - _LOGGER.error('Unable to fetch REST data') + _LOGGER.error("Unable to fetch REST data from %s", resource) return False add_devices([RestBinarySensor( @@ -109,8 +108,8 @@ class RestBinarySensor(BinarySensorDevice): return False if self._value_template is not None: - response = self._value_template.render_with_possible_json_value( - self.rest.data, False) + response = self._value_template.\ + async_render_with_possible_json_value(self.rest.data, False) try: return bool(int(response)) From 9a0bb62654cc608e974ab7991f16b53fc98a2ee1 Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Thu, 13 Oct 2016 18:21:22 +0200 Subject: [PATCH 051/147] Hotfix for Netatmo discovery (#3837) This should definetly fix #2601 Signed-off-by: Hugo D. (jabesq) --- .../components/binary_sensor/netatmo.py | 5 ++- homeassistant/components/camera/netatmo.py | 13 ++++--- homeassistant/components/sensor/netatmo.py | 34 +++++++++++-------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index 7a7f865494a..e5004db0a4b 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -49,12 +49,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): import lnetatmo try: data = WelcomeData(netatmo.NETATMO_AUTH, home) + if data.get_camera_names() == []: + return None except lnetatmo.NoDevice: return None - if data.get_camera_names() == []: - return None - sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES) for camera_name in data.get_camera_names(): diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 259feb41a1c..47808de02b9 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -36,16 +36,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): import lnetatmo try: data = WelcomeData(netatmo.NETATMO_AUTH, home) + for camera_name in data.get_camera_names(): + if CONF_CAMERAS in config: + if config[CONF_CAMERAS] != [] and \ + camera_name not in config[CONF_CAMERAS]: + continue + add_devices([WelcomeCamera(data, camera_name, home)]) except lnetatmo.NoDevice: return None - for camera_name in data.get_camera_names(): - if CONF_CAMERAS in config: - if config[CONF_CAMERAS] != [] and \ - camera_name not in config[CONF_CAMERAS]: - continue - add_devices([WelcomeCamera(data, camera_name, home)]) - class WelcomeCamera(Camera): """Representation of the images published from Welcome camera.""" diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index e59bb4a5553..2d321752483 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -65,20 +65,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None): data = NetAtmoData(netatmo.NETATMO_AUTH, config.get(CONF_STATION, None)) dev = [] - if CONF_MODULES in config: - # Iterate each module - for module_name, monitored_conditions in config[CONF_MODULES].items(): - # Test if module exist """ - if module_name not in data.get_module_names(): - _LOGGER.error('Module name: "%s" not found', module_name) - continue - # Only create sensor for monitored """ - for variable in monitored_conditions: - dev.append(NetAtmoSensor(data, module_name, variable)) - else: - for module_name in data.get_module_names(): - for variable in data.station_data.monitoredConditions(module_name): - dev.append(NetAtmoSensor(data, module_name, variable)) + import lnetatmo + try: + if CONF_MODULES in config: + # Iterate each module + for module_name, monitored_conditions in\ + config[CONF_MODULES].items(): + # Test if module exist """ + if module_name not in data.get_module_names(): + _LOGGER.error('Module name: "%s" not found', module_name) + continue + # Only create sensor for monitored """ + for variable in monitored_conditions: + dev.append(NetAtmoSensor(data, module_name, variable)) + else: + for module_name in data.get_module_names(): + for variable in\ + data.station_data.monitoredConditions(module_name): + dev.append(NetAtmoSensor(data, module_name, variable)) + except lnetatmo.NoDevice: + return None add_devices(dev) From 6330d9cde6286251e63e132a82f47e14c8aacd1d Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Thu, 13 Oct 2016 12:22:05 -0400 Subject: [PATCH 052/147] Fixed Fitbit resting heart rate attribute (#3835) * Fixed Fitbit restingHeartRate field to grab the correct field from dictionary In [31]: r['activities-heart'][-1].get('value') Out[31]: {'customHeartRateZones': [], 'heartRateZones': [{'caloriesOut': 126.18348, 'max': 94, 'min': 30, 'minutes': 67, 'name': 'Out of Range'}, {'caloriesOut': 27.21339, 'max': 131, 'min': 94, 'minutes': 5, 'name': 'Fat Burn'}, {'caloriesOut': 0, 'max': 159, 'min': 131, 'minutes': 0, 'name': 'Cardio'}, {'caloriesOut': 0, 'max': 220, 'min': 159, 'minutes': 0, 'name': 'Peak'}], 'restingHeartRate': 69} In [32]: r['activities-heart'][-1].get('value').get('restingHeartRate') Out[32]: 69 * Renamed sensor to Resting Heart Rate to match it * Fixed lint style --- homeassistant/components/sensor/fitbit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py index cf11c9bb393..11288bae63a 100644 --- a/homeassistant/components/sensor/fitbit.py +++ b/homeassistant/components/sensor/fitbit.py @@ -359,6 +359,8 @@ class FitbitSensor(Entity): pretty_resource = pretty_resource.title() if pretty_resource == 'Body Bmi': pretty_resource = 'BMI' + elif pretty_resource == 'Heart': + pretty_resource = 'Resting Heart Rate' self._name = pretty_resource unit_type = FITBIT_RESOURCES_LIST[self.resource_type] if unit_type == "": @@ -403,7 +405,8 @@ class FitbitSensor(Entity): response = self.client.time_series(self.resource_type, period='7d') self._state = response[container][-1].get('value') if self.resource_type == 'activities/heart': - self._state = response[container][-1].get('restingHeartRate') + self._state = response[container][-1]. \ + get('value').get('restingHeartRate') config_contents = { ATTR_ACCESS_TOKEN: self.client.client.token['access_token'], ATTR_REFRESH_TOKEN: self.client.client.token['refresh_token'], From 3d94f77998fd142549fc1a3bb51731edb235d081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Thu, 13 Oct 2016 21:53:50 +0200 Subject: [PATCH 053/147] vsure 0.11.1 --- 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 732677a0b5f..878d9a8d6a2 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -16,7 +16,7 @@ from homeassistant.helpers import discovery from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['vsure==0.11.0'] +REQUIREMENTS = ['vsure==0.11.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 980fb226d7b..6ee8231bc41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -500,7 +500,7 @@ urllib3 uvcclient==0.9.0 # homeassistant.components.verisure -vsure==0.11.0 +vsure==0.11.1 # homeassistant.components.sensor.vasttrafik vtjp==0.1.11 From 7771cc2ccf67dad8ae5b7a0e4f03d4e568e4f23b Mon Sep 17 00:00:00 2001 From: Hydreliox Date: Fri, 14 Oct 2016 04:43:51 +0200 Subject: [PATCH 054/147] Add Bbox Modem Routeur for device tracker (#3848) --- .coveragerc | 1 + .../components/device_tracker/bbox.py | 82 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 86 insertions(+) create mode 100644 homeassistant/components/device_tracker/bbox.py diff --git a/.coveragerc b/.coveragerc index a9b9d4e6df5..85a3cfb78ce 100644 --- a/.coveragerc +++ b/.coveragerc @@ -128,6 +128,7 @@ omit = homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py + homeassistant/components/device_tracker/bbox.py homeassistant/components/device_tracker/bluetooth_tracker.py homeassistant/components/device_tracker/bluetooth_le_tracker.py homeassistant/components/device_tracker/bt_home_hub_5.py diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/device_tracker/bbox.py new file mode 100644 index 00000000000..c851b622592 --- /dev/null +++ b/homeassistant/components/device_tracker/bbox.py @@ -0,0 +1,82 @@ +""" +Support for French FAI Bouygues Bbox routers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.bbox/ +""" +from collections import namedtuple +import logging +from datetime import timedelta +import homeassistant.util.dt as dt_util +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.util import Throttle + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=60) + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['pybbox==0.0.5-alpha'] + + +def get_scanner(hass, config): + """Validate the configuration and return a Bbox scanner.""" + scanner = BboxDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update']) + + +class BboxDeviceScanner(object): + """This class scans for devices connected to the bbox.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.last_results = [] # type: List[Device] + + self.success_init = self._update_info() + _LOGGER.info('Bbox scanner initialized') + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + + return [device.mac for device in self.last_results] + + def get_device_name(self, mac): + """Return the name of the given device or None if we don't know.""" + filter_named = [device.name for device in self.last_results if + device.mac == mac] + + if filter_named: + return filter_named[0] + else: + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """Check the bbox for devices. + + Returns boolean if scanning successful. + """ + _LOGGER.info('Scanning') + + import pybbox + + box = pybbox.Bbox() + result = box.get_all_connected_devices() + + now = dt_util.now() + last_results = [] + for device in result: + if device['active'] != 1: + continue + last_results.append( + Device(device['macaddress'], device['hostname'], + device['ipaddress'], now)) + + self.last_results = last_results + + _LOGGER.info('Bbox scan successful') + return True diff --git a/requirements_all.txt b/requirements_all.txt index 6ee8231bc41..48d432b2e0c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -319,6 +319,9 @@ pyasn1-modules==0.0.8 # homeassistant.components.notify.xmpp pyasn1==0.1.9 +# homeassistant.components.device_tracker.bbox +pybbox==0.0.5-alpha + # homeassistant.components.device_tracker.bluetooth_tracker # pybluez==0.22 From 1373db8b60f310f684affaf8066f01aa4a3f3a14 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 14 Oct 2016 06:13:05 +0200 Subject: [PATCH 055/147] Include index and instance in object_id of zwave devices (#3759) * Include index and instance in object_id of zwave devices * Add the instance id if there is more than one instance for the value --- homeassistant/components/sensor/zwave.py | 26 ---------------------- homeassistant/components/zwave/__init__.py | 3 ++- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 4f474dbe73f..3a70e0d521f 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -12,20 +12,6 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity -FIBARO = 0x010f -FIBARO_WALL_PLUG = 0x1000 -FIBARO_WALL_PLUG_SENSOR_METER = (FIBARO, FIBARO_WALL_PLUG, 8) - -WORKAROUND_IGNORE = 'ignore' - -DEVICE_MAPPINGS = { - # For some reason Fibaro Wall Plug reports 2 power consumptions. - # One value updates as the power consumption changes - # and the other does not change. - FIBARO_WALL_PLUG_SENSOR_METER: WORKAROUND_IGNORE, -} - - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Z-Wave sensors.""" # Return on empty `discovery_info`. Given you configure HA with: @@ -46,18 +32,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # groups[1].associations): # node.groups[1].add_association(NETWORK.controller.node_id) - # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16), - value.index) - - # Check workaround mappings for specific devices. - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE: - return - # Generic Device mappings if node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL): add_devices([ZWaveMultilevelSensor(value)]) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 32d4f42c1a1..deec40062ab 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -181,7 +181,8 @@ def _object_id(value): The object_id contains node_id and value instance id to not collide with other entity_ids. """ - object_id = "{}_{}".format(slugify(_value_name(value)), value.node.node_id) + object_id = "{}_{}_{}".format(slugify(_value_name(value)), + value.node.node_id, value.index) # Add the instance id if there is more than one instance for the value if value.instance > 1: From 4d716cec2b51debd2db11dc7ad0fb80d0ada3bd9 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Fri, 14 Oct 2016 06:24:54 +0200 Subject: [PATCH 056/147] Bump pychromecast dependency to 0.7.6 (#3864) --- homeassistant/components/media_player/cast.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index cf764fd723e..831f9857e4b 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -19,7 +19,7 @@ from homeassistant.const import ( STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pychromecast==0.7.4'] +REQUIREMENTS = ['pychromecast==0.7.6'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 48d432b2e0c..390d9a17297 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -326,7 +326,7 @@ pybbox==0.0.5-alpha # pybluez==0.22 # homeassistant.components.media_player.cast -pychromecast==0.7.4 +pychromecast==0.7.6 # homeassistant.components.media_player.cmus pycmus==0.1.0 From 399a0b470a92400c2c914656c2ecb1093d5d41fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Fri, 14 Oct 2016 06:53:47 +0200 Subject: [PATCH 057/147] select next and previous of input select (#3839) --- homeassistant/components/input_select.py | 59 +++++++++++++++++++++- tests/components/test_input_select.py | 62 +++++++++++++++++++++++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index 33c0757f266..f94d8200d00 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -31,6 +31,18 @@ SERVICE_SELECT_OPTION_SCHEMA = vol.Schema({ vol.Required(ATTR_OPTION): cv.string, }) +SERVICE_SELECT_NEXT = 'select_next' + +SERVICE_SELECT_NEXT_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + +SERVICE_SELECT_PREVIOUS = 'select_previous' + +SERVICE_SELECT_PREVIOUS_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + def _cv_input_select(cfg): """Config validation helper for input select (Voluptuous).""" @@ -53,13 +65,27 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: { def select_option(hass, entity_id, option): - """Set input_select to False.""" + """Set value of input_select.""" hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, { ATTR_ENTITY_ID: entity_id, ATTR_OPTION: option, }) +def select_next(hass, entity_id): + """Set next value of input_select.""" + hass.services.call(DOMAIN, SERVICE_SELECT_NEXT, { + ATTR_ENTITY_ID: entity_id, + }) + + +def select_previous(hass, entity_id): + """Set previous value of input_select.""" + hass.services.call(DOMAIN, SERVICE_SELECT_PREVIOUS, { + ATTR_ENTITY_ID: entity_id, + }) + + def setup(hass, config): """Setup input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) @@ -77,7 +103,7 @@ def setup(hass, config): return False def select_option_service(call): - """Handle a calls to the input select services.""" + """Handle a calls to the input select option service.""" target_inputs = component.extract_from_service(call) for input_select in target_inputs: @@ -87,6 +113,28 @@ def setup(hass, config): select_option_service, schema=SERVICE_SELECT_OPTION_SCHEMA) + def select_next_service(call): + """Handle a calls to the input select next service.""" + target_inputs = component.extract_from_service(call) + + for input_select in target_inputs: + input_select.offset_index(1) + + hass.services.register(DOMAIN, SERVICE_SELECT_NEXT, + select_next_service, + schema=SERVICE_SELECT_NEXT_SCHEMA) + + def select_previous_service(call): + """Handle a calls to the input select previous service.""" + target_inputs = component.extract_from_service(call) + + for input_select in target_inputs: + input_select.offset_index(-1) + + hass.services.register(DOMAIN, SERVICE_SELECT_PREVIOUS, + select_previous_service, + schema=SERVICE_SELECT_PREVIOUS_SCHEMA) + component.add_entities(entities) return True @@ -139,3 +187,10 @@ class InputSelect(Entity): return self._current_option = option self.update_ha_state() + + def offset_index(self, offset): + """Offset current index.""" + current_index = self._options.index(self._current_option) + new_index = (current_index + offset) % len(self._options) + self._current_option = self._options[new_index] + self.update_ha_state() diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py index a3f121576fb..8231390410e 100644 --- a/tests/components/test_input_select.py +++ b/tests/components/test_input_select.py @@ -6,7 +6,7 @@ from tests.common import get_test_home_assistant from homeassistant.bootstrap import setup_component from homeassistant.components.input_select import ( - ATTR_OPTIONS, DOMAIN, select_option) + ATTR_OPTIONS, DOMAIN, select_option, select_next, select_previous) from homeassistant.const import ( ATTR_ICON, ATTR_FRIENDLY_NAME) @@ -67,6 +67,66 @@ class TestInputSelect(unittest.TestCase): state = self.hass.states.get(entity_id) self.assertEqual('another option', state.state) + def test_select_next(self): + """Test select_next methods.""" + self.assertTrue( + setup_component(self.hass, DOMAIN, {DOMAIN: { + 'test_1': { + 'options': [ + 'first option', + 'middle option', + 'last option', + ], + 'initial': 'middle option', + }, + }})) + entity_id = 'input_select.test_1' + + state = self.hass.states.get(entity_id) + self.assertEqual('middle option', state.state) + + select_next(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('last option', state.state) + + select_next(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('first option', state.state) + + def test_select_previous(self): + """Test select_previous methods.""" + self.assertTrue( + setup_component(self.hass, DOMAIN, {DOMAIN: { + 'test_1': { + 'options': [ + 'first option', + 'middle option', + 'last option', + ], + 'initial': 'middle option', + }, + }})) + entity_id = 'input_select.test_1' + + state = self.hass.states.get(entity_id) + self.assertEqual('middle option', state.state) + + select_previous(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('first option', state.state) + + select_previous(self.hass, entity_id) + self.hass.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('last option', state.state) + def test_config_options(self): """Test configuration options.""" count_start = len(self.hass.states.entity_ids()) From 8f4608c654d6da81ec68ffd8302460b41f738475 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Fri, 14 Oct 2016 08:45:00 +0200 Subject: [PATCH 058/147] Use only node id to identify node in set_config_parameter (#3801) --- homeassistant/components/zwave/__init__.py | 5 ++--- homeassistant/components/zwave/services.yaml | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index deec40062ab..954a8331b56 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -125,7 +125,7 @@ RENAME_NODE_SCHEMA = vol.Schema({ vol.Required(const.ATTR_NAME): cv.string, }) SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), vol.Required(const.ATTR_CONFIG_VALUE): vol.Coerce(int), vol.Optional(const.ATTR_CONFIG_SIZE): vol.Coerce(int) @@ -428,8 +428,7 @@ def setup(hass, config): def set_config_parameter(service): """Set a config parameter to a node.""" - state = hass.states.get(service.data.get(ATTR_ENTITY_ID)) - node_id = state.attributes.get(const.ATTR_NODE_ID) + node_id = service.data.get(const.ATTR_NODE_ID) node = NETWORK.nodes[node_id] param = service.data.get(const.ATTR_CONFIG_PARAMETER) value = service.data.get(const.ATTR_CONFIG_VALUE) diff --git a/homeassistant/components/zwave/services.yaml b/homeassistant/components/zwave/services.yaml index 2542502badb..7a73cd66fb3 100644 --- a/homeassistant/components/zwave/services.yaml +++ b/homeassistant/components/zwave/services.yaml @@ -16,8 +16,8 @@ remove_node: set_config_parameter: description: Set a config parameter to a node on the Z-Wave network. fields: - entity_id: - description: Name of entity to set config parameter to. + node_id: + description: Node id of the device to set config parameter to (integer). parameter: description: Parameter number to set (integer). value: From f916fc04f98c22e98019d94fb42cef0f93c2af14 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Oct 2016 00:02:21 -0700 Subject: [PATCH 059/147] Update frontend --- homeassistant/components/frontend/version.py | 8 ++++---- .../components/frontend/www_static/core.js | 8 ++++---- .../components/frontend/www_static/core.js.gz | Bin 32200 -> 32810 bytes .../frontend/www_static/frontend.html | 2 +- .../frontend/www_static/frontend.html.gz | Bin 128054 -> 128092 bytes .../www_static/home-assistant-polymer | 2 +- .../www_static/panels/ha-panel-dev-state.html | 2 +- .../panels/ha-panel-dev-state.html.gz | Bin 2786 -> 2811 bytes .../www_static/panels/ha-panel-map.html | 6 +++--- .../www_static/panels/ha-panel-map.html.gz | Bin 43920 -> 43913 bytes .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 2277 -> 2327 bytes 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 2c8b0cc8bed..ea8d4de4aea 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,16 +1,16 @@ """DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.""" FINGERPRINTS = { - "core.js": "9b3e5ab4eac7e3b074e0daf3f619a638", - "frontend.html": "5854807d361de26fe93ad474010f19d2", + "core.js": "5ed5e063d66eb252b5b288738c9c2d16", + "frontend.html": "b13c6ed83e3a003e3d0896cefad4c077", "mdi.html": "46a76f877ac9848899b8ed382427c16f", "panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002", "panels/ha-panel-dev-info.html": "ec613406ce7e20d93754233d55625c8a", "panels/ha-panel-dev-service.html": "c7974458ebc33412d95497e99b785e12", - "panels/ha-panel-dev-state.html": "4be627b74e683af14ef779d8203ec674", + "panels/ha-panel-dev-state.html": "65e5f791cc467561719bf591f1386054", "panels/ha-panel-dev-template.html": "d23943fa0370f168714da407c90091a2", "panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", "panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40", - "panels/ha-panel-map.html": "af7d04aff7dd5479c5a0016bc8d4dd7d" + "panels/ha-panel-map.html": "49ab2d6f180f8bdea7cffaa66b8a5d3e" } diff --git a/homeassistant/components/frontend/www_static/core.js b/homeassistant/components/frontend/www_static/core.js index 862449055fb..a07e5819489 100644 --- a/homeassistant/components/frontend/www_static/core.js +++ b/homeassistant/components/frontend/www_static/core.js @@ -1,4 +1,4 @@ -!(function(){"use strict";function t(t){return t&&t.__esModule?t.default:t}function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}function n(t,e){var n=e.authToken,r=e.host;return Ne({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function r(){return ke.getInitialState()}function i(t,e){var n=e.errorMessage;return t.withMutations((function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)}))}function o(t,e){var n=e.authToken,r=e.host;return Pe({authToken:n,host:r})}function u(){return He.getInitialState()}function a(t,e){var n=e.rememberAuth;return n}function s(t){return t.withMutations((function(t){t.set("isStreaming",!0).set("useStreaming",!0).set("hasError",!1)}))}function c(t){return t.withMutations((function(t){t.set("isStreaming",!1).set("useStreaming",!1).set("hasError",!1)}))}function f(t){return t.withMutations((function(t){t.set("isStreaming",!1).set("hasError",!0)}))}function h(){return Ye.getInitialState()}function l(t,e){var n=e.model,r=e.result,i=e.params,o=n.entity;if(!r)return t;var u=i.replace?tn({}):t.get(o),a=Array.isArray(r)?r:[r],s=n.fromJSON||tn;return t.set(o,u.withMutations((function(t){for(var e=0;e199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function C(t,e){var n=e.message;return t.set(t.size,n)}function z(){return zn.getInitialState()}function R(t,e){t.dispatch(An.NOTIFICATION_CREATED,{message:e})}function M(t){t.registerStores({notifications:zn})}function L(t,e){if("lock"===t)return!0;if("garage_door"===t)return!0;var n=e.get(t);return!!n&&n.services.has("turn_on")}function j(t,e){return!!t&&("group"===t.domain?"on"===t.state||"off"===t.state:L(t.domain,e))}function N(t,e){return[rr(t),function(t){return!!t&&t.services.has(e)}]}function k(t){return[wn.byId(t),nr,j]}function U(t,e,n){function r(){var c=(new Date).getTime()-a;c0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function P(t,e){var n=e.component;return t.push(n)}function H(t,e){var n=e.components;return dr(n)}function x(){return vr.getInitialState()}function V(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.unit_system,u=e.time_zone,a=e.config_dir,s=e.version;return Sr({latitude:n,longitude:r,location_name:i,unit_system:o,time_zone:u,config_dir:a,serverVersion:s})}function q(){return gr.getInitialState()}function F(t,e){t.dispatch(pr.SERVER_CONFIG_LOADED,e)}function G(t){ln(t,"GET","config").then((function(e){return F(t,e)}))}function K(t,e){t.dispatch(pr.COMPONENT_LOADED,{component:e})}function Y(t){return[["serverComponent"],function(e){return e.contains(t)}]}function B(t){t.registerStores({serverComponent:vr,serverConfig:gr})}function J(t,e){var n=e.pane;return n}function W(){return Rr.getInitialState()}function X(t,e){var n=e.panels;return Lr(n)}function Q(){return jr.getInitialState()}function Z(t,e){var n=e.show;return!!n}function $(){return kr.getInitialState()}function tt(t,e){t.dispatch(Cr.SHOW_SIDEBAR,{show:e})}function et(t,e){t.dispatch(Cr.NAVIGATE,{pane:e})}function nt(t,e){t.dispatch(Cr.PANELS_LOADED,{panels:e})}function rt(t,e){var n=e.entityId;return n}function it(){return Kr.getInitialState()}function ot(t,e){t.dispatch(Fr.SELECT_ENTITY,{entityId:e})}function ut(t){t.dispatch(Fr.SELECT_ENTITY,{entityId:null})}function at(t){return!t||(new Date).getTime()-t>6e4}function st(t,e){var n=e.date;return n.toISOString()}function ct(){return Wr.getInitialState()}function ft(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,Qr({})):t.withMutations((function(t){r.forEach((function(e){return t.setIn([n,e[0].entity_id],Qr(e.map(yn.fromJSON)))}))}))}function ht(){return Zr.getInitialState()}function lt(t,e){var n=e.stateHistory;return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,ni(e.map(yn.fromJSON)))}))}))}function pt(){return ri.getInitialState()}function _t(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(ui,r)}))}function dt(){return ai.getInitialState()}function vt(t,e){t.dispatch(Br.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function yt(t,e){void 0===e&&(e=null),t.dispatch(Br.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),ln(t,"GET",n).then((function(e){return t.dispatch(Br.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})}),(function(){return t.dispatch(Br.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})}))}function St(t,e){return t.dispatch(Br.ENTITY_HISTORY_FETCH_START,{date:e}),ln(t,"GET","history/period/"+e).then((function(n){return t.dispatch(Br.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})}),(function(){return t.dispatch(Br.ENTITY_HISTORY_FETCH_ERROR,{})}))}function gt(t){var e=t.evaluate(fi);return St(t,e)}function mt(t){t.registerStores({currentEntityHistoryDate:Wr,entityHistory:Zr,isLoadingEntityHistory:ti,recentEntityHistory:ri,recentEntityHistoryUpdated:ai})}function Et(t){t.registerStores({moreInfoEntityId:Kr})}function It(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;oau}function se(t){t.registerStores({currentLogbookDate:Bo,isLoadingLogbookEntries:Wo,logbookEntries:eu,logbookEntriesUpdated:iu})}function ce(t){return t.set("active",!0)}function fe(t){return t.set("active",!1)}function he(){return gu.getInitialState()}function le(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",ln(t,"POST","notify.html5",{subscription:e,browser:n}).then((function(){return t.dispatch(vu.PUSH_NOTIFICATIONS_SUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Nn.createNotification(t,n),!1}))}function pe(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){return ln(t,"DELETE","notify.html5",{subscription:e}).then((function(){return e.unsubscribe()})).then((function(){return t.dispatch(vu.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Nn.createNotification(t,n),!1}))}function _e(t){t.registerStores({pushNotifications:gu})}function de(t,e){return ln(t,"POST","template",{template:e})}function ve(t){return t.set("isListening",!0)}function ye(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)}))}function Se(t,e){var n=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)}))}function ge(){return Nu.getInitialState()}function me(){return Nu.getInitialState()}function Ee(){return Nu.getInitialState()}function Ie(t){return ku[t.hassId]}function be(t){var e=Ie(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(Mu.VOICE_TRANSMITTING,{finalTranscript:n}),ur.callService(t,"conversation","process",{text:n}).then((function(){t.dispatch(Mu.VOICE_DONE)}),(function(){t.dispatch(Mu.VOICE_ERROR)}))}}function Oe(t){var e=Ie(t);e&&(e.recognition.stop(),ku[t.hassId]=!1)}function we(t){be(t),Oe(t)}function Te(t){var e=we.bind(null,t);e();var n=new webkitSpeechRecognition;ku[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(Mu.VOICE_START)},n.onerror=function(){return t.dispatch(Mu.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ie(t);if(n){for(var r="",i="",o=e.resultIndex;o=n)}function c(t,e){return h(t,e,0)}function f(t,e){return h(t,e,e)}function h(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function l(t){return v(t)?t:C(t)}function p(t){return y(t)?t:z(t)}function _(t){return S(t)?t:R(t)}function d(t){return v(t)&&!g(t)?t:M(t)}function v(t){return!(!t||!t[dn])}function y(t){return!(!t||!t[vn])}function S(t){return!(!t||!t[yn])}function g(t){return y(t)||S(t)}function m(t){return!(!t||!t[Sn])}function E(t){this.next=t}function I(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function b(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(In&&t[In]||t[bn]);if("function"==typeof e)return e}function D(t){return t&&"number"==typeof t.length}function C(t){return null===t||void 0===t?P():v(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?P().toKeyedSeq():v(t)?y(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t.toIndexedSeq():x(t)}function M(t){return(null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t:x(t)).toSetSeq()}function L(t){this._array=t,this.size=t.length}function j(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function U(t){return!(!t||!t[wn])}function P(){return Tn||(Tn=new L([]))}function H(t){var e=Array.isArray(t)?new L(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new j(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=q(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=q(t)||"object"==typeof t&&new j(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function q(t){return D(t)?new L(t):w(t)?new k(t):O(t)?new N(t):void 0}function F(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new E(function(){var t=i[n?o-u:u];return u++>o?b():I(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(){throw TypeError("Abstract")}function Y(){}function B(){}function J(){}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){return e?Q(e,t,"",{"":t}):Z(t)}function Q(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map((function(n,r){return Q(t,n,r,e)}))):$(e)?t.call(r,n,z(e).map((function(n,r){return Q(t,n,r,e)}))):e}function Z(t){return Array.isArray(t)?R(t).map(Z).toList():$(t)?z(t).map(Z).toMap():t}function $(t){return t&&(t.constructor===Object||void 0===t.constructor)}function tt(t){return t>>>1&1073741824|3221225471&t}function et(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return tt(n)}return"string"===e?t.length>jn?nt(t):rt(t):"function"==typeof t.hashCode?t.hashCode():it(t)}function nt(t){var e=Un[t];return void 0===e&&(e=rt(t),kn===Nn&&(kn=0,Un={}),kn++,Un[t]=e),e}function rt(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ut(t,e){if(!t)throw new Error(e)}function at(t){ut(t!==1/0,"Cannot perform this action with an infinite size.")}function st(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ct(t){this._iter=t,this.size=t.size}function ft(t){this._iter=t,this.size=t.size}function ht(t){this._iter=t,this.size=t.size}function lt(t){var e=Lt(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=jt,e.__iterateUncached=function(e,n){var r=this;return t.__iterate((function(t,n){return e(n,t,r)!==!1}),n)},e.__iteratorUncached=function(e,n){if(e===En){var r=t.__iterator(e,n);return new E(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===mn?gn:mn,n)},e}function pt(t,e,n){var r=Lt(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,ln);return o===ln?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate((function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1}),i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(En,i);return new E(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return I(r,a,e.call(n,u[1],a,t),i)})},r}function _t(t,e){var n=Lt(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=lt(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=jt,n.__iterate=function(e,n){var r=this;return t.__iterate((function(t,n){return e(t,n,r)}),!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function dt(t,e,n,r){var i=Lt(t);return r&&(i.has=function(r){var i=t.get(r,ln);return i!==ln&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,ln);return o!==ln&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate((function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)}),o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(En,o),a=0;return new E(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return I(i,r?c:a++,f,o)}})},i}function vt(t,e,n){var r=Ut().asMutable();return t.__iterate((function(i,o){r.update(e.call(n,i,o,t),0,(function(t){return t+1}))})),r.asImmutable()}function yt(t,e,n){var r=y(t),i=(m(t)?be():Ut()).asMutable();t.__iterate((function(o,u){i.update(e.call(n,o,u,t),(function(t){return t=t||[],t.push(r?[u,o]:o),t}))}));var o=Mt(t);return i.map((function(e){return Ct(t,o(e))}))}function St(t,e,n,r){var i=t.size;if(void 0!==e&&(e=0|e),void 0!==n&&(n=0|n),s(e,n,i))return t;var o=c(e,i),a=f(n,i);if(o!==o||a!==a)return St(t.toSeq().cacheResult(),e,n,r);var h,l=a-o;l===l&&(h=l<0?0:l);var p=Lt(t);return p.size=0===h?h:t.size&&h||void 0,!r&&U(t)&&h>=0&&(p.get=function(e,n){return e=u(this,e),e>=0&&eh)return b();var t=i.next();return r||e===mn?t:e===gn?I(e,a-1,void 0,t):I(e,a-1,t.value[1],t)})},p}function gt(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate((function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)})),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(En,i),a=!0;return new E(function(){if(!a)return b();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===En?t:I(r,s,c,t):(a=!1,b())})},r}function mt(t,e,n,r){var i=Lt(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate((function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)})),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(En,o),s=!0,c=0;return new E(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===mn?t:i===gn?I(i,c++,void 0,t):I(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===En?t:I(i,o,f,t)})},i}function Et(t,e){var n=y(t),r=[t].concat(e).map((function(t){return v(t)?n&&(t=p(t)):t=n?H(t):x(Array.isArray(t)?t:[t]),t})).filter((function(t){return 0!==t.size}));if(0===r.length)return t;if(1===r.length){var i=r[0];if(i===t||n&&y(i)||S(t)&&S(i))return i}var o=new L(r);return n?o=o.toKeyedSeq():S(t)||(o=o.toSetSeq()),o=o.flatten(!0),o.size=r.reduce((function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}}),0),o}function It(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){function o(t,s){var c=this;t.__iterate((function(t,i){return(!e||s0}function Dt(t,e,n){var r=Lt(t);return r.size=new L(n).map((function(t){return t.size})).min(),r.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(mn,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},r.__iteratorUncached=function(t,r){var i=n.map((function(t){return t=l(t),T(r?t.reverse():t)})),o=0,u=!1;return new E(function(){var n;return u||(n=i.map((function(t){ -return t.next()})),u=n.some((function(t){return t.done}))),u?b():I(t,o++,e.apply(null,n.map((function(t){return t.value}))))})},r}function Ct(t,e){return U(t)?e:t.constructor(e)}function zt(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Rt(t){return at(t.size),o(t)}function Mt(t){return y(t)?p:S(t)?_:d}function Lt(t){return Object.create((y(t)?z:S(t)?R:M).prototype)}function jt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):C.prototype.cacheResult.call(this)}function Nt(t,e){return t>e?1:t>>n)&hn,a=(0===n?r:r>>>n)&hn,s=u===a?[Zt(t,e,n+cn,r,i)]:(o=new Ft(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new Vt(t,o+1,u)}function ne(t,e,n){for(var r=[],i=0;i>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function ae(t,e,n,r){var o=r?t:i(t);return o[e]=n,o}function se(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&ro?0:o-n,c=u-n;return c>fn&&(c=fn),function(){if(i===c)return Bn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>fn&&(f=fn),function(){for(;;){if(a){var t=a();if(t!==Bn)return t;a=null}if(c===f)return Bn;var o=e?--f:c++;a=n(s&&s[o],r-cn,i+(o<=t.size||n<0)return t.withMutations((function(t){n<0?me(t,n).set(0,r):me(t,0,n+1).set(n,r)}));n+=t._origin;var i=t._tail,o=t._root,a=e(_n);return n>=Ie(t._capacity)?i=ye(i,t.__ownerID,0,n,r,a):o=ye(o,t.__ownerID,t._level,n,r,a),a.value?t.__ownerID?(t._root=o,t._tail=i,t.__hash=void 0,t.__altered=!0,t):_e(t._origin,t._capacity,t._level,o,i):t}function ye(t,e,r,i,o,u){var a=i>>>r&hn,s=t&&a0){var f=t&&t.array[a],h=ye(f,e,r-cn,i,o,u);return h===f?t:(c=Se(t,e),c.array[a]=h,c)}return s&&t.array[a]===o?t:(n(u),c=Se(t,e),void 0===o&&a===c.array.length-1?c.array.pop():c.array[a]=o,c)}function Se(t,e){return e&&t&&e===t.ownerID?t:new le(t?t.array.slice():[],e)}function ge(t,e){if(e>=Ie(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&hn],r-=cn;return n}}function me(t,e,n){void 0!==e&&(e=0|e),void 0!==n&&(n=0|n);var i=t.__ownerID||new r,o=t._origin,u=t._capacity,a=o+e,s=void 0===n?u:n<0?u+n:o+n;if(a===o&&s===u)return t;if(a>=s)return t.clear();for(var c=t._level,f=t._root,h=0;a+h<0;)f=new le(f&&f.array.length?[void 0,f]:[],i),c+=cn,h+=1<=1<l?new le([],i):_;if(_&&p>l&&acn;y-=cn){var S=l>>>y&hn;v=v.array[S]=Se(v.array[S],i)}v.array[l>>>cn&hn]=_}if(s=p)a-=p,s-=p,c=cn,f=null,d=d&&d.removeBefore(i,0,a);else if(a>o||p>>c&hn;if(g!==p>>>c&hn)break;g&&(h+=(1<o&&(f=f.removeBefore(i,c,a-h)),f&&pi&&(i=a.size),v(u)||(a=a.map((function(t){return X(t)}))),r.push(a)}return i>t.size&&(t=t.setSize(i)),ie(t,e,r)}function Ie(t){return t>>cn<=fn&&u.size>=2*o.size?(i=u.filter((function(t,e){return void 0!==t&&a!==e})),r=i.toKeyedSeq().map((function(t){return t[0]})).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):we(r,i)}function De(t){return null===t||void 0===t?Re():Ce(t)?t:Re().unshiftAll(t)}function Ce(t){return!(!t||!t[Wn])}function ze(t,e,n,r){var i=Object.create(Xn);return i.size=t,i._head=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Re(){return Qn||(Qn=ze(0))}function Me(t){return null===t||void 0===t?ke():Le(t)&&!m(t)?t:ke().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function Le(t){return!(!t||!t[Zn])}function je(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function Ne(t,e){var n=Object.create($n);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function ke(){return tr||(tr=Ne(Jt()))}function Ue(t){return null===t||void 0===t?xe():Pe(t)?t:xe().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function Pe(t){return Le(t)&&m(t)}function He(t,e){var n=Object.create(er);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function xe(){return nr||(nr=He(Te()))}function Ve(t,e){var n,r=function(o){if(o instanceof r)return o;if(!(this instanceof r))return new r(o);if(!n){n=!0;var u=Object.keys(t);Ge(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=Ut(o)},i=r.prototype=Object.create(rr);return i.constructor=r,r}function qe(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return r._map=e,r.__ownerID=n,r}function Fe(t){return t._name||t.constructor.name||"Record"}function Ge(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(t){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){ut(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Ye(t,e){if(t===e)return!0;if(!v(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||y(t)!==y(e)||S(t)!==S(e)||m(t)!==m(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!g(t);if(m(t)){var r=t.entries();return e.every((function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))}))&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var o=t;t=e,e=o}var u=!0,a=e.__iterate((function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,ln)):!W(t.get(r,ln),e))return u=!1,!1}));return u&&t.size===a}function Be(t,e,n){if(!(this instanceof Be))return new Be(t,e,n);if(ut(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),ee?-1:0}function rn(t){if(t.size===1/0)return 0;var e=m(t),n=y(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+un(et(t),et(e))|0}:function(t,e){r=r+un(et(t),et(e))|0}:e?function(t){r=31*r+et(t)|0}:function(t){r=r+et(t)|0});return on(i,r)}function on(t,e){return e=Dn(e,3432918353),e=Dn(e<<15|e>>>-15,461845907),e=Dn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Dn(e^e>>>16,2246822507),e=Dn(e^e>>>13,3266489909),e=tt(e^e>>>16)}function un(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var an=Array.prototype.slice,sn="delete",cn=5,fn=1<r?b():I(t,i,n[e?r-i++:i++])})},t(j,z),j.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},j.prototype.has=function(t){return this._object.hasOwnProperty(t)},j.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},j.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new E(function(){var u=r[e?i-o:o];return o++>i?b():I(t,u,n[u])})},j.prototype[Sn]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new E(b);var i=0;return new E(function(){var e=r.next();return e.done?e:I(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return I(t,i,r[i++])})};var Tn;t(K,l),t(Y,K),t(B,K),t(J,K),K.Keyed=Y,K.Indexed=B,K.Set=J;var An,Dn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t=0|t,e=0|e;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Cn=Object.isExtensible,zn=(function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}})(),Rn="function"==typeof WeakMap;Rn&&(An=new WeakMap);var Mn=0,Ln="__immutablehash__";"function"==typeof Symbol&&(Ln=Symbol(Ln));var jn=16,Nn=255,kn=0,Un={};t(st,z),st.prototype.get=function(t,e){return this._iter.get(t,e)},st.prototype.has=function(t){return this._iter.has(t)},st.prototype.valueSeq=function(){return this._iter.valueSeq()},st.prototype.reverse=function(){var t=this,e=_t(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=pt(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},st.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Rt(this):0,function(i){return t(i,e?--n:n++,r)}),e)},st.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(mn,e),r=e?Rt(this):0;return new E(function(){var i=n.next();return i.done?i:I(t,e?--r:r++,i.value,i)})},st.prototype[Sn]=!0,t(ct,R),ct.prototype.includes=function(t){return this._iter.includes(t)},ct.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate((function(e){return t(e,r++,n)}),e)},ct.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e),r=0;return new E(function(){var e=n.next();return e.done?e:I(t,r++,e.value,e)})},t(ft,M),ft.prototype.has=function(t){return this._iter.includes(t)},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){return t(e,e,n)}),e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){var e=n.next();return e.done?e:I(t,e.value,e.value,e)})},t(ht,z),ht.prototype.entrySeq=function(){return this._iter.toSeq()},ht.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){if(e){zt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}}),e)},ht.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){zt(r);var i=v(r);return I(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=ft.prototype.cacheResult=ht.prototype.cacheResult=jt,t(Ut,Y),Ut.prototype.toString=function(){return this.__toString("Map {","}")},Ut.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},Ut.prototype.set=function(t,e){return Wt(this,t,e)},Ut.prototype.setIn=function(t,e){return this.updateIn(t,ln,(function(){return e}))},Ut.prototype.remove=function(t){return Wt(this,t,ln)},Ut.prototype.deleteIn=function(t){return this.updateIn(t,(function(){return ln}))},Ut.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},Ut.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,kt(t),e,n);return r===ln?void 0:r},Ut.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Jt()},Ut.prototype.merge=function(){return ne(this,void 0,arguments)},Ut.prototype.mergeWith=function(t){var e=an.call(arguments,1);return ne(this,t,e)},Ut.prototype.mergeIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]}))},Ut.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},Ut.prototype.mergeDeepWith=function(t){var e=an.call(arguments,1);return ne(this,re(t),e)},Ut.prototype.mergeDeepIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]}))},Ut.prototype.sort=function(t){return be(wt(this,t))},Ut.prototype.sortBy=function(t,e){return be(wt(this,e,t))},Ut.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},Ut.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},Ut.prototype.asImmutable=function(){return this.__ensureOwner()},Ut.prototype.wasAltered=function(){return this.__altered},Ut.prototype.__iterator=function(t,e){return new Gt(this,t,e)},Ut.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate((function(e){return r++,t(e[1],e[0],n)}),e),r},Ut.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Bt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Ut.isMap=Pt;var Pn="@@__IMMUTABLE_MAP__@@",Hn=Ut.prototype;Hn[Pn]=!0,Hn[sn]=Hn.remove,Hn.removeIn=Hn.deleteIn,Ht.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Vn)return $t(t,f,o,u);var _=t&&t===this.ownerID,d=_?f:i(f);return p?c?h===l-1?d.pop():d[h]=d.pop():d[h]=[o,u]:d.push([o,u]),_?(this.entries=d,this):new Ht(t,d)}},xt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&hn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ue(o&i-1)].get(t+cn,e,n,r)},xt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=1<=qn)return ee(t,l,c,a,_);if(f&&!_&&2===l.length&&Qt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&Qt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?ae(l,h,_,d):ce(l,h,d):se(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new xt(t,v,y)},Vt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&hn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Vt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=i===ln,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Xt(f,t,e+cn,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&hn;if(r>=this.array.length)return new le([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-cn,n),i===u&&o)return this}if(o&&!i)return this;var a=Se(this,t);if(!o)for(var s=0;s>>e&hn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-cn,n),i===o&&r===this.array.length-1)return this}var u=Se(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Yn,Bn={};t(be,Ut),be.of=function(){return this(arguments)},be.prototype.toString=function(){return this.__toString("OrderedMap {","}")},be.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Te()},be.prototype.set=function(t,e){return Ae(this,t,e)},be.prototype.remove=function(t){return Ae(this,t,ln)},be.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},be.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate((function(e){return e&&t(e[1],e[0],n)}),e)},be.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},be.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?we(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},be.isOrderedMap=Oe,be.prototype[Sn]=!0,be.prototype[sn]=be.prototype.remove;var Jn;t(De,B),De.of=function(){return this(arguments)},De.prototype.toString=function(){return this.__toString("Stack [","]")},De.prototype.get=function(t,e){var n=this._head;for(t=u(this,t);n&&t--;)n=n.next;return n?n.value:e},De.prototype.peek=function(){return this._head&&this._head.value},De.prototype.push=function(){var t=arguments;if(0===arguments.length)return this;for(var e=this.size+arguments.length,n=this._head,r=arguments.length-1;r>=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pushAll=function(t){if(t=_(t),0===t.size)return this;at(t.size);var e=this.size,n=this._head;return t.reverse().forEach((function(t){e++,n={value:t,next:n}})),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pop=function(){return this.slice(1)},De.prototype.unshift=function(){return this.push.apply(this,arguments)},De.prototype.unshiftAll=function(t){return this.pushAll(t)},De.prototype.shift=function(){return this.pop.apply(this,arguments)},De.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Re()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=f(e,this.size);if(r!==this.size)return B.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):ze(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?ze(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new E(function(){if(r){var e=r.value;return r=r.next,I(t,n++,e)}return b()})},De.isStack=Ce;var Wn="@@__IMMUTABLE_STACK__@@",Xn=De.prototype;Xn[Wn]=!0,Xn.withMutations=Hn.withMutations,Xn.asMutable=Hn.asMutable,Xn.asImmutable=Hn.asImmutable,Xn.wasAltered=Hn.wasAltered;var Qn;t(Me,J),Me.of=function(){return this(arguments)},Me.fromKeys=function(t){return this(p(t).keySeq())},Me.prototype.toString=function(){return this.__toString("Set {","}")},Me.prototype.has=function(t){return this._map.has(t)},Me.prototype.add=function(t){return je(this,this._map.set(t,!0))},Me.prototype.remove=function(t){return je(this,this._map.remove(t))},Me.prototype.clear=function(){return je(this,this._map.clear())},Me.prototype.union=function(){var t=an.call(arguments,0);return t=t.filter((function(t){return 0!==t.size})),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations((function(e){for(var n=0;n1?" by "+this._step:"")+" ]"},Be.prototype.get=function(t,e){return this.has(t)?this._start+u(this,t)*this._step:e},Be.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=0&&nn?b():I(t,o++,u)})},Be.prototype.equals=function(t){return t instanceof Be?this._start===t._start&&this._end===t._end&&this._step===t._step:Ye(this,t)};var ir;t(Je,R),Je.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Je.prototype.get=function(t,e){return this.has(t)?this._value:e},Je.prototype.includes=function(t){return W(this._value,t)},Je.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Je(this._value,f(e,n)-c(t,n))},Je.prototype.reverse=function(){return this},Je.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Je.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Je.prototype.__iterate=function(t,e){for(var n=this,r=0;rthis.size?e:this.find((function(e,n){return n===t}),void 0,e)},has:function(t){return t=u(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){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=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c.default.is(a,s)||i.call(null,s)}}));var r=p.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){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(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){return new M({result:t,reactorState:e})}function o(t,e){return t.withMutations((function(t){(0,R.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&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(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 b(t,[n])}))})),I(t)}))}function u(t,e){return t.withMutations((function(t){(0,R.each)(e,(function(e,n){t.update("stores",(function(t){return t.set(n,e)}))}))}))}function a(t,e,n){if(void 0===e&&f(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){A.default.dispatchStart(t,e,n),t.get("stores").forEach((function(o,u){var a=r.get(u),s=void 0;try{s=o.handle(a,e,n)}catch(e){throw A.default.dispatchError(t,e.message),e}if(void 0===s&&f(t,"throwOnUndefinedStoreReturnValue")){var c="Store handler must return a value, did you forget a return statement";throw A.default.dispatchError(t,c),new Error(c)}r.set(u,s),a!==s&&(i=i.add(u))})),A.default.dispatchEnd(t,r,i)})),u=t.set("state",o).set("dirtyStores",i).update("storeStates",(function(t){return b(t,i)}));return I(u)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations((function(r){(0,R.each)(e,(function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),n.push(i))}}))})),i=w.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 b(t,n)}))}function c(t,e,n){var r=e;(0,z.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),u=w.default.Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=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,w.default.Set()),t.updateIn(["stores",e],(function(t){return t.add(i)}))}))})),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(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,z.isKeyPath)(e)&&(0,z.isKeyPath)(r)?(0,z.isEqual)(e,r):e===r)}));return t.withMutations((function(t){r.forEach((function(e){return l(t,e)}))}))}function l(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 p(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&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(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 b(t,r)})),v(t)}))}function _(t,e){var n=t.get("state");if((0,z.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(E(t,e),t);var r=(0,C.getDeps)(e).map((function(e){return _(t,e).result})),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,m(t,e,o))}function d(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",w.default.Set())}function y(t){return t}function S(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=S(t,e);if(!n)return!1;var r=n.get("storeStates");return 0!==r.size&&r.every((function(e,n){return t.getIn(["storeStates",n])===e}))}function m(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),u=(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],w.default.Map({value:n,storeStates:u,dispatchId:i}))}function E(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function I(t){return t.update("dispatchId",(function(t){return t+1}))}function b(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=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var O=n(3),w=r(O),T=n(9),A=r(T),D=n(5),C=n(10),z=n(11),R=n(4),M=w.default.Record({result:null,reactorState:null})},function(t,e,n){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){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h.default.Set());var n=h.default.Set().withMutations((function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach((function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}}))}));return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(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 f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e.default={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e.default},function(t,e,n){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=a.default.List(t),r=a.default.List(e);return a.default.is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){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 u=(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=u;var a=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=a}])}))})),ze=t(Ce),Re=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},Me=Re,Le=Me({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),je=ze.Store,Ne=ze.toImmutable,ke=new je({getInitialState:function(){return Ne({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Le.VALIDATING_AUTH_TOKEN,n),this.on(Le.VALID_AUTH_TOKEN,r),this.on(Le.INVALID_AUTH_TOKEN,i)}}),Ue=ze.Store,Pe=ze.toImmutable,He=new Ue({getInitialState:function(){return Pe({authToken:null,host:""})},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,o),this.on(Le.LOG_OUT,u)}}),xe=ze.Store,Ve=new xe({getInitialState:function(){return!0},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,a)}}),qe=Me({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),Fe="object"==typeof window&&"EventSource"in window,Ge=ze.Store,Ke=ze.toImmutable,Ye=new Ge({getInitialState:function(){return Ke({isSupported:Fe,isStreaming:!1,useStreaming:!0,hasError:!1})},initialize:function(){this.on(qe.STREAM_START,s),this.on(qe.STREAM_STOP,c),this.on(qe.STREAM_ERROR,f),this.on(qe.LOG_OUT,h)}}),Be=Me({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),Je=ze.Store,We=new Je({getInitialState:function(){return!0},initialize:function(){this.on(Be.API_FETCH_ALL_START,(function(){return!0})),this.on(Be.API_FETCH_ALL_SUCCESS,(function(){return!1})),this.on(Be.API_FETCH_ALL_FAIL,(function(){return!1})),this.on(Be.LOG_OUT,(function(){return!1}))}}),Xe=ze.Store,Qe=new Xe({getInitialState:function(){return!1},initialize:function(){this.on(Be.SYNC_SCHEDULED,(function(){return!0})),this.on(Be.SYNC_SCHEDULE_CANCELLED,(function(){return!1})),this.on(Be.LOG_OUT,(function(){return!1}))}}),Ze=Me({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}),$e=ze.Store,tn=ze.toImmutable,en=new $e({getInitialState:function(){return tn({})},initialize:function(){var t=this;this.on(Ze.API_FETCH_SUCCESS,l),this.on(Ze.API_SAVE_SUCCESS,l),this.on(Ze.API_DELETE_SUCCESS,p),this.on(Ze.LOG_OUT,(function(){return t.getInitialState()}))}}),nn=Object.prototype.hasOwnProperty,rn=Object.prototype.propertyIsEnumerable,on=d()?Object.assign:function(t,e){for(var n,r,i=arguments,o=_(t),u=1;u199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function D(t,e){var n=e.message;return t.set(t.size,n)}function z(){return zn.getInitialState()}function R(t,e){t.dispatch(An.NOTIFICATION_CREATED,{message:e})}function L(t){t.registerStores({notifications:zn})}function M(t,e){if("lock"===t)return!0;if("garage_door"===t)return!0;var n=e.get(t);return!!n&&n.services.has("turn_on")}function j(t,e){return!!t&&("group"===t.domain?"on"===t.state||"off"===t.state:M(t.domain,e))}function N(t,e){return[rr(t),function(t){return!!t&&t.services.has(e)}]}function k(t){return[wn.byId(t),nr,j]}function U(t,e,n){function r(){var c=(new Date).getTime()-a;c0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function P(t,e){var n=e.component;return t.push(n)}function H(t,e){var n=e.components;return dr(n)}function x(){return vr.getInitialState()}function V(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.unit_system,u=e.time_zone,a=e.config_dir,s=e.version;return Sr({latitude:n,longitude:r,location_name:i,unit_system:o,time_zone:u,config_dir:a,serverVersion:s})}function F(){return gr.getInitialState()}function q(t,e){t.dispatch(pr.SERVER_CONFIG_LOADED,e)}function G(t){ln(t,"GET","config").then((function(e){return q(t,e)}))}function K(t,e){t.dispatch(pr.COMPONENT_LOADED,{component:e})}function B(t){return[["serverComponent"],function(e){return e.contains(t)}]}function Y(t){t.registerStores({serverComponent:vr,serverConfig:gr})}function J(t,e){var n=e.pane;return n}function W(){return Rr.getInitialState()}function X(t,e){var n=e.panels;return Mr(n)}function Q(){return jr.getInitialState()}function Z(t,e){var n=e.show;return!!n}function $(){return kr.getInitialState()}function tt(t,e){t.dispatch(Dr.SHOW_SIDEBAR,{show:e})}function et(t,e){t.dispatch(Dr.NAVIGATE,{pane:e})}function nt(t,e){t.dispatch(Dr.PANELS_LOADED,{panels:e})}function rt(t,e){var n=e.entityId;return n}function it(){return Kr.getInitialState()}function ot(t,e){t.dispatch(qr.SELECT_ENTITY,{entityId:e})}function ut(t){t.dispatch(qr.SELECT_ENTITY,{entityId:null})}function at(t){return!t||(new Date).getTime()-t>6e4}function st(t,e){var n=e.date;return n.toISOString()}function ct(){return Wr.getInitialState()}function ft(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,Qr({})):t.withMutations((function(t){r.forEach((function(e){return t.setIn([n,e[0].entity_id],Qr(e.map(yn.fromJSON)))}))}))}function ht(){return Zr.getInitialState()}function lt(t,e){var n=e.stateHistory;return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,ni(e.map(yn.fromJSON)))}))}))}function pt(){return ri.getInitialState()}function _t(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(ui,r)}))}function dt(){return ai.getInitialState()}function vt(t,e){t.dispatch(Yr.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function yt(t,e){void 0===e&&(e=null),t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),ln(t,"GET",n).then((function(e){return t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})}),(function(){return t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})}))}function St(t,e){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_START,{date:e}),ln(t,"GET","history/period/"+e).then((function(n){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})}),(function(){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_ERROR,{})}))}function gt(t){var e=t.evaluate(fi);return St(t,e)}function mt(t){t.registerStores({currentEntityHistoryDate:Wr,entityHistory:Zr,isLoadingEntityHistory:ti,recentEntityHistory:ri,recentEntityHistoryUpdated:ai})}function Et(t){t.registerStores({moreInfoEntityId:Kr})}function It(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;oau}function se(t){t.registerStores({currentLogbookDate:Yo,isLoadingLogbookEntries:Wo,logbookEntries:eu,logbookEntriesUpdated:iu})}function ce(t){return t.set("active",!0)}function fe(t){return t.set("active",!1)}function he(){return gu.getInitialState()}function le(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",ln(t,"POST","notify.html5",{subscription:e,browser:n}).then((function(){return t.dispatch(vu.PUSH_NOTIFICATIONS_SUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Nn.createNotification(t,n),!1}))}function pe(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){return ln(t,"DELETE","notify.html5",{subscription:e}).then((function(){return e.unsubscribe()})).then((function(){return t.dispatch(vu.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Nn.createNotification(t,n),!1}))}function _e(t){t.registerStores({pushNotifications:gu})}function de(t,e){return ln(t,"POST","template",{template:e})}function ve(t){return t.set("isListening",!0)}function ye(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)}))}function Se(t,e){var n=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)}))}function ge(){return Nu.getInitialState()}function me(){return Nu.getInitialState()}function Ee(){return Nu.getInitialState()}function Ie(t){return ku[t.hassId]}function be(t){var e=Ie(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(Lu.VOICE_TRANSMITTING,{finalTranscript:n}),ur.callService(t,"conversation","process",{text:n}).then((function(){t.dispatch(Lu.VOICE_DONE)}),(function(){t.dispatch(Lu.VOICE_ERROR)}))}}function Oe(t){var e=Ie(t);e&&(e.recognition.stop(),ku[t.hassId]=!1)}function we(t){be(t),Oe(t)}function Te(t){var e=we.bind(null,t);e();var n=new webkitSpeechRecognition;ku[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(Lu.VOICE_START)},n.onerror=function(){return t.dispatch(Lu.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ie(t);if(n){for(var r="",i="",o=e.resultIndex;o>>0;if(""+n!==e||4294967295===n)return NaN;e=n}return e<0?_(t)+e:e}function v(){return!0}function y(t,e,n){return(0===t||void 0!==n&&t<=-n)&&(void 0===e||void 0!==n&&e>=n)}function S(t,e){return m(t,e,0)}function g(t,e){return m(t,e,e)}function m(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function E(t){this.next=t}function I(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function b(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(bn&&t[bn]||t[On]);if("function"==typeof e)return e}function C(t){return t&&"number"==typeof t.length}function D(t){return null===t||void 0===t?P():o(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?P().toKeyedSeq():o(t)?u(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?P():o(t)?u(t)?t.entrySeq():t.toIndexedSeq():x(t)}function L(t){return(null===t||void 0===t?P():o(t)?u(t)?t.entrySeq():t:x(t)).toSetSeq()}function M(t){this._array=t,this.size=t.length}function j(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function U(t){return!(!t||!t[Tn])}function P(){return An||(An=new M([]))}function H(t){var e=Array.isArray(t)?new M(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new j(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=F(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=F(t)||"object"==typeof t&&new j(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function F(t){return C(t)?new M(t):w(t)?new k(t):O(t)?new N(t):void 0}function q(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new E(function(){var t=i[n?o-u:u];return u++>o?b():I(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(t,e){return e?B(e,t,"",{"":t}):Y(t)}function B(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map((function(n,r){return B(t,n,r,e)}))):J(e)?t.call(r,n,z(e).map((function(n,r){return B(t,n,r,e)}))):e}function Y(t){return Array.isArray(t)?R(t).map(Y).toList():J(t)?z(t).map(Y).toMap():t}function J(t){return t&&(t.constructor===Object||void 0===t.constructor)}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){if(t===e)return!0;if(!o(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||u(t)!==u(e)||a(t)!==a(e)||c(t)!==c(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!s(t);if(c(t)){var r=t.entries();return e.every((function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))}))&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var f=t;t=e,e=f}var h=!0,l=e.__iterate((function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,yn)):!W(t.get(r,yn),e))return h=!1,!1}));return h&&t.size===l}function Q(t,e){if(!(this instanceof Q))return new Q(t,e);if(this._value=t,this.size=void 0===e?1/0:Math.max(0,e),0===this.size){if(Cn)return Cn;Cn=this}}function Z(t,e){if(!t)throw new Error(e)}function $(t,e,n){if(!(this instanceof $))return new $(t,e,n);if(Z(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),e>>1&1073741824|3221225471&t}function ot(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){if(t!==t||t===1/0)return 0;var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return it(n)}if("string"===e)return t.length>Un?ut(t):at(t);if("function"==typeof t.hashCode)return t.hashCode();if("object"===e)return st(t);if("function"==typeof t.toString)return at(t.toString());throw new Error("Value type "+e+" cannot be hashed.")}function ut(t){var e=xn[t];return void 0===e&&(e=at(t),Hn===Pn&&(Hn=0,xn={}),Hn++,xn[t]=e),e}function at(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ft(t){Z(t!==1/0,"Cannot perform this action with an infinite size.")}function ht(t){return null===t||void 0===t?It():lt(t)&&!c(t)?t:It().withMutations((function(e){var r=n(t);ft(r.size),r.forEach((function(t,n){return e.set(n,t)}))}))}function lt(t){return!(!t||!t[Vn])}function pt(t,e){this.ownerID=t,this.entries=e}function _t(t,e,n){this.ownerID=t,this.bitmap=e,this.nodes=n}function dt(t,e,n){this.ownerID=t,this.count=e,this.nodes=n}function vt(t,e,n){this.ownerID=t,this.keyHash=e,this.entries=n}function yt(t,e,n){this.ownerID=t,this.keyHash=e,this.entry=n}function St(t,e,n){this._type=e,this._reverse=n,this._stack=t._root&&mt(t._root)}function gt(t,e){return I(t,e[0],e[1])}function mt(t,e){return{node:t,index:0,__prev:e}}function Et(t,e,n,r){var i=Object.create(Fn);return i.size=t,i._root=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function It(){return qn||(qn=Et(0))}function bt(t,e,n){var r,i;if(t._root){var o=f(Sn),u=f(gn);if(r=Ot(t._root,t.__ownerID,0,void 0,e,n,o,u),!u.value)return t;i=t.size+(o.value?n===yn?-1:1:0)}else{if(n===yn)return t;i=1,r=new pt(t.__ownerID,[[e,n]])}return t.__ownerID?(t.size=i,t._root=r,t.__hash=void 0,t.__altered=!0,t):r?Et(i,r):It()}function Ot(t,e,n,r,i,o,u,a){return t?t.update(e,n,r,i,o,u,a):o===yn?t:(h(a),h(u),new yt(e,r,[i,o]))}function wt(t){return t.constructor===yt||t.constructor===vt}function Tt(t,e,n,r,i){if(t.keyHash===r)return new vt(e,r,[t.entry,i]);var o,u=(0===n?t.keyHash:t.keyHash>>>n)&vn,a=(0===n?r:r>>>n)&vn,s=u===a?[Tt(t,e,n+_n,r,i)]:(o=new yt(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new dt(t,o+1,u)}function zt(t,e,r){for(var i=[],u=0;u>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function kt(t,e,n,r){var i=r?t:p(t);return i[e]=n,i}function Ut(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&io?0:o-n,c=u-n;return c>dn&&(c=dn),function(){if(i===c)return Xn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>dn&&(f=dn),function(){for(;;){if(a){var t=a();if(t!==Xn)return t;a=null}if(c===f)return Xn;var o=e?--f:c++;a=n(s&&s[o],r-_n,i+(o<=t.size||e<0)return t.withMutations((function(t){e<0?Wt(t,e).set(0,n):Wt(t,0,e+1).set(e,n)}));e+=t._origin;var r=t._tail,i=t._root,o=f(gn);return e>=Qt(t._capacity)?r=Bt(r,t.__ownerID,0,e,n,o):i=Bt(i,t.__ownerID,t._level,e,n,o),o.value?t.__ownerID?(t._root=i,t._tail=r,t.__hash=void 0,t.__altered=!0,t):qt(t._origin,t._capacity,t._level,i,r):t}function Bt(t,e,n,r,i,o){var u=r>>>n&vn,a=t&&u0){var c=t&&t.array[u],f=Bt(c,e,n-_n,r,i,o);return f===c?t:(s=Yt(t,e),s.array[u]=f,s)}return a&&t.array[u]===i?t:(h(o),s=Yt(t,e),void 0===i&&u===s.array.length-1?s.array.pop():s.array[u]=i,s)}function Yt(t,e){return e&&t&&e===t.ownerID?t:new Vt(t?t.array.slice():[],e)}function Jt(t,e){if(e>=Qt(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&vn],r-=_n;return n}}function Wt(t,e,n){void 0!==e&&(e=0|e),void 0!==n&&(n=0|n);var r=t.__ownerID||new l,i=t._origin,o=t._capacity,u=i+e,a=void 0===n?o:n<0?o+n:i+n;if(u===i&&a===o)return t;if(u>=a)return t.clear();for(var s=t._level,c=t._root,f=0;u+f<0;)c=new Vt(c&&c.array.length?[void 0,c]:[],r),s+=_n,f+=1<=1<h?new Vt([],r):_;if(_&&p>h&&u_n;y-=_n){var S=h>>>y&vn;v=v.array[S]=Yt(v.array[S],r)}v.array[h>>>_n&vn]=_}if(a=p)u-=p,a-=p,s=_n,c=null,d=d&&d.removeBefore(r,0,u);else if(u>i||p>>s&vn;if(g!==p>>>s&vn)break;g&&(f+=(1<i&&(c=c.removeBefore(r,s,u-f)),c&&pu&&(u=c.size),o(s)||(c=c.map((function(t){return K(t)}))),i.push(c)}return u>t.size&&(t=t.setSize(u)),Mt(t,e,i)}function Qt(t){return t>>_n<<_n}function Zt(t){return null===t||void 0===t?ee():$t(t)?t:ee().withMutations((function(e){var r=n(t);ft(r.size),r.forEach((function(t,n){return e.set(n,t)}))}))}function $t(t){return lt(t)&&c(t)}function te(t,e,n,r){var i=Object.create(Zt.prototype);return i.size=t?t.size:0,i._map=t,i._list=e,i.__ownerID=n,i.__hash=r,i}function ee(){return Qn||(Qn=te(It(),Gt()))}function ne(t,e,n){var r,i,o=t._map,u=t._list,a=o.get(e),s=void 0!==a;if(n===yn){if(!s)return t;u.size>=dn&&u.size>=2*o.size?(i=u.filter((function(t,e){return void 0!==t&&a!==e})),r=i.toKeyedSeq().map((function(t){return t[0]})).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):te(r,i)}function re(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ie(t){this._iter=t,this.size=t.size}function oe(t){this._iter=t,this.size=t.size}function ue(t){this._iter=t,this.size=t.size}function ae(t){var e=Ce(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=De,e.__iterateUncached=function(e,n){var r=this;return t.__iterate((function(t,n){return e(n,t,r)!==!1}),n)},e.__iteratorUncached=function(e,n){if(e===In){var r=t.__iterator(e,n);return new E(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===En?mn:En,n)},e}function se(t,e,n){var r=Ce(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,yn);return o===yn?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate((function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1}),i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(In,i);return new E(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return I(r,a,e.call(n,u[1],a,t),i)})},r}function ce(t,e){var n=Ce(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=ae(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=De,n.__iterate=function(e,n){var r=this;return t.__iterate((function(t,n){return e(t,n,r)}),!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function fe(t,e,n,r){var i=Ce(t);return r&&(i.has=function(r){var i=t.get(r,yn);return i!==yn&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,yn);return o!==yn&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate((function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)}),o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(In,o),a=0;return new E(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return I(i,r?c:a++,f,o)}})},i}function he(t,e,n){var r=ht().asMutable();return t.__iterate((function(i,o){r.update(e.call(n,i,o,t),0,(function(t){return t+1}))})),r.asImmutable()}function le(t,e,n){var r=u(t),i=(c(t)?Zt():ht()).asMutable();t.__iterate((function(o,u){i.update(e.call(n,o,u,t),(function(t){return t=t||[],t.push(r?[u,o]:o),t}))}));var o=Ae(t);return i.map((function(e){return Oe(t,o(e))}))}function pe(t,e,n,r){var i=t.size;if(void 0!==e&&(e=0|e),void 0!==n&&(n=n===1/0?i:0|n),y(e,n,i))return t;var o=S(e,i),u=g(n,i);if(o!==o||u!==u)return pe(t.toSeq().cacheResult(),e,n,r);var a,s=u-o;s===s&&(a=s<0?0:s);var c=Ce(t);return c.size=0===a?a:t.size&&a||void 0,!r&&U(t)&&a>=0&&(c.get=function(e,n){return e=d(this,e),e>=0&&ea)return b();var t=i.next();return r||e===En?t:e===mn?I(e,s-1,void 0,t):I(e,s-1,t.value[1],t)})},c}function _e(t,e,n){var r=Ce(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate((function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)})),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(In,i),a=!0;return new E(function(){if(!a)return b();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===In?t:I(r,s,c,t):(a=!1,b())})},r}function de(t,e,n,r){var i=Ce(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate((function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)})),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(In,o),s=!0,c=0;return new E(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===En?t:i===mn?I(i,c++,void 0,t):I(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===In?t:I(i,o,f,t)})},i}function ve(t,e){var r=u(t),i=[t].concat(e).map((function(t){return o(t)?r&&(t=n(t)):t=r?H(t):x(Array.isArray(t)?t:[t]),t})).filter((function(t){return 0!==t.size}));if(0===i.length)return t;if(1===i.length){var s=i[0];if(s===t||r&&u(s)||a(t)&&a(s))return s}var c=new M(i);return r?c=c.toKeyedSeq():a(t)||(c=c.toSetSeq()),c=c.flatten(!0),c.size=i.reduce((function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}}),0),c}function ye(t,e,n){var r=Ce(t);return r.__iterateUncached=function(r,i){function u(t,c){var f=this;t.__iterate((function(t,i){return(!e||c0}function be(t,n,r){var i=Ce(t);return i.size=new M(r).map((function(t){return t.size})).min(),i.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(En,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},i.__iteratorUncached=function(t,i){var o=r.map((function(t){return t=e(t),T(i?t.reverse():t)})),u=0,a=!1;return new E(function(){var e;return a||(e=o.map((function(t){return t.next()})),a=e.some((function(t){return t.done}))),a?b():I(t,u++,n.apply(null,e.map((function(t){return t.value}))))})},i}function Oe(t,e){return U(t)?e:t.constructor(e)}function we(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Te(t){return ft(t.size),_(t)}function Ae(t){return u(t)?n:a(t)?r:i}function Ce(t){return Object.create((u(t)?z:a(t)?R:L).prototype)}function De(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):D.prototype.cacheResult.call(this)}function ze(t,e){return t>e?1:te?-1:0}function on(t){if(t.size===1/0)return 0;var e=c(t),n=u(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+an(ot(t),ot(e))|0}:function(t,e){r=r+an(ot(t),ot(e))|0}:e?function(t){r=31*r+ot(t)|0}:function(t){r=r+ot(t)|0});return un(i,r)}function un(t,e){return e=Rn(e,3432918353),e=Rn(e<<15|e>>>-15,461845907),e=Rn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Rn(e^e>>>16,2246822507),e=Rn(e^e>>>13,3266489909),e=it(e^e>>>16)}function an(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var sn=Array.prototype.slice;t(n,e),t(r,e),t(i,e),e.isIterable=o,e.isKeyed=u,e.isIndexed=a,e.isAssociative=s,e.isOrdered=c,e.Keyed=n,e.Indexed=r,e.Set=i;var cn="@@__IMMUTABLE_ITERABLE__@@",fn="@@__IMMUTABLE_KEYED__@@",hn="@@__IMMUTABLE_INDEXED__@@",ln="@@__IMMUTABLE_ORDERED__@@",pn="delete",_n=5,dn=1<<_n,vn=dn-1,yn={},Sn={value:!1},gn={value:!1},mn=0,En=1,In=2,bn="function"==typeof Symbol&&Symbol.iterator,On="@@iterator",wn=bn||On;E.prototype.toString=function(){return"[Iterator]"},E.KEYS=mn,E.VALUES=En,E.ENTRIES=In,E.prototype.inspect=E.prototype.toSource=function(){return this.toString()},E.prototype[wn]=function(){return this},t(D,e),D.of=function(){return D(arguments)},D.prototype.toSeq=function(){return this},D.prototype.toString=function(){return this.__toString("Seq {","}")},D.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},D.prototype.__iterate=function(t,e){return q(this,t,e,!0)},D.prototype.__iterator=function(t,e){return G(this,t,e,!0)},t(z,D),z.prototype.toKeyedSeq=function(){return this},t(R,D),R.of=function(){return R(arguments)},R.prototype.toIndexedSeq=function(){return this},R.prototype.toString=function(){return this.__toString("Seq [","]")},R.prototype.__iterate=function(t,e){return q(this,t,e,!1)},R.prototype.__iterator=function(t,e){return G(this,t,e,!1)},t(L,D),L.of=function(){return L(arguments)},L.prototype.toSetSeq=function(){return this},D.isSeq=U,D.Keyed=z,D.Set=L,D.Indexed=R;var Tn="@@__IMMUTABLE_SEQ__@@";D.prototype[Tn]=!0,t(M,R),M.prototype.get=function(t,e){return this.has(t)?this._array[d(this,t)]:e},M.prototype.__iterate=function(t,e){for(var n=this,r=this._array,i=r.length-1,o=0;o<=i;o++)if(t(r[e?i-o:o],o,n)===!1)return o+1;return o},M.prototype.__iterator=function(t,e){var n=this._array,r=n.length-1,i=0;return new E(function(){return i>r?b():I(t,i,n[e?r-i++:i++])})},t(j,z),j.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},j.prototype.has=function(t){return this._object.hasOwnProperty(t)},j.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},j.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new E(function(){var u=r[e?i-o:o];return o++>i?b():I(t,u,n[u])})},j.prototype[ln]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new E(b);var i=0;return new E(function(){var e=r.next();return e.done?e:I(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return I(t,i,r[i++])})};var An;t(Q,R),Q.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Q.prototype.get=function(t,e){return this.has(t)?this._value:e},Q.prototype.includes=function(t){return W(this._value,t)},Q.prototype.slice=function(t,e){var n=this.size;return y(t,e,n)?this:new Q(this._value,g(e,n)-S(t,n))},Q.prototype.reverse=function(){return this},Q.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Q.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Q.prototype.__iterate=function(t,e){for(var n=this,r=0;r=0&&e=0&&nn?b():I(t,o++,u)})},$.prototype.equals=function(t){return t instanceof $?this._start===t._start&&this._end===t._end&&this._step===t._step:X(this,t)};var Dn;t(tt,e),t(et,tt),t(nt,tt),t(rt,tt),tt.Keyed=et,tt.Indexed=nt,tt.Set=rt;var zn,Rn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t=0|t,e=0|e;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Ln=Object.isExtensible,Mn=(function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}})(),jn="function"==typeof WeakMap;jn&&(zn=new WeakMap);var Nn=0,kn="__immutablehash__";"function"==typeof Symbol&&(kn=Symbol(kn));var Un=16,Pn=255,Hn=0,xn={};t(ht,et),ht.of=function(){var t=sn.call(arguments,0);return It().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},ht.prototype.toString=function(){return this.__toString("Map {","}")},ht.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},ht.prototype.set=function(t,e){return bt(this,t,e)},ht.prototype.setIn=function(t,e){return this.updateIn(t,yn,(function(){return e}))},ht.prototype.remove=function(t){return bt(this,t,yn)},ht.prototype.deleteIn=function(t){return this.updateIn(t,(function(){return yn}))},ht.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},ht.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=jt(this,Re(t),e,n);return r===yn?void 0:r},ht.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):It()},ht.prototype.merge=function(){return zt(this,void 0,arguments)},ht.prototype.mergeWith=function(t){var e=sn.call(arguments,1);return zt(this,t,e)},ht.prototype.mergeIn=function(t){var e=sn.call(arguments,1);return this.updateIn(t,It(),(function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]}))},ht.prototype.mergeDeep=function(){return zt(this,Rt,arguments)},ht.prototype.mergeDeepWith=function(t){var e=sn.call(arguments,1);return zt(this,Lt(t),e)},ht.prototype.mergeDeepIn=function(t){var e=sn.call(arguments,1);return this.updateIn(t,It(),(function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]}))},ht.prototype.sort=function(t){return Zt(me(this,t))},ht.prototype.sortBy=function(t,e){return Zt(me(this,e,t))},ht.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},ht.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new l)},ht.prototype.asImmutable=function(){return this.__ensureOwner()},ht.prototype.wasAltered=function(){return this.__altered},ht.prototype.__iterator=function(t,e){return new St(this,t,e)},ht.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate((function(e){return r++,t(e[1],e[0],n)}),e),r},ht.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Et(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},ht.isMap=lt;var Vn="@@__IMMUTABLE_MAP__@@",Fn=ht.prototype;Fn[Vn]=!0,Fn[pn]=Fn.remove,Fn.removeIn=Fn.deleteIn,pt.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Gn)return At(t,s,r,i);var _=t&&t===this.ownerID,d=_?s:p(s);return l?a?c===f-1?d.pop():d[c]=d.pop():d[c]=[r,i]:d.push([r,i]),_?(this.entries=d,this):new pt(t,d)}},_t.prototype.get=function(t,e,n,r){void 0===e&&(e=ot(n));var i=1<<((0===t?e:e>>>t)&vn),o=this.bitmap;return 0===(o&i)?r:this.nodes[Nt(o&i-1)].get(t+_n,e,n,r)},_t.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=ot(r));var a=(0===e?n:n>>>e)&vn,s=1<=Kn)return Dt(t,l,c,a,_);if(f&&!_&&2===l.length&&wt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&wt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?kt(l,h,_,d):Pt(l,h,d):Ut(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new _t(t,v,y)},dt.prototype.get=function(t,e,n,r){void 0===e&&(e=ot(n));var i=(0===t?e:e>>>t)&vn,o=this.nodes[i];return o?o.get(t+_n,e,n,r):r},dt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=ot(r));var a=(0===e?n:n>>>e)&vn,s=i===yn,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Ot(f,t,e+_n,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&vn;if(r>=this.array.length)return new Vt([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-_n,n),i===u&&o)return this}if(o&&!i)return this;var a=Yt(this,t);if(!o)for(var s=0;s>>e&vn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-_n,n),i===o&&r===this.array.length-1)return this}var u=Yt(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Wn,Xn={};t(Zt,ht),Zt.of=function(){return this(arguments)},Zt.prototype.toString=function(){return this.__toString("OrderedMap {","}")},Zt.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},Zt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):ee()},Zt.prototype.set=function(t,e){return ne(this,t,e)},Zt.prototype.remove=function(t){return ne(this,t,yn)},Zt.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Zt.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate((function(e){return e&&t(e[1],e[0],n)}),e)},Zt.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},Zt.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?te(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},Zt.isOrderedMap=$t,Zt.prototype[ln]=!0,Zt.prototype[pn]=Zt.prototype.remove;var Qn;t(re,z),re.prototype.get=function(t,e){return this._iter.get(t,e)},re.prototype.has=function(t){return this._iter.has(t)},re.prototype.valueSeq=function(){return this._iter.valueSeq()},re.prototype.reverse=function(){var t=this,e=ce(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},re.prototype.map=function(t,e){var n=this,r=se(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},re.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Te(this):0,function(i){return t(i,e?--n:n++,r)}),e)},re.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(En,e),r=e?Te(this):0;return new E(function(){var i=n.next();return i.done?i:I(t,e?--r:r++,i.value,i)})},re.prototype[ln]=!0,t(ie,R),ie.prototype.includes=function(t){return this._iter.includes(t)},ie.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate((function(e){return t(e,r++,n)}),e)},ie.prototype.__iterator=function(t,e){var n=this._iter.__iterator(En,e),r=0;return new E(function(){var e=n.next();return e.done?e:I(t,r++,e.value,e)})},t(oe,L),oe.prototype.has=function(t){return this._iter.includes(t)},oe.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){return t(e,e,n)}),e)},oe.prototype.__iterator=function(t,e){var n=this._iter.__iterator(En,e);return new E(function(){var e=n.next();return e.done?e:I(t,e.value,e.value,e)})},t(ue,z),ue.prototype.entrySeq=function(){return this._iter.toSeq()},ue.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){if(e){we(e);var r=o(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}}),e)},ue.prototype.__iterator=function(t,e){var n=this._iter.__iterator(En,e);return new E(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){we(r);var i=o(r);return I(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ie.prototype.cacheResult=re.prototype.cacheResult=oe.prototype.cacheResult=ue.prototype.cacheResult=De,t(Le,et),Le.prototype.toString=function(){return this.__toString(je(this)+" {","}")},Le.prototype.has=function(t){return this._defaultValues.hasOwnProperty(t)},Le.prototype.get=function(t,e){if(!this.has(t))return e;var n=this._defaultValues[t];return this._map?this._map.get(t,n):n},Le.prototype.clear=function(){if(this.__ownerID)return this._map&&this._map.clear(),this;var t=this.constructor;return t._empty||(t._empty=Me(this,It()))},Le.prototype.set=function(t,e){if(!this.has(t))throw new Error('Cannot set unknown key "'+t+'" on '+je(this));if(this._map&&!this._map.has(t)){var n=this._defaultValues[t];if(e===n)return this}var r=this._map&&this._map.set(t,e);return this.__ownerID||r===this._map?this:Me(this,r)},Le.prototype.remove=function(t){if(!this.has(t))return this;var e=this._map&&this._map.remove(t);return this.__ownerID||e===this._map?this:Me(this,e)},Le.prototype.wasAltered=function(){return this._map.wasAltered()},Le.prototype.__iterator=function(t,e){var r=this;return n(this._defaultValues).map((function(t,e){return r.get(e)})).__iterator(t,e)},Le.prototype.__iterate=function(t,e){var r=this;return n(this._defaultValues).map((function(t,e){return r.get(e)})).__iterate(t,e)},Le.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map&&this._map.__ensureOwner(t);return t?Me(this,e,t):(this.__ownerID=t,this._map=e,this)};var Zn=Le.prototype;Zn[pn]=Zn.remove,Zn.deleteIn=Zn.removeIn=Fn.removeIn,Zn.merge=Fn.merge,Zn.mergeWith=Fn.mergeWith,Zn.mergeIn=Fn.mergeIn,Zn.mergeDeep=Fn.mergeDeep,Zn.mergeDeepWith=Fn.mergeDeepWith,Zn.mergeDeepIn=Fn.mergeDeepIn,Zn.setIn=Fn.setIn,Zn.update=Fn.update,Zn.updateIn=Fn.updateIn,Zn.withMutations=Fn.withMutations,Zn.asMutable=Fn.asMutable,Zn.asImmutable=Fn.asImmutable,t(Ue,rt),Ue.of=function(){return this(arguments)},Ue.fromKeys=function(t){return this(n(t).keySeq())},Ue.prototype.toString=function(){return this.__toString("Set {","}")},Ue.prototype.has=function(t){return this._map.has(t)},Ue.prototype.add=function(t){ +return He(this,this._map.set(t,!0))},Ue.prototype.remove=function(t){return He(this,this._map.remove(t))},Ue.prototype.clear=function(){return He(this,this._map.clear())},Ue.prototype.union=function(){var t=sn.call(arguments,0);return t=t.filter((function(t){return 0!==t.size})),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations((function(e){for(var n=0;n=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):Je(e,n)},Be.prototype.pushAll=function(t){if(t=r(t),0===t.size)return this;ft(t.size);var e=this.size,n=this._head;return t.reverse().forEach((function(t){e++,n={value:t,next:n}})),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):Je(e,n)},Be.prototype.pop=function(){return this.slice(1)},Be.prototype.unshift=function(){return this.push.apply(this,arguments)},Be.prototype.unshiftAll=function(t){return this.pushAll(t)},Be.prototype.shift=function(){return this.pop.apply(this,arguments)},Be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):We()},Be.prototype.slice=function(t,e){if(y(t,e,this.size))return this;var n=S(t,this.size),r=g(e,this.size);if(r!==this.size)return nt.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):Je(i,o)},Be.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Je(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Be.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},Be.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new E(function(){if(r){var e=r.value;return r=r.next,I(t,n++,e)}return b()})},Be.isStack=Ye;var ir="@@__IMMUTABLE_STACK__@@",or=Be.prototype;or[ir]=!0,or.withMutations=Fn.withMutations,or.asMutable=Fn.asMutable,or.asImmutable=Fn.asImmutable,or.wasAltered=Fn.wasAltered;var ur;e.Iterator=E,Xe(e,{toArray:function(){ft(this.size);var t=new Array(this.size||0);return this.valueSeq().__iterate((function(e,n){t[n]=e})),t},toIndexedSeq:function(){return new ie(this)},toJS:function(){return this.toSeq().map((function(t){return t&&"function"==typeof t.toJS?t.toJS():t})).__toJS()},toJSON:function(){return this.toSeq().map((function(t){return t&&"function"==typeof t.toJSON?t.toJSON():t})).__toJS()},toKeyedSeq:function(){return new re(this,!0)},toMap:function(){return ht(this.toKeyedSeq())},toObject:function(){ft(this.size);var t={};return this.__iterate((function(e,n){t[n]=e})),t},toOrderedMap:function(){return Zt(this.toKeyedSeq())},toOrderedSet:function(){return Fe(u(this)?this.valueSeq():this)},toSet:function(){return Ue(u(this)?this.valueSeq():this)},toSetSeq:function(){return new oe(this)},toSeq:function(){return a(this)?this.toIndexedSeq():u(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Be(u(this)?this.valueSeq():this)},toList:function(){return Ht(u(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(t,e){return 0===this.size?t+e:t+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+e},concat:function(){var t=sn.call(arguments,0);return Oe(this,ve(this,t))},includes:function(t){return this.some((function(e){return W(e,t)}))},entries:function(){return this.__iterator(In)},every:function(t,e){ft(this.size);var n=!0;return this.__iterate((function(r,i,o){if(!t.call(e,r,i,o))return n=!1,!1})),n},filter:function(t,e){return Oe(this,fe(this,t,e,!0))},find:function(t,e,n){var r=this.findEntry(t,e);return r?r[1]:n},forEach:function(t,e){return ft(this.size),this.__iterate(e?t.bind(e):t)},join:function(t){ft(this.size),t=void 0!==t?""+t:",";var e="",n=!0;return this.__iterate((function(r){n?n=!1:e+=t,e+=null!==r&&void 0!==r?r.toString():""})),e},keys:function(){return this.__iterator(mn)},map:function(t,e){return Oe(this,se(this,t,e))},reduce:function(t,e,n){ft(this.size);var r,i;return arguments.length<2?i=!0:r=e,this.__iterate((function(e,o,u){i?(i=!1,r=e):r=t.call(n,r,e,o,u)})),r},reduceRight:function(t,e,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return Oe(this,ce(this,!0))},slice:function(t,e){return Oe(this,pe(this,t,e,!0))},some:function(t,e){return!this.every($e(t),e)},sort:function(t){return Oe(this,me(this,t))},values:function(){return this.__iterator(En)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(t,e){return _(t?this.toSeq().filter(t,e):this)},countBy:function(t,e){return he(this,t,e)},equals:function(t){return X(this,t)},entrySeq:function(){var t=this;if(t._cache)return new M(t._cache);var e=t.toSeq().map(Ze).toIndexedSeq();return e.fromEntrySeq=function(){return t.toSeq()},e},filterNot:function(t,e){return this.filter($e(t),e)},findEntry:function(t,e,n){var r=n;return this.__iterate((function(n,i,o){if(t.call(e,n,i,o))return r=[i,n],!1})),r},findKey:function(t,e){var n=this.findEntry(t,e);return n&&n[0]},findLast:function(t,e,n){return this.toKeyedSeq().reverse().find(t,e,n)},findLastEntry:function(t,e,n){return this.toKeyedSeq().reverse().findEntry(t,e,n)},findLastKey:function(t,e){return this.toKeyedSeq().reverse().findKey(t,e)},first:function(){return this.find(v)},flatMap:function(t,e){return Oe(this,Se(this,t,e))},flatten:function(t){return Oe(this,ye(this,t,!0))},fromEntrySeq:function(){return new ue(this)},get:function(t,e){return this.find((function(e,n){return W(n,t)}),void 0,e)},getIn:function(t,e){for(var n,r=this,i=Re(t);!(n=i.next()).done;){var o=n.value;if(r=r&&r.get?r.get(o,yn):yn,r===yn)return e}return r},groupBy:function(t,e){return le(this,t,e)},has:function(t){return this.get(t,yn)!==yn},hasIn:function(t){return this.getIn(t,yn)!==yn},isSubset:function(t){return t="function"==typeof t.includes?t:e(t),this.every((function(e){return t.includes(e)}))},isSuperset:function(t){return t="function"==typeof t.isSubset?t:e(t),t.isSubset(this)},keyOf:function(t){return this.findKey((function(e){return W(e,t)}))},keySeq:function(){return this.toSeq().map(Qe).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(t){return this.toKeyedSeq().reverse().keyOf(t)},max:function(t){return Ee(this,t)},maxBy:function(t,e){return Ee(this,e,t)},min:function(t){return Ee(this,t?tn(t):rn)},minBy:function(t,e){return Ee(this,e?tn(e):rn,t)},rest:function(){return this.slice(1)},skip:function(t){return this.slice(Math.max(0,t))},skipLast:function(t){return Oe(this,this.toSeq().reverse().skip(t).reverse())},skipWhile:function(t,e){return Oe(this,de(this,t,e,!0))},skipUntil:function(t,e){return this.skipWhile($e(t),e)},sortBy:function(t,e){return Oe(this,me(this,e,t))},take:function(t){return this.slice(0,Math.max(0,t))},takeLast:function(t){return Oe(this,this.toSeq().reverse().take(t).reverse())},takeWhile:function(t,e){return Oe(this,_e(this,t,e))},takeUntil:function(t,e){return this.takeWhile($e(t),e)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=on(this))}});var ar=e.prototype;ar[cn]=!0,ar[wn]=ar.values,ar.__toJS=ar.toArray,ar.__toStringMapper=en,ar.inspect=ar.toSource=function(){return this.toString()},ar.chain=ar.flatMap,ar.contains=ar.includes,Xe(n,{flip:function(){return Oe(this,ae(this))},mapEntries:function(t,e){var n=this,r=0;return Oe(this,this.toSeq().map((function(i,o){return t.call(e,[o,i],r++,n)})).fromEntrySeq())},mapKeys:function(t,e){var n=this;return Oe(this,this.toSeq().flip().map((function(r,i){return t.call(e,r,i,n)})).flip())}});var sr=n.prototype;sr[fn]=!0,sr[wn]=ar.entries,sr.__toJS=ar.toObject,sr.__toStringMapper=function(t,e){return JSON.stringify(e)+": "+en(t)},Xe(r,{toKeyedSeq:function(){return new re(this,!1)},filter:function(t,e){return Oe(this,fe(this,t,e,!1))},findIndex:function(t,e){var n=this.findEntry(t,e);return n?n[0]:-1},indexOf:function(t){var e=this.keyOf(t);return void 0===e?-1:e},lastIndexOf:function(t){var e=this.lastKeyOf(t);return void 0===e?-1:e},reverse:function(){return Oe(this,ce(this,!1))},slice:function(t,e){return Oe(this,pe(this,t,e,!1))},splice:function(t,e){var n=arguments.length;if(e=Math.max(0|e,0),0===n||2===n&&!e)return this;t=S(t,t<0?this.count():this.size);var r=this.slice(0,t);return Oe(this,1===n?r:r.concat(p(arguments,2),this.slice(t+e)))},findLastIndex:function(t,e){var n=this.findLastEntry(t,e);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(t){return Oe(this,ye(this,t,!1))},get:function(t,e){return t=d(this,t),t<0||this.size===1/0||void 0!==this.size&&t>this.size?e:this.find((function(e,n){return n===t}),void 0,e)},has:function(t){return t=d(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){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=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c.default.is(a,s)||i.call(null,s)}}));var r=p.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,g.toFactory)(E),t.exports=e.default},function(t,e,n){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(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){return new C({result:t,reactorState:e})}function o(t,e){return t.withMutations((function(t){(0,A.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&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,O.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 m(t,[n])}))})),g(t)}))}function u(t,e){return t.withMutations((function(t){(0,A.each)(e,(function(e,n){t.update("stores",(function(t){return t.set(n,e)}))}))}))}function a(t,e,n){var r=t.get("logger");if(void 0===e&&f(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var i=t.get("state"),o=t.get("dirtyStores"),u=i.withMutations((function(u){r.dispatchStart(t,e,n),t.get("stores").forEach((function(i,a){var s=u.get(a),c=void 0;try{c=i.handle(s,e,n)}catch(e){throw r.dispatchError(t,e.message),e}if(void 0===c&&f(t,"throwOnUndefinedStoreReturnValue")){var h="Store handler must return a value, did you forget a return statement";throw r.dispatchError(t,h),new Error(h)}u.set(a,c),s!==c&&(o=o.add(a))})),r.dispatchEnd(t,u,o,i)})),a=t.set("state",u).set("dirtyStores",o).update("storeStates",(function(t){return m(t,o)}));return g(a)}function s(t,e){var n=[],r=(0,O.toImmutable)({}).withMutations((function(r){(0,A.each)(e,(function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),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 m(t,n)}))}function c(t,e,n){var r=e;(0,T.isKeyPath)(e)&&(e=(0,w.fromKeyPath)(e));var i=t.get("nextId"),o=(0,w.getStoreDeps)(e),u=I.default.Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=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)}))}))})),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(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,T.isKeyPath)(e)&&(0,T.isKeyPath)(r)?(0,T.isEqual)(e,r):e===r)}));return t.withMutations((function(t){r.forEach((function(e){return l(t,e)}))}))}function l(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 p(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&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,O.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 m(t,r)})),v(t)}))}function _(t,e){var n=t.get("state");if((0,T.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,w.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");var r=t.get("cache"),o=r.lookup(e),u=!o||y(t,o);return u&&(o=S(t,e)),i(o.get("value"),t.update("cache",(function(t){return u?t.miss(e,o):t.hit(e)})))}function d(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,e){var n=e.get("storeStates");return!n.size||n.some((function(e,n){return t.getIn(["storeStates",n])!==e}))}function S(t,e){var n=(0,w.getDeps)(e).map((function(e){return _(t,e).result})),r=(0,w.getComputeFn)(e).apply(null,n),i=(0,w.getStoreDeps)(e),o=(0,O.toImmutable)({}).withMutations((function(e){i.forEach((function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)}))}));return(0,b.CacheEntry)({value:r,storeStates:o,dispatchId:t.get("dispatchId")})}function g(t){return t.update("dispatchId",(function(t){return t+1}))}function m(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=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var E=n(3),I=r(E),b=n(9),O=n(5),w=n(10),T=n(11),A=n(4),C=I.default.Record({result:null,reactorState:null})},function(t,e,n){function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(){return new s}Object.defineProperty(e,"__esModule",{value:!0});var o=(function(){function t(t,e){for(var n=0;nn.dispatchId)throw new Error("Refusing to cache older value");return n})))}},{key:"evict",value:function(e){return new t(this.cache.remove(e))}}]),t})();e.BasicCache=s;var c=1e3,f=1,h=(function(){function t(){var e=arguments.length<=0||void 0===arguments[0]?c:arguments[0],n=arguments.length<=1||void 0===arguments[1]?f:arguments[1],i=arguments.length<=2||void 0===arguments[2]?new s:arguments[2],o=arguments.length<=3||void 0===arguments[3]?(0,u.OrderedSet)():arguments[3];r(this,t),console.log("using LRU"),this.limit=e,this.evictCount=n,this.cache=i,this.lru=o}return o(t,[{key:"lookup",value:function(t,e){return this.cache.lookup(t,e)}},{key:"has",value:function(t){return this.cache.has(t)}},{key:"asMap",value:function(){return this.cache.asMap()}},{key:"hit",value:function(e){return this.cache.has(e)?new t(this.limit,this.evictCount,this.cache,this.lru.remove(e).add(e)):this}},{key:"miss",value:function(e,n){var r;if(this.lru.size>=this.limit){if(this.has(e))return new t(this.limit,this.evictCount,this.cache.miss(e,n),this.lru.remove(e).add(e));var i=this.lru.take(this.evictCount).reduce((function(t,e){return t.evict(e)}),this.cache).miss(e,n);r=new t(this.limit,this.evictCount,i,this.lru.skip(this.evictCount).add(e))}else r=new t(this.limit,this.evictCount,this.cache.miss(e,n),this.lru.add(e));return r}},{key:"evict",value:function(e){return this.cache.has(e)?new t(this.limit,this.evictCount,this.cache.evict(e),this.lru.remove(e)):this}}]),t})();e.LRUCache=h},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h.default.Set());var n=h.default.Set().withMutations((function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach((function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}}))}));return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(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 f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e.default={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e.default},function(t,e,n){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=a.default.List(t),r=a.default.List(e);return a.default.is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(8),i={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())},dispatchError:function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},dispatchEnd:function(t,e,n,i){(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())}};e.ConsoleGroupLogger=i;var o={dispatchStart:function(t,e,n){},dispatchError:function(t,e){},dispatchEnd:function(t,e,n){}};e.NoopLogger=o},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=n(9),o=n(12),u=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=u;var a=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=a;var s=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,i.DefaultCache)(),logger:o.NoopLogger,storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:u});e.ReactorState=s;var c=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=c}])}))})),ze=t(De),Re=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},Le=Re,Me=Le({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),je=ze.Store,Ne=ze.toImmutable,ke=new je({getInitialState:function(){return Ne({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Me.VALIDATING_AUTH_TOKEN,n),this.on(Me.VALID_AUTH_TOKEN,r),this.on(Me.INVALID_AUTH_TOKEN,i)}}),Ue=ze.Store,Pe=ze.toImmutable,He=new Ue({getInitialState:function(){return Pe({authToken:null,host:""})},initialize:function(){this.on(Me.VALID_AUTH_TOKEN,o),this.on(Me.LOG_OUT,u)}}),xe=ze.Store,Ve=new xe({getInitialState:function(){return!0},initialize:function(){this.on(Me.VALID_AUTH_TOKEN,a)}}),Fe=Le({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),qe="object"==typeof window&&"EventSource"in window,Ge=ze.Store,Ke=ze.toImmutable,Be=new Ge({getInitialState:function(){return Ke({isSupported:qe,isStreaming:!1,useStreaming:!0,hasError:!1})},initialize:function(){this.on(Fe.STREAM_START,s),this.on(Fe.STREAM_STOP,c),this.on(Fe.STREAM_ERROR,f),this.on(Fe.LOG_OUT,h)}}),Ye=Le({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),Je=ze.Store,We=new Je({getInitialState:function(){return!0},initialize:function(){this.on(Ye.API_FETCH_ALL_START,(function(){return!0})),this.on(Ye.API_FETCH_ALL_SUCCESS,(function(){return!1})),this.on(Ye.API_FETCH_ALL_FAIL,(function(){return!1})),this.on(Ye.LOG_OUT,(function(){return!1}))}}),Xe=ze.Store,Qe=new Xe({getInitialState:function(){return!1},initialize:function(){this.on(Ye.SYNC_SCHEDULED,(function(){return!0})),this.on(Ye.SYNC_SCHEDULE_CANCELLED,(function(){return!1})),this.on(Ye.LOG_OUT,(function(){return!1}))}}),Ze=Le({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}),$e=ze.Store,tn=ze.toImmutable,en=new $e({getInitialState:function(){return tn({})},initialize:function(){var t=this;this.on(Ze.API_FETCH_SUCCESS,l),this.on(Ze.API_SAVE_SUCCESS,l),this.on(Ze.API_DELETE_SUCCESS,p),this.on(Ze.LOG_OUT,(function(){return t.getInitialState()}))}}),nn=Object.prototype.hasOwnProperty,rn=Object.prototype.propertyIsEnumerable,on=d()?Object.assign:function(t,e){for(var n,r,i=arguments,o=_(t),u=1;uif({2q$}=_gT+cUPd$1m+I>3>biCHN^o9@ zahYZ!2yePo!8=8nr{l6a(C<5CP=+@-FRL7WudbH8(TEqXvPm`N!?HKw=Sej!qw>yF z;6cf_slwwM{>LoK%ObkDyJKa*8Skk#q5WyA3t9bBl6OSRdr4JZo@5`nU^zTpW<@zr zEg$gU#(a$g!*5Z}(&BA0O(#j2ii>Dv1D^N9CwvpF^cm0dEPus|BDvsEw|f`be)GV< z{E_!Ac)2IiGEJt(Wm59MDVX{Ix=m`(%HHR+ynIzbRj5$}7RwGUdj&6pt_yCLt@K0v zT0sr(Hq<+}r!EuWUFblSEsM6-t!Q&GQCUp9Scr+l?=$CD{OSYGx1n|2o3Nz|Z}1Bc zWH57F<~+HQh|x#@8f|-hnH0N-9zvmqnDL+Lw%^`u|K4uT|B`N<_BZ_YFYP2fT{uZo zf0C}U37-;Cr%-1$O~$bBMR0Q$ zMkRI}WFbr9?L1Geduc&`gFGDO(P@6h3TW~?&#rztK0LU+Eycj2{)|T=tTpNH2gG z6Ctv)^MS*!>6A}8IQkB@*2w_F0@!IP_@oBKcP^CP5au#{8Vm*jfebAFS-1a~LeN%;m3IaWy~#gMgBPVBoz;TFeMj?|EOLms|wby$`9F&_ZPhr3l$^ z<5fs~Q`K!)(Ekx|p(6)kcv=9ooE72EO8Y>d!6Ny@3HkWrPYmXG`|YkCiyi|(jO{wm zUNljB)rw*i6Q{hCz{#I~0=#Ft`@1LY0Lr!<5O&yULt;0d75qTHUPJFw&MhznQdKD1lJ z>1o$3*afWu)EgiE|J&bWw zT3DB*R?WiFnV4qDBz81v+S>;xtns*;%s}iIlbrkQZ$LySM^q>d9r0oYvM<+tj5ARJ zD%VSpBD>)bMQ4;%VH_0-?Ns)J|51jK|7>Lq9@oCWP(Z@2imm<+Kdi3mr_IM38$-~@ z!w9GKj)p)=raK0WP9hGL4}&(e>?n{?@CC`TKGebZ65%i7Hz&{6|IwAG1tv^GD@blk z&rxd#aZL0nbXUi-nh0Olwo&+p|I;tX`E9i?7W^+_k&bjE+jp43w&)!kp6or}+u1(Z zJ3JWe9PMtO>^=jIpo|CHg>S!oy@RNqo+~^Q(HC#Qzmi>|SUb(eAG@SMs5QMo((VQD zoC`jhWT5`}Uo{?3`6{)lu(AT1rT|prpHd*t9;l8%7e9=E)SQlgbImirK47VJFY>IK zQ764gc9o=J*p>8I5~c9l+irG#?!1Tqd{q=0b6P)eTR+WnfUlivp(V+Km3yq`qr=EP zi{6Lpy$LoYa`u~3>W$+)ysF|%G9?%>u_a&>6-!t#koq7AIB|btLvpRL zyNs&1yi8HWPV$Qih5G`O<~A5rRhZ0`R8u&1UWXUtz%le!1Ji)W3Zqesd*dljyhWWt zO?s&tOksqw;vE}X1i$td-Z;Ago&vzzr8ldJ%fMZ9FXmJ+iuNS;%Ku>(>8E_rB7N&O zJcVUdRufLD{4@g&LQg3@$u&0;pj`sWd9Q*tN5wTP&MQC>@0Bp@(HDRVCIMj-=jp|0 zlIE;{$4@*jptgqhI1g@gXCMKXuA-cI9YrZ~x`{GoI*BS~yNG~ZfbMzzR(2B=E?anR zv2n2w8^1SL)-3NG?;gF~JsRyC9z5UsakPK9{R~(ZcWV8J%W8`IVE4z}6X4VmuC5Z7 zgXS);I`V8!FWdUwIehi{@L>1gME84Rrru|kPdyn$;$lY^?K;wlV?@R&DuLOcQNm>t zzqT{pTBFgYoat9M+~~rycmCuP)+`ac#eDhB&dkxmnfayJ+|&^NE1&p(wvGO_u+jH^ zqs3+R+46l`>>J03y|BHqY|P;^pr03q??%Ua&vu_~AF&(k#Gg9eTI*o@?cR^DJEAfs zYCEH7t?_#MV0Zu65Q;>RUokIhW_FS-_a-efla_YWFBgtFYeMrouA2Sbos$ue?%v6- z>_+$G!&8-#)Rw7;eAKO*I6jmWkhEL3rgH0h{+(kjuZ@2K#m(4zWwv*GsBC+#t=qxG zyM>rIZ)u)5DQ)jBU^Qj=weF8Za%FuTDpM=_IY)^QZNQ6s9W53%?z=$9-pcst#+jPq zQ93!p);ZY(udV$Ux81O21$PGge&K+pwE?${bKy|L{X+@-Q81A%HHeurh&)|1h|z*U z!0fe(6aS{MT*>>ZyR9{oCc zwvFmPtv}=-NWT7hz1FDGI;9lOq-^9jeYCrS+g7ay&!l%3DxtfpM*QuJ=V>>=Lyoh;bc9??BLZUqn;GJ05+$j- zW_IU*cLjgY+_I}TxR;*%v{x?eVjOtBv#uN)I&-wg?e=!Xcp0txQM;b9cw>-l>aA#y zt*wR7V1Al8oXJs@JEglk9rIk1!+c5gZvJ*}_Z_?Wl=7PFb6CzbRH3m*^pXaK?j~Y(g$d8xlDM4K9O&sxi+naeQ{*}i0M(eL2s{yjb9^y3Sx3`i9 zI|AH}$5#A$eAorffc8D$4-x%#>_T=4GC@X@Z?W3vR7|qZMhyhoD=$$)3%!N!nli+b&d$bJ#4Q@%*%Z2Aj1tE;L1u7=~=+qu|* z*^$c(xI@}RQ|dG0qf&Y1e^0|3B8!1?tj-$-h6ByA8KG(CG7%Si9~Xw;dGw`pd_-jk zYW-(~@gVt>UeNp>evs-U2)~*934>*RSOE~QJg3}&8^=^ z_)=gYLRtXwF?}C!*yk`43yV@f@mxhOY z0eSZ$$I{j!F=z-rB;${0r)DxVXW;V4m}|I_$z=Bvj8Ico(A8AeWq`6)3Je_w%Dzk4 zlBQ{W4x~GEE^P+rJ8dBm_kDeK;G2S4`urabIP;KzK~av$d+`?Z8m$1)QO=Y(;J2Ki z1>!iJ@DE8Iee`6G-@LFy%U>>CH{1agqN22YO0YU9=!$4jeeZ($CU{d@^*^UT#%`Iw zWjG%l>CY{M)54{})m3WV`0!eh)Ajmp@9$fM!xNQ1WLXLOOfqY=)V1-@L*WL3;MO2l zOaV(XZ+=pi_>leDtjCcG;L}O2ETt%a@(F#DFQ8hg3|-DQ`NG$>8pG8xI|vkQ{YI}f~kLgS+s@GyXs6{9Mf!Bt2{OiHa=y>PF?z1=h zyU$>U9}*`hD)UJgqexV>7d)Akmphky{87_2QcPCmV-BkUy=Lg8X?dTLo0y9+s3mWX_Ka0HX!?|;D;K>e zXh|XNcTD39q?pR*hl`)sYdimq@BoQB7Q;vvPrL&=TAS5&bUv_>iV zUYi>QdZW9Z&YA{ct32rt(sLlBlcU}3SCW-L`3zQmHpAtR4B^4V9RXD)F+~)?dO=EV zgW*cY8+%I<;pomVRuzpNXgy3o0l^q#$6GDmFKnI5;8t&wMQQ@tPA1p1;X?<`MD%v{ z507`Bp`EE^#H6UJ{v1~c(K!*%Ly&j)TH!9qC0BC&D07yeB#>kBu18P3)%LQ~&dO$# zlcH(SbHj`WH7(O2*H2jwmUcD6(0M)yb&!PcL$FPK^!BB=DhE=D7BV2*e9z!}7I(WI zFMGv%Z@2gNo^7A(9sD@jesl6-baMD|_kcOk<6K6MXLp1thp_6Kl!s*9@;!|*cQCL- zLY5ClWbE|Qa%Hx2)g9cOi1?Hx47>vI(I($RhzvJVYLu-h!?Xu)ovoiX~89pjuI(or<44gsI~7Unh}gO`<8gO18i7Djb}7Qev*`m=c+dlpq@t?n4LsKaVsGvo3GrR2 z-1jng`Artwfbdsc(6m}hwq&aD>uha}Io5z*%nEbEb46Q}Z?4TQ;xs=a!-2*wVgc4m zYS4{PcP}^~Ixo~m)w~SW){)GpvaWT1R0Y`$u2mk9r5oQcE~+b@Cm*INwziuPD6&ln zPKIH?3&Nz+o~kn^aJj1=x}bAk&vvw47L7{sf=wZ+{y$*>MS3Tq3{u&u_X z+szvFy|wVOZ&!W+8%eu4ZElpfiiLqzd(!mYRt$sXyrTi{U8&raw`t{$rrCuXIrGie zxa82cp?ml>TjclPe2z;FhM%Xr@WY)1E2|(UguKr@?~?VsI$iT>QP(-2KQi52n{&xM z73Fl6F6=Dh4eRU63|ZgP!^4;NJ9est)prk0j`ntsm+(Ho%5Tu|?K)mYwTZ0f(^Rqc)TDWsJZdJ zdc1SA_jFg$_PmyqUJ~}kHF>>>4I`zYceY~ShHx_)+euv-`Q+RAAImFpHcY~VxMUvoarE)dO`kOsVvxd`>Gte>e7 zLM=P=57?YI?SJN;ZFHdH%?0V(pZSN6X?Z;3e0+Iidmnh+-Z*eYf;FZl>ULQj_ucLt z6M7bpC@7>rH?ru#x$rc40;~J zxKjKtGl4C8C}Rw=P5oTBfhp!CMyI63b41B7UyI%M7*bbSG^LBa4n%Vq#3>AkXFTREqeeEr;wXL? z-P6s;uwjiTupS%l%3-NeCPVo%I6Z@YqGmrs-aDw|VGV3k zxKDRsu*2K5Nk3(R&BBt)Oa-U8KEvO^-x}zFwST!8T39-va}DUkL#vzF>#!>F&LGt- zu$0-~Ak?HiSq&Gq`EaK~gPh~-S1~ybV}UM0Tu?kf2YPVo&Pwf2&w>;*(T&YPbJuQ? z;;d_H7@L$l;U4sj^*nflj-njT!c_3|J-Tu7pb9aGMesOeV|Xe;b`DQZLUsv%`u&hi z@$W{+X7Klikd5Ha-$OQmvLoQHIzdi+bHqWL&GIPGuNW>s_6XI5uD{S(idYw|aPykq{tLbI?k9V-!dbyds4)9m8*Hc(IT4Ln8;Y2Ptv_w`<- z;ym-;d`@!VzoA{hdv)jJV7T)ktP`0!khc<)ofP*aF{V`+wrBR(k-0o@QFM~dcnbSt zCxL(FsBG6y8e~b>ZGHS;npI95mlL?jns5+$(~?;_xZak8PMiV)`KdS?n|W<1mi_t|!cs1Is2(RZbdOnF;*)POj4@xK8Kb2KAwUj^Uvv5dH!qxrD803KC!( zY_O{LDw%}=r5%6)ZmWeaEMy^=(z znL3e3nLQ`YejNtmu*qN?>4Kgu-xRc|6S@~IW=^q7W?ksqGm>AsD<(vDn|UIZTjmvl z7W?Vh%ya?KhOmP^^YjIGJ}X(^i&wzijuTnn$SaW6x>#LZDP*~QuUzIBvja3|uau`_ zapt8WYnC|oN+c~MF1->(Z9KQPi3i2FrG=^2f}hKas0Oqz*Y!xdOGy)@K5uv_xM1Da z^HFgOh29ugR{aWg_TE+U2M|@MIQBYloSalMnN~|+gv-Vt-4ufqczJ%Bp2g*9y2j76 zc$L?3Rn-%#$W!t%m>hn@C`+XvK2THv5fHw1lTDdp>aaU};JKBnt;)W&wIz?Nch>~Q zgWTSJ_vnZ3e)#_HkA8T9E^{q^9wY|?9t+Jf`R2wDfm}oLlamz5F~`-Dve*^sX5U&N zw|u!7uL}(Hv+jJq0N;w;vzN!7g-qr)*{G*cseShjbNC=RI39|qgt0jj%Gb&;vT(sl zkRMZI3Z8ci=RZ>X-WJxd=0vqWTmhf9P{7d z@vR5`mnB+&svzF;iL6h}ReqZ#xp~psb}e7L@H#nK8cfwLw=H2OWX;h1|KT;g@9d2a zZkpN(X%r>B@{(Za%|Af~2bv*0Db`eFyJ`_pD}){)udgP`7cOQickH76^*^-#M^6LTg84G(@<U7r}CgG-!Fo<}rtag724q}1D-0t4`-C<&sI%-x| zHE#YQV3tS5bB6`&xTN6Ey+7G%25YG$+?@u5gfWsbjwiL+CFWjIa!Q|7AeU5|aW<&5 zl9|NmsTgMKRa8-k6%P_T4wVm+(~YyJf^832$4Xzb=xVL61%v9&2wO%vc_RpfN#LjW z`SBiDTj6vvMC}H(bWNoTwE=ByD;vT-8SSAqIxJ}b&{zor!bC+4aI<_4yf6La$it_U zLa_@9ezyzSRv7*2G2W+*gk}C}=g4enx6Gl`Q8SUITNN9FXPM0-jDD)s{_>#Oyq1Kc z6+ECa+?A-+UvUdSFIa$G!yPT7NJvxOyQ!m=|6Z)Z~jxuabVHKRPdOZ&8SV z7Zr7#$cKa;#`0l|18}FwRc%PZrr-69S)_$xacsueNpyUj&6Pk$Uh?a@pFlF0oa9)L z*Jvh9hNjikcR@c^IX`m3PRg$cmJ+YXKYp z-Z;<9%m}vLmvIalJC~Y1U`SIp`;B9A1^i~ekyLpCi-QvPC+u%v3^N@|XB)&F)Q za&@ne5q5P^>Tt&>r8{DrhuexsGZ)n zFXS3Zg#VcPkEwT}Zfk3+zuMpU`{TcV*Z;?(?`|JIdendP=*f3~@2{4A{vX5WR{HWN zdLxgw&U$k{|F7})g{^wcQppYds;|x}>kbBvxrI0~fxJR3Vof9g)0(pL-^wfrVnv;{ z|7UD%Fn>c0Y0Ewj!tz_^GlRk=s3pZD1a}C4%*)tywgjUaZ@m#iN{AFuYNNS`LUxXM zz(Y55pWXzrk`bP5AH~8JL5n1?bZPA0ybMCr1Zxp+Z*h7$*Z@e)e68!`U`JpmQnQ}s zNlwQf;9eMwMgkWfqospq#AsDvf&SI%s(O zxc@i41`J&#QQyWHx!rIk$Ynr<1sIX`n;`IHx=RGn?zx5nsUT3>=|^$qes@2wyplW( zRp6O&=Ij-_#zTY*z3u&?HhDa*gJe_~`n0e={J>te2B?r$p|&S|A3Y6dy_@(rt$ljOTF2`woJ z`8=YCd2P~YC<5pkH-+Yey%D4fml(ea^8>R46KGoO=x#d5)#V()oRzXC<7(tyYBa_A z5x2VEt&OnwoRW9g!g4dl(BOV#m{ujy-z&cNY(O7=upcJbn1;SPrF{K#D$##s>fZUe z*TS3k#LB$sSPu z+O2);*B+5eP|I^U>MaG=A{|P8pCBqM;=Okw-31bQ;%2;$B9Ai3f_>GhNxJ!hWP zNqrk?w_eH=yc7(mkf(qxT@1ow80AC26K3oqw$PUHim6n`cE1<`Yoh}{fnOlX5+xrIgq!K0=Cv^g^3ZUPyak&hV z&G8@sxwzQejQbLla{whd8yHat6slxJo_0@f;{pHysL$ea1B}8I@CjIJeikrG1Wv9v z53GrVYe0H8fg)fc0hS7Yi2JKz$f-Z=(9pJD43JETf@#yp*7{!OGVPX+lhZS04mX+V z9HV%CTG6#wus8-8{70$Ai@7ITU&>Rh7+y)KwhM!eS1Q?CEen?ClO~Y_WTp%LjPu#* zY9?*I<_+3*q0qFTI8D!t=tT`g9#;$m-qhu1N2<9+2ndQ@ zmv%_p*fKIkYQB(S*n$dq)svIp9TkAWv6 zr^9QZE{H40KzJSlSYzhT{yB6oh7QivuA$Ne0GUTYDkaxa9(*a-c_X!x#_)PvlKtnq z$Nlf0Jo)?gPgofT|9J9`AAWfJ-IE_y%Wy4#0*_YBD>AOp%kRR~M^7Go|J{@RW2m); z4gEv??SIeukN)o3k3Kf=ngi@JCww72-Yh?ZfcE{H+h9fn5zih)z zduiDi1t)J1CrdU{WPn>SV7|-qf-w6k-L4C znCuJ9TeVF`U-hSU0AhH==_+L=$$-1E6!;xLQmthMlC+)&Nt#1`RAjlxC`X0_N`T~F zPAFo!Y65eh;6isZSEs;Rf^&Jr!Pds=YPvZQ!{0$Lglz=&YkA=V4CgIgAXsK{ROI5i z_u9r|3;|6s`DaW1WS0%wcApx_SRO3sw8Q zCa7y!!`HZevKm*S0%F#I;cHl8`mJFB9V}5@0aHngHjzlcKqN{W6)mGD(lRRg>8NOl zmo~}fr$-g-orq~Af(Gd&GquT*$S^3Q5Si;El~!`TWp;MHxe+dNFcYH=-^oQnt8xR^ z0lmO5J#{pb%TtvDzGmlYpm`-BfTdcmW|>kIHH+H9S#+i~9Om&;bd&kIBPorr1o2zy zeKT_`i!iM@)-*jiB~+EBWEoZOcY;h2pG$hCPtpTRKTnaREiEjN5+oHPSc=uAT8u_$ zIS*D6?eO*XHYq0%o8Q{dclFrSjKc9bVuS>R^^xwjE-!%0{Hw~LTbP>hIkVz%;EUs`_;Fhp!rtrp`~Wg(7D=UP;-`z7E?Tg z8b+sQUR>!eV~E5$3HZQ64_FL0cMKc+8Bf=QOn_A{B90& ziR>yK*~AKdVkR$n*^2gqY}|6kpf8hSZQP2`E*2|pljbVdjUvFz<9;B5uHl6|j%zcE z1T5EVO~Cd*37#uCSt#;k9w(@_;>03H?fMd@FvIg<@E3Hx*%noV ziCWyP>F@tghlJ3P;Xadiws|=uQb|zWn{8bVRa^3K;Rum3T3v+?xIVMh)sfdhcr(Uj zFh*HigZe2Rv+KARTyH^t*GLO;fgZ!}|6K6&wA8#Ye~<&f*bn znF;nEOybGv>O@KS+vlju<-i9MOt`oe&%$awp229~zXCBlmUnS^wVrZ-l~c9KI#a4G z&`kgzlJap{#{&uZMO*N)%Xo8rg81&Rf0aez}2{_O&0cU|_arsMSD z><%UedLWTzT!*7z)%v--iVFbn%IFamY(_75_bh;JIOTO{al1azS*ozA!v)uyY`g`XfP{YhVlt_*6nC=L@BxHD>rp02zyO*iVfcGR?5EEEznn{&ZoiBH@itPux4?JuPH3YuOzL-DA()X zkN%d?uOa9eRg1OA*`clbRoWcMY?Q~T7lc2TU@`uaGR;lXSzv+}T@j9sM_NR>NOekq zjv3@IbY=6vGt@CzTxb{|epE{Eper|nL~XR8bQ&P%D?;r$psXCQzo7Bf-hKgsIpRXr zCclBIi`_~=w^HP)%gJX|c(8y=Njhh8GNtfIG?X@35D1jWEYp^hb2eh$L?~4Se_0RR zuZQ2K+>gJnMeX~AvxgL}9#S}Qa%=2x_ZW+*%-x!J+c*$Sn*(0nv@ZR3^($oKQh#zB z?@nLy+SIiIyvH3ofLV!KQ){PUJgp|2uHj_dJhm5B*LC74ej9WcKf}>A;6@X}wXR5WjN?7W%4O?Nk$VEcbH`M=QaZL$uA{zy4-D*2 z^Oi3C&_98_J6elQm#qdf*yFw1m&)(-+>O3OLJ<`hO&#s;@N+o8C7>f$BWE070 z?1j=Kn?~w97P>fd+^tx?Xqw$dmzZXB<(SLQ0@IcJK&qdf_SEaSZQ8x@I0)XuJ98FJ zb_(p!PIfBiT5p@`qx@aLg)*cz>Wlzq`LvO~UK;kT8d}V?)*VY(W za-sWVo&2aF+P9KB2a>jh?SfKU*jeZT)>z!y!YdI}HJx7?PYwT7fxhr__|uoCm-LX% zL2a_$hdqp&7>*+x8fgBQ*Z9sAl`m`j8!xQvCB-Y5SRe=&W0c07n?MUo2^n&jjdpkA zT3_lDz$_D+-=&@x)di}Ot`j;IF?^3cHw4p%vmHRPHKz>#F%+Z>=XgUw(K^Kql61X% zcv@jlL`KOK$mS}iYTI2rX-yQjOapVBu`J;AD>Zr3=|+AbH}rD81xv^vYuNkHmc_2g zWu4YsPFZ|Ru4~X^F6dS^qAF-3yS=UO_=+x$!01D~{xJ>h4AowZYCRhiXomzSlDOF1 z01dX#H-dZYM5@>y58oKkn!D!pB+)G;+`*D7Ky4hH+#lp*7DSGj!St@45N zQK)x=EWVU-&n14weW-{X(FgbY@b2?vI>itjLj{(iP&7GL%p>(P3Nv1lH}H;$aR%5f zn>kov7hiGp=<(ye6KTQgye`gpC)C=MSxk>)V`0` zlT4~{BbB9hvc$Oyv@ZJC{cBC!(G7+!tH1tptlrJugbP!hK*g0amwTJ^A}jp%cD%_$ zj3<=&M1W@dVMK8$Adr!uEl>FnaUxPy3TYmY3yow36Z(M-Y>4CN!H959CmZaU?YOzslIW8UPH$u}bCY}QhrYLR`&pfYW!PuT7)qES!fSh}fNIAMf zL1J77T-tgo;2LB{zO!3Pjm;m|X55p$t?=rvQK_9YOgB% z{IoFnga#)C?cu*3g>+??HcByNkplXMZ@}6Gt5Bvzo-TiCZVX2e=M>rk0)ZGqoV%M|V;4V~lw?=zg#-urU)W9UkGYefZDIV(tVLX>7PrkoRK& zs;kix$Sqvh4s{Pz83YYR3ricrYn?Ok6y&yXcIVjY?`;cSs8;-p>s~A;#TmK*kkFG* z>aGo~FsGF97wrhJlWlGIY1$XaeY8;JzLqcWl<7^DQk{7Qr`VrI6SKmkC6A>#3#7Y- zxIEaf0y?@@bYw;aOcW~A8o+g{yafYGl=lgFrIq;e`QwQx!}>{(4jt!8R7$0SJmq%l zX%**8Xlg(C8b!9n36M@-vd-JHPFc;STxK?^=dRbWU!`(AhHT_#hvW5> zl|nLsJaW&kxTSn|c|euED9#b>hmQB$IcLIa=VghST;YEhw7(BR*N13}NCztV@}g&M z=1yDFret-`ocPmHZt-D1DmOWvDRWlvk#SCn_(Z3pQg)J^`{%{xqXMZ4IsE!G{itSW zfGuM4_H2%Gc*Nb{nPW}QsPD|pkda#hY-VmLlb%wLMJz=>dD{^P=iG0k;HeU z_6}VCcrUmNa-igJ75-AmTq#dL5tnyze$c0ViGf>qAS0ife)YY3iqSo%mb>EBs~V5? z+pstOz1F=PV#-lhEJj2#ugf9?c`ifzU$(}4*8_(9HrnkTQ@hG@JDr1 zgi#;8G}`t=gsMF6oIGbYFs^9MfpZ!2amNa`h&&rDdB2W`HEB|Ki6_4CK(sb&cXAp~ zudCM>MKzRUiMNL^_|0_oxEGVbNl2qGRD8yxP$s7k1ry`lq1TlDaRdcEa*vt+;mPSf$(DIT zX17sALPr$|hg`WUl)iOuCd_do%;P^~0FjH6=l9OqOPsaexty+-|H0|{-33jNjUS@* z;{x=FKZm}T4*GILU!KR%9foCjH_YCPzITYeUlscB0k7S~S453)m z!210SaHj#AEy!dDrJKOz>+~G0bCJr@<0Tt+wuD*WbYla>{K!2Nb3oYHstY^j7Ce(^ ztdS*&$GAe7keV+Zi@o z74n;q4d@SMsZ@dv<~!kOK00`OgbL~7?;bz;q5qG^Pab2IL438j*?)42k^SraC+xfL z`~Udv$qyTUcfLMWUmvq4_%`5ckN@%gcaMH})c@i8+l}y_r7HSQEY|;?J$m%r_y2hG z=!tDaetFCuKl=Xr@BZ<_4;w$=7tBwj>bPmzYqR)IzV_(*C*OVd_{rbD|6wgCH#fgm zhct2cWM>sBv#phj>A=K5D!2>-9q3O=ZcDt&L}&2L=#h-8AmY&{ncFx{=xMtsvT>T= z-J)?pZw~VbahNeY$$EG@jjjb9^^SR|?wlBlxcfi|*y5hD>lVH1Ie2}Xi_ z67fW=_t`b3pkl{Dr~JlT-xuD~6=tg1g?emH#E;kqXtJ4TOERt1Rr$B4wPtom@ae}c z`z+!Q;`a7X40fG)()qZWfkxfaJyrLtd&hQrfSKd?O2DtT+xu^JkKx7BA$ zAxJ5fBLjCA+U(qPS@*76>&^|2YT^IX4wYG5^(-mIfQmj9TPn8sW~33!*!3E^E0xzy z5!M{uJR6Tr&Z4gv;dGH?YgIQ0%dPukt!^27Vb4PL#Y3)kt1Ot|Bdm4QKEp@e3?F$- z+a&388$bFJ6MYI3eb%k@`T%tH=Tuz=oqa;*J|bqn9WnbJV)kpKUoNKpw7~Cg;Fn~u zFmJ?Ep+m!HkBGsZG??J=?$5*k2M(!V-gG+961-wZA$#RO;|02S+YaI!gA!5TwQ1*1 zC;C>Pvxwh$P2Fp*l*USZ-HtvQo!f-dG7$^x9hiaz<&z*k<->G6i?TC@u{B;6?93io z&e|bqOr-@w_n(`r78vYuug{^AZso>sm@*JFhxvNCwuVQtDZMq|_&4??WWOzgWB2xr zCcb5^w$qXj?Y8GdM%FGAQnj#*BUqyx={51dxsr_AZS+(~JUbD3bguUL@F zycNcUv#21;UC0_VwaCa^#g@hlIjya2rPe+S(_G2Pb-=@WZ{(Ufz%_NyhTJyGxd*?z z&UKkM4@%|e-d3eejxgF2l~@8>`y8ZUsAsVntOQky^}lkg|DwhEf6zJ$b4Ih|5VG8k zLDa{P!x&@&v6at(Z^zsXKA5<$#ohvY2*DU6aKJ+@7b`jeRmL&7PCw$jef&?%n@x%1 zUwUB{ly?h;VuN~h4J0Q_F-L-&FrNz!{MpXE*&gV{SXnbO+d!+-qI^OgZnmoie@RMF)lbpTBN{)~Qpy`pshmcE@<< zrU<8i`Jd}5&N7$t#z~Dqy>pX{$N1(IXquxEW?dx zz3*97Q??7mkHoOeRXPHgjKI~$$cw2?fqHZ^Yb%!hNo3C4OvST9>|IKNe`;E3PL&>j# za@}by{MI(&q)DMbbAt_;F@w_1pHDOCS@ybXTf5#`wQ1?)nhnYrSEqf=c#NyoeX$wK zyNtfUEHq)H^7L!xl#?`HslgZ0y;OTkBMZYidYa2p9UpX&LoEHjoP_*wykSE3T*Oh% z6hxY^NHbD}V*I$MzgeC=sOwDWJIT}Ee6>8S0<}_yIx*dxM^yRW(#Uz%61L15M#ft_ zQPLt!gYtwKmeMG79Jq@5bC27#DIIm@<$5wuYnsfY!A z^KcgQjSHLf!v?-U7@YRUeh*Rf<+&yZo2rL>%KAaA@0HxT+qu6~qC zNv9GxalkA~)B{X$s0(16k593@wM z|K!QzC#$Fw$zNuhh8bH6f>Iis;kWo(psdfEoBi+cYD%;yvwh=(ON(8T2%fzX-etk) z1NWUkHIMeq9qay2mtmU!l@03=H<$F_1+nb6R-C*8QS?i7{G=#hCQ+PIDdxrU5Y1dm7ZR= zGeD;zZ>j3-QEk#UG z^`!pp)g>ysv$5P}o0WEDy~EQ!r`Z`MA2-=j@VW2YNALICy7Sw#wHkyZPG!RK!iQxH zu$;$QC>V@4&sSGh-r;$u(=mZD7z1d8&S?x^QD>eG>0_UgfJTXH(Z$bnb}GPwqtkG8 zl@f&FF;cX%6vhaPMtl&pF{i11xfTCtPSMdV4Fxkte@B$jRMV0UVx7dJVG+%ogOk%? zGNg3E>;2(G<@%Zcu(9`W3hkan6M4{!9zr%!0))O_W}?IaeP0M>_Pb-F`zO72uNmDT zp?wqm&CMXt*UY0*RpPZ5=$3Kr*l|yq1lekeQTO)ZoYR96U#|DVGigx4D>7u)HSq4C z4102gGBg03d8m)p1~R9$i10=SuCC^wt2D+gq?qs%W;GbE7Sb&P#FP5Bt{_bT20;10 z$OEwln3S^sxY;ze!(kv545z1;`j!Yb!n@%{B(K6uq|^0vHr;$`FXf;MT*g3Qi8>fMU0b6@X07xvia%n0*aYTzW@dS& zW?5d5HFYO>9`P2W}#`EbfnqE1RWPa7gcXd6GNo=I-&9s-$7II>xA?QXj zZ7&`1e|}d>Bqup{9nsgu#H~gIUH1PuKKuW}Huk9j8|-Q9`hIdUt}o%-p@wK2I* zJR)OFePK-$(=z#|wwH@6{)TQrXr~)D%;h0D#yMGsAjP2MQGVvx^Dzi@)Yj+Z!GK7v zIv)&mp3?$CsXJCzvz{BuPy?<;6$@kUN@t!Z{^Gb~yCFMv8$ic>T+0<8t*ugnjqOE# zOvKK`eFHHtS#vP}00s*@QAmFcux`p!D7(H8~bTY^tY@Dqy}__3VTe zZQ6@poJOF`X=yGP9Oe{SWE;k&XfkRIX&;H&ksB>g=5_+6Qn0#R>vsd8JRjqa@f9Yo z2K{cFi82)xzjMCnA3b>;p_}f2_}TA*{fKeW-+sql(%(nyseB%k!Hbd!ywbO9kA1{> z@xmWpe68VmKP^fd==ox?<(ZPc7+)+a$^Tb6{=XZ%t4KzT*;^_^{FCMa_Mp+~s!sS{ zVrF)GN2(9QTx0c1Pr-k&oMG8@FH3Pvf{urb@ZgziCxzXEe#<>WzMjz?2_@@mQ;XMc zDhi%2GS-{=8%?1965FkddJ`S3QnhQNt<2Uw60Vt7t$9q@{!okWk7V)nhJssa)0txp zfxH>rLNDfdb3}Oa4v$&jjVQ)r<3xN>1c(+_kkQJknfV%QiKlofENnoSwb@hODU#Y? z>-~BSbrNmCu$CGMr=f7R-W*5U<=Z+ZJNlfOqi8@>8%JXh^Gxx-%s0iq_$y5Dzsz5& zi|pIKwA;5|{8jetUmEu9I_tsw{;hFLhEmE#t*;H~(rwBJE)~yJZ~dOYf(?*9o|ig% z;(f84P8es5*Griw0YTYZ?&fvAM6wJ_p(^vtJ*4=!g(l^9A3+(hF)q~Y|}hT(L7@AyPoPJa@yJHWwD0xwrXX8Mz~ zoc?4ir#}hhbbYRV;2Y!o{K>bg>fcyK=?%~IFZ)iG)xYt*c7#1QKh0a^*m>?f;#c3F z@x1l|OIVk~bLT;Ru}*{M-XpbaJp#|&C-%0vyUQz28Xi@3{Qw{KH#d1?_D=7rnYz&; zkI7J?Ze_{0=2x*-+Nex*m{^H&+Q{3#Hr%{?O1hC!ei~PJGlX|t3xJF_N(oGR6ytX< zi{G8p4OY?yya`W`>pAToYU_-f2z~ld7fSE+C!POLkEp^ z=WXAlN7lFQ#^UD=jn@3$p|MRUB=}zk_Fi~-Bm9znQI)2|G#m@P&A%`0xoq^3ep7m5 z)kl@QcetUqP#lfzXN-KzIi6$7`SQ_Tl&49C0@Do|eAzQ=R8eZawQ3aMHvh1ss;Wx` z_3Cpji81D+v06(j8-DnQzE=m+tt^B=0icuN**j!HyhO%>1*dO;Prq<@!8};NJFp6w=!c|b(hP_labqc2pZ-o_+ zAkkYff>xj#i~!fG3r{#7@_FZvnyK@059WI=L=m-vXEFulFeQBj$CXEUBk`m=ID42d z<#<9OoAYpg-$_5Q=%in6Px>)^ z)-U61KV;d%IFg6ajQ@$va7k=(i_7egOTUmT$QI3N>G|^L!Ce1T7QONObI|3%Sl_ z8MbI?`^#<){{|p_`TjR4U8x8R18@HN`7`9k%WS(iS*@W5}>kU)_>OJgSAKt9J>|H60 zY>ehi7P9M&702qR4R4mg(|L8b!dD$3Tnke=`xibSS!+}m6#~4sxsI?@lxW=;mrEi| z1=^Ht_H)GKye#Xfgv()`%X;E@%a}3lqlb2x>p3ai-K#Xi@b*^NOQuySTOSQY^`Xp@ z{~NKeATPd;1dM-KjD&gKdU<*ZSJs@Is4dNWg?GHY8{U|zImfAuIkXhf;DZ}@}F>)6YDNs711ac}s4$lC7M(`6$vJ@>N0 z69S3}_saOS)at%z$*HGYin2n#UA>X{Nr3eu@9}S;BS>OtJcEeSUPjDY3+cz zx4N|)#ew zf&hUu>v^FXd$6cyh{|gjqR2SysyHSzsS$%{@9JS*hQ!nfaW+TPB+(1xZkf90?fwOw zx_HYTNXK;dt&g=o5$^XBZ=8oFM)vjRp`SibWNf5ow2Z?seR*NaCOZ`&KgCcD?Z3?3 zw4tfWFX|j7d%CRK_CA<067OvEXp;rDrG(6FK2Yg1G3iNUZcOGxk@~khJ|#(z&PV(z zMS?VM$bu)^J1?a)2t#s*I>>Uo=K~2TyH}9Q<_dDzLO~8&o)d{EZ+OZFyr&b#$Ghwo zWXd-xKe>Ci(7AI+U*DdL*i`q!S}R2@G{VMO%+WjW!VtP9g!Y*`<|YDfx!ef*OqC-5 z3;%Q+&CgHYa^s-009RLAb8=vtL-_~KN=RizKFJOb5B?op9v%$!kG5`g;`KQw$!%l` zmVTAYS_)pOIdpHwrQ$Ln*L~A{QhY)s-ZHVU5TlLm2Hf z-z-;iKB6;+_S#D&Zu>4ZLm!p@&AFu5&`+tH-YEG4XKCr+=@MhWAnul`FUlo`PYOpg z?}7>7DwqM&swPpZl3x>A)p;*)E=(YC8-o|sp&dvWL7E2X`~M%2l@Xs+hwTu6?^(V$qvRCA39=L-9|Ym83K zDz~8=IhQACZ60jg`tfGnkcm4c4bbhgr15jky~`g$tR*HO;xRSY_H`Wc7ebmP?42B)a|n6P#WHV-V0cZ z_*#r90_fz}3f=E&T8FVYcb-JuF5(yH5_8BeK?YYy23IZ z2*BKaTGMtn9}QDLLX;a_t(9@pLF{xGq)0qaHjMDvUUl&_-cX!TvP~5oh+rM17nkKC z8gUt%71!+5i?SJ|8|9v2q)NBoRb$GD#l&k4QA=2i+F66fah#^ZzrH#FrL2nRa zqx2K)m>dP=(Bt3Aw?&!C)c!%$pI)~&aOnlG%S^3p7`2&InGO|;T4QqME%5Tz9QR<$ zEBjWPM!ZeuJr8SJvkhr;!@^r*cuiay*Ua@Gn}adMDhW%LkPPpfvv|b43`>|jz-$4+ z+XC|VG-cvU?E<;%6INq1*G$;X(D7(9UZqX-eG>IEODY)6lw2%gAlIgb+JhZRoFk4_MjZ9-f^DHe z06l1pyryIx=*-8I=Dt_79d6N^f#+^@PkDEZZg`bE0NjMJ*oX&G}^ zMzrjm4W%`jx8_ilea?_XAJnS?)bwiJXet3=n&fJV8HW$$ei z%cIOiYZ1hs`-B0M1d5O8Y`zjh#V89n3@VWKOjDH_8*i|oIWvJ((LHD0sOH{Xrqg>w zuSrw%f{JfMnNAn*EYo;R2AhY=x*VoF?q%|k--D$MR>u=o`bs=u)jFQ=-4b{j`CA6o zx(`XFWd})GtDnEh{1u7zghDt9T|oP*YXyk8z}Nb6N-Dhkos>{~e3_&I>!}T!-esbMw?&-T6B}su z6zt}Fn$8wz9EpxUCOvMqZU9(tMy8cwd?O*rb7nVCGTmv$(lc{tCaj%UlyJQC)XzD= zAFTl3a2s%7qRj>zXkJhdP#rVp1>T2e=e)>I&*hvKX3lj+tHPgjX>!zWy;*N1_S%)l zJY4Hy279_F`3~nVN3->^R?>d>6?T!nwu{i}d9WlsL_wOa22!_zM04^d>kVtrxb_b( zX6AA$+*{m+*)12f^8h8g|20astTbz>C9zevRLIDD%ZhI~+mOz0aeMnnW)NKAwTL)! zGHz}RZSF*q;mBG?VvpIE4f`PG^%TQABK9y&%D}lWn?2Ikw914>Ydj1crDF+VFzU`c zf>@|^$iq1gRUioW^K}9@@zv&riY>Lbd+V$0U0cHHYPn?%3p~0{ufWU-rLR`Z6Ve-L zxPlf_Mgv4AS65WKZ3ik!TM34s&a#3p!rFyz9EKWGo_yj>i^C}vJ8K~kGz7*Uc;K>E zx&oKc;cnhja~C)Vhq*#(AXFd){rQ`T`(zqCu}I>^yOk`lFBYzmkkK!f*$*u#R{{& zw+s2?y$%B%_`X;%a*mw?sA(;--W5mOZ$@58Th936P`)r{3l8zkkvnL5v+p)ZZ+O|L zCdx*=y!0x{OWO1JMtg!H&Tzob*iJT`avF40mJDqpmnCyNfA331E~ytKlWwi2ShI}Y zaOQaWo^y7@$5}p!_Td-a+7^AoCz22ovo;JIIMYrPNu{}*l^lsYgzphCS@+rNzH()BwTqw-ue z=u99wI0iC&zz+blJxXE6>GAfqdn)1Y$XaJz+_s4Fuql$GWBOk|-QKn|tQ(;cP8-&w zu%i0>w)d^YnSR;X6Xids?ht#`gNv8D^5XV(rGFRQS5bqg%Mg@}iK0G3f1GF z2@GDKY)JtVV6Uu{*c`{}t0patS!tkMY*~SUiOokwHo3jMxpTr}FmbA&bbdB+iDSTF zrmxOIFH(Tncj8P(F(#luB%9SB(dkx;IKv#*C8j}2;XiFDBhbgwOthYnu}DIOW(bA2 zo04h5NDm!v;VLow0FjZ5FXJ`~HD&F^e3J^&2y{HyZz+v4Y-kzX0_0V+!R48DDaqH! zvx7=rK|EMngW;~O0#lb)KbIz_8YM%;GO35JGIX<8JJnNY;Edt_P9rY1W=USA$+Tsr z<@%|`pV1~h_jEy0zO6~bdFlw#7>+Z<99w2CO>5hCne5_ z;c>;QY*I}@u8VHeAxt1U%Y0L(%DAUW^z_C9RIGn`EIX^6hyiv364JR@0-;yBs+D%b zeoUdHQ7E$T$n6LlZNJoxv#uR#m#NrVbhQ!lvT<9f|4ipmR$e#eivy`U{7#A*Na)6hHU%}X+NT^2Xi7w6&dBvKcTf9>M z{XD&>%=70wE!FQk zpn_9=hC$69#pv_wfp?!IcMr9n0A|$Pd+K02;-!;x#vq>;odo_FrqO*k>FB!=T;8Ao zEbH04@wgv%kNYVnBhJnM%`+MuvHO7AG$U=;pa&)AE&q9zCn39_Rl~0EEwsBk533D9Ku1@^Mp<@`E`X{syt#|q z=Rsu-U#M8060P*^_XRF72Jj@w&+Wz>5n7bso8j&me?ia<=5QS@r04lTjtsy8Se!QMZR z@$otTxY;Oa1#4|vDuGd?RX zP1yV>EuN*t3_qY)4zj9^>SL!jcyDHx<062ry3)|zroHxbnV>NeZ4$^SX}U+trbDaZJg|`1DL0 z+GcoASkFk%oHNqZftvH$>nxV0;%g59{&c6zS(~EvQ6K%*eLuaHCO1*<2=m#fu~%Z@ z=;xhBYF@Bf6^g~17CRWf4Q&2LUV@y`mB~$ELQpl6?@$y0#RD%mg?BIc_3H%MZp)6c zRQoOXs-tAybQpY06zMNSM`Q8O7s^y~IFqFtG~cBm9SXg+_ROztsE>{LOHUzT&Z=Y% za`czZRdU_Iz4ikqT=7ZgGa9)7NqStJv_Lrlc9;uTeuc(bqy3FLysfyo1ZO?nQro&l zOqy$p7lU8g4n`w@8C@D6m!<;XHt!p($jsA+_DKq5P5OF_msw+eADw)Z-tG!KLadv~ z`9)fkNC{a!X9t2BUvF|MHso7Z4`UsmMB@kHRj1vr$yD zS(cYDA%OgTug^LasB?b3;$I|9@nq5sHT!oz2N|iR($Loie=!#ywJEH%+sdHpGh=(4 zO_MQyXopUv*8WWUurfg_O}{i}!$?V(Cqor#wq;wIX32zJEeoZo@KB5tyljD1+JkZTDhsS);)!uk*vP?@5l>Yc+SBbdpqHm!+L4)#aH7nxn&D}!LC`a`R6Ew(va+RN9 zAm%qJ<~FL|;^msMX;c+W+@gf~LTyQOwGO?xOvGeLuQE?IYRokX`juZK_dgHH7J0Eo ztv*>*zI-Khr%cOV1%1b#13PPYW}6k^?QN>8Rd|m`LD!-iF^35InWcrfG~NI=ZxL_v zyBc>V{@p^r4T4=;^D6?2Z>eUJ!`Yha>rFuz)R}P3{-b->52UW#Yz|Dpa56HdhM>#Vl5Pl0yIxv~r}#e* z^}os}kdZd)AhM|x+|XK1(fUcG*RsW=`rIxQeU_BT(^O1gwu(T;hhNPy0pjqpbn?oN z6?Yk@maHoA87KR>C_qVI4kFq+U|x)~$&|0z5xn$ItkcCNv}@%kAEe}03=sov*YaY# zEWGmu@D2o(H{z;32LBSV<~X!1B`p2d>f)t<^os{m>sOdZYqv!21UHhAMW9tWyd~my zVa;&n*s14jO#l!~)^;n^;u*ijJ|oURlJ~_$VvL<+3eZfhJCc$oXvXiPlTO^}u8Gj3 zE3Hwb%x!AyDzJe812byG8=)Gb9*$@%WHIfwC{GfKM*E#BRI0Rg-;qa^K~tD?uCoe( zgc|T(6)3P0DuHs#cY!&`gfYxYNm{9OhYf>nh=%pA0}y6hH#G;=GLtHzV<$!hN2Rn2 z%tW;(bdr}d$@GS{2FZ_$D&$@TS2UVa4bzB;S}#zZ#yqFe6}czQl;FZHR2s6nP4>@| z^n)>_{<*FK?+}!fexy09>r*tJL($vR5*m=(X!&1S|NIxswWuk(WDpRYe^t7ze{~XN zzC=rXS7!iLV9D8~E2mooTXL+;!(;{MFsiWBH08jVXhwF}Rj7Y}XPi=##nvbw0V^XeM^CJYJq}+{jyk)oN4{>(?@g!8}MW zL&tP+8QxVyP6-=_tXLs<1I$^0t0a-+?zBOtXeyRbM);5=u_Q*lPODHpdaE#d;6kHK zQrqAz00>Lng)7^@W}e5yt`=v4!&Sx-zTJD0~B9$2@7qRthkuK}1!mu6E;UAd!+uKs6 zg#~8rQw1h2Fo|G2P5M446`3V*B3Gh9GE3L`q3W!zL#Jm*syCjx9VPPEV^lc;-??Xz zw;H*0s2FA3-nQM`J@y1JF!(I%Q24ZsMzuX%w(epc*kNm0Q?IKYV(=}ngxGUOB~6Hbb2 zEZ0wF`M_pOa_s}wf=aMSEFsG0@dll8>Fg4FoTkr6Kmy(6ocil zRFfCb6KeARsaT9wOBMNzRU8#Hk%x4Fvm~w~GCHRIg2?`~4&gTbzob88bAcm!l z29n{Q&>9M}3)6Opp=ydg9<+hcqGX@yBKxSp%q;N$dYX(NG^PomSRbZ-FkZFKj#rJh zph#O+3}$9YQGm>x;4K5#M+cNr<4W`R7&*FWpUivHEc;l^ zD_5nCT$NEUPkt6l5^y4SJ~cEWUmU7$thVO0O93u-R1~GLx*}9wDbYyF;+_}xrAAGa zz_HLa?~&fI+p!jXwA52O4CSRhfLh8@Ok%mIyOUkbs**n!)PRhqBZDzr+So2!We*gc z(fisqrQV*~I{pE7HFdi~LjNCn6tO{Wa_C-~5_9IX079NtKK3S&Q4{r>Zs=N;E_{*K zNXMph`!2NB_onA+`F)lz_F!O8KF5$gd2hp7Kk7@qCeu#J{rxMV_I&>##prn+J_^>; z!;TY*$WgAf7!+DJJ|Z47ZvE!*IpZ~teSFz?rQ#amDSg4KR%RHhs~B87V%)Ccc%lnA zj*9pbF}PhBNVON|XkGsxgQX5-P}FA?BXy!;rnY5})Q+PP$T)ptawLNHtd98RF1*h~ z|5u07IrdS{I9hzIJ^la1AUoh0Zb(<0%2a`pU=n0qm70IUHl&Bl;T%*5#72w@=lT9^McY$a-qVvQigHDxtaoA$7K-xKOXw66=RW7wA;flCw}kW1l)jGm zc~zw1LJFJEa3`Bic-~QxkySxMC%A)uO2^Hi@%g}-3kX8brT8={(y^ptnU{7P_xWRX z9{1VhUr2IekI-7;@;BptYu*0Y@H}!KL1<{!ebid_(b%^Wl1}_f+xp}4dIWLAAzMOO9bpc-46-a~lc|ZT3s{y|*t83o>(a0(6sFZ><6CG^AQ?>Mt&d4nQM;m{oU6ysw3)7kK85a{S z#@E(n)Op!r5>eM*1&NFucQwh5{w921krM5j&^JKSf<5RDC0BiR(0!pnK~uF!+HhL3 z(V6EHQHk(sw{?PG@0eRddzqzutxi1QpyuanHYupWaKU9>c!1P7@IFUM_h4c-Zmdzi z7HiaZVvYJtU&(4^R+TOMoXuo+Rgxe70KlCI8_?oc&`3N&c*OJqKtmP`@LsO9+1p{C)!~CsPkXwxv)|v6%4wwC zdrhSLGjPjZ?b!oP?UP6C9zYstmC^V8HK5F^;{8jJK*lZh4kiRq!p($%ab|2L(p4&wThp*VRJsk(i z$X~Z&9#h;i8q+d$^00~tsbr71@un3yyMtZz=uBZmcX-T;5Ni8c_hva|8jdFCTk~K} zgsEczP3|egxY9>Y=MHGYAJ9f)KpP7Ow6Vm1HWmzMW6^*%Xh7aEOKW_>5gDVFW3hrg zf`(#;97;o1;k}aQBH>Ls?>J=U9@DIu%5rNev#6r?QYUBf_$ADjK2llHSiHD$ZQk&Q zl89@sr3UObu*bNL-r6S_!w^Yg@XlMr<2w_S_l3u0@C=yW5pO0jU_u=#uNivPk^I|R zFK0=shzKgvtCW8Bdc82}Y%3du>*|yVJuuD<4YM-hJ8X!B6jUHEaN7-jVEa5i;_MZV z_jz#hc6)#C+4jla!H=WuHzzMfCxK52oMNKItiia7K)0*5JO^zdL%$M|LOjQ`^)Y*wyVnZO42=C zY1I;4@$>k+Y#$#V5472n+*9X;Rwa2!TYeiSK;%K)YPkmhHv4F|p%n9nxTJ?|375R# zhQV|o9w^5jQ{ii~JoEM1ACu)ob*{YSWDI)@ZzOxdFk0D*2Jm%GtA*%@cezFxGuumL z=MjWXY_IqTa)Od>4kafFB^O(vqzk1I6v&}VB>c!IO#nq|1e%l>1RubDGQb`xAfL7Y z5_Hf{K;p_ij}&wE2faq^93K|yDaImkN~fDjX!LrWUc`Hk)X7CZS}{Hms}CQn#u8*n zh1SG#2bE5ajX|E}Xmg;3eY_QI&Lq3`qlPo zBLLGt3-J@unOU7KI$Gv)B%;V|Dl4M*RO0?h;l9>wj*c3G`pKx)YMCU+WL(tijefr< z>!{XjX(C;<>!W`Cq;YuBY8)1%jq0^_z0ry?0g6+DAeRdLr*Fe`SF~C%Hd69R04cAg zMqX5LZ*4`y%Qm$lvXm4>T75RK^hsgq^Hx~e+X72zs%?Xy5}LlzV83J;vxN5wc{O21 zOzYQ%jeK>LnyxL=lFEO>w8KWLF({}XrKelasYx4|nex3s!+>X159N}o@3&Gt0mpSr z8ELaOeIW6R$(s>F&l1Aud=p&-*=c-`MXEqKVIx--^CpNUHRsNT@H@mSD6-I*O}`PF zn~2~J?7^b@aIWvOyEXCt%_Aw;){Io(t-by5-?><5As&85P*m90@H%-W(8VPg-{Cxp z0p{Xb3}c!(9R5geE)5axK$CIxpXWDX<^ztLb@}Me9rfiqyZipXy$CEHBVfh-dSCr( z)ir)$@ACiT@EkUTVUFuu?ktHPWBAsi^aVIjnJsGzruH$u?Lr6IN5sA%D*MFs@uHiy zp>=d>g9j{lhNZSnU+MIN=jKEw4S?`tbYfo2+-HmjNUVhmbL9j+LjJ(Tid)y@mS>_S ztC*m$R%c_X;gYun%mf`c6nPAWZ+}_%Fm(FiaW>kHhhd61=&#vrUe=C@x zGCmut&%v-i6)XG({ye7>=E6Qd@Wc$0mSP4h(}(pa_0*0I5_O0G)w-E5RHbQ9y2ADK5+OUPmB@Tej9iB^3V^4Q<5C#Df7BUtdo4elg0H4Oia`!g)zu23hMXOEMMLewVK027$+Jpg0;pM~Wn) zIab$ADKeKpqB(FaESfjUTF~6QVK~51B@N6mFJIjl*Btq7gyB;`KaEfo>Sh&pmb4oz zeaQTohBK^QLWVIT=>lh=Q`oVruS?ND>>AR*j`jS##LBGFUhdy0bjJP|GV$828fyN|QZ5hjJ*&9xFQl&cCT_dhI z`)b)$u@q!T?2(*8t({C`JQDtG+QGu3088iE!7gqfL(fHj5vql${kCdLS15tbr-71L zCl{0`)tH{WWPqV>=2V8al`?Gx;_hxnyJBTNAfg+U3o`w3p;UYEd%H?MRdS7~^z}o< zLL*$y71rk4ROY$5YLthts$XAUV`GBL|Kc`qPINM^B>Mfw+}v3daSn~j?^IJVs#>fb zqo`>`@Zbs(Xq8z~7LZG2Uo3uplt#hAhwP~g!ObzKAndV5Y?9G*D!n-&1zV*u`JkRf z?1&xon#{B zF4NX+WzLOq{MhZDs49A1F>+HW_*6uFib3`#xr7?-R%w!ap^uES31+!OTdGZ9J@f-H zmnXZ*szWTb-?etgGj~!xG_j`zRqpEQc}w%^UPUVQzQ*(34m3@Hii5DC^h4Y`n3h!Wp9jY(fSa z(ZEdz;78a`<9VDyvX??7GZ7{-BoozJn0EPO_AF!uU7bh7vkZhL$y~O-XO;< za&+icWo!4U5yD?n4J+KA!^}R*YPYnYMsLTc3V988DAV6f1ofqUo4QMMew$;xCUsa2cJioD43)hty< z22Ss^ouWIzvChV)M)mOV)*`a@XslRX#x0Qv+MflVBrp+VP>@!%PLabb)7_a{G4yq( zvIGtO98trWaEanRcU)e1h9Y;|>Bx^ccUJPF!JUEpXmaQ4oRboV!cs3na|G2Xn@lL) zig>*yH#xiGRGAd*JFb-np4~&2H>ji#SA7(0P=#;#;ogo~0 z{-mY~HAGbTP^lVBDNj8i-{#f40AY36Df&hJYQYv_JO=*04QKBVp2;tkJlc}bKB6iG zV97D6Qv5hWRf-=6rK<1IRDH)<^_?N-UvcM%lT+>-aB^9y=B^TXImKF^GxG8&cLwwY zBuMWSEb?B-(C0*k%+6g2i;@`X0S^YSgytUbAbxlr@F0FLuZ#!r z&4ri;xd%L`Xl#2Po_=Y%+ify-`9K;e)QoIDpx>Q`G725+3oja8PwRC$DSUj%GlGR; z`2ULMsS8FQ;}&$hOTJ@!P66+H<-m|aJ~yX!Iyep$s{@mf?*qUEJ5d5jY zy_C<;+YjcO*y2G1p2x!`?_0devt@*aRNtoY<;^<3z1`e6tejPtZ0B2y5H%DpS@eD^Ri=mKi572fi<&I0rm^%c z9wTUX6^0OZ8c0D`;_nfaQdB}z>f@Y6DT`&GD zVHwbU^uq@ZYqDEt#bW*uRq=bjif}JH<+Ru64I8~tz0*Ew9*qWc$$A>Hi$+9FQbnRA_mf<5KO1A{=f-fS#_+t= z82V6Z2Fw_QlgLOV_+9F!;#F<4KLK@M5ozP`pFy|IZEH?pn>Dsbz~*%HA~=+~7}PC& z32QWAUNVbQZwP#dJA&Bd;&mGg7NTfah@vBjqIr(v6gZmW9!WI0+Q&&4W1V>h^&7y* zUo8c4UDgDJ6d?*YI-eJz4Sh%ocKr#K0|UkM6-23tLX&IGKsz*fQ&1*BFcjz%6i8tl z%B*roiMl!N_U(oj6T6MHZhD%8hd{v_FGIf$N@+cVxdXu5z;osvslmMOk-As9)w+te zT7yJC)lkk4m7KS0!{#yUKD9gNN#}CZZyq)dYRV6nbZhNKtG`avUkownDWQ1TMkpfd z$|k-%^r{h%xNIL>q8daodBZ6&Vf-Z~FF1A-KG$mc@nK1t}>26PV! zmbUjMKAzB(NjCDO5Q_K8c|FG|=sePA6MEx%_z*64m+Vf);10;b<#gt2p5yp3b{Dx{ zB?F(I;IDD$dBB-nOoX&2Nb7o#MM`eOtJp^0zf0!1CPOJt2@KH0Q4U7cqYdpMGcT#^ zObX9QD#?BV@iE3TL_U9v=Cf9#J{ZC5&>UP9^6{}jp)*CHq)9y45UTReA^69fDhI4 z9(Vd{=kI%HD}sy@5qf@Qm&kvZ270I8QBVI?HfoV8uey$4#5Wk_Dq)ndVwBIu2tVbN z-WN6I&s(war>&G;9^ACs@oh1H`3I?e(F{Z7iWmMp(dE*i2u zW<(EH9!mpIsnMYUZJJ3t5?vagJU~c2vzfRxQTt`evYk)w%`} z0u;9+pG#LwVOxVdeN4f@6Hmz+^-O9rW;wmGyU8R(yxC#WLn5C8ie*Oa6%wMxoCDY% zGH1ZZVL2%q2`Os{DeKt63o}>G!)D_$T3huDJ=jtTuE*YLx%|SaMwg^g=r2`WGP*Kj z@sYI`qMAzSowMSl^$;4fZ%_d4S^7|=4LV(VpG2WiM%b}M9t?GewSmJgm^Atoaqd>f z>4}$1bdG?hAf|sj^n*!}y9EeMo2di$Znp1-tOAvIixK9~IMl3;l-p)Ignz8aJKaoP zs9zUIs)Hdo^@|Vo&-n2EdQQ_3evT1TbAPJEO24L{z$CQTQX zJ;0E)DHB@KMx2pqL!XI^y!dF#vcrM{ucRG)>ZgC{f*5s?3Fl9sl@LI(KS+pMz9&S!rITDU9$Wk~9 zL7vOm`Z!_hQNq?sY@sh4>w?p#gKRLWn&Q zCZst%orlpT$_(jAV2VIRrI1GSRRot_d1N{mK=w+IY>^-Drv?qp1=&29VmQjq)jm=i zPu1$G^^O35HZWg8&UGhHo4 z%PN&7iviRL+G^^D%%~$9zX<0}ke+Z31!s&_n@tkj=t$P zlfL#uH47zlS2YJ$vey2n{IvkBdMP!GgK@v6r9J9wDXM0Ax*mp!Fb%#@rBhZlk~4Mo zKq?B#CQzW+eyyNAA|!axDdFS)^VRmS2(-mL}cv_Pe6~0c5^Tq zG|s!Dx^4aM|I{s#q#R+&k@ZT3P!JTf%O}b${8J#qvcQgKz~O}Fbv;#WFV6L*p-m`u>Hw_ z|G{T)lMf$)Dn0lRSn0{H!Ie(@daWOtj{j*FzZ%B(OdR;pf$x*pA@2Oegxi>jH*zkeg>1%^MDj3!*UQ1n6udm#243%N;Pr~(WeQ#qZZ?VvYFI%-3{V{A zM&^cc&*q1Tfk(5ywsemn#}8o%I!mO2QD55#OP0FtT$(Zw-WE4fRy(nGhN1L`f_+(N zn(JHHW@cfDc}0#Lx{eKq%4RqY4O1hw0VE=(XdKt8_xpcWcj4dO`jR`^=fSp2FGmwL>&|m|paPzlWvZ|e!jS*)xmgyMVRapG$k-6Y z;<5&rK;}}V<8xspxbqwf`QeQ}_Sg+Y^qXVwZ9HbP&D{_0Xw> z z6o{08w0LYTlYy%i7=x3*X3m|Zk~RxXE36urAxnen<1B?E@?4Z>VvF>PN+90IT~Ur& zOwQ9;k)Sn{pgn|Wf4s(CPB6{&3-s$%MB3f#me9_@c0IbaV-I-R(MMY_wVx35ii3)jv6z0OohP?@m27@<5ci>{{ipU zV`P+lwf%JE0$LT-t=Fe)hL7MP3aXmHt2ffvXKv^rGa*hia>I~WSu;AL*iJMwE)Yc@ z3y@ub>Ft-joptDn?YUIZKRAOajj~q!T3?siuGG`%C=HD_rAR486k1%`o41+%7>@^ N_J29f5V0iB0RSz!S`q*N literal 32200 zcmV($K;yq3iwFofn)Fu!17mM;WiD!S0Mxx}ciXnI0Q&v@3R$bihunr$DQ)_o%;-3_ z(>U=X^&@HP<*iLikj+L06_QHg*#7T#W&jWXAF`Zw?|tuDS+PjsF&GR6^TJ@oo6N;1 zj}zhfx9xexTUnmQqrBbM?_0T-`?o32=PCSNUClegAAmcRt1W)&3l+LXFHbShjH4$$0LyZE)LcrRVF{ z3Tk-QRqyPc+D!QOz6DjXEZW}GqRqy{++gD6LQF(%pDDlQ*B3n9gw}O$!j#UO!7o5i zftlkx<`Xx;0pDBD5w5DFc{jQ&!$z4~r@4|Y5GTe`K{@4D?@nMwM6;UrDn zNxDwPd`d)-@(flki{a-iN~7zHC86kWk;nN>Kc09isjuPhW9@kuL!H?)8o|OB-tD~~ z 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 bs9T=#AWr=t@GgOVK}!A~aah?47{dVot@q>W diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index adf2980c1b2..02afab25c56 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2,4 +2,4 @@ },_distributeDirtyRoots:function(){for(var e,t=this.shadyRoot._dirtyRoots,o=0,i=t.length;o0?~setTimeout(e,t):(this._twiddle.textContent=this._twiddleContent++,this._callbacks.push(e),this._currVal++)},cancel:function(e){if(e<0)clearTimeout(~e);else{var t=e-this._lastVal;if(t>=0){if(!this._callbacks[t])throw"invalid async handle: "+e;this._callbacks[t]=null}}},_atEndOfMicrotask:function(){for(var e=this._callbacks.length,t=0;t \ No newline at end of file +this.currentTarget=t,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},i=window.Element.prototype.animate;window.Element.prototype.animate=function(n,r){var o=i.call(this,n,r);o._cancelHandlers=[],o.oncancel=null;var a=o.cancel;o.cancel=function(){a.call(this);var i=new e(this,null,t()),n=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){n.forEach(function(t){t.call(i.target,i)})},0)};var s=o.addEventListener;o.addEventListener=function(t,e){"function"==typeof e&&"cancel"==t?this._cancelHandlers.push(e):s.call(this,t,e)};var u=o.removeEventListener;return o.removeEventListener=function(t,e){if("cancel"==t){var i=this._cancelHandlers.indexOf(e);i>=0&&this._cancelHandlers.splice(i,1)}else u.call(this,t,e)},o}}}(),function(t){var e=document.documentElement,i=null,n=!1;try{var r=getComputedStyle(e).getPropertyValue("opacity"),o="0"==r?"1":"0";i=e.animate({opacity:[o,o]},{duration:1}),i.currentTime=0,n=getComputedStyle(e).getPropertyValue("opacity")==o}catch(t){}finally{i&&i.cancel()}if(!n){var a=window.Element.prototype.animate;window.Element.prototype.animate=function(e,i){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&e[Symbol.iterator]&&(e=Array.from(e)),Array.isArray(e)||null===e||(e=t.convertToArrayForm(e)),a.call(this,e,i)}}}(c),!function(t,e,i){function n(t){var i=e.timeline;i.currentTime=t,i._discardAnimations(),0==i._animations.length?o=!1:requestAnimationFrame(n)}var r=window.requestAnimationFrame;window.requestAnimationFrame=function(t){return r(function(i){e.timeline._updateAnimationsPromises(),t(i),e.timeline._updateAnimationsPromises()})},e.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},e.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){e.animationsWithPromises=e.animationsWithPromises.filter(function(t){return t._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(t){return"finished"!=t.playState&&"idle"!=t.playState})},_play:function(t){var i=new e.Animation(t,this);return this._animations.push(i),e.restartWebAnimationsNextTick(),i._updatePromises(),i._animation.play(),i._updatePromises(),i},play:function(t){return t&&t.remove(),this._play(t)}};var o=!1;e.restartWebAnimationsNextTick=function(){o||(o=!0,requestAnimationFrame(n))};var a=new e.AnimationTimeline;e.timeline=a;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return a}})}catch(t){}try{window.document.timeline=a}catch(t){}}(c,e,f),function(t,e,i){e.animationsWithPromises=[],e.Animation=function(e,i){if(this.id="",e&&e._id&&(this.id=e._id),this.effect=e,e&&(e._animation=this),!i)throw new Error("Animation with null timeline is not supported");this._timeline=i,this._sequenceNumber=t.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},e.Animation.prototype={_updatePromises:function(){var t=this._oldPlayState,e=this.playState;return this._readyPromise&&e!==t&&("idle"==e?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==t?this._resolveReadyPromise():"pending"==e&&(this._readyPromise=void 0)),this._finishedPromise&&e!==t&&("idle"==e?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==e?this._resolveFinishedPromise():"finished"==t&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var t,i,n,r,o=!!this._animation;o&&(t=this.playbackRate,i=this._paused,n=this.startTime,r=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=e.newUnderlyingAnimationForKeyframeEffect(this.effect),e.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=e.newUnderlyingAnimationForGroup(this.effect),e.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&e.bindAnimationForCustomEffect(this),o&&(1!=t&&(this.playbackRate=t),null!==n?this.startTime=n:null!==r?this.currentTime=r:null!==this._holdTime&&(this.currentTime=this._holdTime),i&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var t=this.effect._timing.delay;this._childAnimations.forEach(function(i){this._arrangeChildren(i,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i.effect))}.bind(this))}},_setExternalAnimation:function(t){if(this.effect&&this._isGroup)for(var e=0;e \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index ac7a962c7130ce2104fd99f55edafb9f15b14ee8..440dc72208e06a4d64e05c77262fbaeb9f475421 100644 GIT binary patch delta 17108 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 M0D?vO4(b%7 delta 17070 zcmV(|K+(V4=m)mw2M8aF2nf3z`B#AmwFm(mvVVt4m7F0dE$S=>DiuDbEqI$Kot=3uh|t4$*e);Mz_iSkujxal zJM=$^@LCl1nac;_mg2xm1Y|uQw2w6|uz$y7HFvVeK;`xvvZn5F2RnNpdzncVC48G? zMwX@nYAWViobZI|if+EWRciXMe6Q zX_H05dW?9L&eIFW9wE|#<{X3G^*?*G?uSQuJ2gI2TSCdiDS*M5d2i)c+%z$JKtjQ? zzc)eQPv(Kl@*1Q%c01YzyLe;ImaP70=^7Isc#Zm)akg16fepi$5$)roO5gK*hR+F!)jq#+4mC<`|0$vzg3>$1XBSKeEqmIZic8{4K98e}L1?bcuJpnEuH~fu(NtR@0m(ynoMha&gN4 zSEN2^XcrFKNw9kL!K{fN6f{-W@J&qbCJxn*5=^bB&ScJ~Mz&ZLm>i^STs)hC?E!?u z8;ZF~ke|ydYy1t3MCBX%NngIKH;JFj&P`b5IH&EzSRhs-Qkk>O5_z|e8$nWx2czO) zro-~0-DFhY<0sMpK_M$nM}IlS>jiPC1OsmCRzgRJh@NDwmv}B9iJl$f55k!}lDPBp^Mq0MC%ZQ?KV{2=n5hrXs zr$-c&KpS8zTZ^pTHP6&v&W9{EYEbN=X3&EMtUUFJ?J_-uwbMKLZhz{BeBs7~h2qh0 zC{OKwTpOU#7+Yba;VQJf!YF`nlOl^ow(xQdVV%Kwq^Oc7B#*rsno;@#RQ0{QBur%e<7H&Cbb z`}2LS-!xS_Evgo4ciKSOrF;`AyZ&rADrFC9I zn~LWd9pOaI9-l;G(y**Le&i2ZPfL9Cl%-4{8=Z*HN0*;loSfQKcAZz^1QnpztMn#k z@~yA;%o(Cy4ech^b-A{Z-CaF$Ei034E}y{xkKqK;F$EYLtbZl8t_1D;ia8zn0P(j)5Ph(&6er@j*9*;R^o!Hg& zK&9uo;R+#&Lz;8pPGQuHDI>xAbW&Kj>cr@NZ0Wi#D zxW_hYj*b1KFJIhiQ#_)eoA9e*D(!=20cl4!)B0-PY=8c$&O2N5`tGCgKpfBQn)qH( znC3jr1o-Tm1QKTwo}=VeWw zCpk)bC+}`XIbPUR1&Sd=!7`d1#%9Ojgnx-e`KD^H1`}sfKAsj6x=`x#wXKlcwCQ4` z_bxP8E+=Ccw?1h7#AV}c7ctwQc*M3gRVhrYv1ZB}i$or1lBeDKCn^B+MgzR+RNs6=B&7p{rPl%|qzC`w>o)n)fIe#(I z6%Ivgi8FfHBn^JB8Lbv{!_8B`>l6>A5-H29T+OO6y?NBrpieWSM4caUy9ebg6jeG0QH}$>$z% zW_dewhJ0gWh^tSZKJ~d}L`b{)j}oN+vFy>pp zM{ua@PcGACRg&{{l`qu2@4dkw6p^AnkTOgWkXfI5R9~;kuQGgY;2bv_Ab((|Cj4uX zqvfB^e_MTmrw(vzjXw=zstc3KI3}A)+;Gm+B%pU`G7VwT$|KG)#h1BDI=HGIW;PQ7 zlEC+J-cY6#ThfU;I!bX9dsBSsoK(paGoLadT9%~v3({tuuW2_a#Ni4E9Z7{jcY-g3 zMl)E)Ak4KtYFDI~;@!>E9e)*vhX`L+X!yE}M7X$X7xVaIUal|#5Qjl~C+2`4FWp)I z;eOwtksu)kJfU~V9Ugz_yOiXhTQmGzyy-G4hr@ZpinhBwHn zOgEX12T2ipY))!0%$phekrnK~=%5j%VoaAXz?jdGO%Q{d<3$CQ|kc*XRxb z%jhR`|15`vfv%Irsw! zR7GpQAvhHHca}XRJl9&MnY{&gLR3GSU(g6T0O|jVqFoF+34i956vD{3q%;Jw)2?#i z)18&4P$Oxp#QX^|F?B)_#8q9It9lJu4SLAxE5I1TP7tI^wpp!z1??#t3vEG|tE04dR47!Q($aKs6@2Ud-T_vK+2c ze#}2SiGisX?|+K_S)iFRCTl+VGQ2C^WFP|}jWhh5&g0YMLzXRG%zr@JK2e-r6B@9( zV~mrl^cw4WF(3BP{YdcLoU_Q6KC?yn+KokL(>`?u zj=vv#6I&mE)#C?{lFe~jdx0#cCqfAIc5s9s7E0*L?SDdeFH*au4Qyg~m<2Z!kYCpR zw1i)3ADP?x$lTmV=EgpfkNs9n@LbyAK=dCTP6R|M5_5`fDHD)dOFmY0j=D}QNvfYW zousG76^kz(js%)UgxO(YVJICqHRs2j+7ev9Ss+3(;)X^7MS!vx)y)%-;;;q} z2B*nt?83G#$FQxLeG}B488zW-b#l2J!U@@s1%F5Zb-9C+1XcsTVUwx{iR9CyK+xA+ zSYq_;Hali~ZJ?OxO)8~%tq?hPGQ+#8?7wH-f6uz#Zr0UMk95Wi%OCSG-~>soY5|;? zbW%+X8th}jk|}^ASm$IO*i5>5PJ%*Tb|&xxk)Fp#h`S_qh~pe~qG7IgARh}$Z$eF5 z1b=F5{eKiWl}O_A72eaHoD34!=-?!Bl+asPeh>BfoH&<+RR((pG>ACWw1Pt-~t zA7fTPT6de{NkF~dAs|i2sbtd^uhlic%GwJOV>*ke0`k-~@PcG3V^PSihN!aASTz=w zx5tZ|FR{?UDpbhBDu5%Aes-_00^FX|KY#d-(iy00vk7-qaiQ=J5;F&s4N~& z%V?~MERJE-6fcI$zbKzwEvnBP`|3H9L1kYRj6JGoJ4c<@3Ju?6cZ*~&6vAB3rf-e< z`!W*+>@0gF>Bc<{&>;|>4Ru*S5HvK_2Gcb}hoB+c6Y2#XqZHsP{-7Y7qHC0hKYwo8 z5AH*A6qqgEHo=u?9lFgCb{?OoS!0CA@=w&MTqO->?l8)M7xeOl%-FG-P9oI?M!pOT z*_MEvfALG`76h)YoaFHqQ$m1fGk3?SOhK>N*@Dk=4iAqM*qSOApnYg-S9Af0VR1IF z&z_u)w6=AS9k0pa1P2gheL6Ya41YUWeOQAeA60BsWlG4VieXLW)4-80#@T;XoS$7~ zDLUKWZB?AxG2t-k$N1oY-Y^s6$xSg=Fs8^n;3KZ0kRS9nI zH31_<^iD4SE?q28BYF4c#b5sN?2Q*Wa)-?!$n|}LLr z(bcGfRwFYcI{dg{xwi0-R-u-7k6xJou4HQ(&9VgJz)mJx zO9(8HhDJBP<5I6d8Z4jYAI}Erw3E+K==a!@o@b3&g+HnM%8mlhXiSu~Z($NfcOtV| zIi!>;xwp9Pc1Ko#OMm%Gda)bNj0R3v&4DE7WqQ4K8qnCGA%@mYkfuqr9U8P@Hnl2JzIXujdp1 zU4GKIJUR;@uAm2nB+q}?V<1S`@195)`ZT{ldSWAP1U3cD(D$-hyjf;e8* zLydFvOMX|VH~IAHgZi~^Vc>rVTx`&yWO^M80W@e)R)3D`o=wZOwRihC34?`Ow*3^Q z)(y$0D#Bf8QAIlYlzuKT$idI~eCsQrO|oc}tDL8cvPC{A4%$>nrogu}%eQB*d!vFb zSJDw2j}jOZA5>TXYPz~EYxPfAVEf^BELKz(#wAYzn}0-` z{Nj2PX_Qj*2jpt!1}@H-gYNj`lwKmI@mYWY2v4>RfMuNVrHkN#UVQ*`QZNgM**_>{<}cxR@# zt}T$yDR7ND4NSzS%ETT$z(7N}c$a%cli;EF0d1Ee4?g}g^He$ydWYjJ!9gh=hMj1N zkbfHE7idHy5%K3ZAKKZ{ad}(~t;{3Y%Y5EZ-4zu=nvCL=UZXp>pEC{Rr1odyv^O6q zcaLfzr)kv|ROBSkX}FG7GAziH^rFig5T zJFbwwy`4!9HM%?LAqL=(od8Xz#v(x-#D7Gb%5^7lcO*?&$_jR#SksBntW?d`^w%}# zb5%1EM3jaQaj*8Ud1F&>=UxpZ22j# zCYPN6ZWV|m5nYR&X4V)0J0^2LEKp161WxZ3pen|>8c{C#VCDL|lLOA>)^|9SeYlHC?EEscV8 z;Pp1AdNm-i%?$R!67PgeMOh-id4JV9uTzDw@riP`>56@s_$6RucdP)$hMc>BoF}s! zlz=yomqPn>)aYLKGV;Ri-mPSuK^T!c<=ns4n zk_R4$XVqI{!UV*HIv76{2Y+*ko5H{3vwZv9!g&@jkP5iM9L$9r;f34{%#myO3T4TT zATR>(PrtmEBaRQmPi+uWPjo2k;#*|-NnC$l7@vAInDU* zug0-)H6zYH1X%iuvD1uCl%6T2=BcCc;56-cYLIb|4j-?>$B1sD-hULF9QBrrW+BMO zUM9?${_6PD$sfx8fBBL>QYs}sswA8hb&Tp%M&8QGTUmLbUR}smS-U&&kqXFwGj2zu zIC4i6bXI{BFGoNs_+HKkxS#OUBnW8)qiBugLWa_rF} zpLf+ELaOwwrZG1WQh)Lx+auisR;$mMyElOiB`fl!YImaOUI7V^b1!IY)` zkPI1*$DmPtUrWqzjEY8K z)CSRDBLGB~#R4zfDPE@$K`2??O#a&{Oh9O- zvLii_`B{|_A2zstV!J|3WD0crG^n6xHms9nWu;XUMt` zs(#$sy(V^(tIw`HHwt--H<{}D(s_$e?w!f$(Hex4Ye3UGb<0^ zks8p(Np2SO_$O)KGGVuJ%-*wo;kGPhrSg@tHsgq*T7oiJq!spsevaeUvO-mgGK(rz z4FXvlKXYemcp#DLpu;x;ckn*}QN$_TxUrBwISMi4*Z*PN%Pl7INieq^Kj zqJMr^>}bJjsXgt)&mYry^b&T3J=h8D-IS4%WQT>f$gd!$w3fINuij#@eYr(#-jrXO z@ZXqY>hR$#VR~0&#;+Flgn)K0a+t5M?TZN+oS}8TYaZ4H&8*QR z$)M_>-RL~Re(h2>j$!v4J0LmQy1<22*SHK?l#_;wEF{IG9JFdwXAz9Di}S ztg7f8*)7>y@v)4JW6ZB{L;i5v_IXKA;8g0;p(QiZpy$=NCSSO1{%*8uR0M5qy^$cR z2ONt~?XXaklLvV4Pijtj){GOljCIrM@bI7sU#aOcYW<3afL6bqj2pJqwi(@9{S026 z*8|0Uxf#WGj7Y7&RQXIdU>yCk#w+aS*=&Js!ewTGI8k_I^=nvdLPYvUwjiYf zBkG+&FgtFBKc&|r8TGo~;z)aVn6}N0Q%zuvx8s1_(y&V0+J>oSOzPq`iC-=2IiBrpXE&pWF zLTZRl-UvN5#v*`u?})DAYe|)>|0Fw28V6nOsm-LzTtK?~69-+<9#X_^*aKUjZS9A> zKR5Tr&I^6A6Re)Os@)BHrGG{7)*k6yzqvO$AM`~35WVmU8680PkgBf`Fvej7jHDgD zhB_Kxwf*Wcn|FN7VoRg#Sc!IchjxfA6U1Tr+P9Pnc2GV8Ni+~)KGVr-^e?MLh@*Qj zx27mix0%nXKib~xp9dzo%>FkF4pG8{DN&MZZI=UKm^>N`l$~+Crhh?_WsM`sX5$+t zjk9f_*cOvxpa&MGq$L*8qD81Lcrhps;rKU9r)fS+mvEFvcnU_Wh!u8DF~_)?SlzpH z5l0N3jK;`$gzf4Vy%zZ0kE_}2vn=S0!CjAzX424&-lXR({C4TVG^S_w`nl^2#zE*E zW@8jN)NPFtw|1X{j(_gNfR^(N>)dm$`9)iqxc4-qkmS03GfLfJvkVy`TMe)5tPOUE zN=B!wX>%L|Z&i{`m#rZrT;5vJjR&#H1wL&&bX9QZ@&sQ}BS}uXK~~qYX#4KT^MN*W z`rDvkD@IkK9w=6py1(hxj`VF(YO|&VxmJN7%^_JC8d#731b+h8dY*U!C{%0PI;{<0 zcSJJDNetU;u(BN}Sz-&|{ac@4E&DWYY&QG1jyu?y_;EulHZ5EQG~X>NhLQSZ2|pk9E+=ikGK~ApIY4oi5V?I0jOuowq6G;7{_WR+iIRc zs%T%K#TnNcc7K@<*=M}DWskCaLi?>a#Hb#8FVi(5qY7+5^)+7p45!hf`?6|d| zYxsTKNi3$>0utGbcD~FrIO}~r!=RS%8`Tf^Cj>v5#M#vf+n%0Xt}^GREPt7%<%jf) zHGlnEel4!D_se{Gkr_F38s$12)#wd!YWq050o=s88GjOn>ohS;P3hO|pTjB#QUawi z9fq?z#?$Yniy*LxGd}{x(I^lEjN-W26T~^K3T$W%qc=faY-m zbq|+YfW|eC2?Tfth){m7-DtDxBk*wj)rkDQc7g6Zlq&&6{Dd7Mp3*x&bc^6M z(DJIdlz$?CemSQ+MVEr3#vk&gnxTt(#ruD#+Q{!IW$!Bd*Myfgi-s2JURpAbgbDIv zXmVkApVqLVoGkOwoJr6C@oI(9E|-Bda#Hn?v=>0xU$Uygu#iGA;NWbDw572tYfRNlxMQmg9pX zL`WbQWr=Lu#duyN#Ylq;^xCZx%dI$8oqaLw@+aMuI1#c0#sW6V&Kj6)3KYa_9*pvb zvaL~m=S~-+vE`>pdU%+McsGZKOYs-lR#jpLsqK!I$F5dC{S2}*p9pATe$TR$4+(M!<5OG1Sh<@2w%Si?b>%fe`*oIi$pGvbOjlSf;)iF6+FM0= zST54JaDaJ|F8Emt{7uRQeXECBmNF0XbALQSE?5JtT&wM1G1hWa%VD+VHah%lmVHbO zi&luBkd--;<(jmv)&d*nT?^GD(c`}bUjGec)GBRE&z`Dwh2L^cv$Rr*xZ;o|q+*F# z(s`a;S7%JNKB0;RcC1BoKvzR+q6QEBQFeS1b5J;FJw64wkN?v7I|fb5nBW02D1R@W zW{+d;a1;+?L3I>!X<}qkQI3nOM@+tbmZQMcIO3>;jKf^O% z@c9cD=&jcjt0te4XDd0&iY>7(na+t1sTdK(ue0zc3?c}274ZgtP{f<5O3GbdNNU%! z*gMo(`L*`V=3^aQ$$WQRNW@3nx zR8StrjEeYy>;@}0McwvzJZ5f7E3t;IHe8W4R(8LA?ZwP%wMA3?g?RpIC_voKdSgZg zvbyIpDVAeOffjQ<^Jx9XA~(ol(_w*fpG`RXmoKPjM0(EP<$$2wJAaYp(gi0MmjNT; zz9>5iXKz^XV|huCSgXy)d0eY*`PO99u5aXG%ESpyH@uNyjE&(PW8C1DW^QxE7|c%c z8XpO#p(7Q|S595wQ^rkyo=;@E=UFwmd_0?VY!AuNp3Mc*zY zE)$OK){AO0q69we-G8|6r)x{|D?- z%%Ey`W*|6W=aCOsjVy?77J9sT%RT|xrF0kH>DJfj=A;UE1%Ge9OE(>-y_@tZ?C}cJ z!B$jO2p8!(vSx>eadCd$&P`XmYv9hDNe|r4!Js?LKmmZkK4?>o*wg%ajtD%-PQ48# zhy8@BWcv;DZ{*p?8Gc(EZlk&2Hf)p>?2ZR$M=K>ox#QD&a?a7R%veS(N2vnlJSzbB2hNb7VFBL1qoIQPNd0{l$aI*>`5KMZL{n zGOXI#*C!*mi{za!nL56WGZDD}D!M9GWrh!*hp=tE#2*x7C!zG!LZ$w)h+$h@O)d$X z8k67QFMk4&7GJXYRHb78DD;+6HTv6^`1le5`74PI>4jDxI`C3Zz=Ij3x~hQyhAZDB z?pT@bQixpox`kX~gw=v7rCV?SpX=#uRU7{D$-R;tdr`i-T!Q3;rK<1{1K-q{8)n&r zU`JtsG4vV*0><$zHCTH1-DS99E0}HqC)utZxqsh)_}ik`uY2f5dM?J|#R{(WI)v*H zD4q24?+*(?w+1y(raT&R(w%}afAy#Tgv_vM83~vdQXnfYa-mHYb9?AS%{{I-QN?>y znmsCmT}lcWibA+7&+*y@d-Vbh>=;HIGbKmKQJ4Jx6aqKZ*I6%zb@A|PBo@>*ltQ_ z!$QYS#QYOeXORNqUd(Y4Pzp@suEc7Wm46v|MCek?l-onjQDc4ry(3+!jtr6k&^n+% zHPb0iA(&8*9>W-Q?mc(_83x_h%g{gi&ddV;AIkLYJ zX-tNha7HymZtsljb?)A~KF8DDh=0jz$v$sH;)pShf6)1iH(a`O?}0cF!74BgLpp== zV9GGGx_Jzft4B}}OGh%hxd*h`OvAa^5jNsnWtGCZJ9=860EB4iTt)>4~!Yn z+=ahk#)w)ri3cH&^*$BLDIDIfgTwIYV?ELGTN|RC2}#wMs)HDfpZT6z9e>mAAqT}N z4pyUR!mR0LwiXs=w{JHO&fZ{*$4z3*i<&&vFog4Lv6-X&R2aD_pVZfbl-ee2Ltyb1 zQq@0))AEZ|osYFyjk(*sHL#f5F|He;jkZ@7s&rGTBt~K_J~YDDNF(nQl8p_m&NPjq zZa2z-j#r(m3yLH@_!744I)7X8JLuNy^Yc#FF5DfqGenD35F?Y$+C7%jogHTUqQAPG zUVk-;_1F#R62g&Q|9S))^NT4K^)tcl5e<(eh?}r45m}-p`tT(0FG0n=fd9Qe<)@EI zHV0qnPzOHbizhnY@y8+;Hx5)I>#VpA=@U{A`we%w~~pN>zLx zRb^%I@0Z1DImy_2Pk+H||4Dnhl558U_g@+i$)@U2{95U z>bCW7&yRNV;=msGw^_C8m>ZP^ClPsx+p89N#Cd4J3Z}Kho2lb>Ee_4e2{Xj;$}@J2$GM&4GVT`_FN8!M9UH{Q}F=`Kj~8-H_1-_Xkocw=`pr_QXveHJu} zJY}yED0I~d9i^e?g!I-IlCf{3+HI^TxETkYu8$9?O3DzwP$A>xYzNd{S+R8LXGkh%F9y2~eO3!;Sy~nB@H;Y3%-)p&a8pzHZ;hexCMQ}-X zhK^xJ`J)9sY@VMaVYI4jBwg`{X-X5{&51uA0AZb@LpwL;$fjp3 zYG60Hg0&{^j=eR(mE6BUCD=Ev<@>#Au~w4k(|3E-q*fW%^6kC7YT*?q2)XzD{&m1S zrAqES`0fVm6>C?}Gq|_kP?;-RP&K&Ux^HL@im+5xv)RhkY3u;Cc{L7=F466NU2}J~ zlYhAzLA}jYEHLE6Gc6Rm0R(YdBB}hgN*hGiId%_5Lr=lCfY5E3Zlmr9H1z7r7vpJI z;AkH%hVZqjSl|e4dVZK@P9=GdeJRDgBXKV}Mo^gir+7CQHh@?(CZ)l!gKBtCTE(oL zYkS|`3eD3iU|=3?nZ1Dw3W?Y-^mtA_rhoBS?`X%${yY?a!8}m)NfFstSuAu*k@u`Z zNodqz4`-{YpvW1IoT>(8Y@4zKpWu*Sse1u*<^#(r{N)Raou=0f#=yXge>i~ud(hSZsegoQ zoJMUoRKs`f_kC%~7zx!$7y4VV{D8imq8S8!%94YDQ?>0v8>}3&Y^{Du&b@&KIG-mb zI;EUCIAFjp8EY3?>&mUlWMK1UYk)Lt317T;cMR{U>j$M}y<) zE-046!Kiky320<(Zd1c@W21-%5P#XA@-JW3n^AdNSmO49HUs{*1O|bfcA4$~-Nx@P zG@QqGbPv(zdU}MPV7@z5cLNB*phCLoys<=PkXi$MBQMckma`6BQbn%C7{&`aLFIpf zKhbhS&;AW#1hY@z__5(?zX$xxpZx&S;2^JQ9JbM>k{}y`J1K09)9k7^j(_jsKX*T6 z?}66-KNc79u!6*Nk>6$FVJj-GKcDf-{=4xV4MTP(KCX%nh@^1GeqK01Z{;)kHl)W( zL+Z%gi~M}_J}tBF?k8^sv%kE4I=g)QKOX<^82|I+(>QK7=T?j}whLY$LXSv9n8CM? z|NP^9>(=Sf!&F@*K~4#La(`J>i*k7PE@DOY5LvCn`>K9k+`V8do*tQZv*}$T2@>x5 zSSXB&7QL7Q16REC82#i1<|oO4T`(!%!2SiSe^u1MQJN#TCudUYuw*nCUEC zUY!Z^DrhNl(9^%#t?rg9Lj%A>8bT-dVo|${k;xsQ1|-67VSoCbW($Bd*5;q3=LXSW{=OcncW4lJ)>ACjTw1y*7aPCaV;RtNBjyrMgDaR zUM7*Qo#5`wi~-dx9V1ZPo9yD*^#a7Ngwma_W-~k@efg61LFR%!+1<1=rRwHwTMee4 zG4xxaGMaoHW1I3Yq-$|93*L@n7()bIhA}q`B(OJdI=<<)(|>s~!PK)-7a6@uvJ-*k ziwk6^XXy>h$lWQ~vCYuT9*4T4vU_tonEe)PV~+H0KEbnCVM5@HF`)V|l!`-9dRvF0pj^(9v<8yEAa)EgWp@Yye4ep`R%>Vk z%C#z=g94lN0)MbN+pzBpap7Xm?W_!Kuiw>>Tm`3LBmZTNo%6Qxy!@#UfHjXe%MQxH zPk|cE3V#YzcO&0?9O?fbTfAJDiiyojb2Vp5x2^M^+Eli!s+JdrJStA<;~e0WHlm zI}V}wMaS|pQw=~K%f(cnkO}glAju9+S+EC~N75bz`-3@TPoiM&Fo)f*sG-N0TlY1p z;Q;0``W|gz3GWF3`Xl8xR6h#gJO+&#@kF5a-n+NBju0><8X$_V?}uZ-%jppA%K4b# ze{UhSUVn%yb{02){BD49q1{fRXBm(v!E6FS%YmK~h;quoZA;i+7o%-2^tqtRn>~%& zF--=_-GgJYp%z>-v$}ES8u?}x^q3Y5^1BDeoY>GoSHd{#r5AR{Q{i6s@X51drr*FV z&+gUN_RI@1R>j!fg?T@wbAmwJgLg9G{SCM#Nq_!&{0ef)2tmFJuPFNNzTBcVZ|wZ3 z2>*>arXlKDp>0O40Q{=Jn_jH%R^XfAe#)rWx6;kkb@Uu6YuQU35d76pySuJpB z)_(w83eDm~Zc$Du{8>mcv%8aI^f;I&>@Xxlvy0crstRChR=;y{d-fz5H{=huZHG`7 z6gZW-bZFTsT)foe7}w;hag7{U?rZ$(vN()J1W1HOttyX!E*lr`EMt|odgTUqh^){ld1z5XLLfE)?+uodDqNj2#bY8Q!5n~4hp)uu$AvpwxYofJfS5~pVCTK>tTg@k{= zH7`bVV2n9OzVK*Mt0z@X@~u8|QV2GaE^`6t@=wcoF0}pKum|?K-P#XZpf>l$&VLJi zvJ?0Y)oqzWSsTMTZx{1b7rnTxlA+_aCrxNDTf%8RmShgQ^muUbch&eK_OVHvAAso^S% zGpx^{Lp@XtDBRYDqn!dNRk8exM}LK7RjySma~T_O^v@n?D#N3VA0E|#SU}X|k<-e* zkq2Z1(=mcY8+EjY;(bI)>n7G0fuZmK@y5Dqi+W-H9E`Mi)5LRt2|wvzV$_SigNC~! z)s^%7I-6=-gprI?J0d8B!D#6MLckytv)M-5NKHa$Jme^R%B#yBx{c#$n16y*EV4PD z**Ob_Ri`ui<-L}nX<;@5qSHpeWv-E5+y_I|VjB%m2IXb(={XPg6A6_}rhw2)h^$0K zohVNfmE`b=PHO0iD6jS?MPx%jQ8Toz-Gm*&%3yJi2LSyeh}-%nROJbn);xsvyu#X? z;kN92+4m$YX3AHI{JSN}Z-2BU%I~J}tZfeWF0F2hEi9@`{E`O;AdF8ciVvKGrqcGH zR(_ezG(+d>=~J~FlStKVQG~fU{#D)AHbJ{g?q_BNI(Ap6db&U^-PO5kgB+Dfx1=g! zS7*=!xB>KN)?-icmW*!V;9nN2+4M)_!>**ba{!15%!0R7I?J}x@_%UyW28q~AoN(E za3{cN!T?UvW*J7gkPk#S(Rg#JZV7{zUS8!_?+|>9x+)=|X9_oR^NLkH8&<>kH}&T{ooo#<`_?bgF)Bqx z+$6mln+FoVtZ4wMZNRjl&2`Vde3>`ym%xMrY0NW7G=G%`;*H$a@R2?SeyAriT(>6S z*x%BJ${W+t2pTs+1NIBA?X3;4-@p4FAK9N7k&*Nbf6%j0`Z4Nky%G-Vh|P@|${M%E ze@hpO+2>BL$9LO3ecRm~x!?Fg+(_rPcuQTqzXXNY`-p`2iL%pOwQ2L7z7I9LVG74g z-Fa$*Lw{{N_zpMB0AZA=YkSn?Q?-R5GDPfbYdx`(DH8_iNU*P{`Z&gvGE$Hz*FW`N z#s~%^xH~&c$t^PWf5tp7;H#3~=5(_*31{x{(^Z0TMZC&AW5t=S^&=T@;J!jcwLBEo zGL9dI{Tp7vh71QttHKb?2d3EVP0e^yYc(z;u-@=-O?X-b>6R~IOM?^~8svPs(!d z-hTj$>@X?n0Fkb$V${1Td#e(aW{y=wQLmx0_aE{q@ZqZXrJ%C#6bbFu^D+=$56eX z4-*`SUTIe%T?L2M^MYx1o~~w<>My@A7=PhG>|%*Q%zC2P?0fvLYWZk0a=X>1=^~q1 z*Y=>X;fDI7K2=4BX0^eZ6S3P&ky&0h;}!vps?a#vI;J}8trV7B>^(360!+fO5vS|> z=s+JDC{d9aWRzd=*LA8d^c`qCoI{3Cm6T>5X{MLTnST&{-#X-}+SiN)!`cek;(vM+ z+7xe>@Wo>ESb`B)4v1kqXV|Si;&7m;z}leZDag}LCZ06Q)t9iA>QH$t;tWSwynY8I zaoN+3u@t^nKpW`PlN0&3XxNx4KrhtA@K!clFHJ*9C1!du6LDp9D`UPUDQy#kTm)#@ zF!Y4QqIA>&>r^l2Yy9Xf)hUke7JpCJ4|lt_uS6t68p{rm>SIv1=^*PHxK>t6e3(h` zPq%bd6bMGUt?98SLK99IE&@IGt_OD*DPHVK?-oB>3|w}dlOYKPuw8F{DlVeetE!bzeWbS)Lh zE&dYG^&D?{z4t3mdKt8wH5Io&8Opr7{_x7jEmiJMgOiR^!jEV6Aw2H z?tf<(h-u0WdlbM_L}pzXDaQ4qhH~Yx@S}ObSpg0S2`CTmi{iuZULyP_hTp=B8K0a= zkQttN{SVpa5`F$)xqm%NCzl=TF~&)D${Nq%;hcKe!KCh{n_!Tjr4ci#Lc_Iq7vEQ> z{j)ht95~)bSyGJWp>#=2et1|MXD4pi(_VJsmOCBh$GxLraeOqan7T)E{#U0wlqryL zfaIDBY3N@pb2O|@Y;s)$JNE}D!Fy~&s#t>neC!BROyScFTz`L%hVW4-fKD7h%E|DS4c*=fswB$-LLYvLZMac8=ynC+|vg5FT4<-~uPJqRdFei@Q>8 zP;sk6`FThC6${xysZu{h7x%6hM(s@?u^=u$&BS2i%A}+Pf;20Ml&sIkNkk$_s(!hc z>tyO&6&7{J6Zd{9=*J|eqJD~qAi5Lkft z)Kl*yWbu!T-9<+j(Av@&`7M{?3W+t{79FDjAQ3B_nZ;kp%D-IUz}ab;?E>h{{U zYnJnoxNp3h)>Cmu6*(XeZmKJX^j@hH4mOD~#yh$}Y}bE*jmv)Hli`4@xxACo+DwH} zE2@$+y1R!D!!{~ z3pXh25T>_kGvFpFDyJ;6_C$I@%4$q6hAr)(I_8f?O%FxAVJe`Wssb(8E-mIL+ABV0 zFXnmmvb=wg6pLFjSWCYwXK&HNP^;*MK(6)hI6xPV%x}}IyjbYE?Q3qb$~0}j);8N< zrkjzkHOp{+#fHSSpUV;q4Q(A5jd9pAJnDPWi+4GaVdBY#yVsE?4S(vOR?H6L30V1J%{wYMOP6TEp;S~;vlYIKOY>*i+iO-O$Vqu?gt%I(v??s1#rYM~}w(MLyE zeMEh#>e+~fg0*S@)miAX=SL;H_v^;RaENjt%?ksUyf7ntxPx9>1oT{@^2Nl8roso* zV(i_FYq}hdVm`L8m&NmC24^066qHZyUQP2}d`B0*6ZbExt67{BiC2BNxXNF2@zHMB dr(PHJ!d?3Bi|J=vhET_&{|82Ow|avB2ml^kT5A9R diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index db109f5dda0..75d760fc3e6 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit db109f5dda043182a7e9647b161851e83be9b91e +Subproject commit 75d760fc3e6f37485ae535a5476c8b00ecd2dfec diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html index d6f91e28853..53c28a1109f 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz index d397aee1a1ebc25940e04a298a1b4cc227bb760f..4c211bbe30dc4744a427eb99bc2804fa36efa92d 100644 GIT binary patch literal 2811 zcmVfzds)3_5SnO@p2*BcKcu8zn-1OtN7DGdL67Y|aP#5>nbI%CffyNd<( ziQUyams_@+=RmUMd<&9z>zs%RVEa6cS0mS!6g(s9m<_kYbCiri~_aHv3 z`L^T*6tbF(@0{Qf61F8H5|;By9vCdsoL92RXob*2N()8{h*ylSA7&-5sHD6|1>g?4 zgV~;DGEcvpPRqyHii-?HdR;zx6=yV?l_bk(ai9JXktoG4F)2$Rf)r~=yBo~nJ**z6 z9L4b+hz;ZW)M%(pwp0i%^!1w^t>}s}Di0Pt+v5e|y&z?}!rc$CTDQrD%I2@gogIQW zMv-_eV}ylmcT96G7SbrO(`hGEfohxysL(Fvdqvg{sLHy?P(?WiDralXxae0#GuPAU zA7(kw`&^pu4KHM@*>r>&vLRc_4n0ik+-~b&>bfyu^uFjpy;$Sc4cjzbBX@KFuY=*< z9Wu$96@QGYoMfn7B~Y)8H0>-xixO3ZUa*FoGt@%!k@H;Iqk2<8tKTZx zd}2aHN3V)YXkxionWpp8~c?)Il^nC zvsP%lOU4U!(70}45I@&cKxPqUp%9o|$ckA+x6nKI-ds(6D8LpI5ES<^*U)~YQRLA@ zbt^<+j_s7Rv|64V)7<2kR59nR1ufUE+=44O!>7zl~6N|9E2C z^qFn0ciFw>7Iv@CHycZ)-OSRw=9~d3EYZbv8};5c;L=8pqn%(nH#-9xCmWuG0(Oow z5K#+025%!qE6kgKwr=$0PHCY7=*6Ia99}x0M;$%p7viEI~{TDlts*S4iGa+oAKWzag`_y^Q?LxfTvoqcg z>836;94|>X!G+$}nd|b>_ZJHdGWv7HurJo);ySvR&UQq2>M1_rb5hw5<01%O!r~*r zmvmQ3)`YP@@razG(*PEyLg^h?X_${_cr6GGV1tqbCV80mr8~zDBUHp$+`1akQki4> z>IW__12C7mu*&IK98o8TUw@r0PKjR*m#OcEj=(3xk(J%NVJIaA!8bp?|JIOw&|5I$ zz1QeqAiClaYl3*)rKZLN`S6T33{f98o{nnhys4*KK%$D;m*&sy^v>Kkg#19eI?MMsF+(Hqf#0eHR z*q56&AH%a%V3%5T9jZTdQKv6uyJfxSLPPXn=SBAIt;NnK`eQjCuj@=>tzY)84tI5E zF~{7t@SDaujdRncv7ZY1q+tK2`uwa9_}vTk|EUZ&;*yd|RLn1qz&&WZv2x9+E{P%gCfyS^k~~M()J9ymk%l^ zb#6fKe|;GAFhkUMBcqAF0(JYBiw@|DEnSC}&c zU#BHz*{lwJ1mU?{zlnL)@#(m$p5TK{8Y=5p1D!PnL&k&W&rT(Vk6=#Xk`(xUWUz}X z)v2C8Y?z_etj<7FYlv^(y5q1cHbb%(o6TmX1~D}nG&U_mN((9}Vf{pK6sKlDBZxM! zgRZ6@I(}I`wh2P0@n>m{|5qNMm?o1sKF}Dd?7ngE`L`LXonJ)kqnY zV#b>uc0@6nlQV{zhy~oESLt!`k<{DJN631h#722VFo&S&&|&HrT}<)x5_*R@7-6@XeK^RIy%U z4A3gw)d{VPV<}5%V8^NR3S<2IdikanLJg-oherpx#=p%k~BC1+_eb48yN98|F zH?Yz8b*9~QXixD<2y%#2JM&$=Mp|doAiFBzR$52<8%QiZE4zA6%IKWfvtyVW_&LDd zA3N38hhUVfPlX|N>-ru+*xt2RZVml?a3eeYNK~)GEDJMw=@q?zp0yn{XdNP0|8-$U zg)LMzL;|H&x;X@p98+af)Qm;G=cz$ig71uKw7-H4sTr11Z8Xezr?VP4lj6|WLDT86 zFZHpd`bjt#B?!}|ZShH3=Z@}noMzadqt^!hTh)4aiUZ!K2gE_UswHaJpgL;_gZfp1OfsF)VFu3evyP N{|CjA2mGoZ0059vdEx*7 literal 2786 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 \ No newline at end of file +case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return this.addPointerListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addPointerListenerStart:function(t,i,n,s){var a="_leaflet_",r=this._pointers,h=function(t){"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&o.DomEvent.preventDefault(t);for(var e=!1,i=0;i1))&&(this._moved||(o.DomUtil.addClass(e._mapPane,"leaflet-touching"),e.fire("movestart").fire("zoomstart"),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),o.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e),n=t.getScaleZoom(this._scale);t._animateZoom(i,n,this._startCenter,this._scale,this._delta,!1,!0)},_onTouchEnd:function(){if(!this._moved||!this._zooming)return void(this._zooming=!1);var t=this._map;this._zooming=!1,o.DomUtil.removeClass(t._mapPane,"leaflet-touching"),o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),n=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r),l=t.getZoomScale(h)/this._scale;t._animateZoom(n,h,i,l)},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),o.DomEvent.on(e,"touchmove",this._onMove,this).on(e,"touchend",this._onUp,this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,"touchmove",this._onMove,this).off(e,"touchend",this._onUp,this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.pointer&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._moved=!1},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown),this._moved=!1},moved:function(){return this._moved},_onMouseDown:function(t){return this._moved=!1,!(!t.shiftKey||1!==t.which&&1!==t.button)&&(o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),void o.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).on(e,"keydown",this._onKeyDown,this))},_onMouseMove:function(t){this._moved||(this._box=o.DomUtil.create("div","leaflet-zoom-box",this._pane),o.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",this._map.fire("boxzoomstart"));var e=this._startLayerPoint,i=this._box,n=this._map.mouseEventToLayerPoint(t),s=n.subtract(e),a=new o.Point(Math.min(n.x,e.x),Math.min(n.y,e.y));o.DomUtil.setPosition(i,a),this._moved=!0,i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_finish:function(){this._moved&&(this._pane.removeChild(this._box),this._container.style.cursor=""),o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp).off(e,"keydown",this._onKeyDown)},_onMouseUp:function(t){this._finish();var e=this._map,i=e.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(i)){var n=new o.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i));e.fitBounds(n),e.fire("boxzoomend",{boxZoomBounds:n})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),o.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;o.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){o.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){o.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(e in this._panKeys){if(i._panAnim&&i._panAnim._inProgress)return;i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds)}else{if(!(e in this._zoomKeys))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}o.DomEvent.stop(t)}}),o.Map.addInitHook("addHandler","keyboard",o.Map.Keyboard),o.Handler.MarkerDrag=o.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new o.Draggable(t,t)),this._draggable.on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this),this._draggable.enable(),o.DomUtil.addClass(this._marker._icon,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off("dragstart",this._onDragStart,this).off("drag",this._onDrag,this).off("dragend",this._onDragEnd,this),this._draggable.disable(),o.DomUtil.removeClass(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=o.DomUtil.getPosition(t._icon),n=t._map.layerPointToLatLng(i);e&&o.DomUtil.setPosition(e,i),t._latlng=n,t.fire("move",{latlng:n}).fire("drag")},_onDragEnd:function(t){this._marker.fire("moveend").fire("dragend",t)}}),o.Control=o.Class.extend({options:{position:"topright"},initialize:function(t){o.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return o.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this},_refocusOnMap:function(){this._map&&this._map.getContainer().focus()}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",a,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){this._container.removeChild(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar");return this._map=t,this._zoomInButton=this._createButton(this.options.zoomInText,this.options.zoomInTitle,e+"-in",i,this._zoomIn,this),this._zoomOutButton=this._createButton(this.options.zoomOutText,this.options.zoomOutTitle,e+"-out",i,this._zoomOut,this),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,n,s,a){var r=o.DomUtil.create("a",i,n);r.innerHTML=t,r.href="#",r.title=e;var h=o.DomEvent.stopPropagation;return o.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",o.DomEvent.preventDefault).on(r,"click",s,a).on(r,"click",this._refocusOnMap,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&o.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):void 0},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):void 0},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new o.Control.Attribution).addTo(this))}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e,i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){o.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange,this).off("layerremove",this._onLayerChange,this)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=o.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=o.DomUtil.create("div",t);e.setAttribute("aria-haspopup",!0),o.Browser.touch?o.DomEvent.on(e,"click",o.DomEvent.stopPropagation):o.DomEvent.disableClickPropagation(e).disableScrollPropagation(e);var i=this._form=o.DomUtil.create("form",t+"-list");if(this.options.collapsed){o.Browser.android||o.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var n=this._layersLink=o.DomUtil.create("a",t+"-toggle",e);n.href="#",n.title="Layers",o.Browser.touch?o.DomEvent.on(n,"click",o.DomEvent.stop).on(n,"click",this._expand,this):o.DomEvent.on(n,"focus",this._expand,this),o.DomEvent.on(i,"click",function(){setTimeout(o.bind(this._onInputClick,this),0)},this),this._map.on("click",this._collapse,this)}else this._expand();this._baseLayersList=o.DomUtil.create("div",t+"-base",i),this._separator=o.DomUtil.create("div",t+"-separator",i),this._overlaysList=o.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var n=o.stamp(t);this._layers[n]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t,e,i=!1,n=!1;for(t in this._layers)e=this._layers[t],this._addItem(e),n=n||e.overlay,i=i||!e.overlay;this._separator.style.display=n&&i?"":"none"}},_onLayerChange:function(t){var e=this._layers[o.stamp(t.layer)];if(e){this._handlingClick||this._update();var i=e.overlay?"layeradd"===t.type?"overlayadd":"overlayremove":"layeradd"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)}},_createRadioElement:function(t,i){var n='t;t++)e=n[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?this._map.addLayer(i.layer):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);this._handlingClick=!1,this._refocusOnMap()},_expand:function(){o.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)},o.PosAnimation=o.Class.extend({includes:o.Mixin.Events,run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._newPos=e,this.fire("start"),t.style[o.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(n||.5)+",1)",o.DomEvent.on(t,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),o.DomUtil.setPosition(t,e),o.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(o.bind(this._onStep,this),50)},stop:function(){this._inProgress&&(o.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),o.Util.falseFn(this._el.offsetWidth))},_onStep:function(){var t=this._getPos();return t?(this._el._leaflet_pos=t,void this.fire("step")):void this._onTransitionEnd()},_transformRe:/([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,_getPos:function(){var e,i,n,s=this._el,a=t.getComputedStyle(s);if(o.Browser.any3d){if(n=a[o.DomUtil.TRANSFORM].match(this._transformRe),!n)return;e=parseFloat(n[1]),i=parseFloat(n[2])}else e=parseFloat(a.left),i=parseFloat(a.top);return new o.Point(e,i,!0)},_onTransitionEnd:function(){o.DomEvent.off(this._el,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[o.DomUtil.TRANSITION]="",this._el._leaflet_pos=this._newPos,clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),o.Map.include({setView:function(t,e,n){if(e=e===i?this._zoom:this._limitZoom(e),t=this._limitCenter(o.latLng(t),e,this.options.maxBounds),n=n||{},this._panAnim&&this._panAnim.stop(),this._loaded&&!n.reset&&n!==!0){n.animate!==i&&(n.zoom=o.extend({animate:n.animate},n.zoom),n.pan=o.extend({animate:n.animate},n.pan));var s=this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan);if(s)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e){if(t=o.point(t).round(),e=e||{},!t.x&&!t.y)return this;if(this._panAnim||(this._panAnim=new o.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),e.animate!==!1){o.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var i=this._getMapPanePos().subtract(t);this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return!((e&&e.animate)!==!0&&!this.getSize().contains(i))&&(this.panBy(i,e),!0)}}),o.PosAnimation=o.DomUtil.TRANSITION?o.PosAnimation:o.PosAnimation.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));o.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){o.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),o.Map.mergeOptions({zoomAnimation:!0,zoomAnimationThreshold:4}),o.DomUtil.TRANSITION&&o.Map.addInitHook(function(){this._zoomAnimated=this.options.zoomAnimation&&o.DomUtil.TRANSITION&&o.Browser.any3d&&!o.Browser.android23&&!o.Browser.mobileOpera,this._zoomAnimated&&o.DomEvent.on(this._mapPane,o.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),o.Map.include(o.DomUtil.TRANSITION?{_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n),s=this._getCenterLayerPoint()._add(o);return!(i.animate!==!0&&!this.getSize().contains(o))&&(this.fire("movestart").fire("zoomstart"),this._animateZoom(t,e,s,n,null,!0),!0)},_animateZoom:function(t,e,i,n,s,a,r){r||(this._animatingZoom=!0),o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this._animateToCenter=t,this._animateToZoom=e,o.Draggable&&(o.Draggable._disabled=!0),o.Util.requestAnimFrame(function(){this.fire("zoomanim",{center:t,zoom:e,origin:i,scale:n,delta:s,backwards:a}),setTimeout(o.bind(this._onZoomTransitionEnd,this),250)},this)},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._animatingZoom=!1,o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),o.Util.requestAnimFrame(function(){this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),o.Draggable&&(o.Draggable._disabled=!1)},this))}}:{}),o.TileLayer.include({_animateZoom:function(t){this._animating||(this._animating=!0,this._prepareBgBuffer());var e=this._bgBuffer,i=o.DomUtil.TRANSFORM,n=t.delta?o.DomUtil.getTranslateString(t.delta):e.style[i],s=o.DomUtil.getScaleString(t.scale,t.origin);e.style[i]=t.backwards?s+" "+n:n+" "+s},_endZoomAnim:function(){var t=this._tileContainer,e=this._bgBuffer;t.style.visibility="",t.parentNode.appendChild(t),o.Util.falseFn(e.offsetWidth);var i=this._map.getZoom();(i>this.options.maxZoom||i.5&&.5>n?(t.style.visibility="hidden",void this._stopLoadingImages(t)):(e.style.visibility="hidden",e.style[o.DomUtil.TRANSFORM]="",this._tileContainer=e,e=this._bgBuffer=t,this._stopLoadingImages(e),void clearTimeout(this._clearBgBufferTimer))},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,n,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)n=s[e],n.complete||(n.onload=o.Util.falseFn,n.onerror=o.Util.falseFn,n.src=o.Util.emptyImageUrl,n.parentNode.removeChild(n))}}),o.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locateOptions=o.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=180*t.coords.accuracy/40075017,a=s/Math.cos(o.LatLng.DEG_TO_RAD*e),r=o.latLngBounds([e-s,i-a],[e+s,i+a]),h=this._locateOptions;if(h.setView){var l=Math.min(this.getBoundsZoom(r),h.maxZoom);this.setView(n,l)}var u={latlng:n,bounds:r,timestamp:t.timestamp};for(var c in t.coords)"number"==typeof t.coords[c]&&(u[c]=t.coords[c]);this.fire("locationfound",u)}})}(window,document)- \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz index f62bdb183b144b81bf9b08b56746c1a1b78c4603..faab27155876979382593dc3bc5197cf26993c96 100644 GIT binary patch delta 35949 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�=i9{n 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 delta 35956 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 delta 2266 zcmV<02qpKI66FyHABzYGyd3#gkq9M!NPt!~nOyFsO`EsL%jAQb%YeW_VnUHB3EAh` z{_kCok}O%0b2pu_O&SDt7tg*t3plG52CZodX`G2(Rnio7HE6-S)h~pcgZk8ztqa~w z17_7!0|>sub?bV8nM37n7VJ$}h~@DQtERm3?oKbQ;A-#z436Nmtu0k;3SCHlVgLE{ z5-RgTlw~gDcNcu)-RPo$cdK^39la6Vx^olsFTN5+;D5Vv0z2j#F?%?UL%z9Q!a3ae zN0tQNuFQ^U2f90p6qH~9_q$o)htuiwZT=0U>Cx*yc|i%)%*=;0cKvSbLZXL4<8+W4njfGo?CSP(Z9Pl z&WpM-h~?-bZq_+>#By1#H#+{-)|KNwU!jnu_-{9M?)(g*fo9;GnLE$VYju78&kt|k z1U7Q28Cuah?NT&@WdqVi4rC95&rKq56#|kb-V0}JHk}%giiD@EfLL){64HZf9h-HZg!>B_6^`seNGD_k+ z$*B?v=L#2wLI`|BMAOrHib~fbkSU32m`Y#}g(`~~DvM*4Bx#gLE;ed7L|jmu%(P0g zG|p*4LYxLTDa6s!YFcQ2E(-}G$udT1!XcM&Oi84&gk}t~EN46cvfWTDl7!?0M4}bX zQ>p-1P6V!0nbry((PV^y5zQfuSk5S7H5y6CrBJ>#jk*wFCiq&E*OaqC5V*E0a$w#+soYk~|kWAz7+LPB1>W0#4(#qu0_3TertrT-uir$)ex!H&|9P zltW3Dg>lY*5h655U{T>A?hug=2_Y;wDJQ@`xIV<{ljKS;)CzW6kQ4Mg*l3y$UD)XM1vIeS|AT1^-M3uzpc#T z>-dW=l==k;+0y6r%SyrT)B7I7^0Fl-F|{=K>HRC@t4ZH>AUz@5ieM?syh7fMR=Ys_slKYcet0&b97~i zD1&-{PoZ7gsKB}{@p@yQgp+;XjJL&PvTf~u+Ij)US~{`bTh|@e#f zD#X+&u#d%d)bV#MgWyUO=2K;g((QK82AJQKyDb!_4+e+Fkq1+^9vZxB>P68)fa=}n zO(U)Yq~V480L|c1lq zofhpMVY@slDhGk-ehyALJNrS{+V92kGa}LlgI(aCfYY91F40Z!9JuCz6Oz5jy# zh_nU~Gv#Dua_`sR79L|FZ(^noz0ikIS3s*NrUjBx-;+(A;K{DC1T#ILE!T2~e@a*{ zO-_-vIw5j6bSmr_W;Q_f_lT4*93E7C_rm$jb)P+Pd!oO^ljN-}3FYnjX!7-{ZoWf< zRDZh=RiUjcdnUwW@&5W3>=E{VAn90gTVmvc60=(}PVUGz_6XcSi`1n)7iRwF7dNEMU;v)WSg5c=r9Nbq2PY3_6p} z2S2@ix{n2N*ZP3I9qz*Qbi6KC?fghuPx{k7LW{M0eUH%E#PD~s`AExuG_SAT>|9#7 zNLYm_EvX)YcX)Hs-F!G0i%?vGa@H3l_D*DdhhiW0V&wiV#Ct3ockfRyi|ysa;Z{%I z?>OBWYY5nl@7>18vTngW817L!GHBhihV$sRJ_dIx<+|u}f%P2A+ZvoJOyc^=Id+TH zbuE51!C1kJ2q)rin^tN`BF@e{tV0ztvOq?*3NcktrK4-PYOyj5s!esQf^N+9Cb(i_ ohRuY}o$AfB!!Pcia|hzL?~X?ufwMb5+ Date: Fri, 14 Oct 2016 03:06:04 -0400 Subject: [PATCH 060/147] add arwn sensor platform (#3846) This adds a sensor component that builds sensors based on the arwn project (https://github.com/sdague/arwn). This uses a 433mhz receiver to collect weather data and publish it over mqtt in a well defined schema, which home-assistant can display as sensors. --- .coveragerc | 1 + homeassistant/components/sensor/arwn.py | 126 ++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 homeassistant/components/sensor/arwn.py diff --git a/.coveragerc b/.coveragerc index 85a3cfb78ce..a5fcdf7f9c1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -218,6 +218,7 @@ omit = homeassistant/components/openalpr.py homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/sensor/arest.py + homeassistant/components/sensor/arwn.py homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bom.py homeassistant/components/sensor/coinmarketcap.py diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/sensor/arwn.py new file mode 100644 index 00000000000..5eb95ba16d1 --- /dev/null +++ b/homeassistant/components/sensor/arwn.py @@ -0,0 +1,126 @@ +"""Support for collecting data from the ARWN project. + +For more details about this platform, please refer to the +documentation at https://home-assistant.io/components/sensor.arwn/ + +""" +import json +import logging +from homeassistant.helpers.entity import Entity +import homeassistant.components.mqtt as mqtt +from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS) +from homeassistant.util import slugify + +DEPENDENCIES = ['mqtt'] + +DOMAIN = "arwn" +TOPIC = 'arwn/#' +SENSORS = {} + +_LOGGER = logging.getLogger(__name__) + + +def discover_sensors(topic, payload): + """Given a topic, dynamically create the right sensor type.""" + parts = topic.split('/') + unit = payload.get('units', '') + domain = parts[1] + if domain == "temperature": + name = parts[2] + if unit == "F": + unit = TEMP_FAHRENHEIT + else: + unit = TEMP_CELSIUS + return (ArwnSensor(name, 'temp', unit),) + if domain == "barometer": + return (ArwnSensor("Barometer", 'pressure', unit),) + if domain == "wind": + return (ArwnSensor("Wind Speed", 'speed', unit), + ArwnSensor("Wind Gust", 'gust', unit), + ArwnSensor("Wind Direction", 'direction', '°')) + + +def _slug(name): + return "sensor.arwn_%s" % slugify(name) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the ARWN platform.""" + def sensor_event_received(topic, payload, qos): + """Process events as sensors. + + When a new event on our topic (arwn/#) is received we map it + into a known kind of sensor based on topic name. If we've + never seen this before, we keep this sensor around in a global + cache. If we have seen it before, we update the values of the + existing sensor. Either way, we push an ha state update at the + end for the new event we've seen. + + This lets us dynamically incorporate sensors without any + configuration on our side. + """ + event = json.loads(payload) + sensors = discover_sensors(topic, event) + if not sensors: + return + + if 'timestamp' in event: + del event['timestamp'] + + for sensor in sensors: + if sensor.name not in SENSORS: + sensor.hass = hass + sensor.set_event(event) + SENSORS[sensor.name] = sensor + _LOGGER.debug("Registering new sensor %(name)s => %(event)s", + dict(name=sensor.name, event=event)) + add_devices((sensor,)) + else: + SENSORS[sensor.name].set_event(event) + SENSORS[sensor.name].update_ha_state() + + mqtt.subscribe(hass, TOPIC, sensor_event_received, 0) + return True + + +class ArwnSensor(Entity): + """Represents an ARWN sensor.""" + + def __init__(self, name, state_key, units): + """Initialize the sensor.""" + self.hass = None + self.entity_id = _slug(name) + self._name = name + self._state_key = state_key + self.event = {} + self._unit_of_measurement = units + + def set_event(self, event): + """Update the sensor with the most recent event.""" + self.event = {} + self.event.update(event) + + @property + def state(self): + """Return the state of the device.""" + return self.event.get(self._state_key, None) + + @property + def name(self): + """Get the name of the sensor.""" + return self._name + + @property + def state_attributes(self): + """Return all the state attributes.""" + return self.event + + @property + def unit_of_measurement(self): + """Unit this state is expressed in.""" + return self._unit_of_measurement + + @property + def should_poll(self): + """Should we poll.""" + return False From a5b756e1e5ac99e4c8d82b5f795b8e020da214a5 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Fri, 14 Oct 2016 03:06:53 -0400 Subject: [PATCH 061/147] Bump proliphix library to 0.4.0 (#3855) There was a bug in setback setting which is now fixed in 0.4.0. This ensures that any users have working setback code. --- homeassistant/components/climate/proliphix.py | 2 +- homeassistant/components/thermostat/proliphix.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index 521ef7d58e6..6aeee6e537c 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -12,7 +12,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['proliphix==0.3.1'] +REQUIREMENTS = ['proliphix==0.4.0'] ATTR_FAN = 'fan' diff --git a/homeassistant/components/thermostat/proliphix.py b/homeassistant/components/thermostat/proliphix.py index e54a5a4aa11..f92407b0d16 100644 --- a/homeassistant/components/thermostat/proliphix.py +++ b/homeassistant/components/thermostat/proliphix.py @@ -9,7 +9,7 @@ from homeassistant.components.thermostat import ( from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT) -REQUIREMENTS = ['proliphix==0.3.1'] +REQUIREMENTS = ['proliphix==0.4.0'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/requirements_all.txt b/requirements_all.txt index 390d9a17297..3987574ca90 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -293,7 +293,7 @@ pmsensor==0.3 # homeassistant.components.climate.proliphix # homeassistant.components.thermostat.proliphix -proliphix==0.3.1 +proliphix==0.4.0 # homeassistant.components.sensor.systemmonitor psutil==4.3.1 From 6ca0d4cd1461d101df8a2ae11f21763d552056f1 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Fri, 14 Oct 2016 09:09:24 +0200 Subject: [PATCH 062/147] Use pass instead of return None (#3856) --- homeassistant/components/climate/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6c6d493084c..7652c5db214 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -368,7 +368,10 @@ class ClimateDevice(Entity): @property def state(self): """Return the current state.""" - return self.current_operation or STATE_UNKNOWN + if self.current_operation: + return self.current_operation + else: + return STATE_UNKNOWN @property def state_attributes(self): @@ -398,17 +401,20 @@ class ClimateDevice(Entity): fan_mode = self.current_fan_mode if fan_mode is not None: data[ATTR_FAN_MODE] = fan_mode - data[ATTR_FAN_LIST] = self.fan_list + if self.fan_list: + data[ATTR_FAN_LIST] = self.fan_list operation_mode = self.current_operation if operation_mode is not None: data[ATTR_OPERATION_MODE] = operation_mode - data[ATTR_OPERATION_LIST] = self.operation_list + if self.operation_list: + data[ATTR_OPERATION_LIST] = self.operation_list swing_mode = self.current_swing_mode if swing_mode is not None: data[ATTR_SWING_MODE] = swing_mode - data[ATTR_SWING_LIST] = self.swing_list + if self.swing_list: + data[ATTR_SWING_LIST] = self.swing_list is_away = self.is_away_mode_on if is_away is not None: From 6951b6f60bb09680030b2719247da21bd997fcf4 Mon Sep 17 00:00:00 2001 From: Igor Shults Date: Fri, 14 Oct 2016 02:09:52 -0500 Subject: [PATCH 063/147] Allow any positive integer for Z-Wave polling intensity (#3859) --- homeassistant/components/zwave/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 homeassistant/components/zwave/__init__.py diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py old mode 100644 new mode 100755 index 954a8331b56..75c2546795f --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -133,7 +133,7 @@ SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({ CUSTOMIZE_SCHEMA = vol.Schema({ vol.Optional(CONF_POLLING_INTENSITY): - vol.All(cv.positive_int, vol.In([0, 1, 2, 3, 4, 5])), + vol.All(cv.positive_int), }) CONFIG_SCHEMA = vol.Schema({ From 7697cdef0a09899ad12ef801949d9720bf35660e Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Fri, 14 Oct 2016 09:11:48 +0200 Subject: [PATCH 064/147] Pushbullet push an url note if an url is provided inside data (#3758) --- homeassistant/components/notify/pushbullet.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 71b3f227e9b..3fe6492525b 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -9,14 +9,15 @@ import logging import voluptuous as vol from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, - BaseNotificationService) + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pushbullet.py==0.10.0'] +ATTR_URL = 'url' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, @@ -40,7 +41,7 @@ def get_service(hass, config): return PushBulletNotificationService(pushbullet) -# pylint: disable=too-few-public-methods +# pylint: disable=too-few-public-methods, too-many-branches class PushBulletNotificationService(BaseNotificationService): """Implement the notification service for Pushbullet.""" @@ -79,11 +80,18 @@ class PushBulletNotificationService(BaseNotificationService): """ targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + data = kwargs.get(ATTR_DATA) + url = None + if data: + url = data.get(ATTR_URL, None) refreshed = False if not targets: # Backward compatebility, notify all devices in own account - self.pushbullet.push_note(title, message) + if url: + self.pushbullet.push_link(title, url, body=message) + else: + self.pushbullet.push_note(title, message) _LOGGER.info('Sent notification to self') return @@ -98,7 +106,11 @@ class PushBulletNotificationService(BaseNotificationService): # Target is email, send directly, don't use a target object # This also seems works to send to all devices in own account if ttype == 'email': - self.pushbullet.push_note(title, message, email=tname) + if url: + self.pushbullet.push_link(title, url, + body=message, email=tname) + else: + self.pushbullet.push_note(title, message, email=tname) _LOGGER.info('Sent notification to email %s', tname) continue @@ -117,7 +129,11 @@ class PushBulletNotificationService(BaseNotificationService): # Attempt push_note on a dict value. Keys are types & target # name. Dict pbtargets has all *actual* targets. try: - self.pbtargets[ttype][tname].push_note(title, message) + if url: + self.pbtargets[ttype][tname].push_link(title, url, + body=message) + else: + self.pbtargets[ttype][tname].push_note(title, message) _LOGGER.info('Sent notification to %s/%s', ttype, tname) except KeyError: _LOGGER.error('No such target: %s/%s', ttype, tname) From df3e904fe76ffe255b2ff88b105f50fc71c3e06f Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 14 Oct 2016 11:17:03 +0200 Subject: [PATCH 065/147] update flux led library --- homeassistant/components/light/flux_led.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 035307f5678..ce84072b5bb 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -18,7 +18,7 @@ from homeassistant.components.light import ( import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.7.zip' - '#flux_led==0.7'] + '#flux_led==0.8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 3987574ca90..44c60f30d48 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -139,7 +139,7 @@ hikvision==0.4 # http://github.com/adafruit/Adafruit_Python_DHT/archive/310c59b0293354d07d94375f1365f7b9b9110c7d.zip#Adafruit_DHT==1.3.0 # homeassistant.components.light.flux_led -https://github.com/Danielhiversen/flux_led/archive/0.7.zip#flux_led==0.7 +https://github.com/Danielhiversen/flux_led/archive/0.7.zip#flux_led==0.8 # homeassistant.components.switch.dlink https://github.com/LinuxChristian/pyW215/archive/v0.3.5.zip#pyW215==0.3.5 From ad259ead5031f7918f50ad88d31713b6dd7ba330 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 14 Oct 2016 17:36:55 +0200 Subject: [PATCH 066/147] Add ignore option to zwave customize configuration (#3865) --- homeassistant/components/zwave/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 75c2546795f..542be2241cc 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -31,11 +31,13 @@ CONF_POLLING_INTENSITY = 'polling_intensity' CONF_POLLING_INTERVAL = 'polling_interval' CONF_USB_STICK_PATH = 'usb_path' CONF_CONFIG_PATH = 'config_path' +CONF_IGNORED = 'ignored' DEFAULT_CONF_AUTOHEAL = True DEFAULT_CONF_USB_STICK_PATH = '/zwaveusbstick' DEFAULT_POLLING_INTERVAL = 60000 DEFAULT_DEBUG = True +DEFAULT_CONF_IGNORED = False DOMAIN = 'zwave' NETWORK = None @@ -134,6 +136,7 @@ SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({ CUSTOMIZE_SCHEMA = vol.Schema({ vol.Optional(CONF_POLLING_INTENSITY): vol.All(cv.positive_int), + vol.Optional(CONF_IGNORED, default=DEFAULT_CONF_IGNORED): cv.boolean, }) CONFIG_SCHEMA = vol.Schema({ @@ -324,6 +327,11 @@ def setup(hass, config): name = "{}.{}".format(component, _object_id(value)) node_config = customize.get(name, {}) + + if node_config.get(CONF_IGNORED): + _LOGGER.info("Ignoring device %s", name) + return + polling_intensity = convert( node_config.get(CONF_POLLING_INTENSITY), int) if polling_intensity: From 1cbf8c804935029ad69497510f8e1e81a71fd889 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 14 Oct 2016 20:56:40 -0700 Subject: [PATCH 067/147] Zoneminder component (#3795) * Initial Zoneminder commit * Fixing bug when ZM sets its function to 'None' * Adding zoneminder to coverage * Quick Doc fix * Update zoneminder.py Doc Fix * making the url base optional --- .coveragerc | 3 + homeassistant/components/sensor/zoneminder.py | 93 ++++++++++++++ homeassistant/components/switch/zoneminder.py | 92 ++++++++++++++ homeassistant/components/zoneminder.py | 119 ++++++++++++++++++ 4 files changed, 307 insertions(+) create mode 100644 homeassistant/components/sensor/zoneminder.py create mode 100644 homeassistant/components/switch/zoneminder.py create mode 100644 homeassistant/components/zoneminder.py diff --git a/.coveragerc b/.coveragerc index a5fcdf7f9c1..340eccd22a4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -104,6 +104,9 @@ omit = homeassistant/components/ffmpeg.py homeassistant/components/*/ffmpeg.py + homeassistant/components/zoneminder.py + homeassistant/components/*/zoneminder.py + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/simplisafe.py diff --git a/homeassistant/components/sensor/zoneminder.py b/homeassistant/components/sensor/zoneminder.py new file mode 100644 index 00000000000..50446f735c3 --- /dev/null +++ b/homeassistant/components/sensor/zoneminder.py @@ -0,0 +1,93 @@ +""" +Support for Zoneminder Sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.zoneminder/ +""" +import logging + +import homeassistant.components.zoneminder as zoneminder +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zoneminder'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup Zoneminder platform.""" + sensors = [] + + monitors = zoneminder.get_state('api/monitors.json') + for i in monitors['monitors']: + sensors.append( + ZMSensorMonitors(int(i['Monitor']['Id']), i['Monitor']['Name']) + ) + sensors.append( + ZMSensorEvents(int(i['Monitor']['Id']), i['Monitor']['Name']) + ) + + add_devices(sensors) + + +class ZMSensorMonitors(Entity): + """Get the status of each monitor.""" + + def __init__(self, monitor_id, monitor_name): + """Initiate monitor sensor.""" + self._monitor_id = monitor_id + self._monitor_name = monitor_name + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return "%s Status" % self._monitor_name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + def update(self): + """Update the sensor.""" + monitor = zoneminder.get_state( + 'api/monitors/%i.json' % self._monitor_id + ) + if monitor['monitor']['Monitor']['Function'] is None: + self._state = "None" + else: + self._state = monitor['monitor']['Monitor']['Function'] + + +class ZMSensorEvents(Entity): + """Get the number of events for each monitor.""" + + def __init__(self, monitor_id, monitor_name): + """Initiate event sensor.""" + self._monitor_id = monitor_id + self._monitor_name = monitor_name + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return "%s Events" % self._monitor_name + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return 'Events' + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + def update(self): + """Update the sensor.""" + event = zoneminder.get_state( + 'api/events/index/MonitorId:%i.json' % self._monitor_id + ) + + self._state = event['pagination']['count'] diff --git a/homeassistant/components/switch/zoneminder.py b/homeassistant/components/switch/zoneminder.py new file mode 100644 index 00000000000..ab9adbca97d --- /dev/null +++ b/homeassistant/components/switch/zoneminder.py @@ -0,0 +1,92 @@ +""" +Support for Zoneminder switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.zoneminder/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_COMMAND_ON, CONF_COMMAND_OFF) +import homeassistant.helpers.config_validation as cv + +import homeassistant.components.zoneminder as zoneminder + + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COMMAND_ON): cv.string, + vol.Required(CONF_COMMAND_OFF): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zoneminder'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Zoneminder switch.""" + on_state = config.get(CONF_COMMAND_ON) + off_state = config.get(CONF_COMMAND_OFF) + + switches = [] + + monitors = zoneminder.get_state('api/monitors.json') + for i in monitors['monitors']: + switches.append( + ZMSwitchMonitors( + int(i['Monitor']['Id']), + i['Monitor']['Name'], + on_state, + off_state + ) + ) + + add_devices(switches) + + +class ZMSwitchMonitors(SwitchDevice): + """Representation of an zoneminder switch.""" + + icon = 'mdi:record-rec' + + def __init__(self, monitor_id, monitor_name, on_state, off_state): + """Initialize the switch.""" + self._monitor_id = monitor_id + self._monitor_name = monitor_name + self._on_state = on_state + self._off_state = off_state + self._state = None + + @property + def name(self): + """Return the name of the switch.""" + return "%s State" % self._monitor_name + + def update(self): + """Update the switch value.""" + monitor = zoneminder.get_state( + 'api/monitors/%i.json' % self._monitor_id + ) + current_state = monitor['monitor']['Monitor']['Function'] + self._state = True if current_state == self._on_state else False + + @property + def is_on(self): + """Return True if entity is on.""" + return self._state + + def turn_on(self): + """Turn the entity on.""" + zoneminder.change_state( + 'api/monitors/%i.json' % self._monitor_id, + {'Monitor[Function]': self._on_state} + ) + + def turn_off(self): + """Turn the entity off.""" + zoneminder.change_state( + 'api/monitors/%i.json' % self._monitor_id, + {'Monitor[Function]': self._off_state} + ) diff --git a/homeassistant/components/zoneminder.py b/homeassistant/components/zoneminder.py new file mode 100644 index 00000000000..3f43ae01904 --- /dev/null +++ b/homeassistant/components/zoneminder.py @@ -0,0 +1,119 @@ +""" +Support for Zoneminder. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zoneminder/ +""" + +import logging +import json +from urllib.parse import urljoin + +import requests +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_URL, CONF_HOST, CONF_PASSWORD, CONF_USERNAME) + + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = [] + +DOMAIN = 'zoneminder' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_URL, default="/zm/"): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string + }) +}, extra=vol.ALLOW_EXTRA) + +LOGIN_RETRIES = 2 +ZM = {} + + +def setup(hass, config): + """Setup the zonminder platform.""" + global ZM + ZM = {} + + conf = config[DOMAIN] + url = urljoin("http://" + conf[CONF_HOST], conf[CONF_URL]) + username = conf.get(CONF_USERNAME, None) + password = conf.get(CONF_PASSWORD, None) + + ZM['url'] = url + ZM['username'] = username + ZM['password'] = password + + return login() + + +# pylint: disable=no-member +def login(): + """Login to the zoneminder api.""" + _LOGGER.debug("Attempting to login to zoneminder") + + login_post = {'view': 'console', 'action': 'login'} + if ZM['username']: + login_post['username'] = ZM['username'] + if ZM['password']: + login_post['password'] = ZM['password'] + + req = requests.post(ZM['url'] + '/index.php', data=login_post) + ZM['cookies'] = req.cookies + + # Login calls returns a 200 repsonse on both failure and success.. + # The only way to tell if you logged in correctly is to issue an api call. + req = requests.get( + ZM['url'] + 'api/host/getVersion.json', + cookies=ZM['cookies'] + ) + + if req.status_code != requests.codes.ok: + _LOGGER.error("Connection error logging into ZoneMinder") + return False + + return True + + +# pylint: disable=no-member +def get_state(api_url): + """Get a state from the zoneminder API service.""" + # Since the API uses sessions that expire, sometimes we need + # to re-auth if the call fails. + for _ in range(LOGIN_RETRIES): + req = requests.get(urljoin(ZM['url'], api_url), cookies=ZM['cookies']) + + if req.status_code != requests.codes.ok: + login() + else: + break + else: + _LOGGER.exception("Unable to get API response") + + return json.loads(req.text) + + +# pylint: disable=no-member +def change_state(api_url, post_data): + """Update a state using the Zoneminder API.""" + for _ in range(LOGIN_RETRIES): + req = requests.post( + urljoin(ZM['url'], api_url), + data=post_data, + cookies=ZM['cookies']) + + if req.status_code != requests.codes.ok: + login() + else: + break + + else: + _LOGGER.exception("Unable to get API response") + + return json.loads(req.text) From bead274b20bf028b6e807a8ca5697f50fa7e614a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 15 Oct 2016 05:58:49 +0200 Subject: [PATCH 068/147] Upgrade slacker to 0.9.28 (#3877) --- homeassistant/components/notify/slack.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 7c0a2b4d118..48f80138073 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -14,7 +14,7 @@ from homeassistant.const import ( CONF_API_KEY, CONF_USERNAME, CONF_ICON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['slacker==0.9.25'] +REQUIREMENTS = ['slacker==0.9.28'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 44c60f30d48..76b41fec1be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -441,7 +441,7 @@ scsgate==0.1.0 sendgrid==3.4.0 # homeassistant.components.notify.slack -slacker==0.9.25 +slacker==0.9.28 # homeassistant.components.notify.xmpp sleekxmpp==1.3.1 From 180e146e149db010b82029982bda6257cc999749 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 15 Oct 2016 06:00:27 +0200 Subject: [PATCH 069/147] Upgrade uber_rides to 0.2.7 (#3876) --- homeassistant/components/sensor/uber.py | 11 +++++------ requirements_all.txt | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sensor/uber.py b/homeassistant/components/sensor/uber.py index 7f250431984..5a3f931d76b 100644 --- a/homeassistant/components/sensor/uber.py +++ b/homeassistant/components/sensor/uber.py @@ -14,7 +14,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['uber_rides==0.2.5'] +REQUIREMENTS = ['uber_rides==0.2.7'] _LOGGER = logging.getLogger(__name__) @@ -49,10 +49,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): wanted_product_ids = config.get(CONF_PRODUCT_IDS) dev = [] - timeandpriceest = UberEstimate(session, config[CONF_START_LATITUDE], - config[CONF_START_LONGITUDE], - config.get(CONF_END_LATITUDE), - config.get(CONF_END_LONGITUDE)) + timeandpriceest = UberEstimate( + session, config[CONF_START_LATITUDE], config[CONF_START_LONGITUDE], + config.get(CONF_END_LATITUDE), config.get(CONF_END_LONGITUDE)) for product_id, product in timeandpriceest.products.items(): if (wanted_product_ids is not None) and \ (product_id not in wanted_product_ids): @@ -114,7 +113,7 @@ class UberSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - time_estimate = self._product.get("time_estimate_seconds") + time_estimate = self._product.get('time_estimate_seconds') params = { 'Product ID': self._product['product_id'], 'Product short description': self._product['short_description'], diff --git a/requirements_all.txt b/requirements_all.txt index 76b41fec1be..560623d7ccc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -491,7 +491,7 @@ transmissionrpc==0.11 twilio==5.4.0 # homeassistant.components.sensor.uber -uber_rides==0.2.5 +uber_rides==0.2.7 # homeassistant.components.device_tracker.unifi unifi==1.2.5 From ce19e6367ff0ff6f77d40b813e25158d312d70e2 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 14 Oct 2016 21:08:44 -0700 Subject: [PATCH 070/147] Catch MQTT encoding errors (#3749) * added error handling to mqtt message receive if payload is not utf-8 unicode added mqtt test for above code as well * change permission back to 644 * attempting to test new code * changed exception to AttributeError fixed test for above * fixed lint errors I made in tests....mqtt/test_init.py * more lint fixes for my added test * remove dual decode of MQTT payload * convert if to try, except, else statement for mqtt payload decode * rework mqtt unicode testing code to properly check for log file entriy on unicode decode exception * fixed lint error * Update test_init.py --- homeassistant/components/mqtt/__init__.py | 21 +++++++++++++------- tests/components/mqtt/test_init.py | 24 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3edd0ffc500..bc7977ae129 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -401,13 +401,20 @@ class MQTT(object): def _mqtt_on_message(self, _mqttc, _userdata, msg): """Message received callback.""" - _LOGGER.debug("received message on %s: %s", - msg.topic, msg.payload.decode('utf-8')) - self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, { - ATTR_TOPIC: msg.topic, - ATTR_QOS: msg.qos, - ATTR_PAYLOAD: msg.payload.decode('utf-8'), - }) + try: + payload = msg.payload.decode('utf-8') + except AttributeError: + _LOGGER.error("Illegal utf-8 unicode payload from " + "MQTT topic: %s, Payload: %s", msg.topic, + msg.payload) + else: + _LOGGER.debug("received message on %s: %s", + msg.topic, payload) + self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, { + ATTR_TOPIC: msg.topic, + ATTR_QOS: msg.qos, + ATTR_PAYLOAD: payload, + }) def _mqtt_on_unsubscribe(self, _mqttc, _userdata, mid, granted_qos): """Unsubscribe successful callback.""" diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index bb7b09c5112..cfa0766c8ed 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -316,3 +316,27 @@ class TestMQTTCallbacks(unittest.TestCase): def test_invalid_mqtt_topics(self): self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'bad+topic') self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'bad\0one') + + def test_receiving_non_utf8_message_gets_logged(self): + """Test receiving a non utf8 encoded message.""" + calls = [] + + def record(event): + """Helper to record calls.""" + calls.append(event) + + payload = 0x9a + topic = 'test_topic' + self.hass.bus.listen_once(mqtt.EVENT_MQTT_MESSAGE_RECEIVED, record) + MQTTMessage = namedtuple('MQTTMessage', ['topic', 'qos', 'payload']) + message = MQTTMessage(topic, 1, payload) + with self.assertLogs(level='ERROR') as test_handle: + mqtt.MQTT_CLIENT._mqtt_on_message( + None, + {'hass': self.hass}, + message) + self.hass.block_till_done() + self.assertIn( + "ERROR:homeassistant.components.mqtt:Illegal utf-8 unicode " + "payload from MQTT topic: %s, Payload: " % topic, + test_handle.output[0]) From 49b1643ff0709b0d54d3490e230d2c082652fb0c Mon Sep 17 00:00:00 2001 From: Georgi Kirichkov Date: Sat, 15 Oct 2016 07:10:04 +0300 Subject: [PATCH 071/147] Relaxes the configuration options for influxdb (#3869) * Relaxes the configuration options for influxdb By default influxdb allows unauthenticated access Home Assistant required at least username and password to be present to properly submit data to influxdb * Removes unused import of 'copy' The copy module was used only in the removed test case responsible for testing the missing keys * Updates InfluxDB config schema to require user and password Current InfluxDB (v 1.0) can work without any authentication, but when authentication is enabled both username and password should be set. * Removes extra white space in test_influxdb.py --- homeassistant/components/influxdb.py | 4 ++-- tests/components/test_influxdb.py | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index 6bac1fa7cfb..420781bcb74 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -33,8 +33,8 @@ TIMEOUT = 5 CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, + vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, + vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, vol.Optional(CONF_BLACKLIST, default=[]): vol.All(cv.ensure_list, [cv.entity_id]), vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string, diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index 3210bf0db9f..3e4e6e0ad16 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -1,5 +1,4 @@ """The tests for the InfluxDB component.""" -import copy import unittest from unittest import mock @@ -53,18 +52,23 @@ class TestInfluxDB(unittest.TestCase): self.assertEqual(EVENT_STATE_CHANGED, self.hass.bus.listen.call_args_list[0][0][0]) - def test_setup_missing_keys(self, mock_client): - """Test the setup with missing keys.""" + def test_setup_minimal_config(self, mock_client): + """Tests the setup with minimal configuration.""" + config = { + 'influxdb': {} + } + + assert setup_component(self.hass, influxdb.DOMAIN, config) + + def test_setup_missing_password(self, mock_client): + """Test the setup with existing username and missing password.""" config = { 'influxdb': { - 'username': 'user', - 'password': 'pass', + 'username': 'user' } } - for missing in config['influxdb'].keys(): - config_copy = copy.deepcopy(config) - del config_copy['influxdb'][missing] - assert not setup_component(self.hass, influxdb.DOMAIN, config_copy) + + assert not setup_component(self.hass, influxdb.DOMAIN, config) def test_setup_query_fail(self, mock_client): """Test the setup for query failures.""" From 1bf5554017c28109a0c55bbfca99744841759a03 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 15 Oct 2016 00:17:48 -0400 Subject: [PATCH 072/147] Zwave rgb fix (#3879) * _zw098 must be set before update_properties * Fix problematic zwave color light value matching See https://community.home-assistant.io/t/color-issues-with-aeotec-zwave-zw098/2830 --- homeassistant/components/light/zwave.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 5d0b334aeee..f71dd158f61 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -115,7 +115,6 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) self._brightness = None self._state = None - self.update_properties() self._alt_delay = None self._zw098 = None @@ -134,6 +133,8 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): " %s", value.parent_id) self._alt_delay = 1 + self.update_properties() + # Used for value change event handling self._refreshing = False self._timer = None @@ -222,17 +223,12 @@ class ZwaveColorLight(ZwaveDimmer): self._rgb = None self._ct = None - # Here we attempt to find a zwave color value with the same instance - # id as the dimmer value. Currently zwave nodes that change colors - # only include one dimmer and one color command, but this will - # hopefully provide some forward compatibility for new devices that - # have multiple color changing elements. + # Currently zwave nodes only exist with one color element per node. for value_color in value.node.get_rgbbulbs().values(): - if value.instance == value_color.instance: - self._value_color = value_color + self._value_color = value_color if self._value_color is None: - raise ValueError("No matching color command found.") + raise ValueError("No color command found.") for value_color_channels in value.node.get_values( class_id=zwave.const.COMMAND_CLASS_SWITCH_COLOR, From 6fcb1b548e4df19d9aaa34beba1e9f1fe9ff511e Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sat, 15 Oct 2016 00:35:27 -0400 Subject: [PATCH 073/147] Added the ability to Weather Underground to track severe weather alerts (#3505) * Added the ability to Weather Underground to track severe weather alerts * * Added message on the advisory attr * Updated tests * * Making use of guard clause * Checking multiple_alerts prior loop * Using a better way to create dict * Fixed issue to set to None only the object that failed * Added unittest * Split update() method to different calls with their one throttle control to minimize API calls * Updated unittest and make sure the alert sensor will not return 'unknown' status' * Removed update() method from state property * Branch rebased and include Weather Underground attribution * Update wunderground.py --- .../components/sensor/wunderground.py | 61 ++++++++++++++++--- tests/components/sensor/test_wunderground.py | 49 ++++++++++----- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index ef20e3f2679..69b53b0c259 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -19,6 +19,7 @@ from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv _RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/' +_ALERTS = 'http://api.wunderground.com/api/{}/alerts/q/' _LOGGER = logging.getLogger(__name__) CONF_ATTRIBUTION = "Data provided by the WUnderground weather service" @@ -28,6 +29,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) # Sensor types are defined like: Name, units SENSOR_TYPES = { + 'alerts': ['Alerts', None], 'weather': ['Weather Summary', None], 'station_id': ['Station ID', None], 'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS], @@ -57,6 +59,14 @@ SENSOR_TYPES = { 'solarradiation': ['Solar Radiation', None] } +# Alert Attributes +ALERTS_ATTRS = [ + 'date', + 'description', + 'expires', + 'message', +] + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, vol.Optional(CONF_PWS_ID): cv.string, @@ -106,15 +116,31 @@ class WUndergroundSensor(Entity): return int(self.rest.data[self._condition][:-1]) else: return self.rest.data[self._condition] - else: - return STATE_UNKNOWN + + if self.rest.alerts and self._condition == 'alerts': + return len(self.rest.alerts) + return STATE_UNKNOWN @property def device_state_attributes(self): """Return the state attributes.""" - return { - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - } + attrs = {} + + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + + if not self.rest.alerts or self._condition != 'alerts': + return attrs + + multiple_alerts = len(self.rest.alerts) > 1 + for data in self.rest.alerts: + for alert in ALERTS_ATTRS: + if data[alert]: + if multiple_alerts: + dkey = alert.capitalize() + '_' + data['type'] + else: + dkey = alert.capitalize() + attrs[dkey] = data[alert] + return attrs @property def entity_picture(self): @@ -129,7 +155,10 @@ class WUndergroundSensor(Entity): def update(self): """Update current conditions.""" - self.rest.update() + if self._condition == 'alerts': + self.rest.update_alerts() + else: + self.rest.update() # pylint: disable=too-few-public-methods @@ -144,9 +173,10 @@ class WUndergroundData(object): self._latitude = hass.config.latitude self._longitude = hass.config.longitude self.data = None + self.alerts = None - def _build_url(self): - url = _RESOURCE.format(self._api_key) + def _build_url(self, baseurl=_RESOURCE): + url = baseurl.format(self._api_key) if self._pws_id: url = url + 'pws:{}'.format(self._pws_id) else: @@ -168,3 +198,18 @@ class WUndergroundData(object): _LOGGER.error("Check WUnderground API %s", err.args) self.data = None raise + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update_alerts(self): + """Get the latest alerts data from WUnderground.""" + try: + result = requests.get(self._build_url(_ALERTS), timeout=10).json() + if "error" in result['response']: + raise ValueError(result['response']["error"] + ["description"]) + else: + self.alerts = result["alerts"] + except ValueError as err: + _LOGGER.error("Check WUnderground API %s", err.args) + self.alerts = None + raise diff --git a/tests/components/sensor/test_wunderground.py b/tests/components/sensor/test_wunderground.py index ffb070f9ab9..f7f2e958ef7 100644 --- a/tests/components/sensor/test_wunderground.py +++ b/tests/components/sensor/test_wunderground.py @@ -11,7 +11,7 @@ VALID_CONFIG_PWS = { 'api_key': 'foo', 'pws_id': 'bar', 'monitored_conditions': [ - 'weather', 'feelslike_c' + 'weather', 'feelslike_c', 'alerts' ] } @@ -19,17 +19,19 @@ VALID_CONFIG = { 'platform': 'wunderground', 'api_key': 'foo', 'monitored_conditions': [ - 'weather', 'feelslike_c' + 'weather', 'feelslike_c', 'alerts' ] } FEELS_LIKE = '40' WEATHER = 'Clear' ICON_URL = 'http://icons.wxug.com/i/c/k/clear.gif' +ALERT_MESSAGE = 'This is a test alert message' def mocked_requests_get(*args, **kwargs): """Mock requests.get invocations.""" + # pylint: disable=too-few-public-methods class MockResponse: """Class to represent a mocked response.""" @@ -61,26 +63,36 @@ def mocked_requests_get(*args, **kwargs): "feelslike_c": FEELS_LIKE, "weather": WEATHER, "icon_url": ICON_URL - } + }, "alerts": [ + { + "type": 'FLO', + "description": "Areal Flood Warning", + "date": "9:36 PM CDT on September 22, 2016", + "expires": "10:00 AM CDT on September 23, 2016", + "message": ALERT_MESSAGE, + }, + + ], }, 200) else: return MockResponse({ - "response": { - "version": "0.1", - "termsofService": - "http://www.wunderground.com/weather/api/d/terms.html", - "features": {}, - "error": { - "type": "keynotfound", - "description": "this key does not exist" - } + "response": { + "version": "0.1", + "termsofService": + "http://www.wunderground.com/weather/api/d/terms.html", + "features": {}, + "error": { + "type": "keynotfound", + "description": "this key does not exist" } - }, 200) + } + }, 200) class TestWundergroundSetup(unittest.TestCase): """Test the WUnderground platform.""" + # pylint: disable=invalid-name DEVICES = [] def add_devices(self, devices): @@ -107,14 +119,13 @@ class TestWundergroundSetup(unittest.TestCase): self.add_devices, None)) self.assertTrue( wunderground.setup_platform(self.hass, VALID_CONFIG, - self.add_devices, - None)) + self.add_devices, None)) invalid_config = { 'platform': 'wunderground', 'api_key': 'BOB', 'pws_id': 'bar', 'monitored_conditions': [ - 'weather', 'feelslike_c' + 'weather', 'feelslike_c', 'alerts' ] } @@ -128,11 +139,17 @@ class TestWundergroundSetup(unittest.TestCase): wunderground.setup_platform(self.hass, VALID_CONFIG, self.add_devices, None) for device in self.DEVICES: + device.update() self.assertTrue(str(device.name).startswith('PWS_')) if device.name == 'PWS_weather': self.assertEqual(ICON_URL, device.entity_picture) self.assertEqual(WEATHER, device.state) self.assertIsNone(device.unit_of_measurement) + elif device.name == 'PWS_alerts': + self.assertEqual(1, device.state) + self.assertEqual(ALERT_MESSAGE, + device.device_state_attributes['Message']) + self.assertIsNone(device.entity_picture) else: self.assertIsNone(device.entity_picture) self.assertEqual(FEELS_LIKE, device.state) From 9743e17d62c147fe70efccc4ff9bee94150841d7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 15 Oct 2016 06:43:46 +0200 Subject: [PATCH 074/147] Minimum/maximum/mean sensor (#3852) * Add min/max sensor * Update min_max.py --- homeassistant/components/sensor/min_max.py | 146 +++++++++++++++++++ tests/components/sensor/test_min_max.py | 161 +++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 homeassistant/components/sensor/min_max.py create mode 100644 tests/components/sensor/test_min_max.py diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/sensor/min_max.py new file mode 100644 index 00000000000..e88c2dffe6a --- /dev/null +++ b/homeassistant/components/sensor/min_max.py @@ -0,0 +1,146 @@ +""" +Support for displaying the minimal and the maximal value. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.min_max/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_NAME, STATE_UNKNOWN, CONF_TYPE, ATTR_UNIT_OF_MEASUREMENT) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import track_state_change + +_LOGGER = logging.getLogger(__name__) + +ATTR_MIN_VALUE = 'min_value' +ATTR_MAX_VALUE = 'max_value' +ATTR_COUNT_SENSORS = 'count_sensors' +ATTR_MEAN = 'mean' + +ATTR_TO_PROPERTY = [ + ATTR_COUNT_SENSORS, + ATTR_MAX_VALUE, + ATTR_MEAN, + ATTR_MIN_VALUE, +] + +CONF_ENTITY_IDS = 'entity_ids' + +DEFAULT_NAME = 'Min/Max Sensor' + +ICON = 'mdi:calculator' + +SENSOR_TYPES = { + ATTR_MIN_VALUE: 'min', + ATTR_MAX_VALUE: 'max', + ATTR_MEAN: 'mean', +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_TYPE, default=SENSOR_TYPES[ATTR_MAX_VALUE]): + vol.All(cv.string, vol.In(SENSOR_TYPES.values())), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITY_IDS): cv.entity_ids, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the min/max sensor.""" + entity_ids = config.get(CONF_ENTITY_IDS) + name = config.get(CONF_NAME) + sensor_type = config.get(CONF_TYPE) + + add_devices([MinMaxSensor(hass, entity_ids, name, sensor_type)]) + + +# pylint: disable=too-many-instance-attributes +class MinMaxSensor(Entity): + """Representation of a min/max sensor.""" + + def __init__(self, hass, entity_ids, name, sensor_type): + """Initialize the min/max sensor.""" + self._hass = hass + self._entity_ids = entity_ids + self._sensor_type = sensor_type + self._name = '{} {}'.format( + name, next(v for k, v in SENSOR_TYPES.items() + if self._sensor_type == v)) + self._unit_of_measurement = None + self.min_value = self.max_value = self.mean = STATE_UNKNOWN + self.count_sensors = len(self._entity_ids) + self.states = {} + self.update() + + def min_max_sensor_state_listener(entity, old_state, new_state): + """Called when the sensor changes state.""" + if new_state.state is None or new_state.state in STATE_UNKNOWN: + return + + if self._unit_of_measurement is None: + self._unit_of_measurement = new_state.attributes.get( + ATTR_UNIT_OF_MEASUREMENT) + + if self._unit_of_measurement != new_state.attributes.get( + ATTR_UNIT_OF_MEASUREMENT): + _LOGGER.warning("Units of measurement do not match") + return + try: + self.states[entity] = float(new_state.state) + except ValueError: + _LOGGER.warning("Unable to store state. " + "Only numerical states are supported") + + self.update_ha_state(True) + + track_state_change(hass, entity_ids, min_max_sensor_state_listener) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return getattr(self, next( + k for k, v in SENSOR_TYPES.items() if self._sensor_type == v)) + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + state_attr = { + attr: getattr(self, attr) for attr + in ATTR_TO_PROPERTY if getattr(self, attr) is not None + } + return state_attr + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return ICON + + def update(self): + """Get the latest data and updates the states.""" + sensor_values = [self.states[k] for k in self._entity_ids + if k in self.states] + if len(sensor_values) == self.count_sensors: + self.min_value = min(sensor_values) + self.max_value = max(sensor_values) + self.mean = round(sum(sensor_values) / self.count_sensors, 2) + else: + self.min_value = self.max_value = self.mean = STATE_UNKNOWN diff --git a/tests/components/sensor/test_min_max.py b/tests/components/sensor/test_min_max.py new file mode 100644 index 00000000000..bf49d4113c4 --- /dev/null +++ b/tests/components/sensor/test_min_max.py @@ -0,0 +1,161 @@ +"""The test for the min/max sensor platform.""" +import unittest + +from homeassistant.bootstrap import setup_component +from homeassistant.const import ( + STATE_UNKNOWN, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from tests.common import get_test_home_assistant + + +class TestMinMaxSensor(unittest.TestCase): + """Test the min/max sensor.""" + + def setup_method(self, method): + """Set up things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.values = [17, 20, 15.2] + self.count = len(self.values) + self.min = min(self.values) + self.max = max(self.values) + self.mean = round(sum(self.values) / self.count, 2) + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + + def test_min_sensor(self): + """Test the min sensor.""" + config = { + 'sensor': { + 'platform': 'min_max', + 'name': 'test', + 'type': 'min', + 'entity_ids': [ + 'sensor.test_1', + 'sensor.test_2', + 'sensor.test_3', + ] + } + } + + assert setup_component(self.hass, 'sensor', config) + + entity_ids = config['sensor']['entity_ids'] + + for entity_id, value in dict(zip(entity_ids, self.values)).items(): + self.hass.states.set(entity_id, value) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_min') + + self.assertEqual(str(float(self.min)), state.state) + self.assertEqual(self.max, state.attributes.get('max_value')) + self.assertEqual(self.mean, state.attributes.get('mean')) + + def test_max_sensor(self): + """Test the max sensor.""" + config = { + 'sensor': { + 'platform': 'min_max', + 'name': 'test', + 'type': 'max', + 'entity_ids': [ + 'sensor.test_1', + 'sensor.test_2', + 'sensor.test_3', + ] + } + } + + assert setup_component(self.hass, 'sensor', config) + + entity_ids = config['sensor']['entity_ids'] + + for entity_id, value in dict(zip(entity_ids, self.values)).items(): + self.hass.states.set(entity_id, value) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_max') + + self.assertEqual(str(float(self.max)), state.state) + self.assertEqual(self.min, state.attributes.get('min_value')) + self.assertEqual(self.mean, state.attributes.get('mean')) + + def test_not_enough_sensor_value(self): + """Test that there is nothing done if not enough values available.""" + config = { + 'sensor': { + 'platform': 'min_max', + 'name': 'test', + 'type': 'max', + 'entity_ids': [ + 'sensor.test_1', + 'sensor.test_2', + 'sensor.test_3', + ] + } + } + + assert setup_component(self.hass, 'sensor', config) + + entity_ids = config['sensor']['entity_ids'] + + self.hass.states.set(entity_ids[0], self.values[0]) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_max') + self.assertEqual(STATE_UNKNOWN, state.state) + + self.hass.states.set(entity_ids[1], self.values[1]) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_max') + self.assertEqual(STATE_UNKNOWN, state.state) + + self.hass.states.set(entity_ids[2], self.values[2]) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_max') + self.assertNotEqual(STATE_UNKNOWN, state.state) + + def test_different_unit_of_measurement(self): + """Test for different unit of measurement.""" + config = { + 'sensor': { + 'platform': 'min_max', + 'name': 'test', + 'type': 'mean', + 'entity_ids': [ + 'sensor.test_1', + 'sensor.test_2', + 'sensor.test_3', + ] + } + } + + assert setup_component(self.hass, 'sensor', config) + + entity_ids = config['sensor']['entity_ids'] + + self.hass.states.set(entity_ids[0], self.values[0], + {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_mean') + + self.assertEqual(STATE_UNKNOWN, state.state) + self.assertEqual('°C', state.attributes.get('unit_of_measurement')) + + self.hass.states.set(entity_ids[1], self.values[1], + {ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT}) + self.hass.block_till_done() + + self.assertEqual(STATE_UNKNOWN, state.state) + self.assertEqual('°C', state.attributes.get('unit_of_measurement')) + + self.hass.states.set(entity_ids[2], self.values[2], + {ATTR_UNIT_OF_MEASUREMENT: '%'}) + self.hass.block_till_done() + + self.assertEqual(STATE_UNKNOWN, state.state) + self.assertEqual('°C', state.attributes.get('unit_of_measurement')) From a10fa903578006d217c576a0d251f2eeaffc55b3 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 15 Oct 2016 13:46:45 +0200 Subject: [PATCH 075/147] Update link to docs repo (#3886) --- .github/PULL_REQUEST_TEMPLATE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 998905c929b..d3106f26bae 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,9 @@ **Description:** -**Related issue (if applicable):** fixes # +**Related issue (if applicable):** fixes # -**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io# +**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io# **Example entry for `configuration.yaml` (if applicable):** ```yaml @@ -13,7 +13,7 @@ **Checklist:** If user exposed functionality or configuration variables are added/changed: - - [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) + - [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) If the code communicates with devices, web services, or third-party tools: - [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass** From 5ef8ca9b03dc6f58c0b793c15854649135525c43 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 16 Oct 2016 09:09:48 +0200 Subject: [PATCH 076/147] Upgrade sendgrid to 3.6.0 (#3887) --- homeassistant/components/notify/sendgrid.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/notify/sendgrid.py index 42921e2be2c..c8afe601ae5 100644 --- a/homeassistant/components/notify/sendgrid.py +++ b/homeassistant/components/notify/sendgrid.py @@ -13,7 +13,7 @@ from homeassistant.components.notify import ( from homeassistant.const import (CONF_API_KEY, CONF_SENDER, CONF_RECIPIENT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['sendgrid==3.4.0'] +REQUIREMENTS = ['sendgrid==3.6.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 560623d7ccc..19ec23a6026 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -438,7 +438,7 @@ schiene==0.17 scsgate==0.1.0 # homeassistant.components.notify.sendgrid -sendgrid==3.4.0 +sendgrid==3.6.0 # homeassistant.components.notify.slack slacker==0.9.28 From a0fdb2778d5a571a174e812c44cd887d9cf1d1d1 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 16 Oct 2016 18:07:34 +0200 Subject: [PATCH 077/147] Fix name allocation (#3890) --- homeassistant/components/sensor/yr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index be7693f6e4f..d73a016003c 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -27,7 +27,7 @@ CONF_ATTRIBUTION = "Weather forecast from yr.no, delivered by the Norwegian " \ # Sensor types are defined like so: SENSOR_TYPES = { 'symbol': ['Symbol', None], - 'precipitation': ['Condition', 'mm'], + 'precipitation': ['Precipitation', 'mm'], 'temperature': ['Temperature', '°C'], 'windSpeed': ['Wind speed', 'm/s'], 'windGust': ['Wind gust', 'm/s'], From 0b8b9ecb9445ef618cc3229b090f36bf85ef0f5a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 16 Oct 2016 18:35:46 +0200 Subject: [PATCH 078/147] Async EntitiesComponent (#3820) * first version * First draft component entities * Change add_entities to callback from coroutine * Fix bug add async_prepare_reload * Group draft v1 * group async * bugfix * bugfix v2 * fix lint * fix extract_entity_ids * fix other things * move get_component out of executor * bugfix * Address minor changes * lint * bugfix - should work now * make group init async only * change update handling to old stuff * fix group handling, remove generator from init * fix lint * protect loop for spaming with updates * fix lint * update test_group * fix * update group handling * fix __init__ async trouble * move device_tracker to new layout * lint * fix group unittest * Test with coroutine * fix bug * now it works :100: * ups * first part of suggestion * add_entities to coroutine * change group * convert add async_add_entity to coroutine * fix unit tests * fix lint * fix lint part 2 * fix wrong import delete * change async_update_tracked_entity_ids to coroutine * fix * revert last change * fix unittest entity id * fix unittest * fix unittest * fix unittest entity_component * fix group * fix group_test * try part 2 to fix test_group * fix all entity_component * rename _process_config * Change Group to init with factory * fix lint * fix lint * fix callback * Tweak entity component and group * More fixes * Final fixes * No longer needed blocks * Address @bbangert comments * Add test for group.stop * More callbacks for automation --- .../components/automation/__init__.py | 33 +-- homeassistant/components/demo.py | 22 +- .../components/device_tracker/__init__.py | 13 +- homeassistant/components/group.py | 250 ++++++++++++------ homeassistant/helpers/discovery.py | 21 +- homeassistant/helpers/entity.py | 33 ++- homeassistant/helpers/entity_component.py | 241 ++++++++++++----- homeassistant/helpers/service.py | 2 + tests/components/binary_sensor/test_nx584.py | 6 - tests/components/camera/test_uvc.py | 42 ++- tests/components/test_group.py | 62 +++-- tests/helpers/test_entity_component.py | 36 +-- tests/helpers/test_service.py | 2 +- tests/helpers/test_template.py | 6 +- 14 files changed, 503 insertions(+), 266 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 355e19f7fa0..81944d6ec57 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -11,6 +11,7 @@ import os import voluptuous as vol +from homeassistant.core import callback from homeassistant.bootstrap import prepare_setup_platform from homeassistant import config as conf_util from homeassistant.const import ( @@ -157,24 +158,24 @@ def setup(hass, config): descriptions = conf_util.load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) - @asyncio.coroutine + @callback def trigger_service_handler(service_call): """Handle automation triggers.""" - for entity in component.extract_from_service(service_call): + for entity in component.async_extract_from_service(service_call): hass.loop.create_task(entity.async_trigger( service_call.data.get(ATTR_VARIABLES), True)) - @asyncio.coroutine + @callback def turn_onoff_service_handler(service_call): """Handle automation turn on/off service calls.""" method = 'async_{}'.format(service_call.service) - for entity in component.extract_from_service(service_call): + for entity in component.async_extract_from_service(service_call): hass.loop.create_task(getattr(entity, method)()) - @asyncio.coroutine + @callback def toggle_service_handler(service_call): """Handle automation toggle service calls.""" - for entity in component.extract_from_service(service_call): + for entity in component.async_extract_from_service(service_call): if entity.is_on: hass.loop.create_task(entity.async_turn_off()) else: @@ -183,8 +184,7 @@ def setup(hass, config): @asyncio.coroutine def reload_service_handler(service_call): """Remove all automations and load new ones from config.""" - conf = yield from hass.loop.run_in_executor( - None, component.prepare_reload) + conf = yield from component.async_prepare_reload() if conf is None: return hass.loop.create_task(_async_process_config(hass, conf, component)) @@ -271,7 +271,9 @@ class AutomationEntity(ToggleEntity): self._async_detach_triggers() self._async_detach_triggers = None self._enabled = False - self.hass.loop.create_task(self.async_update_ha_state()) + # It's important that the update is finished before this method + # ends because async_remove depends on it. + yield from self.async_update_ha_state() @asyncio.coroutine def async_trigger(self, variables, skip_condition=False): @@ -284,11 +286,11 @@ class AutomationEntity(ToggleEntity): self._last_triggered = utcnow() self.hass.loop.create_task(self.async_update_ha_state()) - def remove(self): + @asyncio.coroutine + def async_remove(self): """Remove automation from HASS.""" - run_coroutine_threadsafe(self.async_turn_off(), - self.hass.loop).result() - super().remove() + yield from self.async_turn_off() + yield from super().async_remove() @asyncio.coroutine def async_enable(self): @@ -341,12 +343,11 @@ def _async_process_config(hass, config, component): entity = AutomationEntity(name, async_attach_triggers, cond_func, action, hidden) if config_block[CONF_INITIAL_STATE]: - tasks.append(hass.loop.create_task(entity.async_enable())) + tasks.append(entity.async_enable()) entities.append(entity) yield from asyncio.gather(*tasks, loop=hass.loop) - yield from hass.loop.run_in_executor( - None, component.add_entities, entities) + hass.loop.create_task(component.async_add_entities(entities)) return len(entities) > 0 diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index a2eb40e21e8..9f3042320c9 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -67,31 +67,33 @@ def setup(hass, config): lights = sorted(hass.states.entity_ids('light')) switches = sorted(hass.states.entity_ids('switch')) media_players = sorted(hass.states.entity_ids('media_player')) - group.Group(hass, 'living room', [ + + group.Group.create_group(hass, 'living room', [ lights[1], switches[0], 'input_select.living_room_preset', 'rollershutter.living_room_window', media_players[1], 'scene.romantic_lights']) - group.Group(hass, 'bedroom', [ + group.Group.create_group(hass, 'bedroom', [ lights[0], switches[1], media_players[0], 'input_slider.noise_allowance']) - group.Group(hass, 'kitchen', [ + group.Group.create_group(hass, 'kitchen', [ lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door']) - group.Group(hass, 'doors', [ + group.Group.create_group(hass, 'doors', [ 'lock.front_door', 'lock.kitchen_door', 'garage_door.right_garage_door', 'garage_door.left_garage_door']) - group.Group(hass, 'automations', [ + group.Group.create_group(hass, 'automations', [ 'input_select.who_cooks', 'input_boolean.notify', ]) - group.Group(hass, 'people', [ + group.Group.create_group(hass, 'people', [ 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy', 'device_tracker.demo_paulus']) - group.Group(hass, 'thermostats', [ + group.Group.create_group(hass, 'thermostats', [ 'thermostat.nest', 'thermostat.thermostat']) - group.Group(hass, 'downstairs', [ + group.Group.create_group(hass, 'downstairs', [ 'group.living_room', 'group.kitchen', 'scene.romantic_lights', 'rollershutter.kitchen_window', - 'rollershutter.living_room_window', 'group.doors', 'thermostat.nest', + 'rollershutter.living_room_window', 'group.doors', + 'thermostat.nest', ], view=True) - group.Group(hass, 'Upstairs', [ + group.Group.create_group(hass, 'Upstairs', [ 'thermostat.thermostat', 'group.bedroom', ], view=True) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 3fa8361a44d..72698e189ff 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/device_tracker/ """ # pylint: disable=too-many-instance-attributes, too-many-arguments # pylint: disable=too-many-locals +import asyncio from datetime import timedelta import logging import os @@ -25,6 +26,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType import homeassistant.helpers.config_validation as cv import homeassistant.util as util +from homeassistant.util.async import run_coroutine_threadsafe import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_utc_time_change @@ -252,9 +254,18 @@ class DeviceTracker(object): def setup_group(self): """Initialize group for all tracked devices.""" + run_coroutine_threadsafe( + self.async_setup_group(), self.hass.loop).result() + + @asyncio.coroutine + def async_setup_group(self): + """Initialize group for all tracked devices. + + This method must be run in the event loop. + """ entity_ids = (dev.entity_id for dev in self.devices.values() if dev.track) - self.group = group.Group( + self.group = yield from group.Group.async_create_group( self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False) def update_stale(self, now: dt_util.dt.datetime): diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 41901d87e86..915254bd618 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -4,9 +4,9 @@ Provides functionality to group entities. For more details about this component, please refer to the documentation at https://home-assistant.io/components/group/ """ +import asyncio import logging import os -import threading import voluptuous as vol @@ -15,10 +15,13 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, ATTR_ASSUMED_STATE) -from homeassistant.helpers.entity import Entity, generate_entity_id +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.event import async_track_state_change import homeassistant.helpers.config_validation as cv +from homeassistant.util.async import ( + run_callback_threadsafe, run_coroutine_threadsafe) DOMAIN = 'group' @@ -87,7 +90,10 @@ def reload(hass): def expand_entity_ids(hass, entity_ids): - """Return entity_ids with group entity ids replaced by their members.""" + """Return entity_ids with group entity ids replaced by their members. + + Async friendly. + """ found_ids = [] for entity_id in entity_ids: @@ -118,7 +124,10 @@ def expand_entity_ids(hass, entity_ids): def get_entity_ids(hass, entity_id, domain_filter=None): - """Get members of this group.""" + """Get members of this group. + + Async friendly. + """ group = hass.states.get(entity_id) if not group or ATTR_ENTITY_ID not in group.attributes: @@ -139,20 +148,19 @@ def setup(hass, config): """Setup all groups found definded in the configuration.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - success = _process_config(hass, config, component) - - if not success: - return False + run_coroutine_threadsafe( + _async_process_config(hass, config, component), hass.loop).result() descriptions = conf_util.load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) + @asyncio.coroutine def reload_service_handler(service_call): """Remove all groups and load new ones from config.""" - conf = component.prepare_reload() + conf = yield from component.async_prepare_reload() if conf is None: return - _process_config(hass, conf, component) + hass.loop.create_task(_async_process_config(hass, conf, component)) hass.services.register(DOMAIN, SERVICE_RELOAD, reload_service_handler, descriptions[DOMAIN][SERVICE_RELOAD], @@ -161,48 +169,82 @@ def setup(hass, config): return True -def _process_config(hass, config, component): +@asyncio.coroutine +def _async_process_config(hass, config, component): """Process group configuration.""" + groups = [] for object_id, conf in config.get(DOMAIN, {}).items(): name = conf.get(CONF_NAME, object_id) entity_ids = conf.get(CONF_ENTITIES) or [] icon = conf.get(CONF_ICON) view = conf.get(CONF_VIEW) - group = Group(hass, name, entity_ids, icon=icon, view=view, - object_id=object_id) - component.add_entities((group,)) + # This order is important as groups get a number based on creation + # order. + group = yield from Group.async_create_group( + hass, name, entity_ids, icon=icon, view=view, object_id=object_id) + groups.append(group) - return True + yield from component.async_add_entities(groups) class Group(Entity): """Track a group of entity ids.""" # pylint: disable=too-many-instance-attributes, too-many-arguments - def __init__(self, hass, name, entity_ids=None, user_defined=True, - icon=None, view=False, object_id=None): - """Initialize a group.""" + def __init__(self, hass, name, order=None, user_defined=True, icon=None, + view=False): + """Initialize a group. + + This Object has factory function for creation. + """ self.hass = hass self._name = name self._state = STATE_UNKNOWN - self._order = len(hass.states.entity_ids(DOMAIN)) self._user_defined = user_defined + self._order = order self._icon = icon self._view = view - self.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, object_id or name, hass=hass) self.tracking = [] self.group_on = None self.group_off = None self._assumed_state = False - self._lock = threading.Lock() - self._unsub_state_changed = None + self._async_unsub_state_changed = None + @staticmethod + # pylint: disable=too-many-arguments + def create_group(hass, name, entity_ids=None, user_defined=True, + icon=None, view=False, object_id=None): + """Initialize a group.""" + return run_coroutine_threadsafe( + Group.async_create_group(hass, name, entity_ids, user_defined, + icon, view, object_id), + hass.loop).result() + + @staticmethod + @asyncio.coroutine + # pylint: disable=too-many-arguments + def async_create_group(hass, name, entity_ids=None, user_defined=True, + icon=None, view=False, object_id=None): + """Initialize a group. + + This method must be run in the event loop. + """ + group = Group( + hass, name, + order=len(hass.states.async_entity_ids(DOMAIN)), + user_defined=user_defined, icon=icon, view=view) + + group.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, object_id or name, hass=hass) + + # run other async stuff if entity_ids is not None: - self.update_tracked_entity_ids(entity_ids) + yield from group.async_update_tracked_entity_ids(entity_ids) else: - self.update_ha_state(True) + yield from group.async_update_ha_state(True) + + return group @property def should_poll(self): @@ -249,40 +291,74 @@ class Group(Entity): def update_tracked_entity_ids(self, entity_ids): """Update the member entity IDs.""" - self.stop() + run_coroutine_threadsafe( + self.async_update_tracked_entity_ids(entity_ids), self.hass.loop + ).result() + + @asyncio.coroutine + def async_update_tracked_entity_ids(self, entity_ids): + """Update the member entity IDs. + + This method must be run in the event loop. + """ + yield from self.async_stop() self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) self.group_on, self.group_off = None, None - self.update_ha_state(True) - - self.start() + yield from self.async_update_ha_state(True) + self.async_start() def start(self): """Start tracking members.""" - self._unsub_state_changed = track_state_change( - self.hass, self.tracking, self._state_changed_listener) + run_callback_threadsafe(self.hass.loop, self.async_start).result() + + def async_start(self): + """Start tracking members. + + This method must be run in the event loop. + """ + self._async_unsub_state_changed = async_track_state_change( + self.hass, self.tracking, self._state_changed_listener + ) def stop(self): """Unregister the group from Home Assistant.""" - self.remove() + run_coroutine_threadsafe(self.async_stop(), self.hass.loop).result() - def update(self): + @asyncio.coroutine + def async_stop(self): + """Unregister the group from Home Assistant. + + This method must be run in the event loop. + """ + yield from self.async_remove() + + @asyncio.coroutine + def async_update(self): """Query all members and determine current group state.""" self._state = STATE_UNKNOWN - self._update_group_state() + self._async_update_group_state() - def remove(self): - """Remove group from HASS.""" - super().remove() + @asyncio.coroutine + def async_remove(self): + """Remove group from HASS. - if self._unsub_state_changed: - self._unsub_state_changed() - self._unsub_state_changed = None + This method must be run in the event loop. + """ + yield from super().async_remove() + if self._async_unsub_state_changed: + self._async_unsub_state_changed() + self._async_unsub_state_changed = None + + @callback def _state_changed_listener(self, entity_id, old_state, new_state): - """Respond to a member state changing.""" - self._update_group_state(new_state) - self.update_ha_state() + """Respond to a member state changing. + + This method must be run in the event loop. + """ + self._async_update_group_state(new_state) + self.hass.loop.create_task(self.async_update_ha_state()) @property def _tracking_states(self): @@ -297,62 +373,64 @@ class Group(Entity): return states - def _update_group_state(self, tr_state=None): + @callback + def _async_update_group_state(self, tr_state=None): """Update group state. Optionally you can provide the only state changed since last update allowing this method to take shortcuts. + + This method must be run in the event loop. """ # pylint: disable=too-many-branches # To store current states of group entities. Might not be needed. - with self._lock: - states = None - gr_state = self._state - gr_on = self.group_on - gr_off = self.group_off + states = None + gr_state = self._state + gr_on = self.group_on + gr_off = self.group_off - # We have not determined type of group yet - if gr_on is None: - if tr_state is None: - states = self._tracking_states + # We have not determined type of group yet + if gr_on is None: + if tr_state is None: + states = self._tracking_states - for state in states: - gr_on, gr_off = \ - _get_group_on_off(state.state) - if gr_on is not None: - break - else: - gr_on, gr_off = _get_group_on_off(tr_state.state) + for state in states: + gr_on, gr_off = \ + _get_group_on_off(state.state) + if gr_on is not None: + break + else: + gr_on, gr_off = _get_group_on_off(tr_state.state) - if gr_on is not None: - self.group_on, self.group_off = gr_on, gr_off + if gr_on is not None: + self.group_on, self.group_off = gr_on, gr_off - # We cannot determine state of the group - if gr_on is None: - return + # We cannot determine state of the group + if gr_on is None: + return - if tr_state is None or ((gr_state == gr_on and - tr_state.state == gr_off) or - tr_state.state not in (gr_on, gr_off)): - if states is None: - states = self._tracking_states + if tr_state is None or ((gr_state == gr_on and + tr_state.state == gr_off) or + tr_state.state not in (gr_on, gr_off)): + if states is None: + states = self._tracking_states - if any(state.state == gr_on for state in states): - self._state = gr_on - else: - self._state = gr_off + if any(state.state == gr_on for state in states): + self._state = gr_on + else: + self._state = gr_off - elif tr_state.state in (gr_on, gr_off): - self._state = tr_state.state + elif tr_state.state in (gr_on, gr_off): + self._state = tr_state.state - if tr_state is None or self._assumed_state and \ - not tr_state.attributes.get(ATTR_ASSUMED_STATE): - if states is None: - states = self._tracking_states + if tr_state is None or self._assumed_state and \ + not tr_state.attributes.get(ATTR_ASSUMED_STATE): + if states is None: + states = self._tracking_states - self._assumed_state = any( - state.attributes.get(ATTR_ASSUMED_STATE) for state - in states) + self._assumed_state = any( + state.attributes.get(ATTR_ASSUMED_STATE) for state + in states) - elif tr_state.attributes.get(ATTR_ASSUMED_STATE): - self._assumed_state = True + elif tr_state.attributes.get(ATTR_ASSUMED_STATE): + self._assumed_state = True diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index b0cf8af0747..eb36fc9e1d5 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -1,8 +1,9 @@ """Helper methods to help with platform discovery.""" -from homeassistant import bootstrap +from homeassistant import bootstrap, core from homeassistant.const import ( ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED) +from homeassistant.util.async import run_callback_threadsafe EVENT_LOAD_PLATFORM = 'load_platform.{}' ATTR_PLATFORM = 'platform' @@ -43,8 +44,19 @@ def discover(hass, service, discovered=None, component=None, hass_config=None): def listen_platform(hass, component, callback): """Register a platform loader listener.""" + run_callback_threadsafe( + hass.loop, async_listen_platform, hass, component, callback + ).result() + + +def async_listen_platform(hass, component, callback): + """Register a platform loader listener. + + This method must be run in the event loop. + """ service = EVENT_LOAD_PLATFORM.format(component) + @core.callback def discovery_platform_listener(event): """Listen for platform discovery events.""" if event.data.get(ATTR_SERVICE) != service: @@ -55,9 +67,12 @@ def listen_platform(hass, component, callback): if not platform: return - callback(platform, event.data.get(ATTR_DISCOVERED)) + hass.async_run_job( + callback, platform, event.data.get(ATTR_DISCOVERED) + ) - hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_platform_listener) + hass.bus.async_listen( + EVENT_PLATFORM_DISCOVERED, discovery_platform_listener) def load_platform(hass, component, platform, discovered=None, diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 99384764b5b..08f93b3697b 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -12,7 +12,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import NoEntitySpecifiedError from homeassistant.util import ensure_unique_string, slugify -from homeassistant.util.async import run_coroutine_threadsafe +from homeassistant.util.async import ( + run_coroutine_threadsafe, run_callback_threadsafe) # Entity attributes that we will overwrite _OVERWRITE = {} # type: Dict[str, Any] @@ -27,15 +28,27 @@ def generate_entity_id(entity_id_format: str, name: Optional[str], if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") + else: + return run_callback_threadsafe( + hass.loop, async_generate_entity_id, entity_id_format, name, + current_ids, hass + ).result() - current_ids = hass.states.entity_ids() + name = (name or DEVICE_DEFAULT_NAME).lower() - return async_generate_entity_id(entity_id_format, name, current_ids) + return ensure_unique_string( + entity_id_format.format(slugify(name)), current_ids) def async_generate_entity_id(entity_id_format: str, name: Optional[str], - current_ids: Optional[List[str]]=None) -> str: + current_ids: Optional[List[str]]=None, + hass: Optional[HomeAssistant]=None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" + if current_ids is None: + if hass is None: + raise ValueError("Missing required parameter currentids or hass") + + current_ids = hass.states.async_entity_ids() name = (name or DEVICE_DEFAULT_NAME).lower() return ensure_unique_string( @@ -238,7 +251,17 @@ class Entity(object): def remove(self) -> None: """Remove entitiy from HASS.""" - self.hass.states.remove(self.entity_id) + run_coroutine_threadsafe( + self.async_remove(), self.hass.loop + ).result() + + @asyncio.coroutine + def async_remove(self) -> None: + """Remove entitiy from async HASS. + + This method must be run in the event loop. + """ + self.hass.states.async_remove(self.entity_id) def _attr_setter(self, name, typ, attr, attrs): """Helper method to populate attributes based on properties.""" diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 3146d703d19..e2e25bcfbd3 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,5 +1,5 @@ """Helpers for components that manage entities.""" -from threading import Lock +import asyncio from homeassistant import config as conf_util from homeassistant.bootstrap import (prepare_setup_platform, @@ -7,12 +7,15 @@ from homeassistant.bootstrap import (prepare_setup_platform, from homeassistant.const import ( ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, DEVICE_DEFAULT_NAME) +from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import get_component from homeassistant.helpers import config_per_platform, discovery -from homeassistant.helpers.entity import generate_entity_id -from homeassistant.helpers.event import track_utc_time_change +from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.helpers.service import extract_entity_ids +from homeassistant.util.async import ( + run_callback_threadsafe, run_coroutine_threadsafe) DEFAULT_SCAN_INTERVAL = 15 @@ -37,11 +40,11 @@ class EntityComponent(object): self.group = None self.config = None - self.lock = Lock() self._platforms = { 'core': EntityPlatform(self, self.scan_interval, None), } + self.async_add_entities = self._platforms['core'].async_add_entities self.add_entities = self._platforms['core'].add_entities def setup(self, config): @@ -50,20 +53,38 @@ class EntityComponent(object): Loads the platforms from the config and will listen for supported discovered platforms. """ + run_coroutine_threadsafe( + self.async_setup(config), self.hass.loop + ).result() + + @asyncio.coroutine + def async_setup(self, config): + """Set up a full entity component. + + Loads the platforms from the config and will listen for supported + discovered platforms. + + This method must be run in the event loop. + """ self.config = config # Look in config for Domain, Domain 2, Domain 3 etc and load them + tasks = [] for p_type, p_config in config_per_platform(config, self.domain): - self._setup_platform(p_type, p_config) + tasks.append(self._async_setup_platform(p_type, p_config)) + + yield from asyncio.gather(*tasks, loop=self.hass.loop) # Generic discovery listener for loading platform dynamically # Refer to: homeassistant.components.discovery.load_platform() + @callback def component_platform_discovered(platform, info): """Callback to load a platform.""" - self._setup_platform(platform, {}, info) + self.hass.loop.create_task( + self._async_setup_platform(platform, {}, info)) - discovery.listen_platform(self.hass, self.domain, - component_platform_discovered) + discovery.async_listen_platform( + self.hass, self.domain, component_platform_discovered) def extract_from_service(self, service): """Extract all known entities from a service call. @@ -71,19 +92,36 @@ class EntityComponent(object): 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: - return list(self.entities.values()) + return run_callback_threadsafe( + self.hass.loop, self.async_extract_from_service, service + ).result() - return [self.entities[entity_id] for entity_id - in extract_entity_ids(self.hass, service) - if entity_id in self.entities] + def async_extract_from_service(self, service): + """Extract all known entities from a service call. - def _setup_platform(self, platform_type, platform_config, - discovery_info=None): - """Setup a platform for this component.""" - platform = prepare_setup_platform( - self.hass, self.config, self.domain, platform_type) + Will return all entities if no entities specified in call. + Will return an empty list if entities specified but unknown. + + This method must be run in the event loop. + """ + if ATTR_ENTITY_ID not in service.data: + return list(self.entities.values()) + + return [self.entities[entity_id] for entity_id + in extract_entity_ids(self.hass, service) + if entity_id in self.entities] + + @asyncio.coroutine + def _async_setup_platform(self, platform_type, platform_config, + discovery_info=None): + """Setup a platform for this component. + + This method must be run in the event loop. + """ + platform = yield from self.hass.loop.run_in_executor( + None, prepare_setup_platform, self.hass, self.config, self.domain, + platform_type + ) if platform is None: return @@ -102,9 +140,16 @@ class EntityComponent(object): entity_platform = self._platforms[key] try: - platform.setup_platform(self.hass, platform_config, - entity_platform.add_entities, - discovery_info) + if getattr(platform, 'async_setup_platform', None): + yield from platform.async_setup_platform( + self.hass, platform_config, + entity_platform.async_add_entities, discovery_info + ) + else: + yield from self.hass.loop.run_in_executor( + None, platform.setup_platform, self.hass, platform_config, + entity_platform.add_entities, discovery_info + ) self.hass.config.components.append( '{}.{}'.format(self.domain, platform_type)) @@ -114,6 +159,16 @@ class EntityComponent(object): def add_entity(self, entity, platform=None): """Add entity to component.""" + return run_coroutine_threadsafe( + self.async_add_entity(entity, platform), self.hass.loop + ).result() + + @asyncio.coroutine + def async_add_entity(self, entity, platform=None): + """Add entity to component. + + This method must be run in the event loop. + """ if entity is None or entity in self.entities.values(): return False @@ -126,40 +181,60 @@ class EntityComponent(object): object_id = '{} {}'.format(platform.entity_namespace, object_id) - entity.entity_id = generate_entity_id( + entity.entity_id = async_generate_entity_id( self.entity_id_format, object_id, self.entities.keys()) self.entities[entity.entity_id] = entity - entity.update_ha_state() + yield from entity.async_update_ha_state() return True def update_group(self): """Set up and/or update component group.""" + run_callback_threadsafe( + self.hass.loop, self.async_update_group).result() + + @asyncio.coroutine + def async_update_group(self): + """Set up and/or update component group. + + This method must be run in the event loop. + """ if self.group is None and self.group_name is not None: group = get_component('group') - 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()) + self.group = yield from group.Group.async_create_group( + self.hass, self.group_name, self.entities.keys(), + user_defined=False + ) + elif self.group is not None: + yield from self.group.async_update_tracked_entity_ids( + self.entities.keys()) def reset(self): """Remove entities and reset the entity component to initial values.""" - with self.lock: - for platform in self._platforms.values(): - platform.reset() + run_coroutine_threadsafe(self.async_reset(), self.hass.loop).result() - self._platforms = { - 'core': self._platforms['core'] - } - self.entities = {} - self.config = None + @asyncio.coroutine + def async_reset(self): + """Remove entities and reset the entity component to initial values. - if self.group is not None: - self.group.stop() - self.group = None + This method must be run in the event loop. + """ + tasks = [platform.async_reset() for platform + in self._platforms.values()] + + yield from asyncio.gather(*tasks, loop=self.hass.loop) + + self._platforms = { + 'core': self._platforms['core'] + } + self.entities = {} + self.config = None + + if self.group is not None: + yield from self.group.async_stop() + self.group = None def prepare_reload(self): """Prepare reloading this entity component.""" @@ -178,9 +253,20 @@ class EntityComponent(object): self.reset() return conf + @asyncio.coroutine + def async_prepare_reload(self): + """Prepare reloading this entity component. + + This method must be run in the event loop. + """ + conf = yield from self.hass.loop.run_in_executor( + None, self.prepare_reload + ) + return conf + class EntityPlatform(object): - """Keep track of entities for a single platform.""" + """Keep track of entities for a single platform and stay in loop.""" # pylint: disable=too-few-public-methods def __init__(self, component, scan_interval, entity_namespace): @@ -189,41 +275,58 @@ class EntityPlatform(object): self.scan_interval = scan_interval self.entity_namespace = entity_namespace self.platform_entities = [] - self._unsub_polling = None + self._async_unsub_polling = None 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): - self.platform_entities.append(entity) + run_coroutine_threadsafe( + self.async_add_entities(new_entities), self.component.hass.loop + ).result() - self.component.update_group() + @asyncio.coroutine + def async_add_entities(self, new_entities): + """Add entities for a single platform async. - if self._unsub_polling is not None or \ - not any(entity.should_poll for entity - in self.platform_entities): - return + This method must be run in the event loop. + """ + for entity in new_entities: + ret = yield from self.component.async_add_entity(entity, self) + if ret: + self.platform_entities.append(entity) - self._unsub_polling = track_utc_time_change( - self.component.hass, self._update_entity_states, - second=range(0, 60, self.scan_interval)) + yield from self.component.async_update_group() - def reset(self): - """Remove all entities and reset data.""" - for entity in self.platform_entities: - entity.remove() - if self._unsub_polling is not None: - self._unsub_polling() - self._unsub_polling = None + if self._async_unsub_polling is not None or \ + not any(entity.should_poll for entity + in self.platform_entities): + return + self._async_unsub_polling = async_track_utc_time_change( + self.component.hass, self._update_entity_states, + second=range(0, 60, self.scan_interval)) + + @asyncio.coroutine + def async_reset(self): + """Remove all entities and reset data. + + This method must be run in the event loop. + """ + tasks = [entity.async_remove() for entity in self.platform_entities] + + yield from asyncio.gather(*tasks, loop=self.component.hass.loop) + + if self._async_unsub_polling is not None: + self._async_unsub_polling() + self._async_unsub_polling = None + + @callback 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) + """Update the states of all the polling entities. - for entity in entities: - entity.update_ha_state(True) + This method must be run in the event loop. + """ + for entity in self.platform_entities: + if entity.should_poll: + self.component.hass.loop.create_task( + entity.async_update_ha_state(True) + ) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 06df2eb992d..ccfeb707fea 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -98,6 +98,8 @@ def extract_entity_ids(hass, service_call): """Helper method to extract a list of entity ids from a service call. Will convert group entity ids to the entity ids it represents. + + Async friendly. """ if not (service_call.data and ATTR_ENTITY_ID in service_call.data): return [] diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py index ea4d997c2c3..f56d3967ba4 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/binary_sensor/test_nx584.py @@ -37,12 +37,6 @@ class TestNX584SensorSetup(unittest.TestCase): """Stop everything that was started.""" self._mock_client.stop() - def test_setup_no_config(self): - """Test the setup with no configuration.""" - hass = mock.MagicMock() - hass.pool.worker_count = 2 - assert setup_component(hass, 'binary_sensor', {'nx584': {}}) - @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') def test_setup_defaults(self, mock_nx, mock_watcher): diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index 01ce1cec518..769ba457dc5 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -9,11 +9,22 @@ from uvcclient import nvr from homeassistant.bootstrap import setup_component from homeassistant.components.camera import uvc +from tests.common import get_test_home_assistant class TestUVCSetup(unittest.TestCase): """Test the UVC camera platform.""" + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.hass.wsgi = mock.MagicMock() + self.hass.config.components = ['http'] + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + @mock.patch('uvcclient.nvr.UVCRemote') @mock.patch.object(uvc, 'UnifiVideoCamera') def test_setup_full_config(self, mock_uvc, mock_remote): @@ -37,14 +48,11 @@ class TestUVCSetup(unittest.TestCase): else: return {'model': 'UVC'} - hass = mock.MagicMock() - hass.pool.worker_count = 2 - hass.config.components = ['http'] mock_remote.return_value.index.return_value = fake_cameras mock_remote.return_value.get_camera.side_effect = fake_get_camera mock_remote.return_value.server_version = (3, 2, 0) - assert setup_component(hass, 'camera', {'camera': config}) + assert setup_component(self.hass, 'camera', {'camera': config}) mock_remote.assert_called_once_with('foo', 123, 'secret') mock_uvc.assert_has_calls([ @@ -65,14 +73,11 @@ class TestUVCSetup(unittest.TestCase): {'uuid': 'one', 'name': 'Front', 'id': 'id1'}, {'uuid': 'two', 'name': 'Back', 'id': 'id2'}, ] - hass = mock.MagicMock() - hass.pool.worker_count = 2 - hass.config.components = ['http'] mock_remote.return_value.index.return_value = fake_cameras mock_remote.return_value.get_camera.return_value = {'model': 'UVC'} mock_remote.return_value.server_version = (3, 2, 0) - assert setup_component(hass, 'camera', {'camera': config}) + assert setup_component(self.hass, 'camera', {'camera': config}) mock_remote.assert_called_once_with('foo', 7080, 'secret') mock_uvc.assert_has_calls([ @@ -93,14 +98,11 @@ class TestUVCSetup(unittest.TestCase): {'uuid': 'one', 'name': 'Front', 'id': 'id1'}, {'uuid': 'two', 'name': 'Back', 'id': 'id2'}, ] - hass = mock.MagicMock() - hass.pool.worker_count = 2 - hass.config.components = ['http'] mock_remote.return_value.index.return_value = fake_cameras mock_remote.return_value.get_camera.return_value = {'model': 'UVC'} mock_remote.return_value.server_version = (3, 1, 3) - assert setup_component(hass, 'camera', {'camera': config}) + assert setup_component(self.hass, 'camera', {'camera': config}) mock_remote.assert_called_once_with('foo', 7080, 'secret') mock_uvc.assert_has_calls([ @@ -111,18 +113,14 @@ class TestUVCSetup(unittest.TestCase): @mock.patch.object(uvc, 'UnifiVideoCamera') def test_setup_incomplete_config(self, mock_uvc): """"Test the setup with incomplete configuration.""" - hass = mock.MagicMock() - hass.pool.worker_count = 2 - hass.config.components = ['http'] - assert setup_component( - hass, 'camera', {'platform': 'uvc', 'nvr': 'foo'}) + self.hass, 'camera', {'platform': 'uvc', 'nvr': 'foo'}) assert not mock_uvc.called assert setup_component( - hass, 'camera', {'platform': 'uvc', 'key': 'secret'}) + self.hass, 'camera', {'platform': 'uvc', 'key': 'secret'}) assert not mock_uvc.called assert setup_component( - hass, 'camera', {'platform': 'uvc', 'port': 'invalid'}) + self.hass, 'camera', {'platform': 'uvc', 'port': 'invalid'}) assert not mock_uvc.called @mock.patch.object(uvc, 'UnifiVideoCamera') @@ -136,13 +134,9 @@ class TestUVCSetup(unittest.TestCase): 'nvr': 'foo', 'key': 'secret', } - hass = mock.MagicMock() - hass.pool.worker_count = 2 - hass.config.components = ['http'] - for error in errors: mock_remote.return_value.index.side_effect = error - assert setup_component(hass, 'camera', config) + assert setup_component(self.hass, 'camera', config) assert not mock_uvc.called diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 9a2de824e90..5fe14c6377e 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -4,7 +4,7 @@ from collections import OrderedDict import unittest from unittest.mock import patch -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN, ATTR_ASSUMED_STATE, STATE_NOT_HOME, ) @@ -28,7 +28,7 @@ class TestComponentsGroup(unittest.TestCase): """Try to setup a group with mixed groupable states.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('device_tracker.Paulus', STATE_HOME) - group.Group( + group.Group.create_group( self.hass, 'person_and_light', ['light.Bowl', 'device_tracker.Paulus']) @@ -41,7 +41,7 @@ class TestComponentsGroup(unittest.TestCase): """Try to setup a group with a non existing state.""" self.hass.states.set('light.Bowl', STATE_ON) - grp = group.Group( + grp = group.Group.create_group( self.hass, 'light_and_nothing', ['light.Bowl', 'non.existing']) @@ -52,7 +52,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.states.set('cast.living_room', "Plex") self.hass.states.set('cast.bedroom', "Netflix") - grp = group.Group( + grp = group.Group.create_group( self.hass, 'chromecasts', ['cast.living_room', 'cast.bedroom']) @@ -60,7 +60,7 @@ class TestComponentsGroup(unittest.TestCase): def test_setup_empty_group(self): """Try to setup an empty group.""" - grp = group.Group(self.hass, 'nothing', []) + grp = group.Group.create_group(self.hass, 'nothing', []) self.assertEqual(STATE_UNKNOWN, grp.state) @@ -68,7 +68,7 @@ class TestComponentsGroup(unittest.TestCase): """Test if the group keeps track of states.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) # Test if group setup in our init mode is ok @@ -82,7 +82,7 @@ class TestComponentsGroup(unittest.TestCase): """Test if turn off if the last device that was on turns off.""" self.hass.states.set('light.Bowl', STATE_OFF) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.hass.block_till_done() @@ -94,7 +94,7 @@ class TestComponentsGroup(unittest.TestCase): """Test if turn on if all devices were turned off and one turns on.""" self.hass.states.set('light.Bowl', STATE_OFF) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) # Turn one on @@ -108,7 +108,7 @@ class TestComponentsGroup(unittest.TestCase): """Test is_on method.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertTrue(group.is_on(self.hass, test_group.entity_id)) @@ -123,7 +123,7 @@ class TestComponentsGroup(unittest.TestCase): """Test expand_entity_ids method.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertEqual(sorted(['light.ceiling', 'light.bowl']), @@ -134,7 +134,7 @@ class TestComponentsGroup(unittest.TestCase): """Test that expand_entity_ids does not return duplicates.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertEqual( @@ -155,7 +155,7 @@ class TestComponentsGroup(unittest.TestCase): """Test get_entity_ids method.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertEqual( @@ -166,7 +166,7 @@ class TestComponentsGroup(unittest.TestCase): """Test if get_entity_ids works with a domain_filter.""" self.hass.states.set('switch.AC', STATE_OFF) - mixed_group = group.Group( + mixed_group = group.Group.create_group( self.hass, 'mixed_group', ['light.Bowl', 'switch.AC'], False) self.assertEqual( @@ -188,7 +188,7 @@ class TestComponentsGroup(unittest.TestCase): If no states existed and now a state it is tracking is being added as ON. """ - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'test group', ['light.not_there_1']) self.hass.states.set('light.not_there_1', STATE_ON) @@ -204,7 +204,7 @@ class TestComponentsGroup(unittest.TestCase): If no states existed and now a state it is tracking is being added as OFF. """ - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'test group', ['light.not_there_1']) self.hass.states.set('light.not_there_1', STATE_OFF) @@ -218,7 +218,7 @@ class TestComponentsGroup(unittest.TestCase): """Test setup method.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) group_conf = OrderedDict() @@ -230,7 +230,7 @@ class TestComponentsGroup(unittest.TestCase): group_conf['test_group'] = 'hello.world,sensor.happy' group_conf['empty_group'] = {'name': 'Empty Group', 'entities': None} - _setup_component(self.hass, 'group', {'group': group_conf}) + setup_component(self.hass, 'group', {'group': group_conf}) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) @@ -257,17 +257,19 @@ class TestComponentsGroup(unittest.TestCase): def test_groups_get_unique_names(self): """Two groups with same name should both have a unique entity id.""" - grp1 = group.Group(self.hass, 'Je suis Charlie') - grp2 = group.Group(self.hass, 'Je suis Charlie') + grp1 = group.Group.create_group(self.hass, 'Je suis Charlie') + grp2 = group.Group.create_group(self.hass, 'Je suis Charlie') self.assertNotEqual(grp1.entity_id, grp2.entity_id) def test_expand_entity_ids_expands_nested_groups(self): """Test if entity ids epands to nested groups.""" - group.Group(self.hass, 'light', ['light.test_1', 'light.test_2']) - group.Group(self.hass, 'switch', ['switch.test_1', 'switch.test_2']) - group.Group(self.hass, 'group_of_groups', ['group.light', - 'group.switch']) + group.Group.create_group( + self.hass, 'light', ['light.test_1', 'light.test_2']) + group.Group.create_group( + self.hass, 'switch', ['switch.test_1', 'switch.test_2']) + group.Group.create_group(self.hass, 'group_of_groups', ['group.light', + 'group.switch']) self.assertEqual( ['light.test_1', 'light.test_2', 'switch.test_1', 'switch.test_2'], @@ -278,7 +280,7 @@ class TestComponentsGroup(unittest.TestCase): """Test assumed state.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( + test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling', 'sensor.no_exist']) @@ -304,7 +306,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.states.set('device_tracker.Adam', STATE_HOME) self.hass.states.set('device_tracker.Eve', STATE_NOT_HOME) self.hass.block_till_done() - group.Group( + group.Group.create_group( self.hass, 'peeps', ['device_tracker.Adam', 'device_tracker.Eve']) self.hass.states.set('device_tracker.Adam', 'cool_state_not_home') @@ -315,7 +317,7 @@ class TestComponentsGroup(unittest.TestCase): def test_reloading_groups(self): """Test reloading the group config.""" - _setup_component(self.hass, 'group', {'group': { + assert setup_component(self.hass, 'group', {'group': { 'second_group': { 'entities': 'light.Bowl', 'icon': 'mdi:work', @@ -342,3 +344,11 @@ class TestComponentsGroup(unittest.TestCase): assert self.hass.states.entity_ids() == ['group.hello'] assert self.hass.bus.listeners['state_changed'] == 1 + + def test_stopping_a_group(self): + """Test that a group correctly removes itself.""" + grp = group.Group.create_group( + self.hass, 'light', ['light.test_1', 'light.test_2']) + assert self.hass.states.entity_ids() == ['group.light'] + grp.stop() + assert self.hass.states.entity_ids() == [] diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 0ab87c57452..6f658a70518 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -68,46 +68,46 @@ class TestHelpersEntityComponent(unittest.TestCase): group_name='everyone') # No group after setup - assert 0 == len(self.hass.states.entity_ids()) + assert len(self.hass.states.entity_ids()) == 0 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') + assert len(self.hass.states.entity_ids()) == 2 + assert self.hass.states.entity_ids('group') == ['group.everyone'] group = self.hass.states.get('group.everyone') - assert ('test_domain.hello',) == group.attributes.get('entity_id') + assert group.attributes.get('entity_id') == ('test_domain.hello',) # group extended component.add_entities([EntityTest(name='hello2')]) - assert 3 == len(self.hass.states.entity_ids()) + assert len(self.hass.states.entity_ids()) == 3 group = self.hass.states.get('group.everyone') - assert ['test_domain.hello', 'test_domain.hello2'] == \ - sorted(group.attributes.get('entity_id')) + assert sorted(group.attributes.get('entity_id')) == \ + ['test_domain.hello', 'test_domain.hello2'] def test_polling_only_updates_entities_it_should_poll(self): """Test the polling of only updated entities.""" component = EntityComponent(_LOGGER, DOMAIN, self.hass, 20) no_poll_ent = EntityTest(should_poll=False) - no_poll_ent.update_ha_state = Mock() + no_poll_ent.async_update = Mock() poll_ent = EntityTest(should_poll=True) - poll_ent.update_ha_state = Mock() + poll_ent.async_update = Mock() component.add_entities([no_poll_ent, poll_ent]) - no_poll_ent.update_ha_state.reset_mock() - poll_ent.update_ha_state.reset_mock() + no_poll_ent.async_update.reset_mock() + poll_ent.async_update.reset_mock() fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) self.hass.block_till_done() - assert not no_poll_ent.update_ha_state.called - assert poll_ent.update_ha_state.called + assert not no_poll_ent.async_update.called + assert poll_ent.async_update.called def test_update_state_adds_entities(self): """Test if updating poll entities cause an entity to be added works.""" @@ -118,7 +118,7 @@ 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]) + ent2.update = lambda *_: component.add_entities([ent1]) fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) self.hass.block_till_done() @@ -225,7 +225,7 @@ class TestHelpersEntityComponent(unittest.TestCase): assert platform2_setup.called @patch('homeassistant.helpers.entity_component.EntityComponent' - '._setup_platform') + '._async_setup_platform') @patch('homeassistant.bootstrap.setup_component', return_value=True) def test_setup_does_discovery(self, mock_setup_component, mock_setup): """Test setup for discovery.""" @@ -242,7 +242,8 @@ class TestHelpersEntityComponent(unittest.TestCase): assert ('platform_test', {}, {'msg': 'discovery_info'}) == \ mock_setup.call_args[0] - @patch('homeassistant.helpers.entity_component.track_utc_time_change') + @patch('homeassistant.helpers.entity_component.' + 'async_track_utc_time_change') def test_set_scan_interval_via_config(self, mock_track): """Test the setting of the scan interval via configuration.""" def platform_setup(hass, config, add_devices, discovery_info=None): @@ -264,7 +265,8 @@ class TestHelpersEntityComponent(unittest.TestCase): assert mock_track.called assert [0, 30] == list(mock_track.call_args[1]['second']) - @patch('homeassistant.helpers.entity_component.track_utc_time_change') + @patch('homeassistant.helpers.entity_component.' + 'async_track_utc_time_change') def test_set_scan_interval_via_platform(self, mock_track): """Test the setting of the scan interval via platform.""" def platform_setup(hass, config, add_devices, discovery_info=None): diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 38af2178340..efe21f95d9b 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -139,7 +139,7 @@ class TestServiceHelpers(unittest.TestCase): self.hass.states.set('light.Ceiling', STATE_OFF) self.hass.states.set('light.Kitchen', STATE_OFF) - loader.get_component('group').Group( + loader.get_component('group').Group.create_group( self.hass, 'test', ['light.Ceiling', 'light.Kitchen']) call = ha.ServiceCall('light', 'turn_on', diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 527d99df39e..f59c5405683 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -402,7 +402,8 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude, }) - group.Group(self.hass, 'location group', ['test_domain.object']) + group.Group.create_group( + self.hass, 'location group', ['test_domain.object']) self.assertEqual( 'test_domain.object', @@ -422,7 +423,8 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude, }) - group.Group(self.hass, 'location group', ['test_domain.object']) + group.Group.create_group( + self.hass, 'location group', ['test_domain.object']) self.assertEqual( 'test_domain.object', From 2612c6d6b891008bcd432603500bad106551e49b Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sun, 16 Oct 2016 14:35:39 -0400 Subject: [PATCH 079/147] Zwave alt delay workaround for HS-WD100+ (#3893) --- homeassistant/components/light/zwave.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index f71dd158f61..346482d3ff6 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -40,6 +40,10 @@ ACT = 0x01 ACT_ZDP100_DIMMER = 0x3030 ACT_ZDP100_DIMMER_LIGHT = (ACT, ACT_ZDP100_DIMMER) +HOMESEER = 0x0c +HOMESEER_WD100_DIMMER = 0x3034 +HOMESEER_WD100_DIMMER_LIGHT = (HOMESEER, HOMESEER_WD100_DIMMER) + COLOR_CHANNEL_WARM_WHITE = 0x01 COLOR_CHANNEL_COLD_WHITE = 0x02 COLOR_CHANNEL_RED = 0x04 @@ -54,7 +58,8 @@ DEVICE_MAPPINGS = { LINEAR_WD500Z_DIMMER_LIGHT: WORKAROUND_DELAY, GE_12724_DIMMER_LIGHT: WORKAROUND_DELAY, DRAGONTECH_PD100_DIMMER_LIGHT: WORKAROUND_DELAY, - ACT_ZDP100_DIMMER_LIGHT: WORKAROUND_DELAY + ACT_ZDP100_DIMMER_LIGHT: WORKAROUND_DELAY, + HOMESEER_WD100_DIMMER_LIGHT: WORKAROUND_DELAY, } # Generate midpoint color temperatures for bulbs that have limited From 8b2edc151441eec196bbe5623386f555d623f9c7 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Sun, 16 Oct 2016 20:36:06 +0200 Subject: [PATCH 080/147] ZWave: Add association service (#3894) * Add association service * Refactor service * Requested changes * Grammar in pydocstyle --- homeassistant/components/zwave/__init__.py | 33 ++++++++++++++++++++ homeassistant/components/zwave/const.py | 5 +++ homeassistant/components/zwave/services.yaml | 14 +++++++++ 3 files changed, 52 insertions(+) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 542be2241cc..93802487f25 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -132,6 +132,13 @@ SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({ vol.Required(const.ATTR_CONFIG_VALUE): vol.Coerce(int), vol.Optional(const.ATTR_CONFIG_SIZE): vol.Coerce(int) }) +CHANGE_ASSOCIATION_SCHEMA = vol.Schema({ + vol.Required(const.ATTR_ASSOCIATION): cv.string, + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Required(const.ATTR_TARGET_NODE_ID): vol.Coerce(int), + vol.Required(const.ATTR_GROUP): vol.Coerce(int), + vol.Optional(const.ATTR_INSTANCE, default=0x00): vol.Coerce(int) +}) CUSTOMIZE_SCHEMA = vol.Schema({ vol.Optional(CONF_POLLING_INTENSITY): @@ -240,6 +247,7 @@ def setup(hass, config): from pydispatch import dispatcher from openzwave.option import ZWaveOption from openzwave.network import ZWaveNetwork + from openzwave.group import ZWaveGroup default_zwave_config_path = os.path.join(os.path.dirname( libopenzwave.__file__), 'config') @@ -445,6 +453,26 @@ def setup(hass, config): _LOGGER.info("Setting config parameter %s on Node %s " "with value %s and size=%s", param, node_id, value, size) + def change_association(service): + """Change an association in the zwave network.""" + association_type = service.data.get(const.ATTR_ASSOCIATION) + node_id = service.data.get(const.ATTR_NODE_ID) + target_node_id = service.data.get(const.ATTR_TARGET_NODE_ID) + group = service.data.get(const.ATTR_GROUP) + instance = service.data.get(const.ATTR_INSTANCE) + + node = ZWaveGroup(group, NETWORK, node_id) + if association_type == 'add': + node.add_association(target_node_id, instance) + _LOGGER.info("Adding association for node:%s in group:%s " + "target node:%s, instance=%s", node_id, group, + target_node_id, instance) + if association_type == 'remove': + node.remove_association(target_node_id, instance) + _LOGGER.info("Removing association for node:%s in group:%s " + "target node:%s, instance=%s", node_id, group, + target_node_id, instance) + def start_zwave(_service_or_event): """Startup Z-Wave network.""" _LOGGER.info("Starting ZWave network.") @@ -510,6 +538,11 @@ def setup(hass, config): descriptions[ const.SERVICE_SET_CONFIG_PARAMETER], schema=SET_CONFIG_PARAMETER_SCHEMA) + hass.services.register(DOMAIN, const.SERVICE_CHANGE_ASSOCIATION, + change_association, + descriptions[ + const.SERVICE_CHANGE_ASSOCIATION], + schema=CHANGE_ASSOCIATION_SCHEMA) # Setup autoheal if autoheal: diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index 6b5c5fc55e5..698dad8e063 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -1,6 +1,10 @@ """Z-Wave Constants.""" ATTR_NODE_ID = "node_id" +ATTR_TARGET_NODE_ID = "target_node_id" +ATTR_ASSOCIATION = "association" +ATTR_INSTANCE = "instance" +ATTR_GROUP = "group" ATTR_VALUE_ID = "value_id" ATTR_OBJECT_ID = "object_id" ATTR_NAME = "name" @@ -11,6 +15,7 @@ ATTR_CONFIG_SIZE = "size" ATTR_CONFIG_VALUE = "value" NETWORK_READY_WAIT_SECS = 30 +SERVICE_CHANGE_ASSOCIATION = "change_association" SERVICE_ADD_NODE = "add_node" SERVICE_ADD_NODE_SECURE = "add_node_secure" SERVICE_REMOVE_NODE = "remove_node" diff --git a/homeassistant/components/zwave/services.yaml b/homeassistant/components/zwave/services.yaml index 7a73cd66fb3..cfe2edab5c9 100644 --- a/homeassistant/components/zwave/services.yaml +++ b/homeassistant/components/zwave/services.yaml @@ -1,3 +1,17 @@ +change_association: + description: Change an association in the Z-Wave network. + fields: + association: + description: Specify add or remove assosication + node_id: + description: Node id of the node to set association for. + target_node_id: + description: Node id of the node to associate to. + group: + description: Group number to set association for. + instance: + description: (Optional) Instance of association. Defaults to 0. + add_node: description: Add a new node to the Z-Wave network. Refer to OZW.log for details. From 31ec0ac6a7a274776c2f62bc10cb9ff85583359c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Oct 2016 13:35:01 -0700 Subject: [PATCH 081/147] Add util.async to the dev docs --- docs/source/api/util.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/api/util.rst b/docs/source/api/util.rst index 7d6a22dbc0b..e31a1c98129 100644 --- a/docs/source/api/util.rst +++ b/docs/source/api/util.rst @@ -4,6 +4,14 @@ homeassistant.util package Submodules ---------- +homeassistant.util.async module +------------------------------- + +.. automodule:: homeassistant.util.async + :members: + :undoc-members: + :show-inheritance: + homeassistant.util.color module ------------------------------- From 6581dc2381e1d51122e9ff091cbcd0dca2921c66 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Oct 2016 13:45:17 -0700 Subject: [PATCH 082/147] Document more core pieces --- docs/source/api/core.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/source/api/core.rst b/docs/source/api/core.rst index a32bdc24d11..bbaf591052c 100644 --- a/docs/source/api/core.rst +++ b/docs/source/api/core.rst @@ -8,11 +8,31 @@ .. autoclass:: Config :members: +.. autoclass:: Event + :members: + .. autoclass:: EventBus :members: +.. autoclass:: HomeAssistant + :members: + +.. autoclass:: State + :members: + .. autoclass:: StateMachine :members: +.. autoclass:: ServiceCall + :members: + .. autoclass:: ServiceRegistry :members: + +Module contents +--------------- + +.. automodule:: homeassistant.core + :members: + :undoc-members: + :show-inheritance: From 7484152be132e1e0c70661d119676bf2ec12991b Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 17 Oct 2016 00:35:57 +0200 Subject: [PATCH 083/147] Async speedup add_device callback (#3910) * Speed up entities processing from add_device callback * fix lint * fix bug --- homeassistant/helpers/entity_component.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index e2e25bcfbd3..2576970065f 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -289,11 +289,9 @@ class EntityPlatform(object): This method must be run in the event loop. """ - for entity in new_entities: - ret = yield from self.component.async_add_entity(entity, self) - if ret: - self.platform_entities.append(entity) + tasks = [self._async_process_entity(entity) for entity in new_entities] + yield from asyncio.gather(*tasks, loop=self.component.hass.loop) yield from self.component.async_update_group() if self._async_unsub_polling is not None or \ @@ -305,6 +303,13 @@ class EntityPlatform(object): self.component.hass, self._update_entity_states, second=range(0, 60, self.scan_interval)) + @asyncio.coroutine + def _async_process_entity(self, new_entity): + """Add entities to StateMachine.""" + ret = yield from self.component.async_add_entity(new_entity, self) + if ret: + self.platform_entities.append(new_entity) + @asyncio.coroutine def async_reset(self): """Remove all entities and reset data. From 2a972b233425f3b597312088e880e21845c7dfc3 Mon Sep 17 00:00:00 2001 From: Justin Weberg Date: Sun, 16 Oct 2016 17:47:34 -0500 Subject: [PATCH 084/147] Move micromarkdown to HA (#3908) * Move micromarkdown to HA * Fix requests * Update micromarkdown-js.html& .gz * Update micromarkdown-js files --- .../frontend/www_static/micromarkdown-js.html | 10 ++++++++++ .../frontend/www_static/micromarkdown-js.html.gz | Bin 0 -> 2563 bytes 2 files changed, 10 insertions(+) create mode 100644 homeassistant/components/frontend/www_static/micromarkdown-js.html create mode 100644 homeassistant/components/frontend/www_static/micromarkdown-js.html.gz diff --git a/homeassistant/components/frontend/www_static/micromarkdown-js.html b/homeassistant/components/frontend/www_static/micromarkdown-js.html new file mode 100644 index 00000000000..f9ed2ea7cbb --- /dev/null +++ b/homeassistant/components/frontend/www_static/micromarkdown-js.html @@ -0,0 +1,10 @@ +/* * * * * * * * * * * * + * micromarkdown .js * + * Version 0.3.0 * + * License: MIT * + * Simon Waldherr * + * * * * * * * * * * * */ + +dJITdNLUhn9FX6B-7^A7HaN~*xq{Tw-P4ch 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 ZQH}WDvi`}yRR7y?@?VSy-M*w50064B>YxAs literal 0 HcmV?d00001 From d9ae7ceb0c39263926b115fc188e976dad385b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Mon, 17 Oct 2016 00:56:55 +0200 Subject: [PATCH 085/147] Tellstick switch force update (#3874) --- homeassistant/components/switch/tellstick.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index a0cc4294b23..e5134c07a34 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -61,3 +61,8 @@ class TellstickSwitchDevice(tellstick.TellstickDevice, ToggleEntity): """Turn the switch off.""" from tellcore.constants import TELLSTICK_TURNOFF self.call_tellstick(TELLSTICK_TURNOFF) + + @property + def force_update(self) -> bool: + """Will trigger anytime the state property is updated.""" + return True From 71ee847aee84aab95a17c326ff47f7007cb992bc Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 17 Oct 2016 01:06:07 +0200 Subject: [PATCH 086/147] Add web scrape sensor (#3841) * Add web scrape sensor * Add support for 'value_template', set 'verify_ssl' to true, and remove 'before', 'after' & 'element' * Fix pylint issue --- .coveragerc | 1 + homeassistant/components/sensor/scrape.py | 109 ++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 113 insertions(+) create mode 100644 homeassistant/components/sensor/scrape.py diff --git a/.coveragerc b/.coveragerc index 340eccd22a4..c76edce73fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -261,6 +261,7 @@ omit = homeassistant/components/sensor/plex.py homeassistant/components/sensor/rest.py homeassistant/components/sensor/sabnzbd.py + homeassistant/components/sensor/scrape.py homeassistant/components/sensor/serial_pm.py homeassistant/components/sensor/snmp.py homeassistant/components/sensor/speedtest.py diff --git a/homeassistant/components/sensor/scrape.py b/homeassistant/components/sensor/scrape.py new file mode 100644 index 00000000000..4789703d051 --- /dev/null +++ b/homeassistant/components/sensor/scrape.py @@ -0,0 +1,109 @@ +""" +Support for getting data from websites with scraping. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.scrape/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor.rest import RestData +from homeassistant.const import ( + CONF_NAME, CONF_RESOURCE, CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, + CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL) +from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['beautifulsoup4==4.5.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_SELECT = 'select' + +DEFAULT_NAME = 'Web scrape' +DEFAULT_VERIFY_SSL = True + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_RESOURCE): cv.string, + vol.Required(CONF_SELECT): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, +}) + + +# pylint: disable=too-many-locals +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Web scrape sensor.""" + name = config.get(CONF_NAME) + resource = config.get(CONF_RESOURCE) + method = 'GET' + payload = auth = headers = None + verify_ssl = config.get(CONF_VERIFY_SSL) + select = config.get(CONF_SELECT) + unit = config.get(CONF_UNIT_OF_MEASUREMENT) + value_template = config.get(CONF_VALUE_TEMPLATE) + if value_template is not None: + value_template.hass = hass + + rest = RestData(method, resource, auth, headers, payload, verify_ssl) + rest.update() + + if rest.data is None: + _LOGGER.error("Unable to fetch data from %s", resource) + return False + + add_devices([ + ScrapeSensor(hass, rest, name, select, value_template, unit) + ]) + + +# pylint: disable=too-many-instance-attributes +class ScrapeSensor(Entity): + """Representation of a web scrape sensor.""" + + # pylint: disable=too-many-arguments + def __init__(self, hass, rest, name, select, value_template, unit): + """Initialize a web scrape sensor.""" + self.rest = rest + self._name = name + self._state = STATE_UNKNOWN + self._select = select + self._value_template = value_template + self._unit_of_measurement = unit + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + @property + def state(self): + """Return the state of the device.""" + return self._state + + def update(self): + """Get the latest data from the source and updates the state.""" + self.rest.update() + + from bs4 import BeautifulSoup + + raw_data = BeautifulSoup(self.rest.data, 'html.parser') + _LOGGER.debug(raw_data) + value = raw_data.select(self._select)[0].text + _LOGGER.debug(value) + + if self._value_template is not None: + self._state = self._value_template.render_with_possible_json_value( + value, STATE_UNKNOWN) + else: + self._state = value diff --git a/requirements_all.txt b/requirements_all.txt index 19ec23a6026..c73dc336278 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -41,6 +41,9 @@ astral==1.2 # homeassistant.components.sensor.linux_battery batinfo==0.3 +# homeassistant.components.sensor.scrape +beautifulsoup4==4.5.1 + # homeassistant.components.light.blinksticklight blinkstick==1.1.8 From 10c9132046ef117427698259ea5e19bbc12616eb Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Mon, 17 Oct 2016 00:08:12 +0100 Subject: [PATCH 087/147] Resolve issue with delay not passing variables to render (#3901) --- homeassistant/helpers/script.py | 2 +- tests/helpers/test_script.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index a5869915d46..f6a2f482fc1 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -94,7 +94,7 @@ class Script(): delay = vol.All( cv.time_period, cv.positive_timedelta)( - delay.async_render()) + delay.async_render(variables)) self._async_unsub_delay_listener = \ async_track_point_in_utc_time( diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index b4febc83048..93bf0268337 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -222,7 +222,7 @@ class TestScriptHelper(unittest.TestCase): 'hello': '{{ greeting }}', }, }, - {'delay': {'seconds': 5}}, + {'delay': '{{ delay_period }}'}, { 'service': 'test.script', 'data_template': { @@ -233,6 +233,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run({ 'greeting': 'world', 'greeting2': 'universe', + 'delay_period': '00:00:05' }) self.hass.block_till_done() From 4891ca161027f99f8dcc2d8053aa0769894525ed Mon Sep 17 00:00:00 2001 From: Rob Capellini Date: Sun, 16 Oct 2016 19:13:27 -0400 Subject: [PATCH 088/147] Removing calls to mock.assert_called_once_with (#3896) If a mock's assert_called_once_with method is misspelled (e.g. asert_called_once_with) then the test will appear as passing. Therefore, this commit removes all instances of assert_called_once_with calls and replaces them with two assertions: self.assertEqual(mock.call_count, 1) self.assertEqual(mock.call_args, mock.call(call_args)) --- tests/components/binary_sensor/test_nx584.py | 13 +++- .../components/binary_sensor/test_template.py | 14 +++- tests/components/camera/test_uvc.py | 56 ++++++++++---- tests/components/climate/test_honeywell.py | 28 +++++-- tests/components/cover/test_command_line.py | 5 +- .../components/device_tracker/test_asuswrt.py | 20 +++-- tests/components/device_tracker/test_init.py | 8 +- tests/components/device_tracker/test_unifi.py | 29 +++++-- tests/components/media_player/test_sonos.py | 12 ++- .../rollershutter/test_command_line.py | 5 +- tests/components/sensor/test_mfi.py | 30 ++++++-- tests/components/switch/test_mfi.py | 9 ++- tests/components/test_graphite.py | 76 ++++++++++++++----- tests/components/test_influxdb.py | 34 +++++++-- tests/components/test_logentries.py | 7 +- tests/components/test_splunk.py | 11 ++- tests/components/test_statsd.py | 32 +++++--- tests/components/thermostat/test_honeywell.py | 28 +++++-- 18 files changed, 308 insertions(+), 109 deletions(-) diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py index f56d3967ba4..71efd1ff1b2 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/binary_sensor/test_nx584.py @@ -53,7 +53,10 @@ class TestNX584SensorSetup(unittest.TestCase): mock_nx.assert_has_calls( [mock.call(zone, 'opening') for zone in self.fake_zones]) self.assertTrue(add_devices.called) - nx584_client.Client.assert_called_once_with('http://localhost:5007') + self.assertEqual(nx584_client.Client.call_count, 1) + self.assertEqual( + nx584_client.Client.call_args, mock.call('http://localhost:5007') + ) @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') @@ -73,7 +76,10 @@ class TestNX584SensorSetup(unittest.TestCase): mock.call(self.fake_zones[2], 'motion'), ]) self.assertTrue(add_devices.called) - nx584_client.Client.assert_called_once_with('http://foo:123') + self.assertEqual(nx584_client.Client.call_count, 1) + self.assertEqual( + nx584_client.Client.call_args, mock.call('http://foo:123') + ) self.assertTrue(mock_watcher.called) def _test_assert_graceful_fail(self, config): @@ -174,7 +180,8 @@ class TestNX584Watcher(unittest.TestCase): def run(fake_process): fake_process.side_effect = StopMe self.assertRaises(StopMe, watcher._run) - fake_process.assert_called_once_with(fake_events[0]) + self.assertEqual(fake_process.call_count, 1) + self.assertEqual(fake_process.call_args, mock.call(fake_events[0])) run() self.assertEqual(3, client.get_events.call_count) diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index 3a46cb8c3b0..28098b2f2a0 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -44,9 +44,17 @@ class TestBinarySensorTemplate(unittest.TestCase): add_devices = mock.MagicMock() result = template.setup_platform(self.hass, config, add_devices) self.assertTrue(result) - mock_template.assert_called_once_with( - self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test') - add_devices.assert_called_once_with([mock_template.return_value]) + self.assertEqual(mock_template.call_count, 1) + self.assertEqual( + mock_template.call_args, + mock.call( + self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test' + ) + ) + self.assertEqual(add_devices.call_count, 1) + self.assertEqual( + add_devices.call_args, mock.call([mock_template.return_value]) + ) def test_setup_no_sensors(self): """"Test setup with no sensors.""" diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index 769ba457dc5..5addb3266c3 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -54,7 +54,10 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - mock_remote.assert_called_once_with('foo', 123, 'secret') + self.assertEqual(mock_remote.call_count, 1) + self.assertEqual( + mock_remote.call_args, mock.call('foo', 123, 'secret') + ) mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'id1', 'Front'), mock.call(mock_remote.return_value, 'id2', 'Back'), @@ -79,7 +82,10 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - mock_remote.assert_called_once_with('foo', 7080, 'secret') + self.assertEqual(mock_remote.call_count, 1) + self.assertEqual( + mock_remote.call_args, mock.call('foo', 7080, 'secret') + ) mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'id1', 'Front'), mock.call(mock_remote.return_value, 'id2', 'Back'), @@ -104,7 +110,10 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - mock_remote.assert_called_once_with('foo', 7080, 'secret') + self.assertEqual(mock_remote.call_count, 1) + self.assertEqual( + mock_remote.call_args, mock.call('foo', 7080, 'secret') + ) mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'one', 'Front'), mock.call(mock_remote.return_value, 'two', 'Back'), @@ -173,8 +182,12 @@ class TestUVC(unittest.TestCase): """"Test the login.""" mock_store.return_value.get_camera_password.return_value = 'seekret' self.uvc._login() - mock_camera.assert_called_once_with('host-a', 'admin', 'seekret') - mock_camera.return_value.login.assert_called_once_with() + self.assertEqual(mock_camera.call_count, 1) + self.assertEqual( + mock_camera.call_args, mock.call('host-a', 'admin', 'seekret') + ) + self.assertEqual(mock_camera.return_value.login.call_count, 1) + self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClient') @@ -183,8 +196,12 @@ class TestUVC(unittest.TestCase): mock_store.return_value.get_camera_password.return_value = 'seekret' self.nvr.server_version = (3, 1, 3) self.uvc._login() - mock_camera.assert_called_once_with('host-a', 'admin', 'seekret') - mock_camera.return_value.login.assert_called_once_with() + self.assertEqual(mock_camera.call_count, 1) + self.assertEqual( + mock_camera.call_args, mock.call('host-a', 'admin', 'seekret') + ) + self.assertEqual(mock_camera.return_value.login.call_count, 1) + self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') @@ -192,8 +209,12 @@ class TestUVC(unittest.TestCase): """"Test the login with no password.""" mock_store.return_value.get_camera_password.return_value = None self.uvc._login() - mock_camera.assert_called_once_with('host-a', 'admin', 'ubnt') - mock_camera.return_value.login.assert_called_once_with() + self.assertEqual(mock_camera.call_count, 1) + self.assertEqual( + mock_camera.call_args, mock.call('host-a', 'admin', 'ubnt') + ) + self.assertEqual(mock_camera.return_value.login.call_count, 1) + self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') @@ -216,8 +237,12 @@ class TestUVC(unittest.TestCase): mock_camera.reset_mock() self.uvc._login() - mock_camera.assert_called_once_with('host-b', 'admin', 'ubnt') - mock_camera.return_value.login.assert_called_once_with() + self.assertEqual(mock_camera.call_count, 1) + self.assertEqual( + mock_camera.call_args, mock.call('host-b', 'admin', 'ubnt') + ) + self.assertEqual(mock_camera.return_value.login.call_count, 1) + self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') @@ -232,7 +257,8 @@ class TestUVC(unittest.TestCase): with mock.patch.object(self.uvc, '_login') as mock_login: mock_login.return_value = False self.assertEqual(None, self.uvc.camera_image()) - mock_login.assert_called_once_with() + self.assertEqual(mock_login.call_count, 1) + self.assertEqual(mock_login.call_args, mock.call()) def test_camera_image_logged_in(self): """"Test the login state.""" @@ -262,7 +288,8 @@ class TestUVC(unittest.TestCase): self.uvc._camera.get_snapshot.side_effect = fake_snapshot with mock.patch.object(self.uvc, '_login') as mock_login: self.assertEqual('image', self.uvc.camera_image()) - mock_login.assert_called_once_with() + self.assertEqual(mock_login.call_count, 1) + self.assertEqual(mock_login.call_args, mock.call()) self.assertEqual([], responses) def test_camera_image_reauths_only_once(self): @@ -271,4 +298,5 @@ class TestUVC(unittest.TestCase): self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError with mock.patch.object(self.uvc, '_login') as mock_login: self.assertRaises(camera.CameraAuthError, self.uvc.camera_image) - mock_login.assert_called_once_with() + self.assertEqual(mock_login.call_count, 1) + self.assertEqual(mock_login.call_args, mock.call()) diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py index 6f4888ef3e5..13d7eb65257 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/climate/test_honeywell.py @@ -62,7 +62,8 @@ class TestHoneywell(unittest.TestCase): result = honeywell.setup_platform(hass, config, add_devices) self.assertTrue(result) - mock_sc.assert_called_once_with('user', 'pass') + self.assertEqual(mock_sc.call_count, 1) + self.assertEqual(mock_sc.call_args, mock.call('user', 'pass')) mock_ht.assert_has_calls([ mock.call(mock_sc.return_value, devices_1[0]), mock.call(mock_sc.return_value, devices_2[0]), @@ -174,9 +175,13 @@ class TestHoneywell(unittest.TestCase): hass = mock.MagicMock() add_devices = mock.MagicMock() self.assertTrue(honeywell.setup_platform(hass, config, add_devices)) - mock_evo.assert_called_once_with('user', 'pass') - mock_evo.return_value.temperatures.assert_called_once_with( - force_refresh=True) + self.assertEqual(mock_evo.call_count, 1) + self.assertEqual(mock_evo.call_args, mock.call('user', 'pass')) + self.assertEqual(mock_evo.return_value.temperatures.call_count, 1) + self.assertEqual( + mock_evo.return_value.temperatures.call_args, + mock.call(force_refresh=True) + ) mock_round.assert_has_calls([ mock.call(mock_evo.return_value, 'foo', True, 20.0), mock.call(mock_evo.return_value, 'bar', False, 20.0), @@ -280,17 +285,26 @@ class TestHoneywellRound(unittest.TestCase): self.assertFalse(self.round1.is_away_mode_on) self.round1.turn_away_mode_on() self.assertTrue(self.round1.is_away_mode_on) - self.device.set_temperature.assert_called_once_with('House', 16) + self.assertEqual(self.device.set_temperature.call_count, 1) + self.assertEqual( + self.device.set_temperature.call_args, mock.call('House', 16) + ) self.device.set_temperature.reset_mock() self.round1.turn_away_mode_off() self.assertFalse(self.round1.is_away_mode_on) - self.device.cancel_temp_override.assert_called_once_with('House') + self.assertEqual(self.device.cancel_temp_override.call_count, 1) + self.assertEqual( + self.device.cancel_temp_override.call_args, mock.call('House') + ) def test_set_temperature(self): """Test setting the temperature.""" self.round1.set_temperature(temperature=25) - self.device.set_temperature.assert_called_once_with('House', 25) + self.assertEqual(self.device.set_temperature.call_count, 1) + self.assertEqual( + self.device.set_temperature.call_args, mock.call('House', 25) + ) def test_set_operation_mode(self: unittest.TestCase) -> None: """Test setting the system operation.""" diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py index f687094a038..9d1552b2e73 100644 --- a/tests/components/cover/test_command_line.py +++ b/tests/components/cover/test_command_line.py @@ -40,7 +40,10 @@ class TestCommandCover(unittest.TestCase): mock_run.return_value = b' foo bar ' result = self.rs._query_state_value('runme') self.assertEqual('foo bar', result) - mock_run.assert_called_once_with('runme', shell=True) + self.assertEqual(mock_run.call_count, 1) + self.assertEqual( + mock_run.call_args, mock.call('runme', shell=True) + ) def test_state_value(self): """Test with state value.""" diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index 9ea3ae4dec6..480c76d52b3 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -79,7 +79,8 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): conf_dict[DOMAIN][CONF_MODE] = 'router' conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' - asuswrt_mock.assert_called_once_with(conf_dict[DOMAIN]) + self.assertEqual(asuswrt_mock.call_count, 1) + self.assertEqual(asuswrt_mock.call_args, mock.call(conf_dict[DOMAIN])) @mock.patch( 'homeassistant.components.device_tracker.asuswrt.AsusWrtDeviceScanner', @@ -101,7 +102,8 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): conf_dict[DOMAIN][CONF_MODE] = 'router' conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' - asuswrt_mock.assert_called_once_with(conf_dict[DOMAIN]) + self.assertEqual(asuswrt_mock.call_count, 1) + self.assertEqual(asuswrt_mock.call_args, mock.call(conf_dict[DOMAIN])) def test_ssh_login_with_pub_key(self): """Test that login is done with pub_key when configured to.""" @@ -122,8 +124,11 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): self.addCleanup(update_mock.stop) asuswrt = device_tracker.asuswrt.AsusWrtDeviceScanner(conf_dict) asuswrt.ssh_connection() - ssh.login.assert_called_once_with('fake_host', 'fake_user', - ssh_key=FAKEFILE) + self.assertEqual(ssh.login.call_count, 1) + self.assertEqual( + ssh.login.call_args, + mock.call('fake_host', 'fake_user', ssh_key=FAKEFILE) + ) def test_ssh_login_with_password(self): """Test that login is done with password when configured to.""" @@ -144,8 +149,11 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): self.addCleanup(update_mock.stop) asuswrt = device_tracker.asuswrt.AsusWrtDeviceScanner(conf_dict) asuswrt.ssh_connection() - ssh.login.assert_called_once_with('fake_host', 'fake_user', - password='fake_pass') + self.assertEqual(ssh.login.call_count, 1) + self.assertEqual( + ssh.login.call_args, + mock.call('fake_host', 'fake_user', password='fake_pass') + ) def test_ssh_login_without_password_or_pubkey(self): \ # pylint: disable=invalid-name diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 8b904ca6e8e..34f89d450eb 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -2,7 +2,7 @@ # pylint: disable=protected-access,too-many-public-methods import logging import unittest -from unittest.mock import patch +from unittest.mock import call, patch from datetime import datetime, timedelta import os @@ -288,7 +288,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): device_tracker.see(self.hass, **params) self.hass.block_till_done() assert mock_see.call_count == 1 - mock_see.assert_called_once_with(**params) + self.assertEqual(mock_see.call_count, 1) + self.assertEqual(mock_see.call_args, call(**params)) mock_see.reset_mock() params['dev_id'] += chr(233) # e' acute accent from icloud @@ -296,7 +297,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): device_tracker.see(self.hass, **params) self.hass.block_till_done() assert mock_see.call_count == 1 - mock_see.assert_called_once_with(**params) + self.assertEqual(mock_see.call_count, 1) + self.assertEqual(mock_see.call_args, call(**params)) def test_not_write_duplicate_yaml_keys(self): \ # pylint: disable=invalid-name diff --git a/tests/components/device_tracker/test_unifi.py b/tests/components/device_tracker/test_unifi.py index 8e43eb7485e..32ef8976196 100644 --- a/tests/components/device_tracker/test_unifi.py +++ b/tests/components/device_tracker/test_unifi.py @@ -27,9 +27,16 @@ class TestUnifiScanner(unittest.TestCase): } result = unifi.get_scanner(None, config) self.assertEqual(mock_scanner.return_value, result) - mock_ctrl.assert_called_once_with('localhost', 'foo', 'password', - 8443, 'v4', 'default') - mock_scanner.assert_called_once_with(mock_ctrl.return_value) + self.assertEqual(mock_ctrl.call_count, 1) + self.assertEqual( + mock_ctrl.call_args, + mock.call('localhost', 'foo', 'password', 8443, 'v4', 'default') + ) + self.assertEqual(mock_scanner.call_count, 1) + self.assertEqual( + mock_scanner.call_args, + mock.call(mock_ctrl.return_value) + ) @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') @mock.patch.object(controller, 'Controller') @@ -47,9 +54,16 @@ class TestUnifiScanner(unittest.TestCase): } result = unifi.get_scanner(None, config) self.assertEqual(mock_scanner.return_value, result) - mock_ctrl.assert_called_once_with('myhost', 'foo', 'password', - 123, 'v4', 'abcdef01') - mock_scanner.assert_called_once_with(mock_ctrl.return_value) + self.assertEqual(mock_ctrl.call_count, 1) + self.assertEqual( + mock_ctrl.call_args, + mock.call('myhost', 'foo', 'password', 123, 'v4', 'abcdef01') + ) + self.assertEqual(mock_scanner.call_count, 1) + self.assertEqual( + mock_scanner.call_args, + mock.call(mock_ctrl.return_value) + ) def test_config_error(self): """Test for configuration errors.""" @@ -94,7 +108,8 @@ class TestUnifiScanner(unittest.TestCase): ] ctrl.get_clients.return_value = fake_clients unifi.UnifiScanner(ctrl) - ctrl.get_clients.assert_called_once_with() + self.assertEqual(ctrl.get_clients.call_count, 1) + self.assertEqual(ctrl.get_clients.call_args, mock.call()) def test_scanner_update_error(self): # pylint: disable=no-self-use """Test the scanner update for error.""" diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py index 8647926445d..33b5afcd1ae 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/media_player/test_sonos.py @@ -125,7 +125,8 @@ class TestSonosMediaPlayer(unittest.TestCase): device = sonos.DEVICES[-1] partymodeMock.return_value = True device.group_players() - partymodeMock.assert_called_once_with() + self.assertEqual(partymodeMock.call_count, 1) + self.assertEqual(partymodeMock.call_args, mock.call()) @mock.patch('soco.SoCo', new=SoCoMock) @mock.patch.object(SoCoMock, 'unjoin') @@ -135,7 +136,8 @@ class TestSonosMediaPlayer(unittest.TestCase): device = sonos.DEVICES[-1] unjoinMock.return_value = True device.unjoin() - unjoinMock.assert_called_once_with() + self.assertEqual(unjoinMock.call_count, 1) + self.assertEqual(unjoinMock.call_args, mock.call()) @mock.patch('soco.SoCo', new=SoCoMock) @mock.patch.object(soco.snapshot.Snapshot, 'snapshot') @@ -145,7 +147,8 @@ class TestSonosMediaPlayer(unittest.TestCase): device = sonos.DEVICES[-1] snapshotMock.return_value = True device.snapshot() - snapshotMock.assert_called_once_with() + self.assertEqual(snapshotMock.call_count, 1) + self.assertEqual(snapshotMock.call_args, mock.call()) @mock.patch('soco.SoCo', new=SoCoMock) @mock.patch.object(soco.snapshot.Snapshot, 'restore') @@ -155,4 +158,5 @@ class TestSonosMediaPlayer(unittest.TestCase): device = sonos.DEVICES[-1] restoreMock.return_value = True device.restore() - restoreMock.assert_called_once_with(True) + self.assertEqual(restoreMock.call_count, 1) + self.assertEqual(restoreMock.call_args, mock.call(True)) diff --git a/tests/components/rollershutter/test_command_line.py b/tests/components/rollershutter/test_command_line.py index 5bec5f4e984..d8b5110578c 100644 --- a/tests/components/rollershutter/test_command_line.py +++ b/tests/components/rollershutter/test_command_line.py @@ -41,7 +41,10 @@ class TestCommandRollerShutter(unittest.TestCase): mock_run.return_value = b' foo bar ' result = self.rs._query_state_value('runme') self.assertEqual('foo bar', result) - mock_run.assert_called_once_with('runme', shell=True) + self.assertEqual(mock_run.call_count, 1) + self.assertEqual( + mock_run.call_args, mock.call('runme', shell=True) + ) def test_state_value(self): """Test with state value.""" diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index 5374f34fb12..82577a5b2a0 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -71,8 +71,13 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) del config[self.THING]['port'] assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - mock_client.assert_called_once_with( - 'foo', 'user', 'pass', port=6443, use_tls=True, verify=True) + self.assertEqual(mock_client.call_count, 1) + self.assertEqual( + mock_client.call_args, + mock.call( + 'foo', 'user', 'pass', port=6443, use_tls=True, verify=True + ) + ) @mock.patch('mficlient.client.MFiClient') def test_setup_with_port(self, mock_client): @@ -80,8 +85,13 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) config[self.THING]['port'] = 6123 assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - mock_client.assert_called_once_with( - 'foo', 'user', 'pass', port=6123, use_tls=True, verify=True) + self.assertEqual(mock_client.call_count, 1) + self.assertEqual( + mock_client.call_args, + mock.call( + 'foo', 'user', 'pass', port=6123, use_tls=True, verify=True + ) + ) @mock.patch('mficlient.client.MFiClient') def test_setup_with_tls_disabled(self, mock_client): @@ -91,8 +101,13 @@ class TestMfiSensorSetup(unittest.TestCase): config[self.THING]['ssl'] = False config[self.THING]['verify_ssl'] = False assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - mock_client.assert_called_once_with( - 'foo', 'user', 'pass', port=6080, use_tls=False, verify=False) + self.assertEqual(mock_client.call_count, 1) + self.assertEqual( + mock_client.call_args, + mock.call( + 'foo', 'user', 'pass', port=6080, use_tls=False, verify=False + ) + ) @mock.patch('mficlient.client.MFiClient') @mock.patch('homeassistant.components.sensor.mfi.MfiSensor') @@ -180,4 +195,5 @@ class TestMfiSensor(unittest.TestCase): def test_update(self): """Test the update.""" self.sensor.update() - self.port.refresh.assert_called_once_with() + self.assertEqual(self.port.refresh.call_count, 1) + self.assertEqual(self.port.refresh.call_args, mock.call()) diff --git a/tests/components/switch/test_mfi.py b/tests/components/switch/test_mfi.py index 53e032f3284..a73b35af2f8 100644 --- a/tests/components/switch/test_mfi.py +++ b/tests/components/switch/test_mfi.py @@ -65,7 +65,8 @@ class TestMfiSwitch(unittest.TestCase): def test_update(self): """Test update.""" self.switch.update() - self.port.refresh.assert_called_once_with() + self.assertEqual(self.port.refresh.call_count, 1) + self.assertEqual(self.port.refresh.call_args, mock.call()) def test_update_with_target_state(self): """Test update with target state.""" @@ -82,13 +83,15 @@ class TestMfiSwitch(unittest.TestCase): def test_turn_on(self): """Test turn_on.""" self.switch.turn_on() - self.port.control.assert_called_once_with(True) + self.assertEqual(self.port.control.call_count, 1) + self.assertEqual(self.port.control.call_args, mock.call(True)) self.assertTrue(self.switch._target_state) def test_turn_off(self): """Test turn_off.""" self.switch.turn_off() - self.port.control.assert_called_once_with(False) + self.assertEqual(self.port.control.call_count, 1) + self.assertEqual(self.port.control.call_args, mock.call(False)) self.assertFalse(self.switch._target_state) def test_current_power_mwh(self): diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index e9235c26542..fcbdbd85b19 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -29,7 +29,11 @@ class TestGraphite(unittest.TestCase): def test_setup(self, mock_socket): """Test setup.""" assert setup_component(self.hass, graphite.DOMAIN, {'graphite': {}}) - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) + self.assertEqual(mock_socket.call_count, 1) + self.assertEqual( + mock_socket.call_args, + mock.call(socket.AF_INET, socket.SOCK_STREAM) + ) @patch('socket.socket') @patch('homeassistant.components.graphite.GraphiteFeeder') @@ -44,8 +48,15 @@ class TestGraphite(unittest.TestCase): } self.assertTrue(setup_component(self.hass, graphite.DOMAIN, config)) - mock_gf.assert_called_once_with(self.hass, 'foo', 123, 'me') - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) + self.assertEqual(mock_gf.call_count, 1) + self.assertEqual( + mock_gf.call_args, mock.call(self.hass, 'foo', 123, 'me') + ) + self.assertEqual(mock_socket.call_count, 1) + self.assertEqual( + mock_socket.call_args, + mock.call(socket.AF_INET, socket.SOCK_STREAM) + ) @patch('socket.socket') @patch('homeassistant.components.graphite.GraphiteFeeder') @@ -60,7 +71,11 @@ class TestGraphite(unittest.TestCase): self.assertTrue(setup_component(self.hass, graphite.DOMAIN, config)) self.assertTrue(mock_gf.called) - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) + self.assertEqual(mock_socket.call_count, 1) + self.assertEqual( + mock_socket.call_args, + mock.call(socket.AF_INET, socket.SOCK_STREAM) + ) def test_subscribe(self): """Test the subscription.""" @@ -70,26 +85,34 @@ class TestGraphite(unittest.TestCase): mock.call(EVENT_HOMEASSISTANT_START, gf.start_listen), mock.call(EVENT_HOMEASSISTANT_STOP, gf.shutdown), ]) - fake_hass.bus.listen.assert_called_once_with( - EVENT_STATE_CHANGED, gf.event_listener) + self.assertEqual(fake_hass.bus.listen.call_count, 1) + self.assertEqual( + fake_hass.bus.listen.call_args, + mock.call(EVENT_STATE_CHANGED, gf.event_listener) + ) def test_start(self): """Test the start.""" with mock.patch.object(self.gf, 'start') as mock_start: self.gf.start_listen('event') - mock_start.assert_called_once_with() + self.assertEqual(mock_start.call_count, 1) + self.assertEqual(mock_start.call_args, mock.call()) def test_shutdown(self): """Test the shutdown.""" with mock.patch.object(self.gf, '_queue') as mock_queue: self.gf.shutdown('event') - mock_queue.put.assert_called_once_with(self.gf._quit_object) + self.assertEqual(mock_queue.put.call_count, 1) + self.assertEqual( + mock_queue.put.call_args, mock.call(self.gf._quit_object) + ) def test_event_listener(self): """Test the event listener.""" with mock.patch.object(self.gf, '_queue') as mock_queue: self.gf.event_listener('foo') - mock_queue.put.assert_called_once_with('foo') + self.assertEqual(mock_queue.put.call_count, 1) + self.assertEqual(mock_queue.put.call_args, mock.call('foo')) @patch('time.time') def test_report_attributes(self, mock_time): @@ -164,21 +187,32 @@ class TestGraphite(unittest.TestCase): def test_send_to_graphite(self, mock_socket): """Test the sending of data.""" self.gf._send_to_graphite('foo') - mock_socket.assert_called_once_with(socket.AF_INET, - socket.SOCK_STREAM) + self.assertEqual(mock_socket.call_count, 1) + self.assertEqual( + mock_socket.call_args, + mock.call(socket.AF_INET, socket.SOCK_STREAM) + ) sock = mock_socket.return_value - sock.connect.assert_called_once_with(('foo', 123)) - sock.sendall.assert_called_once_with('foo'.encode('ascii')) - sock.send.assert_called_once_with('\n'.encode('ascii')) - sock.close.assert_called_once_with() + self.assertEqual(sock.connect.call_count, 1) + self.assertEqual(sock.connect.call_args, mock.call(('foo', 123))) + self.assertEqual(sock.sendall.call_count, 1) + self.assertEqual( + sock.sendall.call_args, mock.call('foo'.encode('ascii')) + ) + self.assertEqual(sock.send.call_count, 1) + self.assertEqual(sock.send.call_args, mock.call('\n'.encode('ascii'))) + self.assertEqual(sock.close.call_count, 1) + self.assertEqual(sock.close.call_args, mock.call()) def test_run_stops(self): """Test the stops.""" with mock.patch.object(self.gf, '_queue') as mock_queue: mock_queue.get.return_value = self.gf._quit_object self.assertEqual(None, self.gf.run()) - mock_queue.get.assert_called_once_with() - mock_queue.task_done.assert_called_once_with() + self.assertEqual(mock_queue.get.call_count, 1) + self.assertEqual(mock_queue.get.call_args, mock.call()) + self.assertEqual(mock_queue.task_done.call_count, 1) + self.assertEqual(mock_queue.task_done.call_args, mock.call()) def test_run(self): """Test the running.""" @@ -204,6 +238,8 @@ class TestGraphite(unittest.TestCase): self.gf.run() # Twice for two events, once for the stop self.assertEqual(3, mock_queue.task_done.call_count) - mock_r.assert_called_once_with( - 'entity', - event.data['new_state']) + self.assertEqual(mock_r.call_count, 1) + self.assertEqual( + mock_r.call_args, + mock.call('entity', event.data['new_state']) + ) diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index 3e4e6e0ad16..79a4a83b69c 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -131,7 +131,13 @@ class TestInfluxDB(unittest.TestCase): }, }] self.handler_method(event) - mock_client.return_value.write_points.assert_called_once_with(body) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) mock_client.return_value.write_points.reset_mock() def test_event_listener_no_units(self, mock_client): @@ -162,7 +168,13 @@ class TestInfluxDB(unittest.TestCase): }, }] self.handler_method(event) - mock_client.return_value.write_points.assert_called_once_with(body) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) mock_client.return_value.write_points.reset_mock() def test_event_listener_fail_write(self, mock_client): @@ -205,8 +217,13 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) if state_state == 1: - mock_client.return_value.write_points.assert_called_once_with( - body) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) else: self.assertFalse(mock_client.return_value.write_points.called) mock_client.return_value.write_points.reset_mock() @@ -236,8 +253,13 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) if entity_id == 'ok': - mock_client.return_value.write_points.assert_called_once_with( - body) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) else: self.assertFalse(mock_client.return_value.write_points.called) mock_client.return_value.write_points.reset_mock() diff --git a/tests/components/test_logentries.py b/tests/components/test_logentries.py index 94097fba32c..4bcef23ee7e 100644 --- a/tests/components/test_logentries.py +++ b/tests/components/test_logentries.py @@ -84,6 +84,9 @@ class TestLogentries(unittest.TestCase): 'logs/token', 'event': body} self.handler_method(event) - self.mock_post.assert_called_once_with( - payload['host'], data=payload, timeout=10) + self.assertEqual(self.mock_post.call_count, 1) + self.assertEqual( + self.mock_post.call_args, + mock.call(payload['host'], data=payload, timeout=10) + ) self.mock_post.reset_mock() diff --git a/tests/components/test_splunk.py b/tests/components/test_splunk.py index 84dc4dfaac5..d893a699602 100644 --- a/tests/components/test_splunk.py +++ b/tests/components/test_splunk.py @@ -94,7 +94,12 @@ class TestSplunk(unittest.TestCase): payload = {'host': 'http://host:8088/services/collector/event', 'event': body} self.handler_method(event) - self.mock_post.assert_called_once_with( - payload['host'], data=payload, - headers={'Authorization': 'Splunk secret'}) + self.assertEqual(self.mock_post.call_count, 1) + self.assertEqual( + self.mock_post.call_args, + mock.call( + payload['host'], data=payload, + headers={'Authorization': 'Splunk secret'} + ) + ) self.mock_post.reset_mock() diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py index 6cbb26b7416..696617a92eb 100644 --- a/tests/components/test_statsd.py +++ b/tests/components/test_statsd.py @@ -40,10 +40,11 @@ class TestStatsd(unittest.TestCase): hass = mock.MagicMock() hass.pool.worker_count = 2 self.assertTrue(setup_component(hass, statsd.DOMAIN, config)) - mock_connection.assert_called_once_with( - host='host', - port=123, - prefix='foo') + self.assertEqual(mock_connection.call_count, 1) + self.assertEqual( + mock_connection.call_args, + mock.call(host='host', port=123, prefix='foo') + ) self.assertTrue(hass.bus.listen.called) self.assertEqual(EVENT_STATE_CHANGED, @@ -64,10 +65,11 @@ class TestStatsd(unittest.TestCase): hass = mock.MagicMock() hass.pool.worker_count = 2 self.assertTrue(setup_component(hass, statsd.DOMAIN, config)) - mock_connection.assert_called_once_with( - host='host', - port=8125, - prefix='hass') + self.assertEqual(mock_connection.call_count, 1) + self.assertEqual( + mock_connection.call_args, + mock.call(host='host', port=8125, prefix='hass') + ) self.assertTrue(hass.bus.listen.called) @mock.patch('statsd.StatsClient') @@ -101,8 +103,11 @@ class TestStatsd(unittest.TestCase): mock_client.return_value.gauge.reset_mock() - mock_client.return_value.incr.assert_called_once_with( - state.entity_id, rate=statsd.DEFAULT_RATE) + self.assertEqual(mock_client.return_value.incr.call_count, 1) + self.assertEqual( + mock_client.return_value.incr.call_args, + mock.call(state.entity_id, rate=statsd.DEFAULT_RATE) + ) mock_client.return_value.incr.reset_mock() for invalid in ('foo', '', object): @@ -146,8 +151,11 @@ class TestStatsd(unittest.TestCase): mock_client.return_value.gauge.reset_mock() - mock_client.return_value.incr.assert_called_once_with( - state.entity_id, rate=statsd.DEFAULT_RATE) + self.assertEqual(mock_client.return_value.incr.call_count, 1) + self.assertEqual( + mock_client.return_value.incr.call_args, + mock.call(state.entity_id, rate=statsd.DEFAULT_RATE) + ) mock_client.return_value.incr.reset_mock() for invalid in ('foo', '', object): diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index e4f75e508e5..b95cede77b3 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -52,7 +52,8 @@ class TestHoneywell(unittest.TestCase): self.assertFalse(result) result = honeywell.setup_platform(hass, config, add_devices) self.assertTrue(result) - mock_sc.assert_called_once_with('user', 'pass') + self.assertEqual(mock_sc.call_count, 1) + self.assertEqual(mock_sc.call_args, mock.call('user', 'pass')) mock_ht.assert_has_calls([ mock.call(mock_sc.return_value, devices_1[0]), mock.call(mock_sc.return_value, devices_2[0]), @@ -164,9 +165,13 @@ class TestHoneywell(unittest.TestCase): hass = mock.MagicMock() add_devices = mock.MagicMock() self.assertTrue(honeywell.setup_platform(hass, config, add_devices)) - mock_evo.assert_called_once_with('user', 'pass') - mock_evo.return_value.temperatures.assert_called_once_with( - force_refresh=True) + self.assertEqual(mock_evo.call_count, 1) + self.assertEqual(mock_evo.call_args, mock.call('user', 'pass')) + self.assertEqual(mock_evo.return_value.temperatures.call_count, 1) + self.assertEqual( + mock_evo.return_value.temperatures.call_args, + mock.call(force_refresh=True) + ) mock_round.assert_has_calls([ mock.call(mock_evo.return_value, 'foo', True, 20), mock.call(mock_evo.return_value, 'bar', False, 20), @@ -265,17 +270,26 @@ class TestHoneywellRound(unittest.TestCase): self.assertFalse(self.round1.is_away_mode_on) self.round1.turn_away_mode_on() self.assertTrue(self.round1.is_away_mode_on) - self.device.set_temperature.assert_called_once_with('House', 16) + self.assertEqual(self.device.set_temperature.call_count, 1) + self.assertEqual( + self.device.set_temperature.call_args, mock.call('House', 16) + ) self.device.set_temperature.reset_mock() self.round1.turn_away_mode_off() self.assertFalse(self.round1.is_away_mode_on) - self.device.cancel_temp_override.assert_called_once_with('House') + self.assertEqual(self.device.cancel_temp_override.call_count, 1) + self.assertEqual( + self.device.cancel_temp_override.call_args, mock.call('House') + ) def test_set_temperature(self): """Test setting the temperature.""" self.round1.set_temperature(25) - self.device.set_temperature.assert_called_once_with('House', 25) + self.assertEqual(self.device.set_temperature.call_count, 1) + self.assertEqual( + self.device.set_temperature.call_args, mock.call('House', 25) + ) def test_set_hvac_mode(self: unittest.TestCase) -> None: """Test setting the system operation.""" From 207c9e85756f2aec52f57fffa03642aeb5f2c006 Mon Sep 17 00:00:00 2001 From: Justin Weberg Date: Sun, 16 Oct 2016 18:56:02 -0500 Subject: [PATCH 089/147] Fix Comment (#3913) --- .../frontend/www_static/micromarkdown-js.html | 14 +++++++------- .../www_static/micromarkdown-js.html.gz | Bin 2563 -> 2564 bytes 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/frontend/www_static/micromarkdown-js.html b/homeassistant/components/frontend/www_static/micromarkdown-js.html index f9ed2ea7cbb..82aaa116750 100644 --- a/homeassistant/components/frontend/www_static/micromarkdown-js.html +++ b/homeassistant/components/frontend/www_static/micromarkdown-js.html @@ -1,10 +1,10 @@ -/* * * * * * * * * * * * - * micromarkdown .js * - * Version 0.3.0 * - * License: MIT * - * Simon Waldherr * - * * * * * * * * * * * */ - vol zxyGl7He!J7xxMeK*@7o=QGXUlK;7aDe$Fq`6FjR3m_*E_2C5 zCv4<0V9dp|;;lFpU*jE!ip7rRCpz|w|8$KXC+iEoWdN6(S+tq@n}6AIz5 zat$4RHh`$!*%_edQ9kKrG&@d%WQ7MicE^KB1!>g%s55N}fuqr=ESJT|b}iScly*(m z0MX`7;~%#TggbIiU4IKCjNQp_f?inV(g{fOv+-{r`^or;^D8poN=*a-c!2rO3d~ts!ZyYjXWG^^0YnFr5sn-QG<4;YkYbYDYuqD+e_&6Oqo! zh#xQ^BKnn1hJRqf_Qiz=X^&qOu?O*FUi>GvFK(Gr7 zcBt&pDnyv2?1;wK6Ihp)++=a6bwSSnoV3~O-YPjGh<{$2&S3GR2eCO*xcY2Mixcc1 zaNvr8LA<~B%E-*KRT|Q-ky|rxX*f*#EYY3VTxLtkcxV`WX&BWJ4Z}WDjt(Ao^bz!k z!q&>!93-rYh(ahJ$e68gSsRy091nG!FLk{yNs<`iC>XzzaRdOI&{pcV9CoGh)t*vu z1pmL#uz#x9kh**s+lj0hZ2ROOR%QNS6hj0LBxX1$^C}2?`ykvv3{GL?WozX?98N1> z;9QTU%8OlQPa()0umSAES83;mcovmtEVNufjUGEaRdsJv6M}uC3;WzYi0Ii}TUAft zj6wMbwC=z|gC;&p9g>J?Nrs))T8=^=F2V#I;#AKFzwX%|1p`?(XNUX63z%iq>2VuT|JQHF)bWtr5WB zgntAJB>DVVr4lrw13e%VNuvQsi1kHMh;?xrmqA1uJ%p_T4_bQ9PVhh>;ep?R$E5G) z!n^8A?771F3hO_>UhH^l0=32VOOL>hQY=B;pPtKdJZv>@akoO2(WSURARl7^IJX76oi3H=t4FjO`qCNjpj(F0c}W}JrN?)NDrwq+40 zV^~XZibYUQ*r9RcahD}6ed}rGK*Tov$%{_{t?=#-=e*}y?-^QV1}P%SI2f=wm6o_sxW6+HcNzL(7)t>dt{5n zOO}ymddsDmin5aMW%2r-qPcl?PcL^-{^&);O$SkKsbT^>p}kM_CE`SluWF)rdf%yT zs_mwOGg5P=9m9px#>=R@<;by}5FTd1Y5{%SoQd*HBJlN>`(HyEu~FHt&!w<2v^tn7^!hws2on~6-YcepwzI@gX;zs(-skHaltO~ zROhI!+I6!5;8_#kO<+?2m47R0zR}!4uP9~&o=Jc!fJ*?FQM@Aus(LskhlVh@f7Beq z=D_n{nu;DwQ))~+ex;7U=mYcBQ>H7NX|!Yac%sMVJd0CxwEHVKIm!mfl#6ner!AbN zt%Iq;Fh$MZav0p9VvVoWJ?(tpDZZlV8Xx8~0v z=SKYTyUbTs=J#t~wyf4VCGXMSMK&$XKia4)`q--?O|~D&_&jr0WHjc#90_HTL~HSAyM0;Aqe3?wtq^?IGw$Yyu5hqds`zq zRrdK%5>?%Xs)(|)spi25tP;?Gi{qBpnf8?!J$hbYd{d`}-}2o97CgM)FsGq$K*IX< z33K03A6>Y&<&CEK)z6EST%!jC&8sI+Ie%8SWg6@qg6lkR)CZmY9cLuJ$W#<2VRq2? z>;y3HNL^rxUUP(>I(je;b{=dsy+7SsH^i^iIBGVC)TIF~41{dA{gX#5B0F4tT%nl8 zsW?4VPt#k3BlSwhI1buhoUgcBW_ekR7FoDTuun_S@u|A7ptIi8Yg<}Ialso2dnlq> Y@xNvL=L1XqkIeJ`0>dY-Bd{3&05=`>IsgCw delta 2561 zcmV+c3jXzk6oV87ABzYGQUZ|%9)H?M@^kzv~1P`*{WpsDskOy z9b&V33`kf=SQ365kl%jYGXhCAIL=+Ug4EO9(~s%bDCU`V@b3hEv_<4+>B7yfg7h}g zjCrn=2u=H^$nq#nw61Ysba7#acpv#9$%Un9AAY^8AwES5AlLro#z82uY=4J1_-CG+ z401nvrcr0e1)DP-X2?9U=-4@P zI-e)nhyk|e^u9A^3m!*#k$)orb@DU#IomiLws8XL7#oK%rhzaN*u|Df%>YD(@-3f$C{O}yxB@Y!BWH|6lfe-8oH!I9&0!Lx8UYk_m_sHy zVIzkDV z*fIxnNBi07IL3tO759F{kSmk*IsXCd8v^%&7p@lz6qca&8?;tCb2i!;(|0gMIi_QlN;`(r z0nx@z;~%#*gfntlj(-Uf#?E9oK`*Q_=>(+tx$|!z`^ozLv!fedUXEJ0tBre{|Lyg* z+VR!a@hHf1I>(vL*a-c!pn7&fB|`}kIvXok2GK6M%EGuL(jy^LL7(8ahr;bedmL)5RiNmwl6 zw+SMFus30QHi#fhDKhXfb4cpznp|%sUcT%TrjvoW+nY&0JWJtS?WiezWupd0ERtCf z@&hJ>M8DF>5PwV*T{)y8QbKH*tU(*3z%iq>2VuT|JQH+ZWttro!G zgnt+dB>D11r4lrw13e%RajgMJi1kIBi*4B?bBT52s_xqF*%QOj; zF|4IH#UiN3?9e#!w9Dd#zhb3#T=h;hBg+waBL&H~($>wiTwrpjCu+?4Hfwj6lYe@S z*@?K-K8o4fyCsw(XS~ZK$C+5#NM3hvu7F6NMNorwVnS%;yIsIF-|UMJ9bI=vc<>~7 zs!CZ1dopFIs+MWJTGpkfP&Cmy&-Y4s8>@HDec?EtsER=fVwJ}aHz~6KJQQ@8u@@$h z3fC$GnS~AFSyY58?+ESGYtY`_-hUcXFvCmJt7E$_8yw4cl^e4(oy9^j=#M$z9@$`V z<7Ma??s91)q9`RiE?)mrG}q7W`Qps9 zwb^uVMyk)WW4Mr7co~(q967cV!h_UbEugO(Gf}*a1-|}r|7$=)Hj3=yNPiHSi0s|v zhxcW#wVK+YF)Hvl@$;Kq<7>4D0b?T7^yl`P^ofPN@r8PD_1NHJ%W+D8Ce2O-DK;p>(rGh&>xNcxEZ4j{r7wjTW zb&l$)T{jy5Uep0z2R0>8x__eL8_jL>ieg6Kg#@?)xCDS1#oKbAs)l27Xb6-0hxIXR z4m=O0sqn!xrN+eLSLzUqJ}_TBXS%|fMmu(oCwgSe(0y|HW!dcA^p_6$k%35nQRei*7!-UnPEN}DOc zV*p?KRpLwbe3>S>xPKIP1qW1MKj9Z3$cuZZ??D7%=-%5TNfU9f0_DHlU%Ip3+=ZaT zOM`pDFu9@BizIjxMloF189!J+Dcf#2m=Bbc{Qd1Egdm4X3QM^tO82}2qeVu^n-s1y zNzuVi#*kOAJq+$Hy2!liKnv?Y;w(BaTL~HS0a4o|0SMuKx_?TFD4D$p-7J6Vds{6! zRrYyM5>?%Xs)(}Fspi56tP-#T7soBHGVLoddi1=4=%z{yui?7~EO>aoVNOHifQ0p` z6Xu?+KDuys%WF;ZtDhH3xt$&qG`E^S<@{ORmZ`sY2(EMARv&cscbt*@B2!Wrgjs&= zvlGC)Ep>q@dU6qbZtMQo-+8dn^!{{iodCa9 Date: Mon, 17 Oct 2016 02:05:01 +0200 Subject: [PATCH 090/147] Emoncms history component (#3531) * Emoncms_history component, fix git mess * - switch to track_point_in_time to send all data at foxed interval - don't use json_dump - switch to http post instead of http get --- .coveragerc | 1 + homeassistant/components/emoncms_history.py | 95 +++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 homeassistant/components/emoncms_history.py diff --git a/.coveragerc b/.coveragerc index c76edce73fb..caadb341b59 100644 --- a/.coveragerc +++ b/.coveragerc @@ -149,6 +149,7 @@ omit = homeassistant/components/device_tracker/volvooncall.py homeassistant/components/discovery.py homeassistant/components/downloader.py + homeassistant/components/emoncms_history.py homeassistant/components/fan/mqtt.py homeassistant/components/feedreader.py homeassistant/components/foursquare.py diff --git a/homeassistant/components/emoncms_history.py b/homeassistant/components/emoncms_history.py new file mode 100644 index 00000000000..4e07447b027 --- /dev/null +++ b/homeassistant/components/emoncms_history.py @@ -0,0 +1,95 @@ +""" +A component which allows you to send data to Emoncms. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/emoncms_history/ +""" +import logging +from datetime import timedelta + +import voluptuous as vol +import requests + +from homeassistant.const import ( + CONF_API_KEY, CONF_WHITELIST, + CONF_URL, STATE_UNKNOWN, + STATE_UNAVAILABLE, + CONF_SCAN_INTERVAL) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import state as state_helper +from homeassistant.helpers.event import track_point_in_time +from homeassistant.util import dt as dt_util + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "emoncms_history" +CONF_INPUTNODE = "inputnode" + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_INPUTNODE): cv.positive_int, + vol.Required(CONF_WHITELIST): cv.entity_ids, + vol.Optional(CONF_SCAN_INTERVAL, default=30): cv.positive_int, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Setup the emoncms_history component.""" + conf = config[DOMAIN] + whitelist = conf.get(CONF_WHITELIST) + + def send_data(url, apikey, node, payload): + """Send payload data to emoncms.""" + try: + fullurl = "{}/input/post.json".format(url) + req = requests.post(fullurl, + params={"node": node}, + data={"apikey": apikey, + "data": payload}, + allow_redirects=True, + timeout=5) + + except requests.exceptions.RequestException: + _LOGGER.error("Error saving data '%s' to '%s'", + payload, fullurl) + + else: + if req.status_code != 200: + _LOGGER.error("Error saving data '%s' to '%s'" + + "(http status code = %d)", payload, + fullurl, req.status_code) + + def update_emoncms(time): + """Send whitelisted entities states reguarly to emoncms.""" + payload_dict = {} + + for entity_id in whitelist: + state = hass.states.get(entity_id) + + if state is None or state.state in ( + STATE_UNKNOWN, "", STATE_UNAVAILABLE): + continue + + try: + payload_dict[entity_id] = state_helper.state_as_number( + state) + except ValueError: + continue + + if len(payload_dict) > 0: + payload = "{%s}" % ",".join("{}:{}".format(key, val) + for key, val in + payload_dict.items()) + + send_data(conf.get(CONF_URL), conf.get(CONF_API_KEY), + str(conf.get(CONF_INPUTNODE)), payload) + + track_point_in_time(hass, update_emoncms, time + + timedelta(seconds=conf.get( + CONF_SCAN_INTERVAL))) + + update_emoncms(dt_util.utcnow()) + return True From c8add59ea5190b955696be79d71fd3c0b08cdb08 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Thu, 13 Oct 2016 13:39:49 -0400 Subject: [PATCH 091/147] Fail when rendering undefined objects in Jinja2 templates By not successfully rendering unknown objects to an empty string the caller provided error_value actually gets used. This allows, for instance, the MQTT sensor to retain its state when an unexpected or unwanted message (#2733/#3834) is received. --- homeassistant/components/sensor/mqtt.py | 2 +- homeassistant/helpers/template.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index fadf171d15b..c3cc9e3003f 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -60,7 +60,7 @@ class MqttSensor(Entity): """A new MQTT message has been received.""" if value_template is not None: payload = value_template.render_with_possible_json_value( - payload) + payload, self._state) self._state = payload self.update_ha_state() diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 03029c369e6..c67f4e62665 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -402,7 +402,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): """Test if callback is safe.""" return isinstance(obj, AllStates) or super().is_safe_callable(obj) -ENV = TemplateEnvironment() +ENV = TemplateEnvironment(undefined=jinja2.StrictUndefined) ENV.filters['round'] = forgiving_round ENV.filters['multiply'] = multiply ENV.filters['timestamp_custom'] = timestamp_custom From 118f2f0badc77881bb28350479d8732fec615cca Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Fri, 14 Oct 2016 11:16:30 -0400 Subject: [PATCH 092/147] Use a filter to fail rendering undefined variables Instead of globally using StrictUndefined, introduce a filter that will trigger a render failure on undefined variables. Use as `{{ value_json.someval | is_defined }}`. --- homeassistant/helpers/template.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index c67f4e62665..2a72fc1a088 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -387,6 +387,13 @@ def timestamp_utc(value): return value +def fail_when_undefined(value): + """Filter to force a failure when the value is undefined.""" + if isinstance(value, jinja2.Undefined): + value() + return value + + def forgiving_float(value): """Try to convert value to a float.""" try: @@ -402,12 +409,13 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): """Test if callback is safe.""" return isinstance(obj, AllStates) or super().is_safe_callable(obj) -ENV = TemplateEnvironment(undefined=jinja2.StrictUndefined) +ENV = TemplateEnvironment() ENV.filters['round'] = forgiving_round ENV.filters['multiply'] = multiply ENV.filters['timestamp_custom'] = timestamp_custom ENV.filters['timestamp_local'] = timestamp_local ENV.filters['timestamp_utc'] = timestamp_utc +ENV.filters['is_defined'] = fail_when_undefined ENV.globals['float'] = forgiving_float ENV.globals['now'] = dt_util.now ENV.globals['utcnow'] = dt_util.utcnow From 555e533f6781bba919f07950b3ccc79bb2990dfb Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Sat, 15 Oct 2016 09:38:11 -0400 Subject: [PATCH 093/147] Added tests for the template is_defined filter --- tests/helpers/test_template.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index f59c5405683..e1e08b02b16 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -206,6 +206,34 @@ class TestHelpersTemplate(unittest.TestCase): '-', tpl.render_with_possible_json_value('hello', '-')) + def test_render_with_possible_json_value_with_missing_json_value(self): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.goodbye }}', self.hass) + self.assertEqual( + '', + tpl.render_with_possible_json_value('{"hello": "world"}')) + + def test_render_with_possible_json_value_valid_with_is_defined(self): + """Render with possible JSON value with known JSON object.""" + tpl = template.Template('{{ value_json.hello|is_defined }}', self.hass) + self.assertEqual( + 'world', + tpl.render_with_possible_json_value('{"hello": "world"}')) + + def test_render_with_possible_json_value_undefined_json(self): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) + self.assertEqual( + '{"hello": "world"}', + tpl.render_with_possible_json_value('{"hello": "world"}')) + + def test_render_with_possible_json_value_undefined_json_error_value(self): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) + self.assertEqual( + '', + tpl.render_with_possible_json_value('{"hello": "world"}', '')) + def test_raise_exception_on_error(self): """Test raising an exception on error.""" with self.assertRaises(TemplateError): From 1540bb12797d534eba347664d9c1586e55f06e0b Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 17 Oct 2016 07:00:55 +0200 Subject: [PATCH 094/147] Async template (#3909) * port binary_sensor/template * port sensor/template * port switch/template * fix unittest * fix * use task instead yield on it * fix unittest * fix unittest v2 * fix invalid config * fix lint * fix unuset import --- .../components/binary_sensor/template.py | 23 +++--- homeassistant/components/sensor/template.py | 25 ++++--- homeassistant/components/switch/template.py | 25 ++++--- .../components/binary_sensor/test_template.py | 75 +++++++++---------- 4 files changed, 81 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index d179edfc1d8..d5791edc81a 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -17,8 +17,8 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_SENSOR_CLASS, CONF_SENSORS) from homeassistant.exceptions import TemplateError -from homeassistant.helpers.entity import generate_entity_id -from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.event import async_track_state_change import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup template binary sensors.""" sensors = [] @@ -61,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not sensors: _LOGGER.error('No sensors added') return False - add_devices(sensors) + hass.loop.create_task(add_devices(sensors)) return True @@ -74,21 +74,22 @@ class BinarySensorTemplate(BinarySensorDevice): value_template, entity_ids): """Initialize the Template binary sensor.""" self.hass = hass - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device, - hass=hass) + self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device, + hass=hass) self._name = friendly_name self._sensor_class = sensor_class self._template = value_template self._state = None - self.update() + self._async_render() @callback def template_bsensor_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" hass.loop.create_task(self.async_update_ha_state(True)) - track_state_change(hass, entity_ids, template_bsensor_state_listener) + async_track_state_change( + hass, entity_ids, template_bsensor_state_listener) @property def name(self): @@ -112,7 +113,11 @@ class BinarySensorTemplate(BinarySensorDevice): @asyncio.coroutine def async_update(self): - """Get the latest data and update the state.""" + """Update the state from the template.""" + self._async_render() + + def _async_render(self): + """Render the state from the template.""" try: self._state = self._template.async_render().lower() == 'true' except TemplateError as ex: diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 1abd1d2fd94..6cd7d61b641 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -15,8 +15,8 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, ATTR_ENTITY_ID, CONF_SENSORS) from homeassistant.exceptions import TemplateError -from homeassistant.helpers.entity import Entity, generate_entity_id -from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.event import async_track_state_change import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup the template sensors.""" sensors = [] @@ -59,7 +59,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not sensors: _LOGGER.error("No sensors added") return False - add_devices(sensors) + + hass.loop.create_task(add_devices(sensors)) return True @@ -71,21 +72,23 @@ class SensorTemplate(Entity): state_template, entity_ids): """Initialize the sensor.""" self.hass = hass - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, - hass=hass) + self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, + hass=hass) self._name = friendly_name self._unit_of_measurement = unit_of_measurement self._template = state_template self._state = None - self.update() + # update state + self._async_render() @callback def template_sensor_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" hass.loop.create_task(self.async_update_ha_state(True)) - track_state_change(hass, entity_ids, template_sensor_state_listener) + async_track_state_change( + hass, entity_ids, template_sensor_state_listener) @property def name(self): @@ -109,7 +112,11 @@ class SensorTemplate(Entity): @asyncio.coroutine def async_update(self): - """Get the latest data and update the states.""" + """Update the state from the template.""" + self._async_render() + + def _async_render(self): + """Render the state from the template.""" try: self._state = self._template.async_render() except TemplateError as ex: diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index b6ce400d0ac..b8820de972f 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -16,8 +16,8 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON, ATTR_ENTITY_ID, CONF_SWITCHES) from homeassistant.exceptions import TemplateError -from homeassistant.helpers.entity import generate_entity_id -from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script import homeassistant.helpers.config_validation as cv @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Template switch.""" switches = [] @@ -53,6 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): entity_ids = (device_config.get(ATTR_ENTITY_ID) or state_template.extract_entities()) + state_template.hass = hass + switches.append( SwitchTemplate( hass, @@ -66,7 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not switches: _LOGGER.error("No switches added") return False - add_devices(switches) + + hass.loop.create_task(add_devices(switches)) return True @@ -78,23 +81,23 @@ class SwitchTemplate(SwitchDevice): on_action, off_action, entity_ids): """Initialize the Template switch.""" self.hass = hass - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, - hass=hass) + self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, + hass=hass) self._name = friendly_name self._template = state_template - state_template.hass = hass self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._state = False - self.update() + self._async_render() @callback def template_switch_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" hass.loop.create_task(self.async_update_ha_state(True)) - track_state_change(hass, entity_ids, template_switch_state_listener) + async_track_state_change( + hass, entity_ids, template_switch_state_listener) @property def name(self): @@ -127,6 +130,10 @@ class SwitchTemplate(SwitchDevice): @asyncio.coroutine def async_update(self): """Update the state from the template.""" + self._async_render() + + def _async_render(self): + """Render the state from the template.""" try: state = self._template.async_render().lower() diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index 28098b2f2a0..98462083e6f 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -4,10 +4,10 @@ from unittest import mock from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL import homeassistant.bootstrap as bootstrap -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA from homeassistant.components.binary_sensor import template from homeassistant.exceptions import TemplateError from homeassistant.helpers import template as template_hlpr +from homeassistant.util.async import run_callback_threadsafe from tests.common import get_test_home_assistant, assert_setup_component @@ -29,38 +29,27 @@ class TestBinarySensorTemplate(unittest.TestCase): @mock.patch.object(template, 'BinarySensorTemplate') def test_setup(self, mock_template): """"Test the setup.""" - tpl = template_hlpr.Template('{{ foo }}', self.hass) - config = PLATFORM_SCHEMA({ - 'platform': 'template', - 'sensors': { - 'test': { - 'friendly_name': 'virtual thingy', - 'value_template': tpl, - 'sensor_class': 'motion', - 'entity_id': 'test' + config = { + 'binary_sensor': { + 'platform': 'template', + 'sensors': { + 'test': { + 'friendly_name': 'virtual thingy', + 'value_template': '{{ foo }}', + 'sensor_class': 'motion', + }, }, - } - }) - add_devices = mock.MagicMock() - result = template.setup_platform(self.hass, config, add_devices) - self.assertTrue(result) - self.assertEqual(mock_template.call_count, 1) - self.assertEqual( - mock_template.call_args, - mock.call( - self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test' - ) - ) - self.assertEqual(add_devices.call_count, 1) - self.assertEqual( - add_devices.call_args, mock.call([mock_template.return_value]) - ) + }, + } + with assert_setup_component(1): + assert bootstrap.setup_component( + self.hass, 'binary_sensor', config) def test_setup_no_sensors(self): """"Test setup with no sensors.""" with assert_setup_component(0): - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { 'platform': 'template' } }) @@ -68,8 +57,8 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_setup_invalid_device(self): """"Test the setup with invalid devices.""" with assert_setup_component(0): - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { 'platform': 'template', 'sensors': { 'foo bar': {}, @@ -80,8 +69,8 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_setup_invalid_sensor_class(self): """"Test setup with invalid sensor class.""" with assert_setup_component(0): - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { 'platform': 'template', 'sensors': { 'test': { @@ -95,8 +84,8 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_setup_invalid_missing_template(self): """"Test setup with invalid and missing template.""" with assert_setup_component(0): - assert bootstrap.setup_component(self.hass, 'sensor', { - 'sensor': { + assert bootstrap.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { 'platform': 'template', 'sensors': { 'test': { @@ -108,9 +97,11 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_attributes(self): """"Test the attributes.""" - vs = template.BinarySensorTemplate( + vs = run_callback_threadsafe( + self.hass.loop, template.BinarySensorTemplate, self.hass, 'parent', 'Parent', 'motion', - template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL) + template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL + ).result() self.assertFalse(vs.should_poll) self.assertEqual('motion', vs.sensor_class) self.assertEqual('Parent', vs.name) @@ -126,9 +117,11 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_event(self): """"Test the event.""" - vs = template.BinarySensorTemplate( + vs = run_callback_threadsafe( + self.hass.loop, template.BinarySensorTemplate, self.hass, 'parent', 'Parent', 'motion', - template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL) + template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL + ).result() vs.update_ha_state() self.hass.block_till_done() @@ -140,9 +133,11 @@ class TestBinarySensorTemplate(unittest.TestCase): @mock.patch('homeassistant.helpers.template.Template.render') def test_update_template_error(self, mock_render): """"Test the template update error.""" - vs = template.BinarySensorTemplate( + vs = run_callback_threadsafe( + self.hass.loop, template.BinarySensorTemplate, self.hass, 'parent', 'Parent', 'motion', - template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL) + template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL + ).result() mock_render.side_effect = TemplateError('foo') vs.update() mock_render.side_effect = TemplateError( From daea93d9f9d5656d965cef8e5b0fad4bdf908e89 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 17 Oct 2016 15:14:10 -0400 Subject: [PATCH 095/147] Suppress requests/urllib3 connection pool log messages (#3854) requests/urllib3 is notorious for using the INFO log level for very DEBUG kinds of information. Given the configurability of python logging it's actually pretty easy to just set requests to WARN by default. This cleans out a bunch of largely unuseful log lines from home assistant output. --- homeassistant/bootstrap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 4d4887a1a52..8ad4e16c8cd 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -355,6 +355,11 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False, logging.basicConfig(level=logging.INFO) fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s%(reset)s") + + # suppress overly verbose logs from libraries that aren't helpful + logging.getLogger("requests").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + try: from colorlog import ColoredFormatter logging.getLogger().handlers[0].setFormatter(ColoredFormatter( From 4c8d1d9d2fa0c7564e3f036165b69772f631563b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 Oct 2016 19:38:41 -0700 Subject: [PATCH 096/147] Clean up some async stuff (#3915) * Clean up some async stuff * Adjust comments * Pass hass instance to eventbus --- homeassistant/components/nuimo_controller.py | 5 +- homeassistant/core.py | 169 +++++++++---------- homeassistant/remote.py | 10 +- tests/common.py | 4 +- tests/components/test_api.py | 4 +- tests/test_core.py | 56 ++++-- tests/test_remote.py | 2 +- 7 files changed, 139 insertions(+), 111 deletions(-) diff --git a/homeassistant/components/nuimo_controller.py b/homeassistant/components/nuimo_controller.py index b383b4f45fc..e3d8f0238cf 100644 --- a/homeassistant/components/nuimo_controller.py +++ b/homeassistant/components/nuimo_controller.py @@ -79,8 +79,7 @@ class NuimoThread(threading.Thread): self._name = name self._hass_is_running = True self._nuimo = None - self._listener = hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - self.stop) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) def run(self): """Setup connection or be idle.""" @@ -99,8 +98,6 @@ class NuimoThread(threading.Thread): """Terminate Thread by unsetting flag.""" _LOGGER.debug('Stopping thread for Nuimo %s', self._mac) self._hass_is_running = False - self._hass.bus.remove_listener(EVENT_HOMEASSISTANT_STOP, - self._listener) def _attach(self): """Create a nuimo object from mac address or discovery.""" diff --git a/homeassistant/core.py b/homeassistant/core.py index ebd24558a40..bd59db59f05 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -8,7 +8,6 @@ of entities and react to changes. import asyncio from concurrent.futures import ThreadPoolExecutor import enum -import functools as ft import logging import os import re @@ -137,8 +136,8 @@ class HomeAssistant(object): self.executor = ThreadPoolExecutor(max_workers=5) self.loop.set_default_executor(self.executor) self.loop.set_exception_handler(self._async_exception_handler) - self.pool = pool = create_worker_pool() - self.bus = EventBus(pool, self.loop) + self.pool = create_worker_pool() + self.bus = EventBus(self) self.services = ServiceRegistry(self.bus, self.add_job, self.loop) self.states = StateMachine(self.bus, self.loop) self.config = Config() # type: Config @@ -218,8 +217,8 @@ class HomeAssistant(object): """ # pylint: disable=protected-access self.loop._thread_ident = threading.get_ident() - async_create_timer(self) - async_monitor_worker_pool(self) + _async_create_timer(self) + _async_monitor_worker_pool(self) self.bus.async_fire(EVENT_HOMEASSISTANT_START) yield from self.loop.run_in_executor(None, self.pool.block_till_done) self.state = CoreState.running @@ -235,9 +234,12 @@ class HomeAssistant(object): """ self.pool.add_job(priority, (target,) + args) + @callback def async_add_job(self, target: Callable[..., None], *args: Any): """Add a job from within the eventloop. + This method must be run in the event loop. + target: target to call. args: parameters for method to call. """ @@ -248,9 +250,12 @@ class HomeAssistant(object): else: self.add_job(target, *args) + @callback def async_run_job(self, target: Callable[..., None], *args: Any): """Run a job from within the event loop. + This method must be run in the event loop. + target: target to call. args: parameters for method to call. """ @@ -369,7 +374,10 @@ class Event(object): self.time_fired = time_fired or dt_util.utcnow() def as_dict(self): - """Create a dict representation of this Event.""" + """Create a dict representation of this Event. + + Async friendly. + """ return { 'event_type': self.event_type, 'data': dict(self.data), @@ -400,13 +408,12 @@ class Event(object): class EventBus(object): """Allows firing of and listening for events.""" - def __init__(self, pool: util.ThreadPool, - loop: asyncio.AbstractEventLoop) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" self._listeners = {} - self._pool = pool - self._loop = loop + self._hass = hass + @callback def async_listeners(self): """Dict with events and the number of listeners. @@ -419,23 +426,25 @@ class EventBus(object): def listeners(self): """Dict with events and the number of listeners.""" return run_callback_threadsafe( - self._loop, self.async_listeners + self._hass.loop, self.async_listeners ).result() def fire(self, event_type: str, event_data=None, origin=EventOrigin.local): """Fire an event.""" - if not self._pool.running: - raise HomeAssistantError('Home Assistant has shut down.') - - self._loop.call_soon_threadsafe(self.async_fire, event_type, - event_data, origin) + self._hass.loop.call_soon_threadsafe(self.async_fire, event_type, + event_data, origin) + @callback def async_fire(self, event_type: str, event_data=None, origin=EventOrigin.local, wait=False): """Fire an event. This method must be run in the event loop. """ + if event_type != EVENT_HOMEASSISTANT_STOP and \ + self._hass.state == CoreState.stopping: + raise HomeAssistantError('Home Assistant is shutting down.') + # Copy the list of the current listeners because some listeners # remove themselves as a listener while being executed which # causes the iterator to be confused. @@ -450,20 +459,8 @@ class EventBus(object): if not listeners: return - job_priority = JobPriority.from_event_type(event_type) - - sync_jobs = [] for func in listeners: - if asyncio.iscoroutinefunction(func): - self._loop.create_task(func(event)) - elif is_callback(func): - self._loop.call_soon(func, event) - else: - sync_jobs.append((job_priority, (func, event))) - - # Send all the sync jobs at once - if sync_jobs: - self._pool.add_many_jobs(sync_jobs) + self._hass.async_add_job(func, event) def listen(self, event_type, listener): """Listen for all events or events of a specific type. @@ -471,16 +468,17 @@ class EventBus(object): To listen to all events specify the constant ``MATCH_ALL`` as event_type. """ - future = run_callback_threadsafe( - self._loop, self.async_listen, event_type, listener) - future.result() + async_remove_listener = run_callback_threadsafe( + self._hass.loop, self.async_listen, event_type, listener).result() def remove_listener(): """Remove the listener.""" - self._remove_listener(event_type, listener) + run_callback_threadsafe( + self._hass.loop, async_remove_listener).result() return remove_listener + @callback def async_listen(self, event_type, listener): """Listen for all events or events of a specific type. @@ -496,7 +494,7 @@ class EventBus(object): def remove_listener(): """Remove the listener.""" - self.async_remove_listener(event_type, listener) + self._async_remove_listener(event_type, listener) return remove_listener @@ -508,26 +506,18 @@ class EventBus(object): Returns function to unsubscribe the listener. """ - @ft.wraps(listener) - def onetime_listener(event): - """Remove listener from eventbus and then fire listener.""" - if hasattr(onetime_listener, 'run'): - return - # Set variable so that we will never run twice. - # Because the event bus might have to wait till a thread comes - # available to execute this listener it might occur that the - # listener gets lined up twice to be executed. - # This will make sure the second time it does nothing. - setattr(onetime_listener, 'run', True) + async_remove_listener = run_callback_threadsafe( + self._hass.loop, self.async_listen_once, event_type, listener, + ).result() - remove_listener() - - listener(event) - - remove_listener = self.listen(event_type, onetime_listener) + def remove_listener(): + """Remove the listener.""" + run_callback_threadsafe( + self._hass.loop, async_remove_listener).result() return remove_listener + @callback def async_listen_once(self, event_type, listener): """Listen once for event of a specific type. @@ -538,8 +528,7 @@ class EventBus(object): This method must be run in the event loop. """ - @ft.wraps(listener) - @asyncio.coroutine + @callback def onetime_listener(event): """Remove listener from eventbus and then fire listener.""" if hasattr(onetime_listener, 'run'): @@ -550,34 +539,14 @@ class EventBus(object): # multiple times as well. # This will make sure the second time it does nothing. setattr(onetime_listener, 'run', True) + self._async_remove_listener(event_type, onetime_listener) - self.async_remove_listener(event_type, onetime_listener) + self._hass.async_run_job(listener, event) - if asyncio.iscoroutinefunction(listener): - yield from listener(event) - else: - job_priority = JobPriority.from_event_type(event.event_type) - self._pool.add_job(job_priority, (listener, event)) + return self.async_listen(event_type, onetime_listener) - self.async_listen(event_type, onetime_listener) - - return onetime_listener - - def remove_listener(self, event_type, listener): - """Remove a listener of a specific event_type. (DEPRECATED 0.28).""" - _LOGGER.warning('bus.remove_listener has been deprecated. Please use ' - 'the function returned from calling listen.') - self._remove_listener(event_type, listener) - - def _remove_listener(self, event_type, listener): - """Remove a listener of a specific event_type.""" - future = run_callback_threadsafe( - self._loop, - self.async_remove_listener, event_type, listener - ) - future.result() - - def async_remove_listener(self, event_type, listener): + @callback + def _async_remove_listener(self, event_type, listener): """Remove a listener of a specific event_type. This method must be run in the event loop. @@ -644,6 +613,8 @@ class State(object): def as_dict(self): """Return a dict representation of the State. + Async friendly. + To be used for JSON serialization. Ensures: state == State.from_dict(state.as_dict()) """ @@ -657,6 +628,8 @@ class State(object): def from_dict(cls, json_dict): """Initialize a state from a dict. + Async friendly. + Ensures: state == State.from_json_dict(state.to_json_dict()) """ if not (json_dict and 'entity_id' in json_dict and @@ -709,8 +682,12 @@ class StateMachine(object): ) return future.result() + @callback def async_entity_ids(self, domain_filter=None): - """List of entity ids that are being tracked.""" + """List of entity ids that are being tracked. + + This method must be run in the event loop. + """ if domain_filter is None: return list(self._states.keys()) @@ -723,6 +700,7 @@ class StateMachine(object): """Create a list of all states.""" return run_callback_threadsafe(self._loop, self.async_all).result() + @callback def async_all(self): """Create a list of all states. @@ -763,6 +741,7 @@ class StateMachine(object): return run_callback_threadsafe( self._loop, self.async_remove, entity_id).result() + @callback def async_remove(self, entity_id): """Remove the state of an entity. @@ -800,6 +779,7 @@ class StateMachine(object): self.async_set, entity_id, new_state, attributes, force_update, ).result() + @callback def async_set(self, entity_id, new_state, attributes=None, force_update=False): """Set the state of an entity, add entity if it does not exist. @@ -908,14 +888,21 @@ class ServiceRegistry(object): self._loop, self.async_services, ).result() + @callback def async_services(self): - """Dict with per domain a list of available services.""" + """Dict with per domain a list of available services. + + This method must be run in the event loop. + """ return {domain: {key: value.as_dict() for key, value in self._services[domain].items()} for domain in self._services} def has_service(self, domain, service): - """Test if specified service exists.""" + """Test if specified service exists. + + Async friendly. + """ return service.lower() in self._services.get(domain.lower(), []) # pylint: disable=too-many-arguments @@ -935,6 +922,7 @@ class ServiceRegistry(object): schema ).result() + @callback def async_register(self, domain, service, service_func, description=None, schema=None): """ @@ -985,7 +973,7 @@ class ServiceRegistry(object): self._loop ).result() - @callback + @asyncio.coroutine def async_call(self, domain, service, service_data=None, blocking=False): """ Call a service. @@ -1121,18 +1109,27 @@ class Config(object): self.config_dir = None def distance(self: object, lat: float, lon: float) -> float: - """Calculate distance from Home Assistant.""" + """Calculate distance from Home Assistant. + + Async friendly. + """ return self.units.length( location.distance(self.latitude, self.longitude, lat, lon), 'm') def path(self, *path): - """Generate path to the file within the config dir.""" + """Generate path to the file within the config dir. + + Async friendly. + """ if self.config_dir is None: raise HomeAssistantError("config_dir is not set") return os.path.join(self.config_dir, *path) def as_dict(self): - """Create a dict representation of this dict.""" + """Create a dict representation of this dict. + + Async friendly. + """ time_zone = self.time_zone or dt_util.UTC return { @@ -1147,7 +1144,7 @@ class Config(object): } -def async_create_timer(hass, interval=TIMER_INTERVAL): +def _async_create_timer(hass, interval=TIMER_INTERVAL): """Create a timer that will start on HOMEASSISTANT_START.""" stop_event = asyncio.Event(loop=hass.loop) @@ -1230,7 +1227,7 @@ def create_worker_pool(worker_count=None): return util.ThreadPool(job_handler, worker_count) -def async_monitor_worker_pool(hass): +def _async_monitor_worker_pool(hass): """Create a monitor for the thread pool to check if pool is misbehaving.""" busy_threshold = hass.pool.worker_count * 3 diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 8725990f146..15a84e08ffe 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -124,9 +124,9 @@ class HomeAssistant(ha.HomeAssistant): self.remote_api = remote_api self.loop = loop or asyncio.get_event_loop() - self.pool = pool = ha.create_worker_pool() + self.pool = ha.create_worker_pool() - self.bus = EventBus(remote_api, pool, self.loop) + self.bus = EventBus(remote_api, self) self.services = ha.ServiceRegistry(self.bus, self.add_job, self.loop) self.states = StateMachine(self.bus, self.loop, self.remote_api) self.config = ha.Config() @@ -143,7 +143,7 @@ class HomeAssistant(ha.HomeAssistant): 'Unable to setup local API to receive events') self.state = ha.CoreState.starting - ha.async_create_timer(self) + ha._async_create_timer(self) # pylint: disable=protected-access self.bus.fire(ha.EVENT_HOMEASSISTANT_START, origin=ha.EventOrigin.remote) @@ -180,9 +180,9 @@ class EventBus(ha.EventBus): """EventBus implementation that forwards fire_event to remote API.""" # pylint: disable=too-few-public-methods - def __init__(self, api, pool, loop): + def __init__(self, api, hass): """Initalize the eventbus.""" - super().__init__(pool, loop) + super().__init__(hass) self._api = api def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local): diff --git a/tests/common.py b/tests/common.py index 9dc98d2f4b4..b185a47e66c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -76,8 +76,8 @@ def get_test_home_assistant(num_threads=None): """Fake stop.""" yield None - @patch.object(ha, 'async_create_timer') - @patch.object(ha, 'async_monitor_worker_pool') + @patch.object(ha, '_async_create_timer') + @patch.object(ha, '_async_monitor_worker_pool') @patch.object(hass.loop, 'add_signal_handler') @patch.object(hass.loop, 'run_forever') @patch.object(hass.loop, 'close') diff --git a/tests/components/test_api.py b/tests/components/test_api.py index dee4320824b..ca494305073 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -145,14 +145,14 @@ class TestAPI(unittest.TestCase): requests.post(_url(const.URL_API_STATES_ENTITY.format("test.test")), data=json.dumps({"state": "not_to_be_set"}), headers=HA_HEADERS) - hass.bus._pool.block_till_done() + hass.block_till_done() self.assertEqual(0, len(events)) requests.post(_url(const.URL_API_STATES_ENTITY.format("test.test")), data=json.dumps({"state": "not_to_be_set", "force_update": True}), headers=HA_HEADERS) - hass.bus._pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(events)) # pylint: disable=invalid-name diff --git a/tests/test_core.py b/tests/test_core.py index 39301b5614a..b3ab2ba4dbd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -179,19 +179,16 @@ class TestEventBus(unittest.TestCase): def listener(_): pass - self.bus.listen('test', listener) + unsub = self.bus.listen('test', listener) self.assertEqual(old_count + 1, len(self.bus.listeners)) - # Try deleting a non registered listener, nothing should happen - self.bus._remove_listener('test', lambda x: len) - # Remove listener - self.bus._remove_listener('test', listener) + unsub() self.assertEqual(old_count, len(self.bus.listeners)) - # Try deleting listener while category doesn't exist either - self.bus._remove_listener('test', listener) + # Should do nothing now + unsub() def test_unsubscribe_listener(self): """Test unsubscribe listener from returned function.""" @@ -215,11 +212,48 @@ class TestEventBus(unittest.TestCase): assert len(calls) == 1 - def test_listen_once_event(self): + def test_listen_once_event_with_callback(self): """Test listen_once_event method.""" runs = [] - self.bus.listen_once('test_event', lambda x: runs.append(1)) + @ha.callback + def event_handler(event): + runs.append(event) + + self.bus.listen_once('test_event', event_handler) + + self.bus.fire('test_event') + # Second time it should not increase runs + self.bus.fire('test_event') + + self.hass.block_till_done() + self.assertEqual(1, len(runs)) + + def test_listen_once_event_with_coroutine(self): + """Test listen_once_event method.""" + runs = [] + + @asyncio.coroutine + def event_handler(event): + runs.append(event) + + self.bus.listen_once('test_event', event_handler) + + self.bus.fire('test_event') + # Second time it should not increase runs + self.bus.fire('test_event') + + self.hass.block_till_done() + self.assertEqual(1, len(runs)) + + def test_listen_once_event_with_thread(self): + """Test listen_once_event method.""" + runs = [] + + def event_handler(event): + runs.append(event) + + self.bus.listen_once('test_event', event_handler) self.bus.fire('test_event') # Second time it should not increase runs @@ -604,7 +638,7 @@ class TestWorkerPoolMonitor(object): schedule_handle = MagicMock() hass.loop.call_later.return_value = schedule_handle - ha.async_monitor_worker_pool(hass) + ha._async_monitor_worker_pool(hass) assert hass.loop.call_later.called assert hass.bus.async_listen_once.called assert not schedule_handle.called @@ -650,7 +684,7 @@ class TestAsyncCreateTimer(object): now.second = 1 mock_utcnow.reset_mock() - ha.async_create_timer(hass) + ha._async_create_timer(hass) assert len(hass.bus.async_listen_once.mock_calls) == 2 start_timer = hass.bus.async_listen_once.mock_calls[1][1][1] diff --git a/tests/test_remote.py b/tests/test_remote.py index 653971f8bc1..316f13c5fc2 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -69,7 +69,7 @@ def setUpModule(): # pylint: disable=invalid-name {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, http.CONF_SERVER_PORT: SLAVE_PORT}}) - with patch.object(ha, 'async_create_timer', return_value=None): + with patch.object(ha, '_async_create_timer', return_value=None): slave.start() From ae8a8e22ade3f636d12925c235b37a97115cc725 Mon Sep 17 00:00:00 2001 From: sam-io Date: Tue, 18 Oct 2016 03:41:49 +0100 Subject: [PATCH 097/147] Support for Apple Push Notification Service (#3756) * added push notification implementation * some lint changes * added docs * added push notification implementation * some lint changes * added docs * Fixed comment formatting issues * Added requirments * Update requirements_all.txt * Update apns.py * re-generated requirments_all.txt * Added link to online docs * added push notification implementation * some lint changes * added docs * added push notification implementation * some lint changes * added docs * Fixed comment formatting issues * Added requirments * Update requirements_all.txt * Update apns.py * re-generated requirments_all.txt * Added link to online docs * changed to use http/2 library for push notifications * fixed lint issue * fixed test that fails on CI * another go at fixing test that fails on CI * another go at fixing test that fails on CI * another go at fixing test that fails on CI * added missing docstring * moved service description to main services.yaml file * renamed apns service --- homeassistant/components/notify/apns.py | 289 ++++++++++++++ homeassistant/components/notify/services.yaml | 12 + requirements_all.txt | 3 + tests/components/notify/test_apns.py | 358 ++++++++++++++++++ 4 files changed, 662 insertions(+) create mode 100644 homeassistant/components/notify/apns.py create mode 100644 tests/components/notify/test_apns.py diff --git a/homeassistant/components/notify/apns.py b/homeassistant/components/notify/apns.py new file mode 100644 index 00000000000..5e5a8088aa7 --- /dev/null +++ b/homeassistant/components/notify/apns.py @@ -0,0 +1,289 @@ +""" +APNS Notification platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.apns/ +""" +import logging +import os +import voluptuous as vol + +from homeassistant.helpers.event import track_state_change +from homeassistant.config import load_yaml_config_file +from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_DATA, BaseNotificationService) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import template as template_helper + +DOMAIN = "apns" +APNS_DEVICES = "apns.yaml" +DEVICE_TRACKER_DOMAIN = "device_tracker" +SERVICE_REGISTER = "apns_register" + +ATTR_PUSH_ID = "push_id" +ATTR_NAME = "name" + +REGISTER_SERVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_PUSH_ID): cv.string, + vol.Optional(ATTR_NAME, default=None): cv.string, +}) + +REQUIREMENTS = ["apns2==0.1.1"] + + +def get_service(hass, config): + """Return push service.""" + descriptions = load_yaml_config_file( + os.path.join(os.path.dirname(__file__), 'services.yaml')) + + name = config.get("name") + if name is None: + logging.error("Name must be specified.") + return None + + cert_file = config.get('cert_file') + if cert_file is None: + logging.error("Certificate must be specified.") + return None + + topic = config.get('topic') + if topic is None: + logging.error("Topic must be specified.") + return None + + sandbox = bool(config.get('sandbox', False)) + + service = ApnsNotificationService(hass, name, topic, sandbox, cert_file) + hass.services.register(DOMAIN, + name, + service.register, + descriptions.get(SERVICE_REGISTER), + schema=REGISTER_SERVICE_SCHEMA) + return service + + +class ApnsDevice(object): + """ + Apns Device class. + + Stores information about a device that is + registered for push notifications. + """ + + def __init__(self, push_id, name, tracking_device_id=None, disabled=False): + """Initialize Apns Device.""" + self.device_push_id = push_id + self.device_name = name + self.tracking_id = tracking_device_id + self.device_disabled = disabled + + @property + def push_id(self): + """The apns id for the device.""" + return self.device_push_id + + @property + def name(self): + """The friendly name for the device.""" + return self.device_name + + @property + def tracking_device_id(self): + """ + Device Id. + + The id of a device that is tracked by the device + tracking component. + """ + return self.tracking_id + + @property + def full_tracking_device_id(self): + """ + Fully qualified device id. + + The full id of a device that is tracked by the device + tracking component. + """ + return DEVICE_TRACKER_DOMAIN + '.' + self.tracking_id + + @property + def disabled(self): + """Should receive notifications.""" + return self.device_disabled + + def disable(self): + """Disable the device from recieving notifications.""" + self.device_disabled = True + + def __eq__(self, other): + """Return the comparision.""" + if isinstance(other, self.__class__): + return self.push_id == other.push_id and self.name == other.name + return NotImplemented + + def __ne__(self, other): + """Return the comparision.""" + return not self.__eq__(other) + + +class ApnsNotificationService(BaseNotificationService): + """Implement the notification service for the APNS service.""" + + # pylint: disable=too-many-arguments + # pylint: disable=too-many-instance-attributes + def __init__(self, hass, app_name, topic, sandbox, cert_file): + """Initialize APNS application.""" + self.hass = hass + self.app_name = app_name + self.sandbox = sandbox + self.certificate = cert_file + self.yaml_path = hass.config.path(app_name + '_' + APNS_DEVICES) + self.devices = {} + self.device_states = {} + self.topic = topic + if os.path.isfile(self.yaml_path): + self.devices = { + str(key): ApnsDevice( + str(key), + value.get('name'), + value.get('tracking_device_id'), + value.get('disabled', False) + ) + for (key, value) in + load_yaml_config_file(self.yaml_path).items() + } + + tracking_ids = [ + device.full_tracking_device_id + for (key, device) in self.devices.items() + if device.tracking_device_id is not None + ] + track_state_change( + hass, + tracking_ids, + self.device_state_changed_listener) + + def device_state_changed_listener(self, entity_id, from_s, to_s): + """ + Listener for sate change. + + Track device state change if a device + has a tracking id specified. + """ + self.device_states[entity_id] = str(to_s.state) + return + + @staticmethod + def write_device(out, device): + """Write a single device to file.""" + attributes = [] + if device.name is not None: + attributes.append( + 'name: {}'.format(device.name)) + if device.tracking_device_id is not None: + attributes.append( + 'tracking_device_id: {}'.format(device.tracking_device_id)) + if device.disabled: + attributes.append('disabled: True') + + out.write(device.push_id) + out.write(": {") + if len(attributes) > 0: + separator = ", " + out.write(separator.join(attributes)) + + out.write("}\n") + + def write_devices(self): + """Write all known devices to file.""" + with open(self.yaml_path, 'w+') as out: + for _, device in self.devices.items(): + ApnsNotificationService.write_device(out, device) + + def register(self, call): + """Register a device to receive push messages.""" + push_id = call.data.get(ATTR_PUSH_ID) + if push_id is None: + return False + + device_name = call.data.get(ATTR_NAME) + current_device = self.devices.get(push_id) + current_tracking_id = None if current_device is None \ + else current_device.tracking_device_id + + device = ApnsDevice( + push_id, + device_name, + current_tracking_id) + + if current_device is None: + self.devices[push_id] = device + with open(self.yaml_path, 'a') as out: + self.write_device(out, device) + return True + + if device != current_device: + self.devices[push_id] = device + self.write_devices() + + return True + + def send_message(self, message=None, **kwargs): + """Send push message to registered devices.""" + from apns2.client import APNsClient + from apns2.payload import Payload + from apns2.errors import Unregistered + + apns = APNsClient( + self.certificate, + use_sandbox=self.sandbox, + use_alternative_port=False) + + device_state = kwargs.get(ATTR_TARGET) + message_data = kwargs.get(ATTR_DATA) + + if message_data is None: + message_data = {} + + if isinstance(message, str): + rendered_message = message + elif isinstance(message, template_helper.Template): + rendered_message = message.render() + else: + rendered_message = "" + + payload = Payload( + alert=rendered_message, + badge=message_data.get("badge"), + sound=message_data.get("sound"), + category=message_data.get("category"), + custom=message_data.get("custom", {}), + content_available=message_data.get("content_available", False)) + + device_update = False + + for push_id, device in self.devices.items(): + if not device.disabled: + state = None + if device.tracking_device_id is not None: + state = self.device_states.get( + device.full_tracking_device_id) + + if device_state is None or state == str(device_state): + try: + apns.send_notification( + push_id, + payload, + topic=self.topic) + except Unregistered: + logging.error( + "Device %s has unregistered.", + push_id) + device_update = True + device.disable() + + if device_update: + self.write_devices() + + return True diff --git a/homeassistant/components/notify/services.yaml b/homeassistant/components/notify/services.yaml index a3980f658ea..4fe66844aa9 100644 --- a/homeassistant/components/notify/services.yaml +++ b/homeassistant/components/notify/services.yaml @@ -17,3 +17,15 @@ notify: data: description: Extended information for notification. Optional depending on the platform. example: platform specific + +apns_register: + description: Registers a device to receive push notifications. + + fields: + push_id: + description: The device token, a 64 character hex string (256 bits). The device token is provided to you by your client app, which receives the token after registering itself with the remote notification service. + example: '72f2a8633655c5ce574fdc9b2b34ff8abdfc3b739b6ceb7a9ff06c1cbbf99f62' + + name: + description: A friendly name for the device (optional). + example: 'Sam''s iPhone' diff --git a/requirements_all.txt b/requirements_all.txt index c73dc336278..240972252ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,6 +35,9 @@ Werkzeug==0.11.11 # homeassistant.components.apcupsd apcaccess==0.0.4 +# homeassistant.components.notify.apns +apns2==0.1.1 + # homeassistant.components.sun astral==1.2 diff --git a/tests/components/notify/test_apns.py b/tests/components/notify/test_apns.py new file mode 100644 index 00000000000..7103b6cdc8b --- /dev/null +++ b/tests/components/notify/test_apns.py @@ -0,0 +1,358 @@ +"""The tests for the APNS component.""" +import unittest +import os + +import homeassistant.components.notify as notify +from homeassistant.core import State +from homeassistant.components.notify.apns import ApnsNotificationService +from tests.common import get_test_home_assistant +from homeassistant.config import load_yaml_config_file +from unittest.mock import patch +from apns2.errors import Unregistered + + +class TestApns(unittest.TestCase): + """Test the APNS component.""" + + def test_apns_setup_full(self): + """Test setup with all data.""" + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'sandbox': 'True', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + self.assertTrue(notify.setup(hass, config)) + + def test_apns_setup_missing_name(self): + """Test setup with missing name.""" + config = { + 'notify': { + 'platform': 'apns', + 'sandbox': 'True', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + self.assertFalse(notify.setup(hass, config)) + + def test_apns_setup_missing_certificate(self): + """Test setup with missing name.""" + config = { + 'notify': { + 'platform': 'apns', + 'topic': 'testapp.appname', + 'name': 'test_app' + } + } + hass = get_test_home_assistant() + self.assertFalse(notify.setup(hass, config)) + + def test_apns_setup_missing_topic(self): + """Test setup with missing topic.""" + config = { + 'notify': { + 'platform': 'apns', + 'cert_file': 'test_app.pem', + 'name': 'test_app' + } + } + hass = get_test_home_assistant() + self.assertFalse(notify.setup(hass, config)) + + def test_register_new_device(self): + """Test registering a new device with a name.""" + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('5678: {name: test device 2}\n') + + notify.setup(hass, config) + self.assertTrue(hass.services.call('apns', + 'test_app', + {'push_id': '1234', + 'name': 'test device'}, + blocking=True)) + + devices = {str(key): value for (key, value) in + load_yaml_config_file(devices_path).items()} + + test_device_1 = devices.get('1234') + test_device_2 = devices.get('5678') + + self.assertIsNotNone(test_device_1) + self.assertIsNotNone(test_device_2) + + self.assertEqual('test device', test_device_1.get('name')) + + os.remove(devices_path) + + def test_register_device_without_name(self): + """Test registering a without a name.""" + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('5678: {name: test device 2}\n') + + notify.setup(hass, config) + self.assertTrue(hass.services.call('apns', 'test_app', + {'push_id': '1234'}, + blocking=True)) + + devices = {str(key): value for (key, value) in + load_yaml_config_file(devices_path).items()} + + test_device = devices.get('1234') + + self.assertIsNotNone(test_device) + self.assertIsNone(test_device.get('name')) + + os.remove(devices_path) + + def test_update_existing_device(self): + """Test updating an existing device.""" + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1}\n') + out.write('5678: {name: test device 2}\n') + + notify.setup(hass, config) + self.assertTrue(hass.services.call('apns', + 'test_app', + {'push_id': '1234', + 'name': 'updated device 1'}, + blocking=True)) + + devices = {str(key): value for (key, value) in + load_yaml_config_file(devices_path).items()} + + test_device_1 = devices.get('1234') + test_device_2 = devices.get('5678') + + self.assertIsNotNone(test_device_1) + self.assertIsNotNone(test_device_2) + + self.assertEqual('updated device 1', test_device_1.get('name')) + + os.remove(devices_path) + + def test_update_existing_device_with_tracking_id(self): + """Test updating an existing device that has a tracking id.""" + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1, tracking_device_id: tracking123}\n') # nopep8 + out.write('5678: {name: test device 2, tracking_device_id: tracking456}\n') # nopep8 + + notify.setup(hass, config) + self.assertTrue(hass.services.call('apns', + 'test_app', + {'push_id': '1234', + 'name': 'updated device 1'}, + blocking=True)) + + devices = {str(key): value for (key, value) in + load_yaml_config_file(devices_path).items()} + + test_device_1 = devices.get('1234') + test_device_2 = devices.get('5678') + + self.assertIsNotNone(test_device_1) + self.assertIsNotNone(test_device_2) + + self.assertEqual('tracking123', + test_device_1.get('tracking_device_id')) + self.assertEqual('tracking456', + test_device_2.get('tracking_device_id')) + + os.remove(devices_path) + + @patch('apns2.client.APNsClient') + def test_send(self, mock_client): + """Test updating an existing device.""" + send = mock_client.return_value.send_notification + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1}\n') + + notify.setup(hass, config) + + self.assertTrue(hass.services.call('notify', 'test_app', + {'message': 'Hello', + 'data': { + 'badge': 1, + 'sound': 'test.mp3', + 'category': 'testing' + } + }, + blocking=True)) + + self.assertTrue(send.called) + self.assertEqual(1, len(send.mock_calls)) + + target = send.mock_calls[0][1][0] + payload = send.mock_calls[0][1][1] + + self.assertEqual('1234', target) + self.assertEqual('Hello', payload.alert) + self.assertEqual(1, payload.badge) + self.assertEqual('test.mp3', payload.sound) + self.assertEqual('testing', payload.category) + + @patch('apns2.client.APNsClient') + def test_send_when_disabled(self, mock_client): + """Test updating an existing device.""" + send = mock_client.return_value.send_notification + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1, disabled: True}\n') + + notify.setup(hass, config) + + self.assertTrue(hass.services.call('notify', 'test_app', + {'message': 'Hello', + 'data': { + 'badge': 1, + 'sound': 'test.mp3', + 'category': 'testing' + } + }, + blocking=True)) + + self.assertFalse(send.called) + + @patch('apns2.client.APNsClient') + def test_send_with_state(self, mock_client): + """Test updating an existing device.""" + send = mock_client.return_value.send_notification + + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1, tracking_device_id: tracking123}\n') # nopep8 + out.write('5678: {name: test device 2, tracking_device_id: tracking456}\n') # nopep8 + + notify_service = ApnsNotificationService( + hass, + 'test_app', + 'testapp.appname', + False, + 'test_app.pem' + ) + + notify_service.device_state_changed_listener( + 'device_tracker.tracking456', + State('device_tracker.tracking456', None), + State('device_tracker.tracking456', 'home')) + + hass.block_till_done() + + notify_service.send_message(message='Hello', target='home') + + self.assertTrue(send.called) + self.assertEqual(1, len(send.mock_calls)) + + target = send.mock_calls[0][1][0] + payload = send.mock_calls[0][1][1] + + self.assertEqual('5678', target) + self.assertEqual('Hello', payload.alert) + + @patch('apns2.client.APNsClient') + def test_disable_when_unregistered(self, mock_client): + """Test disabling a device when it is unregistered.""" + send = mock_client.return_value.send_notification + send.side_effect = Unregistered() + + config = { + 'notify': { + 'platform': 'apns', + 'name': 'test_app', + 'topic': 'testapp.appname', + 'cert_file': 'test_app.pem' + } + } + hass = get_test_home_assistant() + + devices_path = hass.config.path('test_app_apns.yaml') + with open(devices_path, 'w+') as out: + out.write('1234: {name: test device 1}\n') + + notify.setup(hass, config) + + self.assertTrue(hass.services.call('notify', 'test_app', + {'message': 'Hello'}, + blocking=True)) + + devices = {str(key): value for (key, value) in + load_yaml_config_file(devices_path).items()} + + test_device_1 = devices.get('1234') + self.assertIsNotNone(test_device_1) + self.assertEqual(True, test_device_1.get('disabled')) + + os.remove(devices_path) From c54476b62fb6e05dc8208efcbe2e061df41436d3 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Mon, 17 Oct 2016 22:53:50 -0400 Subject: [PATCH 098/147] Protocol is an int (#3928) --- homeassistant/components/switch/rpi_rf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/rpi_rf.py b/homeassistant/components/switch/rpi_rf.py index 8bff88c48a6..61a9fdb0333 100644 --- a/homeassistant/components/switch/rpi_rf.py +++ b/homeassistant/components/switch/rpi_rf.py @@ -28,7 +28,7 @@ SWITCH_SCHEMA = vol.Schema({ vol.Required(CONF_CODE_OFF): cv.positive_int, vol.Required(CONF_CODE_ON): cv.positive_int, vol.Optional(CONF_PULSELENGTH): cv.positive_int, - vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): cv.string, + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): cv.positive_int, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ From 76598bc4d22be2b723ab81529092932fbbc77a62 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 17 Oct 2016 22:55:53 -0400 Subject: [PATCH 099/147] #3829 - Fixed issued LowHighTuple doesn't define __round__ method for Nest Sensor (#3881) * #3829 - Fixed type LowHighTuple doesn't define __round__ method issue when Nest is operating in range mode * Testing if temperature is a tuple instead int or float --- homeassistant/components/sensor/nest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index 135fc22895d..98d018a7c0b 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -135,7 +135,11 @@ class NestTempSensor(NestSensor): if temp is None: return None - return round(temp, 1) + if isinstance(temp, tuple): + low, high = temp + return "%s-%s" % (int(low), int(high)) + else: + return round(temp, 1) class NestWeatherSensor(NestSensor): From 3b424b034a04144a2f38ea2b301f0deec101211e Mon Sep 17 00:00:00 2001 From: Giel Janssens Date: Tue, 18 Oct 2016 04:57:02 +0200 Subject: [PATCH 100/147] Netatmo thermostat (#3888) * Added Netatmo-thermostat * Remove-CONF_DEVICES --- homeassistant/components/climate/netatmo.py | 178 ++++++++++++++++++++ homeassistant/components/netatmo.py | 7 +- 2 files changed, 182 insertions(+), 3 deletions(-) create mode 100755 homeassistant/components/climate/netatmo.py diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py new file mode 100755 index 00000000000..b0a5059ef44 --- /dev/null +++ b/homeassistant/components/climate/netatmo.py @@ -0,0 +1,178 @@ +""" +Support for Netatmo Smart Thermostat. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.netatmo/ +""" +import logging +from datetime import timedelta +import voluptuous as vol + +from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE +from homeassistant.components.climate import ( + STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) +from homeassistant.util import Throttle +from homeassistant.loader import get_component +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['netatmo'] + +_LOGGER = logging.getLogger(__name__) + +CONF_RELAY = 'relay' +CONF_THERMOSTAT = 'thermostat' + +DEFAULT_AWAY_TEMPERATURE = 14 +# # The default offeset is 2 hours (when you use the thermostat itself) +DEFAULT_TIME_OFFSET = 7200 +# # Return cached results if last scan was less then this time ago +# # NetAtmo Data is uploaded to server every hour +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_RELAY): cv.string, + vol.Optional(CONF_THERMOSTAT, default=[]): + vol.All(cv.ensure_list, [cv.string]), +}) + + +def setup_platform(hass, config, add_callback_devices, discovery_info=None): + """Setup the NetAtmo Thermostat.""" + netatmo = get_component('netatmo') + device = config.get(CONF_RELAY) + + import lnetatmo + try: + data = ThermostatData(netatmo.NETATMO_AUTH, device) + for module_name in data.get_module_names(): + if CONF_THERMOSTAT in config: + if config[CONF_THERMOSTAT] != [] and \ + module_name not in config[CONF_THERMOSTAT]: + continue + add_callback_devices([NetatmoThermostat(data, module_name)]) + except lnetatmo.NoDevice: + return None + + +# pylint: disable=abstract-method +class NetatmoThermostat(ClimateDevice): + """Representation a Netatmo thermostat.""" + + def __init__(self, data, module_name, away_temp=None): + """Initialize the sensor.""" + self._data = data + self._state = None + self._name = module_name + self._target_temperature = None + self._away = None + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._target_temperature + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_CELSIUS + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._data.current_temperature + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._target_temperature + + @property + def current_operation(self): + """Return the current state of the thermostat.""" + state = self._data.thermostatdata.relay_cmd + if state == 0: + return STATE_IDLE + elif state == 100: + return STATE_HEAT + + @property + def is_away_mode_on(self): + """Return true if away mode is on.""" + return self._away + + def turn_away_mode_on(self): + """Turn away on.""" + mode = "away" + temp = None + self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None) + self._away = True + self.update_ha_state() + + def turn_away_mode_off(self): + """Turn away off.""" + mode = "program" + temp = None + self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None) + self._away = False + self.update_ha_state() + + def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs): + """Set new target temperature for 2 hours.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is None: + return + mode = "manual" + self._data.thermostatdata.setthermpoint( + mode, temperature, endTimeOffset) + self._target_temperature = temperature + self._away = False + self.update_ha_state() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from NetAtmo API and updates the states.""" + self._data.update() + self._target_temperature = self._data.thermostatdata.setpoint_temp + self._away = self._data.setpoint_mode == 'away' + + +class ThermostatData(object): + """Get the latest data from Netatmo.""" + + def __init__(self, auth, device=None): + """Initialize the data object.""" + self.auth = auth + self.thermostatdata = None + self.module_names = [] + self.device = device + self.current_temperature = None + self.target_temperature = None + self.setpoint_mode = None + # self.operation = + + def get_module_names(self): + """Return all module available on the API as a list.""" + self.update() + if not self.device: + for device in self.thermostatdata.modules: + for module in self.thermostatdata.modules[device].values(): + self.module_names.append(module['module_name']) + else: + for module in self.thermostatdata.modules[self.device].values(): + self.module_names.append(module['module_name']) + return self.module_names + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Call the NetAtmo API to update the data.""" + import lnetatmo + self.thermostatdata = lnetatmo.ThermostatData(self.auth) + self.target_temperature = self.thermostatdata.setpoint_temp + self.setpoint_mode = self.thermostatdata.setpoint_mode + self.current_temperature = self.thermostatdata.temp diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index 81feba85d63..77432411e1a 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -1,5 +1,5 @@ """ -Support for the Netatmo devices (Weather Station and Welcome camera). +Support for the Netatmo devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/netatmo/ @@ -51,13 +51,14 @@ def setup(hass, config): NETATMO_AUTH = lnetatmo.ClientAuth( config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY], config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD], - 'read_station read_camera access_camera') + 'read_station read_camera access_camera ' + 'read_thermostat write_thermostat') except HTTPError: _LOGGER.error("Unable to connect to Netatmo API") return False if config[DOMAIN][CONF_DISCOVERY]: - for component in 'camera', 'sensor', 'binary_sensor': + for component in 'camera', 'sensor', 'binary_sensor', 'climate': discovery.load_platform(hass, component, DOMAIN, {}, config) return True From 9cf2acb4955ef4bd24c8ee878f097a23cb3115c8 Mon Sep 17 00:00:00 2001 From: Jason Carter Date: Mon, 17 Oct 2016 22:59:41 -0400 Subject: [PATCH 101/147] Concord232 alarm panel (#3842) * Adding Concord232 Alarm Panel * Adding Concord232 Zones as Sensors * Updating requirements_all.txt * Adding DOCType and making helper function a closure for clarity * Fixing D400 error in build * Fixing pylint errors * Adding # pylint: disable=too-many-locals * Updating with proper polling methods * Fixing Merge issues * Fixing DocStyle Issue * Moving Import out of setup * Fixing DocString * Removing overthought GLOBAL --- .coveragerc | 2 + .../alarm_control_panel/concord232.py | 136 +++++++++++++++++ .../components/binary_sensor/concord232.py | 143 ++++++++++++++++++ requirements_all.txt | 4 + 4 files changed, 285 insertions(+) create mode 100755 homeassistant/components/alarm_control_panel/concord232.py create mode 100755 homeassistant/components/binary_sensor/concord232.py diff --git a/.coveragerc b/.coveragerc index caadb341b59..e9856c7a51e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -108,9 +108,11 @@ omit = homeassistant/components/*/zoneminder.py homeassistant/components/alarm_control_panel/alarmdotcom.py + homeassistant/components/alarm_control_panel/concord232.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/simplisafe.py homeassistant/components/binary_sensor/arest.py + homeassistant/components/binary_sensor/concord232.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py homeassistant/components/camera/bloomsky.py diff --git a/homeassistant/components/alarm_control_panel/concord232.py b/homeassistant/components/alarm_control_panel/concord232.py new file mode 100755 index 00000000000..0e0fd026b60 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/concord232.py @@ -0,0 +1,136 @@ +""" +Support for Concord232 alarm control panels. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/alarm_control_panel.concord232/ +""" + +import datetime + +import logging + +import homeassistant.components.alarm_control_panel as alarm +from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_PORT, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, STATE_UNKNOWN) +import homeassistant.helpers.config_validation as cv + +import requests + +import voluptuous as vol + +REQUIREMENTS = ['concord232==0.14'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_HOST = 'localhost' +DEFAULT_NAME = 'CONCORD232' +DEFAULT_PORT = 5007 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, +}) + +SCAN_INTERVAL = 1 + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup concord232 platform.""" + name = config.get(CONF_NAME) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + + url = 'http://{}:{}'.format(host, port) + + try: + add_devices([Concord232Alarm(hass, url, name)]) + except requests.exceptions.ConnectionError as ex: + _LOGGER.error('Unable to connect to Concord232: %s', str(ex)) + return False + + +class Concord232Alarm(alarm.AlarmControlPanel): + """Represents the Concord232-based alarm panel.""" + + def __init__(self, hass, url, name): + """Initalize the concord232 alarm panel.""" + from concord232 import client as concord232_client + + self._state = STATE_UNKNOWN + self._hass = hass + self._name = name + self._url = url + + try: + client = concord232_client.Client(self._url) + except requests.exceptions.ConnectionError as ex: + _LOGGER.error('Unable to connect to Concord232: %s', str(ex)) + + self._alarm = client + self._alarm.partitions = self._alarm.list_partitions() + self._alarm.last_partition_update = datetime.datetime.now() + self.update() + + @property + def should_poll(self): + """Polling needed.""" + return True + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def code_format(self): + """The characters if code is defined.""" + return '[0-9]{4}([0-9]{2})?' + + @property + def state(self): + """Return the state of the device.""" + return self._state + + def update(self): + """Update values from API.""" + try: + part = self._alarm.list_partitions()[0] + except requests.exceptions.ConnectionError as ex: + _LOGGER.error('Unable to connect to %(host)s: %(reason)s', + dict(host=self._url, reason=ex)) + newstate = STATE_UNKNOWN + except IndexError: + _LOGGER.error('concord232 reports no partitions') + newstate = STATE_UNKNOWN + + if part['arming_level'] == "Off": + newstate = STATE_ALARM_DISARMED + elif "Home" in part['arming_level']: + newstate = STATE_ALARM_ARMED_HOME + else: + newstate = STATE_ALARM_ARMED_AWAY + + if not newstate == self._state: + _LOGGER.info("State Chnage from %s to %s", self._state, newstate) + self._state = newstate + return self._state + + def alarm_disarm(self, code=None): + """Send disarm command.""" + self._alarm.disarm(code) + + def alarm_arm_home(self, code=None): + """Send arm home command.""" + self._alarm.arm('home') + + def alarm_arm_away(self, code=None): + """Send arm away command.""" + self._alarm.arm('auto') + + def alarm_trigger(self, code=None): + """Alarm trigger command.""" + raise NotImplementedError() diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py new file mode 100755 index 00000000000..bc1eab4694a --- /dev/null +++ b/homeassistant/components/binary_sensor/concord232.py @@ -0,0 +1,143 @@ +""" +Support for exposing Concord232 elements as sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.concord232/ +""" +import datetime + +import logging + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES) +from homeassistant.const import (CONF_HOST, CONF_PORT) + +import homeassistant.helpers.config_validation as cv + +import requests + +import voluptuous as vol + + +REQUIREMENTS = ['concord232==0.14'] + +_LOGGER = logging.getLogger(__name__) + +CONF_EXCLUDE_ZONES = 'exclude_zones' +CONF_ZONE_TYPES = 'zone_types' + +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = '5007' +DEFAULT_SSL = False + +ZONE_TYPES_SCHEMA = vol.Schema({ + cv.positive_int: vol.In(SENSOR_CLASSES), +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_EXCLUDE_ZONES, default=[]): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, +}) + +SCAN_INTERVAL = 1 + +DEFAULT_NAME = "Alarm" + + +# pylint: disable=too-many-locals +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Concord232 binary sensor platform.""" + from concord232 import client as concord232_client + + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + exclude = config.get(CONF_EXCLUDE_ZONES) + zone_types = config.get(CONF_ZONE_TYPES) + sensors = [] + + try: + _LOGGER.debug('Initializing Client.') + client = concord232_client.Client('http://{}:{}' + .format(host, port)) + client.zones = client.list_zones() + client.last_zone_update = datetime.datetime.now() + + except requests.exceptions.ConnectionError as ex: + _LOGGER.error('Unable to connect to Concord232: %s', str(ex)) + return False + + for zone in client.zones: + _LOGGER.info('Loading Zone found: %s', zone['name']) + if zone['number'] not in exclude: + sensors.append(Concord232ZoneSensor( + hass, + client, + zone, + zone_types.get(zone['number'], get_opening_type(zone)))) + + add_devices(sensors) + + return True + + +def get_opening_type(zone): + """Helper function to try to guess sensor type frm name.""" + if "MOTION" in zone["name"]: + return "motion" + if "KEY" in zone["name"]: + return "safety" + if "SMOKE" in zone["name"]: + return "smoke" + if "WATER" in zone["name"]: + return "water" + return "opening" + + +class Concord232ZoneSensor(BinarySensorDevice): + """Representation of a Concord232 zone as a sensor.""" + + def __init__(self, hass, client, zone, zone_type): + """Initialize the Concord232 binary sensor.""" + self._hass = hass + self._client = client + self._zone = zone + self._number = zone['number'] + self._zone_type = zone_type + self.update() + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return self._zone_type + + @property + def should_poll(self): + """No polling needed.""" + return True + + @property + def name(self): + """Return the name of the binary sensor.""" + return self._zone['name'] + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + # True means "faulted" or "open" or "abnormal state" + return bool(self._zone['state'] == 'Normal') + + def update(self): + """"Get updated stats from API.""" + last_update = datetime.datetime.now() - self._client.last_zone_update + _LOGGER.debug("Zone: %s ", self._zone) + if last_update > datetime.timedelta(seconds=1): + self._client.zones = self._client.list_zones() + self._client.last_zone_update = datetime.datetime.now() + _LOGGER.debug("Updated from Zone: %s", self._zone['name']) + + if hasattr(self._client, 'zones'): + self._zone = next((x for x in self._client.zones + if x['number'] == self._number), None) diff --git a/requirements_all.txt b/requirements_all.txt index 240972252ac..5c44237bb83 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -72,6 +72,10 @@ coinmarketcap==2.0.1 # homeassistant.scripts.check_config colorlog>2.1,<3 +# homeassistant.components.alarm_control_panel.concord232 +# homeassistant.components.binary_sensor.concord232 +concord232==0.14 + # homeassistant.components.media_player.directv directpy==0.1 From d921073e77e7a719af753f1404c20a36c77f6ead Mon Sep 17 00:00:00 2001 From: juggie Date: Mon, 17 Oct 2016 23:06:03 -0400 Subject: [PATCH 102/147] Ecobee - Celsius (#3906) * #3899 - Ecobee tempoeratures * #3899 Remove unused import * #3899 Implement min/max_temp in ecobee.py as these temperatures have to be in F for ecobee api. * #3899 Stale print * #3899 Use min/max_temp from base class * #3899 Removed unused import (again) so tests pass since changing to use super class * #3899 Fix long lines * #3899 Install tox locally... make it happy, commit * #3899 Remove overridden min/max_temp and instead update __init__:min/max_temp to convert to self.temperature_unit (of the thermostat) as opposed to self.unit_of_measurement (of the system), which is wrong * Remove unused import from ecobee --- homeassistant/components/climate/__init__.py | 4 ++-- homeassistant/components/climate/ecobee.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 7652c5db214..714581ba331 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -545,12 +545,12 @@ class ClimateDevice(Entity): @property def min_temp(self): """Return the minimum temperature.""" - return convert_temperature(7, TEMP_CELSIUS, self.unit_of_measurement) + return convert_temperature(7, TEMP_CELSIUS, self.temperature_unit) @property def max_temp(self): """Return the maximum temperature.""" - return convert_temperature(35, TEMP_CELSIUS, self.unit_of_measurement) + return convert_temperature(35, TEMP_CELSIUS, self.temperature_unit) @property def min_humidity(self): diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index 4b414935136..1ed826e55dc 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -14,7 +14,7 @@ from homeassistant.components.climate import ( DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH) from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, TEMP_CELSIUS) + ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT) from homeassistant.config import load_yaml_config_file import homeassistant.helpers.config_validation as cv @@ -107,10 +107,7 @@ class Thermostat(ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - if self.thermostat['settings']['useCelsius']: - return TEMP_CELSIUS - else: - return TEMP_FAHRENHEIT + return TEMP_FAHRENHEIT @property def current_temperature(self): From 7d67017de7aff09ced5d14d8be62d510b9bb8aa4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 18 Oct 2016 05:13:43 +0200 Subject: [PATCH 103/147] Upgrade python-digitalocean to 1.10.0 (#3921) --- homeassistant/components/digital_ocean.py | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/digital_ocean.py b/homeassistant/components/digital_ocean.py index b91ec2672af..b507d9448e5 100644 --- a/homeassistant/components/digital_ocean.py +++ b/homeassistant/components/digital_ocean.py @@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-digitalocean==1.9.0'] +REQUIREMENTS = ['python-digitalocean==1.10.0'] _LOGGER = logging.getLogger(__name__) @@ -44,7 +44,7 @@ CONFIG_SCHEMA = vol.Schema({ # pylint: disable=unused-argument,too-few-public-methods def setup(hass, config): - """Setup the Digital Ocean component.""" + """Set up the Digital Ocean component.""" conf = config[DOMAIN] access_token = conf.get(CONF_ACCESS_TOKEN) diff --git a/requirements_all.txt b/requirements_all.txt index 5c44237bb83..63114a68abc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -384,7 +384,7 @@ pyserial==3.1.1 pysnmp==4.3.2 # homeassistant.components.digital_ocean -python-digitalocean==1.9.0 +python-digitalocean==1.10.0 # homeassistant.components.sensor.darksky python-forecastio==1.3.5 From 272539105f949ac28e37adac0474ac28d65256e0 Mon Sep 17 00:00:00 2001 From: Rob Capellini Date: Mon, 17 Oct 2016 23:16:36 -0400 Subject: [PATCH 104/147] Replacing tempfile with mock_open in tests (#3753) - test_bootstrap.py - test_config.py - components/test_init.py - components/test_panel_custom.py - components/test_api.py - components/notify/test_file.py - components/notify/test_demo.py - components/camera/test_local_file.py - helpers/test_config_validation.py - util/test_package.py - util/test_yaml.py No changes needed in: - components/cover/test_command_line.py - components/switch/test_command_line.py - components/rollershutter/test_command_line.py - components/test_shell_command.py - components/notify/test_command_line.py Misc changes in: - components/mqtt/test_server.py Also, removed some unused mock parameters in tests/components/mqtt/test_server.py. --- tests/components/camera/test_local_file.py | 44 ++--- tests/components/mqtt/test_server.py | 18 +- tests/components/notify/test_demo.py | 60 +++--- tests/components/notify/test_file.py | 26 ++- tests/components/test_api.py | 24 ++- tests/components/test_init.py | 46 ++--- tests/components/test_panel_custom.py | 62 +++--- tests/helpers/test_config_validation.py | 18 +- tests/test_bootstrap.py | 24 ++- tests/test_config.py | 62 +++--- tests/util/test_package.py | 138 ++++++++++--- tests/util/test_yaml.py | 216 +++++++++------------ 12 files changed, 403 insertions(+), 335 deletions(-) diff --git a/tests/components/camera/test_local_file.py b/tests/components/camera/test_local_file.py index c29aa4b6da0..0c131b441b5 100644 --- a/tests/components/camera/test_local_file.py +++ b/tests/components/camera/test_local_file.py @@ -1,5 +1,4 @@ """The tests for local file camera component.""" -from tempfile import NamedTemporaryFile import unittest from unittest import mock @@ -26,45 +25,46 @@ class TestLocalCamera(unittest.TestCase): def test_loading_file(self): """Test that it loads image from disk.""" + test_string = 'hello' self.hass.wsgi = mock.MagicMock() - with NamedTemporaryFile() as fptr: - fptr.write('hello'.encode('utf-8')) - fptr.flush() - + with mock.patch('os.path.isfile', mock.Mock(return_value=True)), \ + mock.patch('os.access', mock.Mock(return_value=True)): assert setup_component(self.hass, 'camera', { 'camera': { 'name': 'config_test', 'platform': 'local_file', - 'file_path': fptr.name, + 'file_path': 'mock.file', }}) - image_view = self.hass.wsgi.mock_calls[0][1][0] + image_view = self.hass.wsgi.mock_calls[0][1][0] + m_open = mock.mock_open(read_data=test_string) + with mock.patch( + 'homeassistant.components.camera.local_file.open', + m_open, create=True + ): builder = EnvironBuilder(method='GET') Request = request_class() # pylint: disable=invalid-name request = Request(builder.get_environ()) request.authenticated = True resp = image_view.get(request, 'camera.config_test') - assert resp.status_code == 200, resp.response - assert resp.response[0].decode('utf-8') == 'hello' + assert resp.status_code == 200, resp.response + assert resp.response[0].decode('utf-8') == test_string def test_file_not_readable(self): """Test local file will not setup when file is not readable.""" self.hass.wsgi = mock.MagicMock() - with NamedTemporaryFile() as fptr: - fptr.write('hello'.encode('utf-8')) - fptr.flush() + with mock.patch('os.path.isfile', mock.Mock(return_value=True)), \ + mock.patch('os.access', return_value=False), \ + assert_setup_component(0): + assert setup_component(self.hass, 'camera', { + 'camera': { + 'name': 'config_test', + 'platform': 'local_file', + 'file_path': 'mock.file', + }}) - with mock.patch('os.access', return_value=False), \ - assert_setup_component(0): - assert setup_component(self.hass, 'camera', { - 'camera': { - 'name': 'config_test', - 'platform': 'local_file', - 'file_path': fptr.name, - }}) - - assert [] == self.hass.states.all() + assert [] == self.hass.states.all() diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py index cf869082f4b..eb7dabb28b3 100644 --- a/tests/components/mqtt/test_server.py +++ b/tests/components/mqtt/test_server.py @@ -1,5 +1,5 @@ """The tests for the MQTT component embedded server.""" -from unittest.mock import MagicMock, patch +from unittest.mock import Mock, MagicMock, patch from homeassistant.bootstrap import _setup_component import homeassistant.components.mqtt as mqtt @@ -18,14 +18,12 @@ class TestMQTT: """Stop everything that was started.""" self.hass.stop() - @patch('passlib.apps.custom_app_context', return_value='') - @patch('tempfile.NamedTemporaryFile', return_value=MagicMock()) + @patch('passlib.apps.custom_app_context', Mock(return_value='')) + @patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock())) + @patch('asyncio.new_event_loop', Mock()) @patch('homeassistant.components.mqtt.MQTT') @patch('asyncio.gather') - @patch('asyncio.new_event_loop') - def test_creating_config_with_http_pass(self, mock_new_loop, mock_gather, - mock_mqtt, mock_temp, - mock_context): + def test_creating_config_with_http_pass(self, mock_gather, mock_mqtt): """Test if the MQTT server gets started and subscribe/publish msg.""" self.hass.config.components.append('http') password = 'super_secret' @@ -45,10 +43,10 @@ class TestMQTT: assert mock_mqtt.mock_calls[0][1][5] is None assert mock_mqtt.mock_calls[0][1][6] is None - @patch('tempfile.NamedTemporaryFile', return_value=MagicMock()) + @patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock())) + @patch('asyncio.new_event_loop', Mock()) @patch('asyncio.gather') - @patch('asyncio.new_event_loop') - def test_broker_config_fails(self, mock_new_loop, mock_gather, mock_temp): + def test_broker_config_fails(self, mock_gather): """Test if the MQTT component fails if server fails.""" self.hass.config.components.append('http') from hbmqtt.broker import BrokerException diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index a0d9f28fe1a..3ec00a84bda 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -1,12 +1,10 @@ """The tests for the notify demo platform.""" -import tempfile import unittest from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import demo from homeassistant.helpers import script -from homeassistant.util import yaml from tests.common import get_test_home_assistant @@ -70,21 +68,18 @@ class TestNotifyDemo(unittest.TestCase): def test_calling_notify_from_script_loaded_from_yaml_without_title(self): """Test if we can call a notify from a script.""" - yaml_conf = """ -service: notify.notify -data: - data: - push: - sound: US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav -data_template: - message: > - Test 123 {{ 2 + 2 }} -""" - - with tempfile.NamedTemporaryFile() as fp: - fp.write(yaml_conf.encode('utf-8')) - fp.flush() - conf = yaml.load_yaml(fp.name) + conf = { + 'service': 'notify.notify', + 'data': { + 'data': { + 'push': { + 'sound': + 'US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav' + } + } + }, + 'data_template': {'message': 'Test 123 {{ 2 + 2 }}\n'}, + } script.call_from_config(self.hass, conf) self.hass.block_till_done() @@ -99,22 +94,21 @@ data_template: def test_calling_notify_from_script_loaded_from_yaml_with_title(self): """Test if we can call a notify from a script.""" - yaml_conf = """ -service: notify.notify -data: - data: - push: - sound: US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav -data_template: - title: Test - message: > - Test 123 {{ 2 + 2 }} -""" - - with tempfile.NamedTemporaryFile() as fp: - fp.write(yaml_conf.encode('utf-8')) - fp.flush() - conf = yaml.load_yaml(fp.name) + conf = { + 'service': 'notify.notify', + 'data': { + 'data': { + 'push': { + 'sound': + 'US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav' + } + } + }, + 'data_template': { + 'message': 'Test 123 {{ 2 + 2 }}\n', + 'title': 'Test' + } + } script.call_from_config(self.hass, conf) self.hass.pool.block_till_done() diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py index eaca5c3d962..f63d16a5711 100644 --- a/tests/components/notify/test_file.py +++ b/tests/components/notify/test_file.py @@ -1,8 +1,7 @@ """The tests for the notify file platform.""" import os import unittest -import tempfile -from unittest.mock import patch +from unittest.mock import call, mock_open, patch from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify @@ -34,13 +33,19 @@ class TestNotifyFile(unittest.TestCase): }, })) + @patch('homeassistant.components.notify.file.os.stat') @patch('homeassistant.util.dt.utcnow') - def test_notify_file(self, mock_utcnow): + def test_notify_file(self, mock_utcnow, mock_stat): """Test the notify file output.""" mock_utcnow.return_value = dt_util.as_utc(dt_util.now()) + mock_stat.return_value.st_size = 0 - with tempfile.TemporaryDirectory() as tempdirname: - filename = os.path.join(tempdirname, 'notify.txt') + m_open = mock_open() + with patch( + 'homeassistant.components.notify.file.open', + m_open, create=True + ): + filename = 'mock_file' message = 'one, two, testing, testing' self.assertTrue(setup_component(self.hass, notify.DOMAIN, { 'notify': { @@ -58,5 +63,12 @@ class TestNotifyFile(unittest.TestCase): self.hass.services.call('notify', 'test', {'message': message}, blocking=True) - result = open(filename).read() - self.assertEqual(result, "{}{}\n".format(title, message)) + full_filename = os.path.join(self.hass.config.path(), filename) + self.assertEqual(m_open.call_count, 1) + self.assertEqual(m_open.call_args, call(full_filename, 'a')) + + self.assertEqual(m_open.return_value.write.call_count, 2) + self.assertEqual( + m_open.return_value.write.call_args_list, + [call(title), call(message + '\n')] + ) diff --git a/tests/components/test_api.py b/tests/components/test_api.py index ca494305073..78affc70648 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -2,10 +2,9 @@ # pylint: disable=protected-access,too-many-public-methods from contextlib import closing import json -import tempfile import time import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch import requests @@ -244,15 +243,20 @@ class TestAPI(unittest.TestCase): def test_api_get_error_log(self): """Test the return of the error log.""" - test_content = 'Test String°' - with tempfile.NamedTemporaryFile() as log: - log.write(test_content.encode('utf-8')) - log.flush() + test_string = 'Test String°'.encode('UTF-8') - with patch.object(hass.config, 'path', return_value=log.name): - req = requests.get(_url(const.URL_API_ERROR_LOG), - headers=HA_HEADERS) - self.assertEqual(test_content, req.text) + # Can't use read_data with wsgiserver in Python 3.4.2. Due to a + # bug in read_data, it can't handle byte types ('Type str doesn't + # support the buffer API'), but wsgiserver requires byte types + # ('WSGI Applications must yield bytes'). So just mock our own + # read method. + m_open = Mock(return_value=Mock( + read=Mock(side_effect=[test_string])) + ) + with patch('homeassistant.components.http.open', m_open, create=True): + req = requests.get(_url(const.URL_API_ERROR_LOG), + headers=HA_HEADERS) + self.assertEqual(test_string, req.text.encode('UTF-8')) self.assertIsNone(req.headers.get('expires')) def test_api_get_event_listeners(self): diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 76878432ecd..44a60ee986f 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -1,8 +1,7 @@ """The testd for Core components.""" # pylint: disable=protected-access,too-many-public-methods import unittest -from unittest.mock import patch -from tempfile import TemporaryDirectory +from unittest.mock import patch, Mock import yaml @@ -13,7 +12,8 @@ from homeassistant.const import ( import homeassistant.components as comps from homeassistant.helpers import entity -from tests.common import get_test_home_assistant, mock_service +from tests.common import ( + get_test_home_assistant, mock_service, patch_yaml_files) class TestComponentsCore(unittest.TestCase): @@ -89,6 +89,7 @@ class TestComponentsCore(unittest.TestCase): ('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False), mock_call.call_args_list[1][0]) + @patch('homeassistant.config.os.path.isfile', Mock(return_value=True)) def test_reload_core_conf(self): """Test reload core conf service.""" ent = entity.Entity() @@ -101,23 +102,20 @@ class TestComponentsCore(unittest.TestCase): assert state.state == 'unknown' assert state.attributes == {} - with TemporaryDirectory() as conf_dir: - self.hass.config.config_dir = conf_dir - conf_yaml = self.hass.config.path(config.YAML_CONFIG_FILE) - - with open(conf_yaml, 'a') as fp: - fp.write(yaml.dump({ - ha.DOMAIN: { - 'latitude': 10, - 'longitude': 20, - 'customize': { - 'test.Entity': { - 'hello': 'world' - } + files = { + config.YAML_CONFIG_FILE: yaml.dump({ + ha.DOMAIN: { + 'latitude': 10, + 'longitude': 20, + 'customize': { + 'test.Entity': { + 'hello': 'world' } } - })) - + } + }) + } + with patch_yaml_files(files, True): comps.reload_core_config(self.hass) self.hass.block_till_done() @@ -131,17 +129,15 @@ class TestComponentsCore(unittest.TestCase): assert state.state == 'unknown' assert state.attributes.get('hello') == 'world' + @patch('homeassistant.config.os.path.isfile', Mock(return_value=True)) @patch('homeassistant.components._LOGGER.error') @patch('homeassistant.config.process_ha_core_config') def test_reload_core_with_wrong_conf(self, mock_process, mock_error): """Test reload core conf service.""" - with TemporaryDirectory() as conf_dir: - self.hass.config.config_dir = conf_dir - conf_yaml = self.hass.config.path(config.YAML_CONFIG_FILE) - - with open(conf_yaml, 'a') as fp: - fp.write(yaml.dump(['invalid', 'config'])) - + files = { + config.YAML_CONFIG_FILE: yaml.dump(['invalid', 'config']) + } + with patch_yaml_files(files, True): comps.reload_core_config(self.hass) self.hass.block_till_done() diff --git a/tests/components/test_panel_custom.py b/tests/components/test_panel_custom.py index 6a41706db98..1ef12161bcb 100644 --- a/tests/components/test_panel_custom.py +++ b/tests/components/test_panel_custom.py @@ -1,9 +1,8 @@ """The tests for the panel_custom component.""" import os import shutil -from tempfile import NamedTemporaryFile import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch from homeassistant import bootstrap from homeassistant.components import panel_custom @@ -47,31 +46,40 @@ class TestPanelCustom(unittest.TestCase): @patch('homeassistant.components.panel_custom.register_panel') def test_webcomponent_custom_path(self, mock_register, _mock_setup): """Test if a web component is found in config panels dir.""" - with NamedTemporaryFile() as fp: - config = { - 'panel_custom': { - 'name': 'todomvc', - 'webcomponent_path': fp.name, - 'sidebar_title': 'Sidebar Title', - 'sidebar_icon': 'mdi:iconicon', - 'url_path': 'nice_url', - 'config': 5, - } - } + filename = 'mock.file' - with patch('os.path.isfile', return_value=False): - assert not bootstrap.setup_component(self.hass, 'panel_custom', - config) - assert not mock_register.called - - assert bootstrap.setup_component(self.hass, 'panel_custom', config) - assert mock_register.called - args = mock_register.mock_calls[0][1] - kwargs = mock_register.mock_calls[0][2] - assert args == (self.hass, 'todomvc', fp.name) - assert kwargs == { - 'config': 5, - 'url_path': 'nice_url', + config = { + 'panel_custom': { + 'name': 'todomvc', + 'webcomponent_path': filename, + 'sidebar_title': 'Sidebar Title', 'sidebar_icon': 'mdi:iconicon', - 'sidebar_title': 'Sidebar Title' + 'url_path': 'nice_url', + 'config': 5, } + } + + with patch('os.path.isfile', Mock(return_value=False)): + assert not bootstrap.setup_component( + self.hass, 'panel_custom', config + ) + assert not mock_register.called + + with patch('os.path.isfile', Mock(return_value=True)): + with patch('os.access', Mock(return_value=True)): + assert bootstrap.setup_component( + self.hass, 'panel_custom', config + ) + + assert mock_register.called + + args = mock_register.mock_calls[0][1] + assert args == (self.hass, 'todomvc', filename) + + kwargs = mock_register.mock_calls[0][2] + assert kwargs == { + 'config': 5, + 'url_path': 'nice_url', + 'sidebar_icon': 'mdi:iconicon', + 'sidebar_title': 'Sidebar Title' + } diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 9f929244888..287219aa669 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -3,10 +3,10 @@ from collections import OrderedDict from datetime import timedelta import enum import os -import tempfile import pytest import voluptuous as vol +from unittest.mock import Mock, patch import homeassistant.helpers.config_validation as cv @@ -68,18 +68,18 @@ def test_isfile(): """Validate that the value is an existing file.""" schema = vol.Schema(cv.isfile) - with tempfile.NamedTemporaryFile() as fp: - pass + fake_file = 'this-file-does-not.exist' + assert not os.path.isfile(fake_file) - for value in ('invalid', None, -1, 0, 80000, fp.name): + for value in ('invalid', None, -1, 0, 80000, fake_file): with pytest.raises(vol.Invalid): schema(value) - with tempfile.TemporaryDirectory() as tmp_path: - tmp_file = os.path.join(tmp_path, "test.txt") - with open(tmp_file, "w") as tmp_handl: - tmp_handl.write("test file") - schema(tmp_file) + # patching methods that allow us to fake a file existing + # with write access + with patch('os.path.isfile', Mock(return_value=True)), \ + patch('os.access', Mock(return_value=True)): + schema('test.txt') def test_url(): diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index d3f3caf795c..c84c95f396c 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,6 +1,5 @@ """Test the bootstrapping.""" # pylint: disable=too-many-public-methods,protected-access -import tempfile from unittest import mock import threading import logging @@ -12,7 +11,8 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from tests.common import \ - get_test_home_assistant, MockModule, MockPlatform, assert_setup_component + get_test_home_assistant, MockModule, MockPlatform, \ + assert_setup_component, patch_yaml_files ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE @@ -45,17 +45,27 @@ class TestBootstrap: self.hass.stop() loader._COMPONENT_CACHE = self.backup_cache + @mock.patch( + # prevent .HA_VERISON file from being written + 'homeassistant.bootstrap.conf_util.process_ha_config_upgrade', + mock.Mock() + ) @mock.patch('homeassistant.util.location.detect_location_info', return_value=None) def test_from_config_file(self, mock_detect): """Test with configuration file.""" components = ['browser', 'conversation', 'script'] - with tempfile.NamedTemporaryFile() as fp: - for comp in components: - fp.write('{}:\n'.format(comp).encode('utf-8')) - fp.flush() + files = { + 'config.yaml': ''.join( + '{}:\n'.format(comp) + for comp in components + ) + } - self.hass = bootstrap.from_config_file(fp.name) + with mock.patch('os.path.isfile', mock.Mock(return_value=True)), \ + mock.patch('os.access', mock.Mock(return_value=True)), \ + patch_yaml_files(files, True): + self.hass = bootstrap.from_config_file('config.yaml') components.append('group') assert sorted(components) == sorted(self.hass.config.components) diff --git a/tests/test_config.py b/tests/test_config.py index 1512a7688ea..4787da5bcde 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,6 @@ """Test config utils.""" # pylint: disable=too-many-public-methods,protected-access import os -import tempfile import unittest import unittest.mock as mock @@ -212,49 +211,56 @@ class TestConfig(unittest.TestCase): assert state.attributes['hidden'] - def test_remove_lib_on_upgrade(self): + @mock.patch('homeassistant.config.shutil') + @mock.patch('homeassistant.config.os') + def test_remove_lib_on_upgrade(self, mock_os, mock_shutil): """Test removal of library on upgrade.""" - with tempfile.TemporaryDirectory() as config_dir: - version_path = os.path.join(config_dir, '.HA_VERSION') - lib_dir = os.path.join(config_dir, 'deps') - check_file = os.path.join(lib_dir, 'check') + ha_version = '0.7.0' - with open(version_path, 'wt') as outp: - outp.write('0.7.0') + mock_os.path.isdir = mock.Mock(return_value=True) - os.mkdir(lib_dir) - - with open(check_file, 'w'): - pass + mock_open = mock.mock_open() + with mock.patch('homeassistant.config.open', mock_open, create=True): + opened_file = mock_open.return_value + opened_file.readline.return_value = ha_version self.hass = get_test_home_assistant() - self.hass.config.config_dir = config_dir + self.hass.config.path = mock.Mock() - assert os.path.isfile(check_file) config_util.process_ha_config_upgrade(self.hass) - assert not os.path.isfile(check_file) - def test_not_remove_lib_if_not_upgrade(self): + hass_path = self.hass.config.path.return_value + + self.assertEqual(mock_os.path.isdir.call_count, 1) + self.assertEqual( + mock_os.path.isdir.call_args, mock.call(hass_path) + ) + + self.assertEqual(mock_shutil.rmtree.call_count, 1) + self.assertEqual( + mock_shutil.rmtree.call_args, mock.call(hass_path) + ) + + @mock.patch('homeassistant.config.shutil') + @mock.patch('homeassistant.config.os') + def test_not_remove_lib_if_not_upgrade(self, mock_os, mock_shutil): """Test removal of library with no upgrade.""" - with tempfile.TemporaryDirectory() as config_dir: - version_path = os.path.join(config_dir, '.HA_VERSION') - lib_dir = os.path.join(config_dir, 'deps') - check_file = os.path.join(lib_dir, 'check') + ha_version = __version__ - with open(version_path, 'wt') as outp: - outp.write(__version__) + mock_os.path.isdir = mock.Mock(return_value=True) - os.mkdir(lib_dir) - - with open(check_file, 'w'): - pass + mock_open = mock.mock_open() + with mock.patch('homeassistant.config.open', mock_open, create=True): + opened_file = mock_open.return_value + opened_file.readline.return_value = ha_version self.hass = get_test_home_assistant() - self.hass.config.config_dir = config_dir + self.hass.config.path = mock.Mock() config_util.process_ha_config_upgrade(self.hass) - assert os.path.isfile(check_file) + assert mock_os.path.isdir.call_count == 0 + assert mock_shutil.rmtree.call_count == 0 def test_loading_configuration(self): """Test loading core config onto hass object.""" diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 3aa742516e4..d7af4f2d6a3 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -1,9 +1,12 @@ """Test Home Assistant package util methods.""" import os -import tempfile +import pkg_resources +import subprocess import unittest -from homeassistant.bootstrap import mount_local_lib_path +from distutils.sysconfig import get_python_lib +from unittest.mock import call, patch + import homeassistant.util.package as package RESOURCE_DIR = os.path.abspath( @@ -15,43 +18,114 @@ TEST_ZIP_REQ = 'file://{}#{}' \ .format(os.path.join(RESOURCE_DIR, 'pyhelloworld3.zip'), TEST_NEW_REQ) -class TestPackageUtil(unittest.TestCase): +@patch('homeassistant.util.package.subprocess.call') +@patch('homeassistant.util.package.check_package_exists') +class TestPackageUtilInstallPackage(unittest.TestCase): """Test for homeassistant.util.package module.""" - def setUp(self): - """Create local library for testing.""" - self.tmp_dir = tempfile.TemporaryDirectory() - self.lib_dir = mount_local_lib_path(self.tmp_dir.name) - - def tearDown(self): - """Stop everything that was started.""" - self.tmp_dir.cleanup() - - def test_install_existing_package(self): + def test_install_existing_package(self, mock_exists, mock_subprocess): """Test an install attempt on an existing package.""" - self.assertTrue(package.check_package_exists( - TEST_EXIST_REQ, self.lib_dir)) + mock_exists.return_value = True self.assertTrue(package.install_package(TEST_EXIST_REQ)) - 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)) - self.assertFalse(package.check_package_exists( - TEST_NEW_REQ, self.lib_dir)) + self.assertEqual(mock_exists.call_count, 1) + self.assertEqual(mock_exists.call_args, call(TEST_EXIST_REQ, None)) - self.assertTrue(package.install_package( - TEST_ZIP_REQ, True, self.lib_dir)) + self.assertEqual(mock_subprocess.call_count, 0) - self.assertTrue(package.check_package_exists( - TEST_ZIP_REQ, self.lib_dir)) - self.assertTrue(package.check_package_exists( - TEST_NEW_REQ, self.lib_dir)) + @patch('homeassistant.util.package.sys') + def test_install(self, mock_sys, mock_exists, mock_subprocess): + """Test an install attempt on a package that doesn't exist.""" + mock_exists.return_value = False + mock_subprocess.return_value = 0 - try: - import pyhelloworld3 - except ImportError: - self.fail('Unable to import pyhelloworld3 after installing it.') + self.assertTrue(package.install_package(TEST_NEW_REQ, False)) - self.assertEqual(pyhelloworld3.__version__, '1.0.0') + self.assertEqual(mock_exists.call_count, 1) + + self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual( + mock_subprocess.call_args, + call([ + mock_sys.executable, '-m', 'pip', + 'install', '--quiet', TEST_NEW_REQ + ]) + ) + + @patch('homeassistant.util.package.sys') + def test_install_upgrade(self, mock_sys, mock_exists, mock_subprocess): + """Test an upgrade attempt on a package.""" + mock_exists.return_value = False + mock_subprocess.return_value = 0 + + self.assertTrue(package.install_package(TEST_NEW_REQ)) + + self.assertEqual(mock_exists.call_count, 1) + + self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual( + mock_subprocess.call_args, + call([ + mock_sys.executable, '-m', 'pip', 'install', + '--quiet', TEST_NEW_REQ, '--upgrade' + ]) + ) + + @patch('homeassistant.util.package.sys') + def test_install_target(self, mock_sys, mock_exists, mock_subprocess): + """Test an install with a target.""" + target = 'target_folder' + mock_exists.return_value = False + mock_subprocess.return_value = 0 + + self.assertTrue( + package.install_package(TEST_NEW_REQ, False, target=target) + ) + + self.assertEqual(mock_exists.call_count, 1) + + self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual( + mock_subprocess.call_args, + call([ + mock_sys.executable, '-m', 'pip', 'install', '--quiet', + TEST_NEW_REQ, '--target', os.path.abspath(target) + ]) + ) + + @patch('homeassistant.util.package._LOGGER') + @patch('homeassistant.util.package.sys') + def test_install_error( + self, mock_sys, mock_logger, mock_exists, mock_subprocess + ): + """Test an install with a target.""" + mock_exists.return_value = False + mock_subprocess.side_effect = [subprocess.SubprocessError] + + self.assertFalse(package.install_package(TEST_NEW_REQ)) + + self.assertEqual(mock_logger.exception.call_count, 1) + + +class TestPackageUtilCheckPackageExists(unittest.TestCase): + """Test for homeassistant.util.package module.""" + + def test_check_package_global(self): + """Test for a globally-installed package""" + installed_package = list(pkg_resources.working_set)[0].project_name + + self.assertTrue(package.check_package_exists(installed_package, None)) + + def test_check_package_local(self): + """Test for a locally-installed package""" + lib_dir = get_python_lib() + installed_package = list(pkg_resources.working_set)[0].project_name + + self.assertTrue( + package.check_package_exists(installed_package, lib_dir) + ) + + def test_check_package_zip(self): + """Test for an installed zip package""" + self.assertFalse(package.check_package_exists(TEST_ZIP_REQ, None)) diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index b1214c2ff17..7c7bb0b9255 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -2,7 +2,6 @@ import io import unittest import os -import tempfile from unittest.mock import patch from homeassistant.exceptions import HomeAssistantError @@ -68,146 +67,116 @@ class TestYaml(unittest.TestCase): def test_include_yaml(self): """Test include yaml.""" - with tempfile.NamedTemporaryFile() as include_file: - include_file.write(b"value") - include_file.seek(0) - conf = "key: !include {}".format(include_file.name) + with patch_yaml_files({'test.yaml': 'value'}): + conf = 'key: !include test.yaml' with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert doc["key"] == "value" - def test_include_dir_list(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_list(self, mock_walk): """Test include dir list yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_1 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_1.write(b"one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_2.write(b"two") - file_2.close() - conf = "key: !include_dir_list {}".format(include_dir) + mock_walk.return_value = [['/tmp', [], ['one.yaml', 'two.yaml']]] + + with patch_yaml_files({ + '/tmp/one.yaml': 'one', '/tmp/two.yaml': 'two' + }): + conf = "key: !include_dir_list /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert sorted(doc["key"]) == sorted(["one", "two"]) - def test_include_dir_list_recursive(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_list_recursive(self, mock_walk): """Test include dir recursive list yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_0 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_0.write(b"zero") - file_0.close() - temp_dir = tempfile.TemporaryDirectory(dir=include_dir) - file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_1.write(b"one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_2.write(b"two") - file_2.close() - conf = "key: !include_dir_list {}".format(include_dir) + mock_walk.return_value = [ + ['/tmp', ['tmp2'], ['zero.yaml']], + ['/tmp/tmp2', [], ['one.yaml', 'two.yaml']], + ] + + with patch_yaml_files({ + '/tmp/zero.yaml': 'zero', '/tmp/tmp2/one.yaml': 'one', + '/tmp/tmp2/two.yaml': 'two' + }): + conf = "key: !include_dir_list /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) - def test_include_dir_named(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_named(self, mock_walk): """Test include dir named yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_1 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_1.write(b"one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_2.write(b"two") - file_2.close() - conf = "key: !include_dir_named {}".format(include_dir) - correct = {} - correct[os.path.splitext(os.path.basename(file_1.name))[0]] = "one" - correct[os.path.splitext(os.path.basename(file_2.name))[0]] = "two" + mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] + + with patch_yaml_files({ + '/tmp/first.yaml': 'one', '/tmp/second.yaml': 'two' + }): + conf = "key: !include_dir_named /tmp" + correct = {'first': 'one', 'second': 'two'} with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert doc["key"] == correct - def test_include_dir_named_recursive(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_named_recursive(self, mock_walk): """Test include dir named yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_0 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_0.write(b"zero") - file_0.close() - temp_dir = tempfile.TemporaryDirectory(dir=include_dir) - file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_1.write(b"one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_2.write(b"two") - file_2.close() - conf = "key: !include_dir_named {}".format(include_dir) - correct = {} - correct[os.path.splitext( - os.path.basename(file_0.name))[0]] = "zero" - correct[os.path.splitext(os.path.basename(file_1.name))[0]] = "one" - correct[os.path.splitext(os.path.basename(file_2.name))[0]] = "two" + mock_walk.return_value = [ + ['/tmp', ['tmp2'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ] + + with patch_yaml_files({ + '/tmp/first.yaml': 'one', '/tmp/tmp2/second.yaml': 'two', + '/tmp/tmp2/third.yaml': 'three' + }): + conf = "key: !include_dir_named /tmp" + correct = {'first': 'one', 'second': 'two', 'third': 'three'} with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert doc["key"] == correct - def test_include_dir_merge_list(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_merge_list(self, mock_walk): """Test include dir merge list yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_1 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_1.write(b"- one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_2.write(b"- two\n- three") - file_2.close() - conf = "key: !include_dir_merge_list {}".format(include_dir) + mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] + + with patch_yaml_files({ + '/tmp/first.yaml': '- one', + '/tmp/second.yaml': '- two\n- three' + }): + conf = "key: !include_dir_merge_list /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert sorted(doc["key"]) == sorted(["one", "two", "three"]) - def test_include_dir_merge_list_recursive(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_merge_list_recursive(self, mock_walk): """Test include dir merge list yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_0 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_0.write(b"- zero") - file_0.close() - temp_dir = tempfile.TemporaryDirectory(dir=include_dir) - file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_1.write(b"- one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_2.write(b"- two\n- three") - file_2.close() - conf = "key: !include_dir_merge_list {}".format(include_dir) + mock_walk.return_value = [ + ['/tmp', ['tmp2'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ] + + with patch_yaml_files({ + '/tmp/first.yaml': '- one', '/tmp/tmp2/second.yaml': '- two', + '/tmp/tmp2/third.yaml': '- three\n- four' + }): + conf = "key: !include_dir_merge_list /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) - assert sorted(doc["key"]) == sorted(["zero", "one", "two", - "three"]) + assert sorted(doc["key"]) == sorted(["one", "two", + "three", "four"]) - def test_include_dir_merge_named(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_merge_named(self, mock_walk): """Test include dir merge named yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_1 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_1.write(b"key1: one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_2.write(b"key2: two\nkey3: three") - file_2.close() - conf = "key: !include_dir_merge_named {}".format(include_dir) + mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] + + with patch_yaml_files({ + '/tmp/first.yaml': 'key1: one', + '/tmp/second.yaml': 'key2: two\nkey3: three' + }): + conf = "key: !include_dir_merge_named /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert doc["key"] == { @@ -216,30 +185,27 @@ class TestYaml(unittest.TestCase): "key3": "three" } - def test_include_dir_merge_named_recursive(self): + @patch('homeassistant.util.yaml.os.walk') + def test_include_dir_merge_named_recursive(self, mock_walk): """Test include dir merge named yaml.""" - with tempfile.TemporaryDirectory() as include_dir: - file_0 = tempfile.NamedTemporaryFile(dir=include_dir, - suffix=".yaml", delete=False) - file_0.write(b"key0: zero") - file_0.close() - temp_dir = tempfile.TemporaryDirectory(dir=include_dir) - file_1 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_1.write(b"key1: one") - file_1.close() - file_2 = tempfile.NamedTemporaryFile(dir=temp_dir.name, - suffix=".yaml", delete=False) - file_2.write(b"key2: two\nkey3: three") - file_2.close() - conf = "key: !include_dir_merge_named {}".format(include_dir) + mock_walk.return_value = [ + ['/tmp', ['tmp2'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ] + + with patch_yaml_files({ + '/tmp/first.yaml': 'key1: one', + '/tmp/tmp2/second.yaml': 'key2: two', + '/tmp/tmp2/third.yaml': 'key3: three\nkey4: four' + }): + conf = "key: !include_dir_merge_named /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) assert doc["key"] == { - "key0": "zero", "key1": "one", "key2": "two", - "key3": "three" + "key3": "three", + "key4": "four" } FILES = {} From 14ef0ca7868ce69831b109b8b3b2fa03bd1acea8 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 18 Oct 2016 07:27:32 +0200 Subject: [PATCH 105/147] Bugfix Template sensors (#3931) --- homeassistant/components/binary_sensor/template.py | 1 + homeassistant/components/sensor/template.py | 1 + homeassistant/components/switch/template.py | 1 + 3 files changed, 3 insertions(+) diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index d5791edc81a..98d98930c05 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -35,6 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) +@asyncio.coroutine def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup template binary sensors.""" sensors = [] diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 6cd7d61b641..7e72a2406f6 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -33,6 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) +@asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup the template sensors.""" diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index b8820de972f..75d18a28d53 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -40,6 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) +@asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Template switch.""" From 09bcd7321aaca9b055af7d6d05ac0a5409f20deb Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Tue, 18 Oct 2016 07:13:35 +0100 Subject: [PATCH 106/147] Reset Bravia playing info to ensure state reflects correctly (#3903) --- homeassistant/components/media_player/braviatv.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py index b4bab417742..1550c487433 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -236,6 +236,7 @@ class BraviaTVDevice(MediaPlayerDevice): if power_status == 'active': self._state = STATE_ON playing_info = self._braviarc.get_playing_info() + self._reset_playing_info() if playing_info is None or len(playing_info) == 0: self._channel_name = 'App' else: @@ -255,6 +256,16 @@ class BraviaTVDevice(MediaPlayerDevice): _LOGGER.error(exception_instance) self._state = STATE_OFF + def _reset_playing_info(self): + self._program_name = None + self._channel_name = None + self._program_media_type = None + self._channel_number = None + self._source = None + self._content_uri = None + self._duration = None + self._start_date_time = None + def _refresh_volume(self): """Refresh volume information.""" volume_info = self._braviarc.get_volume_info() From 53b5dc8e84a9e9d25137530f64b775db09070d96 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 18 Oct 2016 09:10:09 -0700 Subject: [PATCH 107/147] Build frontend --- homeassistant/components/frontend/version.py | 3 ++- .../frontend/www_static/frontend.html | 4 ++-- .../frontend/www_static/frontend.html.gz | Bin 128092 -> 128228 bytes .../www_static/home-assistant-polymer | 2 +- .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 2327 -> 2327 bytes 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index ea8d4de4aea..12dcfb6ca55 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -2,8 +2,9 @@ FINGERPRINTS = { "core.js": "5ed5e063d66eb252b5b288738c9c2d16", - "frontend.html": "b13c6ed83e3a003e3d0896cefad4c077", + "frontend.html": "7d56d6bc46a61004c76838518ff92209", "mdi.html": "46a76f877ac9848899b8ed382427c16f", + "micromarkdown-js.html": "c31103ca5b81380b230376c70f323d6c", "panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002", "panels/ha-panel-dev-info.html": "ec613406ce7e20d93754233d55625c8a", "panels/ha-panel-dev-service.html": "c7974458ebc33412d95497e99b785e12", diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 02afab25c56..45b10022f7f 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,5 +1,5 @@ \ No newline at end of file +},customStyle:null,getComputedStyleValue:function(e){return!i&&this._styleProperties&&this._styleProperties[e]||getComputedStyle(this).getPropertyValue(e)},_setupStyleProperties:function(){this.customStyle={},this._styleCache=null,this._styleProperties=null,this._scopeSelector=null,this._ownStyleProperties=null,this._customStyle=null},_needsStyleProperties:function(){return Boolean(!i&&this._ownStylePropertyNames&&this._ownStylePropertyNames.length)},_validateApplyShim:function(){if(this.__applyShimInvalid){Polymer.ApplyShim.transform(this._styles,this.__proto__);var e=n.elementStyles(this);if(s){var t=this._template.content.querySelector("style");t&&(t.textContent=e)}else{var r=this._scopeStyle&&this._scopeStyle.nextSibling;r&&(r.textContent=e)}}},_beforeAttached:function(){this._scopeSelector&&!this.__stylePropertiesInvalid||!this._needsStyleProperties()||(this.__stylePropertiesInvalid=!1,this._updateStyleProperties())},_findStyleHost:function(){for(var e,t=this;e=Polymer.dom(t).getOwnerRoot();){if(Polymer.isInstance(e.host))return e.host;t=e.host}return r},_updateStyleProperties:function(){var e,n=this._findStyleHost();n._styleProperties||n._computeStyleProperties(),n._styleCache||(n._styleCache=new Polymer.StyleCache);var r=t.propertyDataFromStyles(n._styles,this),i=!this.__notStyleScopeCacheable;i&&(r.key.customStyle=this.customStyle,e=n._styleCache.retrieve(this.is,r.key,this._styles));var a=Boolean(e);a?this._styleProperties=e._styleProperties:this._computeStyleProperties(r.properties),this._computeOwnStyleProperties(),a||(e=o.retrieve(this.is,this._ownStyleProperties,this._styles));var l=Boolean(e)&&!a,c=this._applyStyleProperties(e);a||(c=c&&s?c.cloneNode(!0):c,e={style:c,_scopeSelector:this._scopeSelector,_styleProperties:this._styleProperties},i&&(r.key.customStyle={},this.mixin(r.key.customStyle,this.customStyle),n._styleCache.store(this.is,e,r.key,this._styles)),l||o.store(this.is,Object.create(e),this._ownStyleProperties,this._styles))},_computeStyleProperties:function(e){var n=this._findStyleHost();n._styleProperties||n._computeStyleProperties();var r=Object.create(n._styleProperties),s=t.hostAndRootPropertiesForScope(this);this.mixin(r,s.hostProps),e=e||t.propertyDataFromStyles(n._styles,this).properties,this.mixin(r,e),this.mixin(r,s.rootProps),t.mixinCustomStyle(r,this.customStyle),t.reify(r),this._styleProperties=r},_computeOwnStyleProperties:function(){for(var e,t={},n=0;n0&&l.push(t);return[{removed:a,added:l}]}},Polymer.Collection.get=function(e){return Polymer._collections.get(e)||new Polymer.Collection(e)},Polymer.Collection.applySplices=function(e,t){var n=Polymer._collections.get(e);return n?n._applySplices(t):null},Polymer({is:"dom-repeat",extends:"template",_template:null,properties:{items:{type:Array},as:{type:String,value:"item"},indexAs:{type:String,value:"index"},sort:{type:Function,observer:"_sortChanged"},filter:{type:Function,observer:"_filterChanged"},observe:{type:String,observer:"_observeChanged"},delay:Number,renderedItemCount:{type:Number,notify:!0,readOnly:!0},initialCount:{type:Number,observer:"_initializeChunking"},targetFramerate:{type:Number,value:20},_targetFrameTime:{type:Number,computed:"_computeFrameTime(targetFramerate)"}},behaviors:[Polymer.Templatizer],observers:["_itemsChanged(items.*)"],created:function(){this._instances=[],this._pool=[],this._limit=1/0;var e=this;this._boundRenderChunk=function(){e._renderChunk()}},detached:function(){this.__isDetached=!0;for(var e=0;e=0;t--){var n=this._instances[t];n.isPlaceholder&&t=this._limit&&(n=this._downgradeInstance(t,n.__key__)),e[n.__key__]=t,n.isPlaceholder||n.__setProperty(this.indexAs,t,!0)}this._pool.length=0,this._setRenderedItemCount(this._instances.length),this.fire("dom-change"),this._tryRenderChunk()},_applyFullRefresh:function(){var e,t=this.collection;if(this._sortFn)e=t?t.getKeys():[];else{e=[];var n=this.items;if(n)for(var r=0;r=r;a--)this._detachAndRemoveInstance(a)},_numericSort:function(e,t){return e-t},_applySplicesUserSort:function(e){for(var t,n,r=this.collection,s={},i=0;i=0;i--){var c=a[i];void 0!==c&&this._detachAndRemoveInstance(c)}var h=this;if(l.length){this._filterFn&&(l=l.filter(function(e){return h._filterFn(r.getItem(e))})),l.sort(function(e,t){return h._sortFn(r.getItem(e),r.getItem(t))});var u=0;for(i=0;i>1,a=this._instances[o].__key__,l=this._sortFn(n.getItem(a),r);if(l<0)e=o+1;else{if(!(l>0)){i=o;break}s=o-1}}return i<0&&(i=s+1),this._insertPlaceholder(i,t),i},_applySplicesArrayOrder:function(e){for(var t,n=0;n=0?(e=this.as+"."+e.substring(n+1),i._notifyPath(e,t,!0)):i.__setProperty(this.as,t,!0))}},itemForElement:function(e){var t=this.modelForElement(e);return t&&t[this.as]},keyForElement:function(e){var t=this.modelForElement(e);return t&&t.__key__},indexForElement:function(e){var t=this.modelForElement(e);return t&&t[this.indexAs]}}),Polymer({is:"array-selector",_template:null,properties:{items:{type:Array,observer:"clearSelection"},multi:{type:Boolean,value:!1,observer:"clearSelection"},selected:{type:Object,notify:!0},selectedItem:{type:Object,notify:!0},toggle:{type:Boolean,value:!1}},clearSelection:function(){if(Array.isArray(this.selected))for(var e=0;e \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 440dc72208e06a4d64e05c77262fbaeb9f475421..163a4eabdff2529cc710210be430c9afe230f8bf 100644 GIT binary patch delta 53766 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 delta 53564 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

`s? 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 d>P;{FrT>00{fx^H>UjMB0D?vO4(WC4K+^ diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 75d760fc3e6..78c7756aa18 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 75d760fc3e6f37485ae535a5476c8b00ecd2dfec +Subproject commit 78c7756aa18b7abcaf8f531209d8d9b910527f27 diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index 099e51fe6a1..545fbc20797 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -1 +1 @@ -"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","d1a2c76a28a87f52ecea00ecd0c5dcbe"],["/frontend/panels/dev-event-550bf85345c454274a40d15b2795a002.html","6977c253b5b4da588d50b0aaa50b21f4"],["/frontend/panels/dev-info-ec613406ce7e20d93754233d55625c8a.html","8e28a4c617fd6963b45103d5e5c80617"],["/frontend/panels/dev-service-c7974458ebc33412d95497e99b785e12.html","3a551b1ea5fd8b64dee7b1a458d9ffde"],["/frontend/panels/dev-state-65e5f791cc467561719bf591f1386054.html","78158786a6597ef86c3fd6f4985cde92"],["/frontend/panels/dev-template-d23943fa0370f168714da407c90091a2.html","2cf2426a6aa4ee9c1df74926dc475bc8"],["/frontend/panels/map-49ab2d6f180f8bdea7cffaa66b8a5d3e.html","6e6c9c74e0b2424b62d4cc55b8e89be3"],["/static/core-5ed5e063d66eb252b5b288738c9c2d16.js","59dabb570c57dd421d5197009bf1d07f"],["/static/frontend-b13c6ed83e3a003e3d0896cefad4c077.html","c904acdbfd064cf39c1ed3bdbfa05cee"],["/static/mdi-46a76f877ac9848899b8ed382427c16f.html","a846c4082dd5cffd88ac72cbe943e691"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]],cacheName="sw-precache-v2--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},createCacheKey=function(e,t,a,n){var c=new URL(e);return n&&c.toString().match(n)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,t){var a=new URL(e);return a.search=a.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),a.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),c=createCacheKey(n,hashParamName,a,!1);return[n.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(a){if(!t.has(a))return e.add(new Request(a,{credentials:"same-origin"}))}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(a){return Promise.all(a.map(function(a){if(!t.has(a.url))return e.delete(a)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,a=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(a);var n="index.html";!t&&n&&(a=addDirectoryIndex(a,n),t=urlsToCacheKeys.has(a));var c="/";!t&&c&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(a=new URL(c,self.location).toString(),t=urlsToCacheKeys.has(a)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url,t&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var a,n;for(a=0;a&$$K;1?ZbnE)HVFn7FX0^S{3Ba}uUBTrv;*CpMG8tVfc|cF z_~CRqeUpEG1!;Qp`cGa^LNzn<;c&fF))hu#B~9IIHw=e+;(QH8&Ru6Lmc_U;?>d;9 zMVXA{+6KqJS%8_>>fCwz=KTl9pMy})wC5JxfAr7pjq|Lo3}QL@fX+JSj#w_swWH&& zZCyG3(-jJ7ivM=+&YfRDG|&v3Gjr$pd9AL`|M~uZ&FjEsPBlX-`k-BkX0U8P+RQ-> zx|mHbE}XIBJE2HvltTt2XF3m~oTnm!B+`;|!3A?Z`?ydyompzU6cv>1Si$88Y|KYV z66TsGF-;^*XvC zXsksTvrvqrxDH5NKq-1B$fG2}{E0YcF-JL(B5CloMY&0)M4l%slnGOcMnokfW9VJ3i3*wCle=+X#8i^1 zG{*pll!PIbEafpz2-iA`qA=T7tumfwgo-RnDaok>o`;I&oJLwpM)svIR53zIlVvasDNm9KLEI3Ui4SA%0RDyshEIG*{iRDORK$V@~jn!y$J8tT{Hud;j z_cQvnE>&QRwl>C5CMC~fnx;$>Yb{ntgaxKZj`;md)XQQvH)~lePjt??CfFDxCzv{6 z2ob@8h@yx@^k=eOfhtyuwJ;4N21;0n2`;g%Bs@(*0*o=t6O{ckVeeqJDn+w@mL@R! zS(d3J&xOVqr&{DB5jj`DX-wps zgo-7@y2Zp&l&5JJYXm04CWZNvssti`H$f|FBx1+${9;yNjp8KoRx~KlAcegW$b(2d z(~I%%E3^1A{_G2-enUdG^m+ZFQt*9xKVw*4w&WzHmIgn*dx?BC>BkPFCuCa@EQOg@ zC_QMA3Ber^Z#Z=CU!P5l=ZnD9?>owKy*pnvfNifEll}$Qd)j>ASG`7gsBut#4To|^ z5>8B()GP1y)|EkvOe^PS)9vFEXXscM&Y+UDf{*WBJlh1uHQt#sb&h=zp6AOwbBp#Z z`toUxp)3()P!I4av}*?ySdS%MZ|tLRvJaf`wwO$|qg`7s;Mhnf*1mPqfp4dU<%=$u zYA>39EK(?~M`XHrSjUQZL5uT$i*XSt@AUk+UNd#<5UPt3H6L4O0vtJ->-$e;^XUUJJ$G8Pe}v=mtf(9Wru%bn(%JC` zVQaq?%TI_%pA2?`e*#W>j=97z?c>=;dUIwv>EPu5!2QHwPaH*C{0K^W_S=E8Khxlv zKLV623(G_O78QaZuzlSX#OaDyuG;yL z#xd#d6EJwl7VDde)|Q^X+b@m`W%K&#_0F}3i0+0UcKDkws=3P_uU&B>=X9-TZPyRP^F`5xoWZZ4604WS3&pA^(MGuV}`A@Po3(`vcoU#p9=@#w;zrl1_D=i Rak%p){{!|rTEvwU005L^Z0`U7 delta 2295 zcmVg+XhYLK(|}ojHPrxuZ*bkZUSQ@>xtj%h z6Bc55{KKj#@4UOyODnh4|q2>fqXPGHA;BW4fBamY8y_Ct?Lc>Dk%AHo;C?qN z{BSy*zRACTf;2sP{Uk2)wlBRC93x>lzalQs4=dLpr%VOM_cOA^l zqNHQFw!!gl7GUPJI(Oc_dH=!j=O7d`?YTwwAN{*~<2I}IH!4{1m#@e!cYi-kBI2xw4S2U^$282 z;v`IeB`}CWmBkE|#j&C^ji}^eqlN=+B*Do{t29gFJfS4SX@HYL96hb3h32x5Fp?}| zNkTc~GL91xsf;EWgDlG#r$DwFiUp-4Cm^U+JWmq^z;YsRrOLEcKLD;Z!oVm+n>5Ra zlu61cnnto*(~M{m^E9N%Mi1kJa>i4UQZz$<^Hj#THJxOfN(EVTT91Lnvb3sH6lY1S zMHsVClaw=pE=WQqvoOri{~J}2)KL$5mlj86}y>BeaU} zQ1e`YU{Y%#(lqCSsu;FW0IAF*O8}joL`j}TDv^@XoCD8thN9ti8yC5M>&xqY4Ej0xinKE&nd&8WlALxQIup1cb99TLZvfV2X1gS%g`K?t(NUo#2htXmmSn>by4f_+9rH z{aTkQFh<)vj-yOUp2ta=GEGP#Fm9s=6HJjD@%x#mm&I&u*0NYmb~Zp z5y6CrqKHJv&t$y%eEA0vkQ@vQ$6;|JNR2Bw_y{P&$$U_}R zLdB9{-eO>>gr{j3YXm04B8Bmjssti`k)V||60zfWele>sN3j!mD;kt&kiuRGBac>m05flfA)n^zab%8`n-NoDfm9U?=dVdTXGUpOM@TZy+pp6^lb;y6SA!cmcq;{ zlpeImgy4>dHypb6!)H_D`64j&`;PKl@6MMEVA<>1q<_Kno;F|jRj*MVY8+I5!=c=f zgdLM5^~$@wb!E^Z)5`hTbo=_m89Ek*GpJ;(;N!a&&o+*6jd$iuonv2w=lOEa+@gJp zd-*g+SC)t}s0a8I+O>@etlJW=H}+9D*$2*eTTCX~)~>A=aIB>h>%Dc|fp5Eo<%`al zYR{X0%u^_>M`XHrS^J83L5ux=i*XSt?{xpUo-=i96RL|6H6NR30&F>&>-$e;^XU;* zE@-hqy$C;hT`PN1*VnqJHmdq}H0?qB%tbrzFzD0d%TAs+J~qqkhk6rSZHLAy)VAMV z9T;GtGgE7zFYrM=`_;YY{4UALx^MI_KBoI2>{k0WVH7_i8}u)#9pimB`T5TCCY?OY zyRI*PX@jalOq~MzSZqfff7dbyu0&xzR;DQ3ZU=3E`CYl&LUHybfCe3Z z@Sy0j0Nso**bLgb6Zhy^7~jEvRyG2Je}m8|+;t(!_S|XF{t>pzv!ZeknC|D`q_eXh zgsuHnEI%P4eK6Ps{s}nkIpz}Gw2x=+>CKtxM!{h0>m z{1KpJSy&FjrzjRbfvP(m?c;=2IDdbC{=w-=Thse5=#NNi5HV9uRwnm;4Q}B_Oyo_> z^r08}FzO0uHN~_*QtErM$s;`3RhD3;2ejo{?(k0u>!ryl(pD!#4u?*KJ;Tff$o?LY z5{AQrs_$Mnzq{^}M{ZB_w|J7gwI!jvT^~)pT-D7tXprh}7osY(m1WO_m@M9ZU;lzV z!X6|YOKwYyd{AO`OUB6^`NkfB8)%Wb)ThGC|9qVGEb~zQ3_2b>T!%3M+i0Aac~f5v zo;OX65%ggWgHELy^cJ-+FmquBSE3!j_a!t9-DVCz+H!TI2qH;?zxMs{3Z0c?l6ggBiM%T+r+ zQaC34eF8cU*uQpZhY^)N0xPd3+};ikJ^zz z>z*~7N5Az^zEg|WWvvU{r&vJO;9OyusIQ!3_hemP Date: Tue, 18 Oct 2016 21:13:00 +0200 Subject: [PATCH 108/147] Add support for matrix notifications (#3827) --- .coveragerc | 1 + homeassistant/components/notify/matrix.py | 169 ++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 173 insertions(+) create mode 100644 homeassistant/components/notify/matrix.py diff --git a/.coveragerc b/.coveragerc index e9856c7a51e..79b71dda807 100644 --- a/.coveragerc +++ b/.coveragerc @@ -205,6 +205,7 @@ omit = homeassistant/components/notify/joaoapps_join.py homeassistant/components/notify/kodi.py homeassistant/components/notify/llamalab_automate.py + homeassistant/components/notify/matrix.py homeassistant/components/notify/message_bird.py homeassistant/components/notify/nma.py homeassistant/components/notify/pushbullet.py diff --git a/homeassistant/components/notify/matrix.py b/homeassistant/components/notify/matrix.py new file mode 100644 index 00000000000..566bd1a4652 --- /dev/null +++ b/homeassistant/components/notify/matrix.py @@ -0,0 +1,169 @@ +""" +Matrix notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.matrix/ +""" +import logging +import json +import os + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_VERIFY_SSL + +REQUIREMENTS = ['matrix-client==0.0.5'] + +SESSION_FILE = 'matrix.conf' +AUTH_TOKENS = dict() + +CONF_HOMESERVER = 'homeserver' +CONF_DEFAULT_ROOM = 'default_room' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOMESERVER): cv.url, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_DEFAULT_ROOM): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config): + """Get the Matrix notification service.""" + if not AUTH_TOKENS: + load_token(hass.config.path(SESSION_FILE)) + + return MatrixNotificationService( + config.get(CONF_HOMESERVER), + config.get(CONF_DEFAULT_ROOM), + config.get(CONF_VERIFY_SSL), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD) + ) + + +# pylint: disable=too-few-public-methods +class MatrixNotificationService(BaseNotificationService): + """Wrapper for the MatrixNotificationClient.""" + + # pylint: disable=too-many-arguments + def __init__(self, homeserver, default_room, verify_ssl, + username, password): + """Buffer configuration data for send_message.""" + self.homeserver = homeserver + self.default_room = default_room + self.verify_tls = verify_ssl + self.username = username + self.password = password + + def send_message(self, message, **kwargs): + """Wrapper function pass default parameters to actual send_message.""" + send_message( + message, + self.homeserver, + kwargs.get(ATTR_TARGET) or [self.default_room], + self.verify_tls, + self.username, + self.password + ) + + +def load_token(session_file): + """Load authentication tokens from persistent storage, if exists.""" + if not os.path.exists(session_file): + return + + with open(session_file) as handle: + data = json.load(handle) + + for mx_id, token in data.items(): + AUTH_TOKENS[mx_id] = token + + +def store_token(mx_id, token): + """Store authentication token to session and persistent storage.""" + AUTH_TOKENS[mx_id] = token + + with open(SESSION_FILE, 'w') as handle: + handle.write(json.dumps(AUTH_TOKENS)) + + +# pylint: disable=too-many-locals, too-many-arguments +def send_message(message, homeserver, target_rooms, verify_tls, + username, password): + """Do everything thats necessary to send a message to a Matrix room.""" + from matrix_client.client import MatrixClient, MatrixRequestError + + def login_by_token(): + """Login using authentication token.""" + try: + return MatrixClient( + base_url=homeserver, + token=AUTH_TOKENS[mx_id], + user_id=username, + valid_cert_check=verify_tls + ) + except MatrixRequestError as ex: + _LOGGER.info( + 'login_by_token: (%d) %s', ex.code, ex.content + ) + + def login_by_password(): + """Login using password authentication.""" + try: + _client = MatrixClient( + base_url=homeserver, + valid_cert_check=verify_tls + ) + _client.login_with_password(username, password) + store_token(mx_id, _client.token) + return _client + except MatrixRequestError as ex: + _LOGGER.error( + 'login_by_password: (%d) %s', ex.code, ex.content + ) + + # this is as close as we can get to the mx_id, since there is no + # homeserver discovery protocol we have to fall back to the homeserver url + # instead of the actual domain it serves. + mx_id = "{user}@{homeserver}".format( + user=username, + homeserver=homeserver + ) + + if mx_id in AUTH_TOKENS: + client = login_by_token() + if not client: + client = login_by_password() + if not client: + _LOGGER.error( + 'login failed, both token and username/password ' + 'invalid' + ) + return + else: + client = login_by_password() + if not client: + _LOGGER.error('login failed, username/password invalid') + return + + rooms = client.get_rooms() + for target_room in target_rooms: + try: + if target_room in rooms: + room = rooms[target_room] + else: + room = client.join_room(target_room) + + _LOGGER.debug(room.send_text(message)) + except MatrixRequestError as ex: + _LOGGER.error( + 'Unable to deliver message to room \'%s\': (%d): %s', + target_room, ex.code, ex.content + ) diff --git a/requirements_all.txt b/requirements_all.txt index 63114a68abc..9882bef2c3e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -257,6 +257,9 @@ lightify==1.0.3 # homeassistant.components.light.limitlessled limitlessled==1.0.2 +# homeassistant.components.notify.matrix +matrix-client==0.0.5 + # homeassistant.components.notify.message_bird messagebird==1.2.0 From 947c1efca2b0d827ab4139b54b20d07cc64f1579 Mon Sep 17 00:00:00 2001 From: David-Leon Pohl Date: Tue, 18 Oct 2016 23:16:20 +0200 Subject: [PATCH 109/147] New pilight sensor component (#3822) * Pilight daemon expects JSON serializable data. Thus dict is needed and not a mapping proxy. * Add explanation why dict as message data is needed * Use pytest-caplog and no unittest.TestCase --- .coveragerc | 2 +- homeassistant/components/sensor/pilight.py | 96 +++++++++++++++++ tests/components/sensor/test_pilight.py | 120 +++++++++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/sensor/pilight.py create mode 100644 tests/components/sensor/test_pilight.py diff --git a/.coveragerc b/.coveragerc index 79b71dda807..b2acc2d5657 100644 --- a/.coveragerc +++ b/.coveragerc @@ -96,7 +96,7 @@ omit = homeassistant/components/*/homematic.py homeassistant/components/pilight.py - homeassistant/components/*/pilight.py + homeassistant/components/switch/pilight.py homeassistant/components/knx.py homeassistant/components/*/knx.py diff --git a/homeassistant/components/sensor/pilight.py b/homeassistant/components/sensor/pilight.py new file mode 100644 index 00000000000..99caebd708c --- /dev/null +++ b/homeassistant/components/sensor/pilight.py @@ -0,0 +1,96 @@ +""" +Support for pilight sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.pilight/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, + CONF_PAYLOAD) +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.helpers.entity import Entity +import homeassistant.components.pilight as pilight +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'Pilight Sensor' +DEPENDENCIES = ['pilight'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required("variable"): cv.string, + vol.Required(CONF_PAYLOAD): vol.Schema(dict), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=None): cv.string, +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup pilight Sensor.""" + add_devices([PilightSensor( + hass=hass, + name=config.get(CONF_NAME), + variable=config.get("variable"), + payload=config.get(CONF_PAYLOAD), + unit_of_measurement=config.get(CONF_UNIT_OF_MEASUREMENT) + )]) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class PilightSensor(Entity): + """Representation of a sensor that can be updated using pilight.""" + + def __init__(self, hass, name, variable, payload, unit_of_measurement): + """Initialize the sensor.""" + self._state = STATE_UNKNOWN + self._hass = hass + self._name = name + self._variable = variable + self._payload = payload + self._unit_of_measurement = unit_of_measurement + + hass.bus.listen(pilight.EVENT, self._handle_code) + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + return self._unit_of_measurement + + @property + def state(self): + """Return the state of the entity.""" + return self._state + + def _handle_code(self, call): + """Handle received code by the pilight-daemon. + + If the code matches the defined playload + of this sensor the sensor state is changed accordingly. + """ + # Check if received code matches defined playoad + # True if payload is contained in received code dict, not + # all items have to match + if self._payload.items() <= call.data.items(): + try: + value = call.data[self._variable] + self._state = value + self.update_ha_state() + except KeyError: + _LOGGER.error( + 'No variable %s in received code data %s', + str(self._variable), str(call.data)) diff --git a/tests/components/sensor/test_pilight.py b/tests/components/sensor/test_pilight.py new file mode 100644 index 00000000000..c78c91545ab --- /dev/null +++ b/tests/components/sensor/test_pilight.py @@ -0,0 +1,120 @@ +"""The tests for the Pilight sensor platform.""" +import logging + +from homeassistant.bootstrap import setup_component +import homeassistant.components.sensor as sensor +from homeassistant.components import pilight + +from tests.common import get_test_home_assistant, assert_setup_component + +HASS = None + + +def fire_pilight_message(protocol, data): + """Fire the fake pilight message.""" + message = {pilight.ATTR_PROTOCOL: protocol} + message.update(data) + HASS.bus.fire(pilight.EVENT, message) + + +def setup_function(): # pylint: disable=invalid-name + """Initialize a Home Assistant server.""" + global HASS + + HASS = get_test_home_assistant() + HASS.config.components = ['pilight'] + + +def teardown_function(): # pylint: disable=invalid-name + """Stop the Home Assistant server.""" + HASS.stop() + + +def test_sensor_value_from_code(): + """Test the setting of value via pilight.""" + with assert_setup_component(1): + setup_component(HASS, sensor.DOMAIN, { + sensor.DOMAIN: { + 'platform': 'pilight', + 'name': 'test', + 'variable': 'test', + 'payload': {'protocol': 'test-protocol'}, + 'unit_of_measurement': 'fav unit' + } + }) + + state = HASS.states.get('sensor.test') + assert state.state == 'unknown' + + unit_of_measurement = state.attributes.get('unit_of_measurement') + assert unit_of_measurement == 'fav unit' + + # Set value from data with correct payload + fire_pilight_message(protocol='test-protocol', + data={'test': 42}) + HASS.block_till_done() + state = HASS.states.get('sensor.test') + assert state.state == '42' + + +def test_disregard_wrong_payload(): + """Test omitting setting of value with wrong payload.""" + with assert_setup_component(1): + setup_component(HASS, sensor.DOMAIN, { + sensor.DOMAIN: { + 'platform': 'pilight', + 'name': 'test_2', + 'variable': 'test', + 'payload': {'uuid': '1-2-3-4', + 'protocol': 'test-protocol_2'} + } + }) + + # Try set value from data with incorrect payload + fire_pilight_message(protocol='test-protocol_2', + data={'test': 'data', + 'uuid': '0-0-0-0'}) + HASS.block_till_done() + state = HASS.states.get('sensor.test_2') + assert state.state == 'unknown' + + # Try set value from data with partially matched payload + fire_pilight_message(protocol='wrong-protocol', + data={'test': 'data', + 'uuid': '1-2-3-4'}) + HASS.block_till_done() + state = HASS.states.get('sensor.test_2') + assert state.state == 'unknown' + + # Try set value from data with fully matched payload + fire_pilight_message(protocol='test-protocol_2', + data={'test': 'data', + 'uuid': '1-2-3-4', + 'other_payload': 3.141}) + HASS.block_till_done() + state = HASS.states.get('sensor.test_2') + assert state.state == 'data' + + +def test_variable_missing(caplog): + """Check if error message when variable missing.""" + caplog.set_level(logging.ERROR) + with assert_setup_component(1): + setup_component(HASS, sensor.DOMAIN, { + sensor.DOMAIN: { + 'platform': 'pilight', + 'name': 'test_3', + 'variable': 'test', + 'payload': {'protocol': 'test-protocol'} + } + }) + + # Create code without sensor variable + fire_pilight_message(protocol='test-protocol', + data={'uuid': '1-2-3-4', + 'other_variable': 3.141}) + HASS.block_till_done() + + logs = caplog.text + + assert 'No variable test in received code' in logs From 754e93ff6ae11c9457ec98bf665dc24bdccf5639 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 19 Oct 2016 00:35:22 +0200 Subject: [PATCH 110/147] Rename add_devices to async_add_devices like dev guide (#3938) --- homeassistant/components/binary_sensor/template.py | 4 ++-- homeassistant/components/sensor/template.py | 4 ++-- homeassistant/components/switch/template.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 98d98930c05..365b29eb308 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup template binary sensors.""" sensors = [] @@ -63,7 +63,7 @@ def async_setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('No sensors added') return False - hass.loop.create_task(add_devices(sensors)) + hass.loop.create_task(async_add_devices(sensors)) return True diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 7e72a2406f6..600d188bdc0 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument -def async_setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup the template sensors.""" sensors = [] @@ -61,7 +61,7 @@ def async_setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No sensors added") return False - hass.loop.create_task(add_devices(sensors)) + hass.loop.create_task(async_add_devices(sensors)) return True diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 75d18a28d53..2bac825b5b4 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument -def async_setup_platform(hass, config, add_devices, discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup the Template switch.""" switches = [] @@ -70,7 +70,7 @@ def async_setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No switches added") return False - hass.loop.create_task(add_devices(switches)) + hass.loop.create_task(async_add_devices(switches)) return True From f1b658ea5d40b956bd0b90a187266436a65f834e Mon Sep 17 00:00:00 2001 From: Justin Weberg Date: Tue, 18 Oct 2016 19:59:14 -0500 Subject: [PATCH 111/147] Fix Typo (#3942) --- .../frontend/www_static/micromarkdown-js.html | 2 +- .../www_static/micromarkdown-js.html.gz | Bin 2564 -> 2565 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/www_static/micromarkdown-js.html b/homeassistant/components/frontend/www_static/micromarkdown-js.html index 82aaa116750..a80c564cb7b 100644 --- a/homeassistant/components/frontend/www_static/micromarkdown-js.html +++ b/homeassistant/components/frontend/www_static/micromarkdown-js.html @@ -7,4 +7,4 @@ * * * * * * * * * * * */ var micromarkdown={useajax:!1,regexobject:{headline:/^(\#{1,6})([^\#\n]+)$/m,code:/\s\`\`\`\n?([^`]+)\`\`\`/g,hr:/^(?:([\*\-_] ?)+)\1\1$/gm,lists:/^((\s*((\*|\-)|\d(\.|\))) [^\n]+)\n)+/gm,bolditalic:/(?:([\*_~]{1,3}))([^\*_~\n]+[^\*_~\s])\1/g,links:/!?\[([^\]<>]+)\]\(([^ \)<>]+)( "[^\(\)\"]+")?\)/g,reflinks:/\[([^\]]+)\]\[([^\]]+)\]/g,smlinks:/\@([a-z0-9]{3,})\@(t|gh|fb|gp|adn)/gi,mail:/<(([a-z0-9_\-\.])+\@([a-z0-9_\-\.])+\.([a-z]{2,7}))>/gim,tables:/\n(([^|\n]+ *\| *)+([^|\n]+\n))((:?\-+:?\|)+(:?\-+:?)*\n)((([^|\n]+ *\| *)+([^|\n]+)\n)+)/g,include:/[\[<]include (\S+) from (https?:\/\/[a-z0-9\.\-]+\.[a-z]{2,9}[a-z0-9\.\-\?\&\/]+)[\]>]/gi,url:/<([a-zA-Z0-9@:%_\+.~#?&\/\/=]{2,256}\.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)>/g},parse:function(a,b){"use strict";var c,d,e,f,g,h,i,j,k,l,m,n=0,o=[],p=0,q=0,r=0;for(a="\n"+a+"\n",b!==!0&&(micromarkdown.regexobject.lists=/^((\s*(\*|\d\.) [^\n]+)\n)+/gm);null!==(m=micromarkdown.regexobject.code.exec(a));)a=a.replace(m[0],"\n"+micromarkdown.htmlEncode(m[1]).replace(/\n/gm,"
").replace(/\ /gm," ")+"
\n");for(;null!==(m=micromarkdown.regexobject.headline.exec(a));)k=m[1].length,a=a.replace(m[0],""+m[2]+"\n");for(;null!==(m=micromarkdown.regexobject.lists.exec(a));){for(p=0,l="*"===m[0].trim().substr(0,1)||"-"===m[0].trim().substr(0,1)?"

    ":"
      ",h=m[0].split("\n"),i=[],d=0,g=!1,q=0;qn;)l+=i.pop(),d--,p--;for(;n>d;)"*"===c[0].trim().substr(0,1)||"-"===c[0].trim().substr(0,1)?(l+="
        ",i.push("
      ")):(l+="
        ",i.push("
      ")),d++,p++;l+="
    1. "+c[6]+"
    2. \n"}for(;p>0;)l+="
",p--;l+="*"===m[0].trim().substr(0,1)||"-"===m[0].trim().substr(0,1)?"":"",a=a.replace(m[0],l+"\n")}for(;null!==(m=micromarkdown.regexobject.tables.exec(a));){for(l="",h=m[1].split("|"),f=m[4].split("|"),q=0;q",'",e=["\n"}l+="
','',''],q=0;q";for(l+="
",'','',''],i=m[7].split("\n"),q=0;q",r=0;r";l+="
",a=a.replace(m[0],l)}for(q=0;3>q;q++)for(;null!==(m=micromarkdown.regexobject.bolditalic.exec(a));)if(l=[],"~~"===m[1])a=a.replace(m[0],""+m[2]+"");else{switch(m[1].length){case 1:l=["",""];break;case 2:l=["",""];break;case 3:l=["",""]}a=a.replace(m[0],l[0]+m[2]+l[1])}for(;null!==(m=micromarkdown.regexobject.links.exec(a));)a="!"===m[0].substr(0,1)?a.replace(m[0],''+m[1]+'\n'):a.replace(m[0],"
'+m[1]+"\n");for(;null!==(m=micromarkdown.regexobject.mail.exec(a));)a=a.replace(m[0],''+m[1]+"");for(;null!==(m=micromarkdown.regexobject.url.exec(a));)l=m[1],-1===l.indexOf("://")&&(l="http://"+l),a=a.replace(m[0],"'+l.replace(/(https:\/\/|http:\/\/|mailto:|ftp:\/\/)/gim,"")+"");for(;null!==(m=micromarkdown.regexobject.reflinks.exec(a));)i=new RegExp("\\["+m[2]+"\\]: ?([^ \n]+)","gi"),null!==(h=i.exec(a))&&(a=a.replace(m[0],"'+m[1]+""),o.push(h[0]));for(q=0;q'+m[1]+"")}for(;null!==(m=micromarkdown.regexobject.hr.exec(a));)a=a.replace(m[0],"\n
\n");if(micromarkdown.useajax!==!1&&b!==!0)for(;null!==(m=micromarkdown.regexobject.include.exec(a));)if(h=m[2].replace(/[\.\:\/]+/gm,""),i="",document.getElementById(h)?i=document.getElementById(h).innerHTML.trim():micromarkdown.ajax(m[2]),"csv"===m[1]&&""!==i){for(j={";":[]," ":[],",":[],"|":[]},j[0]=[";"," ",",","|"],i=i.split("\n"),r=0;r0&&j[j[0][r]]!==!1&&(j[j[0][r]][q]!==j[j[0][r]][q-1]||1===j[j[0][r]][q])&&(j[j[0][r]]=!1);if(j[";"]!==!1||j[" "]!==!1||j[","]!==!1||j["|"]!==!1){for(j[";"]!==!1?j=";":j[" "]?j=" ":j[","]?j=",":j["|"]&&(j="|"),l="",q=0;q",r=0;r"+micromarkdown.htmlEncode(h[r])+"";l+=""}l+="
",a=a.replace(m[0],l)}else a=a.replace(m[0],""+i.join("\n")+"")}else a=a.replace(m[0],"");return a=a.replace(/ {2,}[\n]{1,}/gim,"

")},ajax:function(a){"use strict";var b;if(document.getElementById(a.replace(/[\.\:\/]+/gm,"")))return!1;if(window.ActiveXObject)try{b=new ActiveXObject("Microsoft.XMLHTTP")}catch(c){return b=null,c}else b=new XMLHttpRequest;b.onreadystatechange=function(){if(4===b.readyState){var c=document.createElement("code");c.innerHTML=b.responseText,c.id=a.replace(/[\.\:\/]+/gm,""),c.style.display="none",document.getElementsByTagName("body")[0].appendChild(c),micromarkdown.useajax()}},b.open("GET",a,!0),b.setRequestHeader("Content-type","application/x-www-form-urlencoded"),b.send()},countingChars:function(a,b){"use strict";return a=a.split(b),"object"==typeof a?a.length-1:0},htmlEncode:function(a){"use strict";var b=document.createElement("div");return b.appendChild(document.createTextNode(a)),a=b.innerHTML,b=void 0,a},mmdCSSclass:function(a,b){"use strict";var c;return-1!==a.indexOf("/")&&b!==!0?(c=a.split("/"),c=0===c[1].length?c[2].split("."):c[0].split("."),'class="mmd_'+c[c.length-2].replace(/[^\w\d]/g,"")+c[c.length-1]+'" '):""}};!function(a,b){"use strict";"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?module.exports=b():a.returnExports=b()}(this,function(){"use strict";return micromarkdown}); - diff --git a/homeassistant/components/frontend/www_static/micromarkdown-js.html.gz b/homeassistant/components/frontend/www_static/micromarkdown-js.html.gz index a3f56718995d06b9570c8c0b696139ff12e34e82..7b13f03175e2425613db1f5b33cb28094852d018 100644 GIT binary patch delta 2535 zcmV-b@m8FQukj8<#bQVE6P>u-|Ma>) zPS+QF%K$DnVYHcfn{c^tgA}%m`NEA8%N)`j?I)+}7*nR#-1`+nu1weG{0FdqZv@;6 zUby|cDg$JYyeTcvok=^qkPiMXm*_X$qEm4;!K9q z3eu?aQD@o=0!O1!SuTsQ<(Q6HDeV|e7epI7jep!W5YE^+bxe>jai*gwdSR7ICm_ww z-G2kwPuBOJ9NqZ*Vtk6b+PKGm`QKh`>m6Ti9gl)Mr+b{~jE&JxTfTI2NSN6w^-D-9 z>T-{*3BFiwfz9Z`KE4%yCYmAcgil?@ z2F$fxKra*57j!Y{O*t7N#R&ClZxWS@M*M&Y5z()7G6EAO=TnqhEqt3~>Gg)#HFDxv zCN?=Ewr!&{1HxcInNh5NJctO|<9)W-knSVsF&VCs5wXZHg9nckU9n8!l43wG9%Il1 zASkqcButQ~!Rw2SnWk)o*p2E7cCj{M0h8AkxX32mi*%TZ4%U+pOsNAUj(4YP_3smqtKoyeNOwoeXXRpuW?F+}iC zVn)La#Nn(0hW7P%ro7l=)(nEo1{=Ume3f>7h{LEvW1-~= z+U>K`Q&soIH6hqHzOc@%gNVM>vsCpI_5_rVKsYLi6FN;2xS)^Zg3a1kb; zQsl5YJ!BCapqQY{sUGcgr?PaRL@8e#xpbRSB~Ypa2UH1v4yh6xP$dR;|In@iO1t<$ z-p}nA^=h0;58O4!c&hSH9`Im)H1pjCY=1(v8PW=DOLm>|KD# zPysTU4n?dAR+~1m7p0N=mCL98Vx`~Y#@?Ctc%;Y z^ds8nA#Ck`yU^16R)Pl#2@m`xJf;IL7w*+SV$T)UQ&{f-_F~6d6R0h=UwQ<7lwt|$ z{`6dy<6*0LlaqZlX_k=(0z1Qa5o$&5+oYoecL>B_sldJj%eYKLbHRke?J(;Lzq;DP z$zZVvUVr@PCvH(t+#NsGqN0! zH&T#%t8CrOss$#8dZNaxZ?krHIcesYor+uSg9zW;EukDalU*h`&eYPz^16d_1w?un zLk-%0iHV?GI9-nceylDQC1SZEMEUpG&j%g>E$lUAHAqJ z=`hMoRZO5KwD+mLM4YJcRZSF6?>p5^wcT`ZMrzKq6S$CCco~(q967cV!h_6TEugO( zp(x)Z0$+c*|23cy8^`u>B#2B!{`T_yyQA@W&+RpT z84wG5<1_W(>aoGcmgAHFO=>_8&*k5S;Or5%29IhUs7DGp64Xi78cD84aCMzekQ&E@ z%HgzLfy9#oN)31WaNWRS+9F~tF4#q$>KxToyKXiBJZl2H32Z8$az)KIp4;dZ#f-o+ z32+5)2>>&Sx8*=p569%t5GMDJnq%019C#i~Q_+KIN{xxfuhbD3ePF(N%5;S@jdtuF zPxRQBXK|{Ic7FvYN7*2ma#61Gw1qRZ^*P^8pysdpe5+cGyzf_+{wd6*Vwt6dxDh%m+$J z{{H3?LXblxg{4B2m3!WT(IThhbq3d&wCv(%ZOALw9tL+8U1Z*Mp@nrJaTZ;et%QvE zfT(TK0EBQqTcu^3hOZ+xFCP2e)`(7(eIArVRkxukqU>y@xiA8&1nk0p#c|8)O#4cV z9zCxhzNu5gYx(X03m)EYnA6ZWAYr}wgt=#{k1pKZ@gUBuZnqBw&8;U;Ie%8S zW$N!8g6rJ3)d!vZ9cL`R$W#;tVV2+c>;y3HNL^rxUId>yx1Az^~Of zYBq?}r2#GsglxBilSeF4B0F4tT!EOysW?4VPs3dVWA#cWI1XB0oUgcBW_ekR7g?}M xuun_S_NcnBptIi8Yg<}Ialso2dnlq>@xNt*=R;HdkId-#e*sC!vx~7A002kL;kW<* delta 2534 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 From 7da47852d4e7e27f19b565ee71c325a397e4ef52 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Tue, 18 Oct 2016 21:04:15 -0400 Subject: [PATCH 112/147] Yamaha zones (#3920) * Enhance yamaha component This enhances the yamaha component to create 1 player per zone, instead of only one play, which provides direct control of the various zones for the player. It also exposes play_media for NET_RADIO sources, which allows direct setting of that. This requires code changes in rxv 0.2.0, so the requirement dependency is raised. * Support current playback metadata for NET_RADIO When on NET RADIO, support the currently playing information. --- .../components/media_player/yamaha.py | 57 ++++++++++++++++--- requirements_all.txt | 2 +- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index c40d932ab92..027fd607730 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -10,16 +10,19 @@ import voluptuous as vol from homeassistant.components.media_player import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA) + SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, + MEDIA_TYPE_MUSIC, + MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_HOST, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['rxv==0.1.11'] +REQUIREMENTS = ['rxv==0.2.0'] _LOGGER = logging.getLogger(__name__) SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE | \ + SUPPORT_PLAY_MEDIA CONF_SOURCE_NAMES = 'source_names' CONF_SOURCE_IGNORE = 'source_ignore' @@ -45,11 +48,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): source_names = config.get(CONF_SOURCE_NAMES) if host is None: - receivers = rxv.find() + receivers = [] + for recv in rxv.find(): + receivers.extend(recv.zone_controllers()) else: - receivers = \ - [rxv.RXV("http://{}:80/YamahaRemoteControl/ctrl".format(host), - name)] + ctrl_url = "http://{}:80/YamahaRemoteControl/ctrl".format(host) + receivers = rxv.RXV(ctrl_url, name).zone_controllers() add_devices( YamahaDevice(name, receiver, source_ignore, source_names) @@ -74,6 +78,7 @@ class YamahaDevice(MediaPlayerDevice): self._reverse_mapping = None self.update() self._name = name + self._zone = receiver.zone def update(self): """Get the latest details from the device.""" @@ -104,7 +109,11 @@ class YamahaDevice(MediaPlayerDevice): @property def name(self): """Return the name of the device.""" - return self._name + name = self._name + if self._zone != "Main_Zone": + # Zone will be one of Main_Zone, Zone_2, Zone_3 + name += " " + self._zone.replace('_', ' ') + return name @property def state(self): @@ -158,3 +167,35 @@ class YamahaDevice(MediaPlayerDevice): def select_source(self, source): """Select input source.""" self._receiver.input = self._reverse_mapping.get(source, source) + + def play_media(self, media_type, media_id, **kwargs): + """Play media from an ID. + + This exposes a pass through for various input sources in the + Yamaha to direct play certain kinds of media. media_type is + treated as the input type that we are setting, and media id is + specific to it. + """ + if media_type == "NET RADIO": + self._receiver.net_radio(media_id) + + @property + def media_content_type(self): + """Return the media content type.""" + if self.source == "NET RADIO": + return MEDIA_TYPE_MUSIC + + @property + def media_title(self): + """Return the media title. + + This will vary by input source, as they provide different + information in metadata. + + """ + if self.source == "NET RADIO": + info = self._receiver.play_status() + if info.song: + return "%s: %s" % (info.station, info.song) + else: + return info.station diff --git a/requirements_all.txt b/requirements_all.txt index 9882bef2c3e..3d0eeb337d4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -439,7 +439,7 @@ radiotherm==1.2 # rpi-rf==0.9.5 # homeassistant.components.media_player.yamaha -rxv==0.1.11 +rxv==0.2.0 # homeassistant.components.media_player.samsungtv samsungctl==0.5.1 From 57f32fa629878717c3659312bf757f264082061e Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Wed, 19 Oct 2016 03:10:28 +0200 Subject: [PATCH 113/147] Fixup device_tracekt.mqtt voluptuous & unit tests (#3904) --- .../components/device_tracker/__init__.py | 16 +- .../components/device_tracker/mqtt.py | 3 +- homeassistant/components/mqtt/__init__.py | 12 +- homeassistant/helpers/config_validation.py | 3 +- .../components/device_tracker/test_asuswrt.py | 14 +- .../device_tracker/test_owntracks.py | 194 ++++++++++-------- 6 files changed, 137 insertions(+), 105 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 72698e189ff..5ea3690852a 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -68,15 +68,11 @@ ATTR_ATTRIBUTES = 'attributes' PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds -}, extra=vol.ALLOW_EXTRA) - -_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [ - vol.Schema({ - vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, - vol.Optional( - CONF_CONSIDER_HOME, default=timedelta(seconds=180)): vol.All( - cv.time_period, cv.positive_timedelta) - }, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA) + vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, + vol.Optional(CONF_CONSIDER_HOME, + default=timedelta(seconds=DEFAULT_CONSIDER_HOME)): vol.All( + cv.time_period, cv.positive_timedelta) +}) DISCOVERY_PLATFORMS = { SERVICE_NETGEAR: 'netgear', @@ -116,7 +112,7 @@ def setup(hass: HomeAssistantType, config: ConfigType): yaml_path = hass.config.path(YAML_DEVICES) try: - conf = _CONFIG_SCHEMA(config).get(DOMAIN, []) + conf = config.get(DOMAIN, []) except vol.Invalid as ex: log_exception(ex, DOMAIN, config) return False diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py index 2318eb44dd1..f9a85da98b2 100644 --- a/homeassistant/components/device_tracker/mqtt.py +++ b/homeassistant/components/device_tracker/mqtt.py @@ -11,13 +11,14 @@ import voluptuous as vol import homeassistant.components.mqtt as mqtt from homeassistant.const import CONF_DEVICES from homeassistant.components.mqtt import CONF_QOS +from homeassistant.components.device_tracker import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['mqtt'] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({ vol.Required(CONF_DEVICES): {cv.string: mqtt.valid_subscribe_topic}, }) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index bc7977ae129..307b287ea0d 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -18,8 +18,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import template, config_validation as cv from homeassistant.helpers.event import threaded_listener_factory from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_VALUE_TEMPLATE) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_VALUE_TEMPLATE) _LOGGER = logging.getLogger(__name__) @@ -107,12 +106,11 @@ CONFIG_SCHEMA = vol.Schema({ }), }, extra=vol.ALLOW_EXTRA) -MQTT_BASE_PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): DOMAIN, - vol.Optional(CONF_SCAN_INTERVAL): - vol.All(vol.Coerce(int), vol.Range(min=1)), +SCHEMA_BASE = { vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, -}) +} + +MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE) # Sensor type platforms subscribe to MQTT events MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1d368a37d3c..dcbbbb4b3db 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -358,7 +358,8 @@ def key_dependency(key, dependency): PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): string, - CONF_SCAN_INTERVAL: vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Optional(CONF_SCAN_INTERVAL): + vol.All(vol.Coerce(int), vol.Range(min=1)), }, extra=vol.ALLOW_EXTRA) EVENT_SCHEMA = vol.Schema({ diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index 480c76d52b3..ad42fd9d9a6 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -1,5 +1,6 @@ """The tests for the ASUSWRT device tracker platform.""" import os +from datetime import timedelta import unittest from unittest import mock @@ -7,8 +8,11 @@ import voluptuous as vol from homeassistant.bootstrap import setup_component from homeassistant.components import device_tracker +from homeassistant.components.device_tracker import ( + CONF_CONSIDER_HOME, CONF_TRACK_NEW) from homeassistant.components.device_tracker.asuswrt import ( - CONF_PROTOCOL, CONF_MODE, CONF_PUB_KEY, PLATFORM_SCHEMA, DOMAIN) + CONF_PROTOCOL, CONF_MODE, CONF_PUB_KEY, DOMAIN, + PLATFORM_SCHEMA) from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) @@ -70,7 +74,9 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): CONF_PLATFORM: 'asuswrt', CONF_HOST: 'fake_host', CONF_USERNAME: 'fake_user', - CONF_PASSWORD: 'fake_pass' + CONF_PASSWORD: 'fake_pass', + CONF_TRACK_NEW: True, + CONF_CONSIDER_HOME: timedelta(seconds=180) } } @@ -93,7 +99,9 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): CONF_PLATFORM: 'asuswrt', CONF_HOST: 'fake_host', CONF_USERNAME: 'fake_user', - CONF_PUB_KEY: FAKEFILE + CONF_PUB_KEY: FAKEFILE, + CONF_TRACK_NEW: True, + CONF_CONSIDER_HOME: timedelta(seconds=180) } } diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 9ee9c80dc43..38aae9021ec 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -12,7 +12,8 @@ from homeassistant.const import (STATE_NOT_HOME, CONF_PLATFORM) import homeassistant.components.device_tracker.owntracks as owntracks from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) + assert_setup_component, get_test_home_assistant, mock_mqtt_component, + fire_mqtt_message) USER = 'greg' DEVICE = 'phone' @@ -207,20 +208,60 @@ MOCK_ENCRYPTED_LOCATION_MESSAGE = { } -class TestDeviceTrackerOwnTracks(unittest.TestCase): +class BaseMQTT(unittest.TestCase): + """Base MQTT assert functions.""" + + hass = None + + def send_message(self, topic, message, corrupt=False): + """Test the sending of a message.""" + str_message = json.dumps(message) + if corrupt: + mod_message = BAD_JSON_PREFIX + str_message + BAD_JSON_SUFFIX + else: + mod_message = str_message + fire_mqtt_message(self.hass, topic, mod_message) + self.hass.block_till_done() + + def assert_location_state(self, location): + """Test the assertion of a location state.""" + state = self.hass.states.get(DEVICE_TRACKER_STATE) + self.assertEqual(state.state, location) + + def assert_location_latitude(self, latitude): + """Test the assertion of a location latitude.""" + state = self.hass.states.get(DEVICE_TRACKER_STATE) + self.assertEqual(state.attributes.get('latitude'), latitude) + + def assert_location_longitude(self, longitude): + """Test the assertion of a location longitude.""" + state = self.hass.states.get(DEVICE_TRACKER_STATE) + self.assertEqual(state.attributes.get('longitude'), longitude) + + def assert_location_accuracy(self, accuracy): + """Test the assertion of a location accuracy.""" + state = self.hass.states.get(DEVICE_TRACKER_STATE) + self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) + + +# pylint: disable=too-many-public-methods +class TestDeviceTrackerOwnTracks(BaseMQTT): """Test the OwnTrack sensor.""" - def setup_method(self, method): + # pylint: disable=invalid-name + + def setup_method(self, _): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) - self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_MAX_GPS_ACCURACY: 200, - CONF_WAYPOINT_IMPORT: True, - CONF_WAYPOINT_WHITELIST: ['jon', 'greg'] - }})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_MAX_GPS_ACCURACY: 200, + CONF_WAYPOINT_IMPORT: True, + CONF_WAYPOINT_WHITELIST: ['jon', 'greg'] + }}) self.hass.states.set( 'zone.inner', 'zoning', @@ -254,7 +295,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): owntracks.REGIONS_ENTERED = defaultdict(list) owntracks.MOBILE_BEACONS_ACTIVE = defaultdict(list) - def teardown_method(self, method): + def teardown_method(self, _): """Stop everything that was started.""" self.hass.stop() @@ -263,40 +304,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): except FileNotFoundError: pass - def mock_see(**kwargs): - """Fake see method for owntracks.""" - return - - def send_message(self, topic, message, corrupt=False): - """Test the sending of a message.""" - str_message = json.dumps(message) - if corrupt: - mod_message = BAD_JSON_PREFIX + str_message + BAD_JSON_SUFFIX - else: - mod_message = str_message - fire_mqtt_message(self.hass, topic, mod_message) - self.hass.block_till_done() - - def assert_location_state(self, location): - """Test the assertion of a location state.""" - state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.state, location) - - def assert_location_latitude(self, latitude): - """Test the assertion of a location latitude.""" - state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('latitude'), latitude) - - def assert_location_longitude(self, longitude): - """Test the assertion of a location longitude.""" - state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('longitude'), longitude) - - def assert_location_accuracy(self, accuracy): - """Test the assertion of a location accuracy.""" - state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) - def assert_tracker_state(self, location): """Test the assertion of a tracker state.""" state = self.hass.states.get(REGION_TRACKER_STATE) @@ -312,7 +319,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): state = self.hass.states.get(REGION_TRACKER_STATE) self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) - def test_location_invalid_devid(self): + def test_location_invalid_devid(self): # pylint: disable=invalid-name """Test the update of a location.""" self.send_message('owntracks/paulus/nexus-5x', LOCATION_MESSAGE) @@ -588,7 +595,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): exit_message = REGION_LEAVE_MESSAGE.copy() exit_message['desc'] = IBEACON_DEVICE - for i in range(0, 20): + for _ in range(0, 20): fire_mqtt_message( self.hass, EVENT_TOPIC, json.dumps(enter_message)) fire_mqtt_message( @@ -637,12 +644,16 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): def test_waypoint_import_no_whitelist(self): """Test import of list of waypoints with no whitelist set.""" + def mock_see(**kwargs): + """Fake see method for owntracks.""" + return + test_config = { CONF_PLATFORM: 'owntracks', CONF_MAX_GPS_ACCURACY: 200, CONF_WAYPOINT_IMPORT: True } - owntracks.setup_scanner(self.hass, test_config, self.mock_see) + owntracks.setup_scanner(self.hass, test_config, mock_see) waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() self.send_message(WAYPOINT_TOPIC_BLOCKED, waypoints_message) # Check if it made it into states @@ -690,7 +701,18 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.send_message(LOCATION_TOPIC, ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(2.0) - def mock_cipher(): + +class TestDeviceTrackerOwnTrackConfigs(BaseMQTT): + """Test the OwnTrack sensor.""" + + # pylint: disable=invalid-name + + def setup_method(self, method): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + mock_mqtt_component(self.hass) + + def mock_cipher(): # pylint: disable=no-method-argument """Return a dummy pickle-based cipher.""" def mock_decrypt(ciphertext, key): """Decrypt/unpickle.""" @@ -705,11 +727,12 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): mock_cipher) def test_encrypted_payload(self): """Test encrypted payload.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: SECRET_KEY, - }})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: SECRET_KEY, + }}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(2.0) @@ -717,24 +740,26 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): mock_cipher) def test_encrypted_payload_topic_key(self): """Test encrypted payload with a topic key.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: { - LOCATION_TOPIC: SECRET_KEY, - }}})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: { + LOCATION_TOPIC: SECRET_KEY, + }}}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(2.0) @patch('homeassistant.components.device_tracker.owntracks.get_cipher', mock_cipher) def test_encrypted_payload_no_key(self): - """Test encrypted payload with no key.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - # key missing - }})) + """Test encrypted payload with no key, .""" + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + # key missing + }}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(None) @@ -742,11 +767,12 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): mock_cipher) def test_encrypted_payload_wrong_key(self): """Test encrypted payload with wrong key.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: 'wrong key', - }})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: 'wrong key', + }}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(None) @@ -754,12 +780,13 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): mock_cipher) def test_encrypted_payload_wrong_topic_key(self): """Test encrypted payload with wrong topic key.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: { - LOCATION_TOPIC: 'wrong key' - }}})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: { + LOCATION_TOPIC: 'wrong key' + }}}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(None) @@ -767,11 +794,12 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): mock_cipher) def test_encrypted_payload_no_topic_key(self): """Test encrypted payload with no topic key.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: { - 'owntracks/{}/{}'.format(USER, 'otherdevice'): 'foobar' - }}})) + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: { + 'owntracks/{}/{}'.format(USER, 'otherdevice'): 'foobar' + }}}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(None) From 7d32e5eeeb286b008ff885ccd3890c3a9fa718f1 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Tue, 18 Oct 2016 21:11:35 -0400 Subject: [PATCH 114/147] Logbook filtering of automations by entity_id (#3927) * Logbook filtering of automations by entity_id * Trigger action function parameters required --- .../components/automation/__init__.py | 7 ++-- homeassistant/components/logbook.py | 10 ++++- tests/components/test_logbook.py | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 81944d6ec57..244887ca10a 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -282,7 +282,7 @@ class AutomationEntity(ToggleEntity): This method is a coroutine. """ if skip_condition or self._cond_func(variables): - yield from self._async_action(variables) + yield from self._async_action(self.entity_id, variables) self._last_triggered = utcnow() self.hass.loop.create_task(self.async_update_ha_state()) @@ -357,10 +357,11 @@ def _async_get_action(hass, config, name): script_obj = script.Script(hass, config, name) @asyncio.coroutine - def action(variables=None): + def action(entity_id, variables): """Action to be executed.""" _LOGGER.info('Executing %s', name) - logbook.async_log_entry(hass, name, 'has been triggered', DOMAIN) + logbook.async_log_entry( + hass, name, 'has been triggered', DOMAIN, entity_id) hass.loop.create_task(script_obj.async_run(variables)) return action diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 557c59a33ec..266496fff78 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -257,7 +257,7 @@ def humanify(events): event.time_fired, "Home Assistant", action, domain=HA_DOMAIN) - elif event.event_type.lower() == EVENT_LOGBOOK_ENTRY: + elif event.event_type == EVENT_LOGBOOK_ENTRY: domain = event.data.get(ATTR_DOMAIN) entity_id = event.data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: @@ -290,6 +290,8 @@ def _exclude_events(events, config): filtered_events = [] for event in events: + domain, entity_id = None, None + if event.event_type == EVENT_STATE_CHANGED: to_state = State.from_dict(event.data.get('new_state')) # Do not report on new entities @@ -303,6 +305,12 @@ def _exclude_events(events, config): domain = to_state.domain entity_id = to_state.entity_id + + elif event.event_type == EVENT_LOGBOOK_ENTRY: + domain = event.data.get(ATTR_DOMAIN) + entity_id = event.data.get(ATTR_ENTITY_ID) + + if domain or entity_id: # filter if only excluded is configured for this domain if excluded_domains and domain in excluded_domains and \ not included_domains: diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index a98273b6521..2dcc47549df 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -186,6 +186,43 @@ class TestComponentLogbook(unittest.TestCase): self.assert_entry(entries[1], pointB, 'blu', domain='sensor', entity_id=entity_id2) + def test_exclude_automation_events(self): + """Test if automation entries can be excluded by entity_id.""" + name = 'My Automation Rule' + message = 'has been triggered' + domain = 'automation' + entity_id = 'automation.my_automation_rule' + entity_id2 = 'automation.my_automation_rule_2' + entity_id2 = 'sensor.blu' + + eventA = ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { + logbook.ATTR_NAME: name, + logbook.ATTR_MESSAGE: message, + logbook.ATTR_DOMAIN: domain, + logbook.ATTR_ENTITY_ID: entity_id, + }) + eventB = ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { + logbook.ATTR_NAME: name, + logbook.ATTR_MESSAGE: message, + logbook.ATTR_DOMAIN: domain, + logbook.ATTR_ENTITY_ID: entity_id2, + }) + + config = logbook.CONFIG_SCHEMA({ + ha.DOMAIN: {}, + logbook.DOMAIN: {logbook.CONF_EXCLUDE: { + logbook.CONF_ENTITIES: [entity_id, ]}}}) + events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), + eventA, eventB), config) + entries = list(logbook.humanify(events)) + + self.assertEqual(2, len(entries)) + self.assert_entry( + entries[0], name='Home Assistant', message='stopped', + domain=ha.DOMAIN) + self.assert_entry( + entries[1], name=name, domain=domain, entity_id=entity_id2) + def test_include_events_entity(self): """Test if events are filtered if entity is included in config.""" entity_id = 'sensor.bla' From 5799d1aec9a80c0f650b7e9ec7556b59d9ab8446 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 18 Oct 2016 18:25:47 -0700 Subject: [PATCH 115/147] Fixing verbage (#3940) --- homeassistant/components/zoneminder.py | 6 +++--- homeassistant/const.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zoneminder.py b/homeassistant/components/zoneminder.py index 3f43ae01904..0ed985fb427 100644 --- a/homeassistant/components/zoneminder.py +++ b/homeassistant/components/zoneminder.py @@ -14,7 +14,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_URL, CONF_HOST, CONF_PASSWORD, CONF_USERNAME) + CONF_PATH, CONF_HOST, CONF_PASSWORD, CONF_USERNAME) _LOGGER = logging.getLogger(__name__) @@ -26,7 +26,7 @@ DOMAIN = 'zoneminder' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_URL, default="/zm/"): cv.string, + vol.Optional(CONF_PATH, default="/zm/"): cv.string, vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string }) @@ -42,7 +42,7 @@ def setup(hass, config): ZM = {} conf = config[DOMAIN] - url = urljoin("http://" + conf[CONF_HOST], conf[CONF_URL]) + url = urljoin("http://" + conf[CONF_HOST], conf[CONF_PATH]) username = conf.get(CONF_USERNAME, None) password = conf.get(CONF_PASSWORD, None) diff --git a/homeassistant/const.py b/homeassistant/const.py index e2670d2b08f..763d3639b68 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -104,6 +104,7 @@ CONF_NAME = 'name' CONF_OFFSET = 'offset' CONF_OPTIMISTIC = 'optimistic' CONF_PASSWORD = 'password' +CONF_PATH = 'path' CONF_PAYLOAD = 'payload' CONF_PAYLOAD_OFF = 'payload_off' CONF_PAYLOAD_ON = 'payload_on' From 081e61528ddeb75324a4daff5a5501892c6cf258 Mon Sep 17 00:00:00 2001 From: David-Leon Pohl Date: Wed, 19 Oct 2016 22:02:11 +0200 Subject: [PATCH 116/147] Bugfixes for pilight hub component and unit tests (#3948) --- .coveragerc | 1 - homeassistant/components/pilight.py | 4 +- requirements_all.txt | 2 +- tests/components/test_pilight.py | 298 ++++++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 tests/components/test_pilight.py diff --git a/.coveragerc b/.coveragerc index b2acc2d5657..a57f5ac99b7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -95,7 +95,6 @@ omit = homeassistant/components/homematic.py homeassistant/components/*/homematic.py - homeassistant/components/pilight.py homeassistant/components/switch/pilight.py homeassistant/components/knx.py diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight.py index 3475a6be65a..2cfbc0063a1 100644 --- a/homeassistant/components/pilight.py +++ b/homeassistant/components/pilight.py @@ -14,7 +14,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT, CONF_WHITELIST) -REQUIREMENTS = ['pilight==0.0.2'] +REQUIREMENTS = ['pilight==0.1.1'] _LOGGER = logging.getLogger(__name__) @@ -102,7 +102,7 @@ def setup(hass, config): if not whitelist: hass.bus.fire(EVENT, data) # Check if data matches the defined whitelist - elif all(data[key] in whitelist[key] for key in whitelist): + elif all(str(data[key]) in whitelist[key] for key in whitelist): hass.bus.fire(EVENT, data) pilight_client.set_callback(handle_received_code) diff --git a/requirements_all.txt b/requirements_all.txt index 3d0eeb337d4..d17775487fc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -294,7 +294,7 @@ pexpect==4.0.1 phue==0.8 # homeassistant.components.pilight -pilight==0.0.2 +pilight==0.1.1 # homeassistant.components.media_player.plex # homeassistant.components.sensor.plex diff --git a/tests/components/test_pilight.py b/tests/components/test_pilight.py new file mode 100644 index 00000000000..ca491ee838d --- /dev/null +++ b/tests/components/test_pilight.py @@ -0,0 +1,298 @@ +"""The tests for the pilight component.""" +import logging +import unittest +from unittest.mock import patch +import socket + +from homeassistant.bootstrap import setup_component +from homeassistant.components import pilight + +from tests.common import get_test_home_assistant, assert_setup_component + +_LOGGER = logging.getLogger(__name__) + + +class PilightDaemonSim: + """Class to fake the interface of the pilight python package. + + Is used in an asyncio loop, thus the mock cannot be accessed to + determine if methods where called?! + This is solved here in a hackish way by printing errors + that can be checked using logging.error mocks. + """ + + callback = None + called = None + + test_message = {"protocol": "kaku_switch", + "uuid": "1-2-3-4", + "message": { + "id": 0, + "unit": 0, + "off": 1}} + + def __init__(self, host, port): + """Init pilight client, ignore parameters.""" + pass + + def send_code(self, call): # pylint: disable=no-self-use + """Called pilight.send service is called.""" + _LOGGER.error('PilightDaemonSim payload: ' + str(call)) + + def start(self): + """Called homeassistant.start is called. + + Also sends one test message after start up + """ + _LOGGER.error('PilightDaemonSim start') + # Fake one code receive after daemon started + if not self.called: + self.callback(self.test_message) + self.called = True + + def stop(self): # pylint: disable=no-self-use + """Called homeassistant.stop is called.""" + _LOGGER.error('PilightDaemonSim stop') + + def set_callback(self, function): + """Callback called on event pilight.pilight_received.""" + self.callback = function + _LOGGER.error('PilightDaemonSim callback: ' + str(function)) + + +class TestPilight(unittest.TestCase): + """Test the Pilight component.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + @patch('homeassistant.components.pilight._LOGGER.error') + def test_connection_failed_error(self, mock_error): + """Try to connect at 127.0.0.1:5000 with socket error.""" + with assert_setup_component(3): + with patch('pilight.pilight.Client', + side_effect=socket.error) as mock_client: + self.assertFalse(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + mock_client.assert_called_once_with(host=pilight.DEFAULT_HOST, + port=pilight.DEFAULT_PORT) + self.assertEqual(1, mock_error.call_count) + + @patch('homeassistant.components.pilight._LOGGER.error') + def test_connection_timeout_error(self, mock_error): + """Try to connect at 127.0.0.1:5000 with socket timeout.""" + with assert_setup_component(3): + with patch('pilight.pilight.Client', + side_effect=socket.timeout) as mock_client: + self.assertFalse(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + mock_client.assert_called_once_with(host=pilight.DEFAULT_HOST, + port=pilight.DEFAULT_PORT) + self.assertEqual(1, mock_error.call_count) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.error') + @patch('tests.components.test_pilight._LOGGER.error') + def test_send_code_no_protocol(self, mock_pilight_error, mock_error): + """Try to send data without protocol information, should give error.""" + with assert_setup_component(3): + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + + # Call without protocol info, should be ignored with error + self.hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, + service_data={'noprotocol': 'test', + 'value': 42}, + blocking=True) + self.hass.block_till_done() + error_log_call = mock_error.call_args_list[-1] + self.assertTrue( + 'required key not provided @ data[\'protocol\']' in + str(error_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('tests.components.test_pilight._LOGGER.error') + def test_send_code(self, mock_pilight_error): + """Try to send proper data.""" + with assert_setup_component(3): + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + + # Call with protocol info, should not give error + service_data = {'protocol': 'test', + 'value': 42} + self.hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, + service_data=service_data, + blocking=True) + self.hass.block_till_done() + error_log_call = mock_pilight_error.call_args_list[-1] + service_data['protocol'] = [service_data['protocol']] + self.assertTrue(str(service_data) in str(error_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.components.pilight._LOGGER.error') + def test_send_code_fail(self, mock_pilight_error): + """Check IOError exception error message.""" + with assert_setup_component(3): + with patch('pilight.pilight.Client.send_code', + side_effect=IOError): + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + + # Call with protocol info, should not give error + service_data = {'protocol': 'test', + 'value': 42} + self.hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, + service_data=service_data, + blocking=True) + self.hass.block_till_done() + error_log_call = mock_pilight_error.call_args_list[-1] + self.assertTrue('Pilight send failed' in str(error_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('tests.components.test_pilight._LOGGER.error') + def test_start_stop(self, mock_pilight_error): + """Check correct startup and stop of pilight daemon.""" + with assert_setup_component(3): + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + + # Test startup + self.hass.start() + self.hass.block_till_done() + error_log_call = mock_pilight_error.call_args_list[-2] + self.assertTrue( + 'PilightDaemonSim callback' in str(error_log_call)) + error_log_call = mock_pilight_error.call_args_list[-1] + self.assertTrue( + 'PilightDaemonSim start' in str(error_log_call)) + + # Test stop + self.hass.stop() + error_log_call = mock_pilight_error.call_args_list[-1] + self.assertTrue( + 'PilightDaemonSim stop' in str(error_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.info') + def test_receive_code(self, mock_info): + """Check if code receiving via pilight daemon works.""" + with assert_setup_component(3): + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + + # Test startup + self.hass.start() + self.hass.block_till_done() + + expected_message = dict( + {'protocol': PilightDaemonSim.test_message['protocol'], + 'uuid': PilightDaemonSim.test_message['uuid']}, + **PilightDaemonSim.test_message['message']) + error_log_call = mock_info.call_args_list[-1] + + # Check if all message parts are put on event bus + for key, value in expected_message.items(): + self.assertTrue(str(key) in str(error_log_call)) + self.assertTrue(str(value) in str(error_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.info') + def test_whitelist_exact_match(self, mock_info): + """Check whitelist filter with matched data.""" + with assert_setup_component(3): + whitelist = { + 'protocol': [PilightDaemonSim.test_message['protocol']], + 'uuid': [PilightDaemonSim.test_message['uuid']], + 'id': [PilightDaemonSim.test_message['message']['id']], + 'unit': [PilightDaemonSim.test_message['message']['unit']]} + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, + {pilight.DOMAIN: {"whitelist": whitelist}})) + + self.hass.start() + self.hass.block_till_done() + + expected_message = dict( + {'protocol': PilightDaemonSim.test_message['protocol'], + 'uuid': PilightDaemonSim.test_message['uuid']}, + **PilightDaemonSim.test_message['message']) + info_log_call = mock_info.call_args_list[-1] + + # Check if all message parts are put on event bus + for key, value in expected_message.items(): + self.assertTrue(str(key) in str(info_log_call)) + self.assertTrue(str(value) in str(info_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.info') + def test_whitelist_partial_match(self, mock_info): + """Check whitelist filter with partially matched data, should work.""" + with assert_setup_component(3): + whitelist = { + 'protocol': [PilightDaemonSim.test_message['protocol']], + 'id': [PilightDaemonSim.test_message['message']['id']]} + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, + {pilight.DOMAIN: {"whitelist": whitelist}})) + + self.hass.start() + self.hass.block_till_done() + + expected_message = dict( + {'protocol': PilightDaemonSim.test_message['protocol'], + 'uuid': PilightDaemonSim.test_message['uuid']}, + **PilightDaemonSim.test_message['message']) + info_log_call = mock_info.call_args_list[-1] + + # Check if all message parts are put on event bus + for key, value in expected_message.items(): + self.assertTrue(str(key) in str(info_log_call)) + self.assertTrue(str(value) in str(info_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.info') + def test_whitelist_or_match(self, mock_info): + """Check whitelist filter with several subsection, should work.""" + with assert_setup_component(3): + whitelist = { + 'protocol': [PilightDaemonSim.test_message['protocol'], + 'other_protocoll'], + 'id': [PilightDaemonSim.test_message['message']['id']]} + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, + {pilight.DOMAIN: {"whitelist": whitelist}})) + + self.hass.start() + self.hass.block_till_done() + + expected_message = dict( + {'protocol': PilightDaemonSim.test_message['protocol'], + 'uuid': PilightDaemonSim.test_message['uuid']}, + **PilightDaemonSim.test_message['message']) + info_log_call = mock_info.call_args_list[-1] + + # Check if all message parts are put on event bus + for key, value in expected_message.items(): + self.assertTrue(str(key) in str(info_log_call)) + self.assertTrue(str(value) in str(info_log_call)) + + @patch('pilight.pilight.Client', PilightDaemonSim) + @patch('homeassistant.core._LOGGER.info') + def test_whitelist_no_match(self, mock_info): + """Check whitelist filter with unmatched data, should not work.""" + with assert_setup_component(3): + whitelist = { + 'protocol': ['wrong_protocoll'], + 'id': [PilightDaemonSim.test_message['message']['id']]} + self.assertTrue(setup_component( + self.hass, pilight.DOMAIN, + {pilight.DOMAIN: {"whitelist": whitelist}})) + + self.hass.start() + self.hass.block_till_done() + + info_log_call = mock_info.call_args_list[-1] + + self.assertFalse('Event pilight_received' in info_log_call) From d60c2d604f355e370b829578081b5b1f6b6df83b Mon Sep 17 00:00:00 2001 From: hcooper Date: Wed, 19 Oct 2016 22:15:00 -0700 Subject: [PATCH 117/147] Add support for multiple inputs to nmap tracking module as a list (#3944) --- homeassistant/components/device_tracker/nmap_tracker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index fb217e66c48..68155910ffc 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -30,7 +30,7 @@ CONF_EXCLUDE = 'exclude' REQUIREMENTS = ['python-nmap==0.6.1'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOSTS): cv.string, + vol.Required(CONF_HOSTS): cv.ensure_list, vol.Required(CONF_HOME_INTERVAL, default=0): cv.positive_int, vol.Optional(CONF_EXCLUDE, default=[]): vol.All(cv.ensure_list, vol.Length(min=1)) @@ -120,7 +120,8 @@ class NmapDeviceScanner(object): options += ' --exclude {}'.format(','.join(exclude_hosts)) try: - result = scanner.scan(hosts=self.hosts, arguments=options) + result = scanner.scan(hosts=' '.join(self.hosts), + arguments=options) except PortScannerError: return False From c32afcd961587f2dd19cf13b0ddbc0c3cb36d06b Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 20 Oct 2016 17:36:48 +0200 Subject: [PATCH 118/147] Bugfix sonos (#3926) * Bugfix Sonos * lint * Use player uid for looking of exists * fix lint * fix unittest * Change player_id to unique_id --- homeassistant/components/media_player/sonos.py | 10 ++++++++++ tests/components/media_player/test_sonos.py | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 5fc0166aefa..29ed4b9b90a 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -62,6 +62,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info: player = soco.SoCo(discovery_info) + + # if device allready exists by config + if player.uid in DEVICES: + return True + if player.is_visible: device = SonosDevice(hass, player) add_devices([device]) @@ -212,6 +217,11 @@ class SonosDevice(MediaPlayerDevice): """Update state, called by track_utc_time_change.""" self.update_ha_state(True) + @property + def unique_id(self): + """Return an unique ID.""" + return self._player.uid + @property def name(self): """Return the name of the device.""" diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py index 33b5afcd1ae..add1f0c3ce5 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/media_player/test_sonos.py @@ -67,6 +67,10 @@ class SoCoMock(): """Cause the speaker to separate itself from other speakers.""" return + def uid(self): + """Return a player uid.""" + return "RINCON_XXXXXXXXXXXXXXXXX" + class TestSonosMediaPlayer(unittest.TestCase): """Test the media_player module.""" From 3aa1b6a3f80686653a8de3c7035b17b6974c4c70 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 20 Oct 2016 19:10:12 +0200 Subject: [PATCH 119/147] Fix PEP257 issues (#3962) --- homeassistant/components/history.py | 14 ++-- tests/components/media_player/test_cast.py | 4 +- tests/components/test_http.py | 79 ++++++++++---------- tests/components/test_influxdb.py | 83 +++++++++------------- tests/util/test_package.py | 23 +++--- 5 files changed, 93 insertions(+), 110 deletions(-) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 4cebf637c16..199a6b47b99 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -28,13 +28,13 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ CONF_EXCLUDE: vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, - vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, - [cv.string]) + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) }), CONF_INCLUDE: vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, - vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, - [cv.string]) + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) }) }), }, extra=vol.ALLOW_EXTRA) @@ -244,7 +244,7 @@ class Filters(object): self.included_domains = [] def apply(self, query, entity_ids=None): - """Apply the Include/exclude filter on domains and entities on query. + """Apply the include/exclude filter on domains and entities on query. Following rules apply: * only the include section is configured - just query the specified @@ -278,8 +278,8 @@ class Filters(object): filter_query &= (states.domain.in_(self.included_domains) | states.entity_id.in_(self.included_entities)) else: - filter_query &= (states.domain.in_(self.included_domains) & - ~states.domain.in_(self.excluded_domains)) + filter_query &= (states.domain.in_(self.included_domains) & ~ + states.domain.in_(self.excluded_domains)) # no domain filter just included entities elif not self.excluded_domains and not self.included_domains and \ self.included_entities: diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index b4d4b15351c..3fd4ab9929d 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -7,7 +7,10 @@ from homeassistant.components.media_player import cast class FakeChromeCast(object): + """A fake Chrome Cast.""" + def __init__(self, host, port): + """Initialize the fake Chrome Cast.""" self.host = host self.port = port @@ -19,7 +22,6 @@ class TestCastMediaPlayer(unittest.TestCase): @patch('pychromecast.get_chromecasts') def test_filter_duplicates(self, mock_get_chromecasts, mock_device): """Test filtering of duplicates.""" - mock_get_chromecasts.return_value = [ FakeChromeCast('some_host', cast.DEFAULT_PORT) ] diff --git a/tests/components/test_http.py b/tests/components/test_http.py index feb6efaf9fd..57f21fd76d2 100644 --- a/tests/components/test_http.py +++ b/tests/components/test_http.py @@ -12,18 +12,16 @@ import homeassistant.components.http as http from tests.common import get_test_instance_port, get_test_home_assistant -API_PASSWORD = "test1234" +API_PASSWORD = 'test1234' SERVER_PORT = get_test_instance_port() -HTTP_BASE = "127.0.0.1:{}".format(SERVER_PORT) -HTTP_BASE_URL = "http://{}".format(HTTP_BASE) +HTTP_BASE = '127.0.0.1:{}'.format(SERVER_PORT) +HTTP_BASE_URL = 'http://{}'.format(HTTP_BASE) HA_HEADERS = { const.HTTP_HEADER_HA_AUTH: API_PASSWORD, const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON, } -# dont' add 127.0.0.1/::1 as trusted, as it may interfere with other test cases -TRUSTED_NETWORKS = ["192.0.2.0/24", - "2001:DB8:ABCD::/48", - '100.64.0.1', +# Don't add 127.0.0.1/::1 as trusted, as it may interfere with other test cases +TRUSTED_NETWORKS = ['192.0.2.0/24', '2001:DB8:ABCD::/48', '100.64.0.1', 'FD01:DB8::1'] CORS_ORIGINS = [HTTP_BASE_URL, HTTP_BASE] @@ -31,12 +29,13 @@ CORS_ORIGINS = [HTTP_BASE_URL, HTTP_BASE] hass = None -def _url(path=""): +def _url(path=''): """Helper method to generate URLs.""" return HTTP_BASE_URL + path -def setUpModule(): # pylint: disable=invalid-name +# pylint: disable=invalid-name +def setUpModule(): """Initialize a Home Assistant server.""" global hass @@ -46,10 +45,14 @@ def setUpModule(): # pylint: disable=invalid-name hass.states.set('test.test', 'a_state') bootstrap.setup_component( - hass, http.DOMAIN, - {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: SERVER_PORT, - http.CONF_CORS_ORIGINS: CORS_ORIGINS}}) + hass, http.DOMAIN, { + http.DOMAIN: { + http.CONF_API_PASSWORD: API_PASSWORD, + http.CONF_SERVER_PORT: SERVER_PORT, + http.CONF_CORS_ORIGINS: CORS_ORIGINS, + } + } + ) bootstrap.setup_component(hass, 'api') @@ -85,16 +88,13 @@ class TestHttp: def test_access_denied_with_untrusted_ip(self, caplog): """Test access with an untrusted ip address.""" - - for remote_addr in ['198.51.100.1', - '2001:DB8:FA1::1', - '127.0.0.1', + for remote_addr in ['198.51.100.1', '2001:DB8:FA1::1', '127.0.0.1', '::1']: with patch('homeassistant.components.http.' 'HomeAssistantWSGI.get_real_ip', return_value=remote_addr): - req = requests.get(_url(const.URL_API), - params={'api_password': ''}) + req = requests.get( + _url(const.URL_API), params={'api_password': ''}) assert req.status_code == 401, \ "{} shouldn't be trusted".format(remote_addr) @@ -102,8 +102,8 @@ class TestHttp: def test_access_with_password_in_header(self, caplog): """Test access with password in URL.""" # Hide logging from requests package that we use to test logging - caplog.set_level(logging.WARNING, - logger='requests.packages.urllib3.connectionpool') + caplog.set_level( + logging.WARNING, logger='requests.packages.urllib3.connectionpool') req = requests.get( _url(const.URL_API), @@ -118,19 +118,19 @@ class TestHttp: def test_access_denied_with_wrong_password_in_url(self): """Test access with wrong password.""" - req = requests.get(_url(const.URL_API), - params={'api_password': 'wrongpassword'}) + req = requests.get( + _url(const.URL_API), params={'api_password': 'wrongpassword'}) assert req.status_code == 401 def test_access_with_password_in_url(self, caplog): """Test access with password in URL.""" # Hide logging from requests package that we use to test logging - caplog.set_level(logging.WARNING, - logger='requests.packages.urllib3.connectionpool') + caplog.set_level( + logging.WARNING, logger='requests.packages.urllib3.connectionpool') - req = requests.get(_url(const.URL_API), - params={'api_password': API_PASSWORD}) + req = requests.get( + _url(const.URL_API), params={'api_password': API_PASSWORD}) assert req.status_code == 200 @@ -141,18 +141,16 @@ class TestHttp: def test_access_with_trusted_ip(self, caplog): """Test access with trusted addresses.""" - for remote_addr in ['100.64.0.1', - '192.0.2.100', - 'FD01:DB8::1', + for remote_addr in ['100.64.0.1', '192.0.2.100', 'FD01:DB8::1', '2001:DB8:ABCD::1']: with patch('homeassistant.components.http.' 'HomeAssistantWSGI.get_real_ip', return_value=remote_addr): - req = requests.get(_url(const.URL_API), - params={'api_password': ''}) + req = requests.get( + _url(const.URL_API), params={'api_password': ''}) assert req.status_code == 200, \ - "{} should be trusted".format(remote_addr) + '{} should be trusted'.format(remote_addr) def test_cors_allowed_with_password_in_url(self): """Test cross origin resource sharing with password in url.""" @@ -162,7 +160,7 @@ class TestHttp: allow_origin = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN allow_headers = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS - all_allow_headers = ", ".join(const.ALLOWED_CORS_HEADERS) + all_allow_headers = ', '.join(const.ALLOWED_CORS_HEADERS) assert req.status_code == 200 assert req.headers.get(allow_origin) == HTTP_BASE_URL @@ -174,12 +172,11 @@ class TestHttp: const.HTTP_HEADER_HA_AUTH: API_PASSWORD, const.HTTP_HEADER_ORIGIN: HTTP_BASE_URL } - req = requests.get(_url(const.URL_API), - headers=headers) + req = requests.get(_url(const.URL_API), headers=headers) allow_origin = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN allow_headers = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS - all_allow_headers = ", ".join(const.ALLOWED_CORS_HEADERS) + all_allow_headers = ', '.join(const.ALLOWED_CORS_HEADERS) assert req.status_code == 200 assert req.headers.get(allow_origin) == HTTP_BASE_URL @@ -190,8 +187,7 @@ class TestHttp: headers = { const.HTTP_HEADER_HA_AUTH: API_PASSWORD } - req = requests.get(_url(const.URL_API), - headers=headers) + req = requests.get(_url(const.URL_API), headers=headers) allow_origin = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN allow_headers = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS @@ -207,12 +203,11 @@ class TestHttp: 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'x-ha-access' } - req = requests.options(_url(const.URL_API), - headers=headers) + req = requests.options(_url(const.URL_API), headers=headers) allow_origin = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN allow_headers = const.HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS - all_allow_headers = ", ".join(const.ALLOWED_CORS_HEADERS) + all_allow_headers = ', '.join(const.ALLOWED_CORS_HEADERS) assert req.status_code == 200 assert req.headers.get(allow_origin) == HTTP_BASE_URL diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index 79a4a83b69c..1f934e64a19 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -34,8 +34,8 @@ class TestInfluxDB(unittest.TestCase): } assert setup_component(self.hass, influxdb.DOMAIN, config) self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + self.assertEqual( + EVENT_STATE_CHANGED, self.hass.bus.listen.call_args_list[0][0][0]) self.assertTrue(mock_client.return_value.query.called) def test_setup_config_defaults(self, mock_client): @@ -49,11 +49,11 @@ class TestInfluxDB(unittest.TestCase): } assert setup_component(self.hass, influxdb.DOMAIN, config) self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + self.assertEqual( + EVENT_STATE_CHANGED, self.hass.bus.listen.call_args_list[0][0][0]) def test_setup_minimal_config(self, mock_client): - """Tests the setup with minimal configuration.""" + """Test the setup with minimal configuration.""" config = { 'influxdb': {} } @@ -100,23 +100,22 @@ class TestInfluxDB(unittest.TestCase): """Test the event listener.""" self._setup() - valid = {'1': 1, - '1.0': 1.0, - STATE_ON: 1, - STATE_OFF: 0, - 'foo': 'foo'} + valid = { + '1': 1, + '1.0': 1.0, + STATE_ON: 1, + STATE_OFF: 0, + 'foo': 'foo' + } for in_, out in valid.items(): attrs = { - 'unit_of_measurement': 'foobars', - 'longitude': '1.1', - 'latitude': '2.2' - } - state = mock.MagicMock(state=in_, - domain='fake', - object_id='entity', - attributes=attrs) - event = mock.MagicMock(data={'new_state': state}, - time_fired=12345) + 'unit_of_measurement': 'foobars', + 'longitude': '1.1', + 'latitude': '2.2' + } + state = mock.MagicMock( + state=in_, domain='fake', object_id='entity', attributes=attrs) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ 'measurement': 'foobars', 'tags': { @@ -149,13 +148,10 @@ class TestInfluxDB(unittest.TestCase): attrs = {'unit_of_measurement': unit} else: attrs = {} - state = mock.MagicMock(state=1, - domain='fake', - entity_id='entity-id', - object_id='entity', - attributes=attrs) - event = mock.MagicMock(data={'new_state': state}, - time_fired=12345) + state = mock.MagicMock( + state=1, domain='fake', entity_id='entity-id', + object_id='entity', attributes=attrs) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ 'measurement': 'entity-id', 'tags': { @@ -181,13 +177,10 @@ class TestInfluxDB(unittest.TestCase): """Test the event listener for write failures.""" self._setup() - state = mock.MagicMock(state=1, - domain='fake', - entity_id='entity-id', - object_id='entity', - attributes={}) - event = mock.MagicMock(data={'new_state': state}, - time_fired=12345) + state = mock.MagicMock( + state=1, domain='fake', entity_id='entity-id', object_id='entity', + attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) mock_client.return_value.write_points.side_effect = \ influx_client.exceptions.InfluxDBClientError('foo') self.handler_method(event) @@ -197,13 +190,10 @@ class TestInfluxDB(unittest.TestCase): self._setup() for state_state in (1, 'unknown', '', 'unavailable'): - state = mock.MagicMock(state=state_state, - domain='fake', - entity_id='entity-id', - object_id='entity', - attributes={}) - event = mock.MagicMock(data={'new_state': state}, - time_fired=12345) + state = mock.MagicMock( + state=state_state, domain='fake', entity_id='entity-id', + object_id='entity', attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ 'measurement': 'entity-id', 'tags': { @@ -233,13 +223,10 @@ class TestInfluxDB(unittest.TestCase): self._setup() for entity_id in ('ok', 'blacklisted'): - state = mock.MagicMock(state=1, - domain='fake', - entity_id='fake.{}'.format(entity_id), - object_id=entity_id, - attributes={}) - event = mock.MagicMock(data={'new_state': state}, - time_fired=12345) + state = mock.MagicMock( + state=1, domain='fake', entity_id='fake.{}'.format(entity_id), + object_id=entity_id, attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ 'measurement': 'fake.{}'.format(entity_id), 'tags': { diff --git a/tests/util/test_package.py b/tests/util/test_package.py index d7af4f2d6a3..20fb8ca9a2f 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -12,8 +12,8 @@ import homeassistant.util.package as package RESOURCE_DIR = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'resources')) -TEST_EXIST_REQ = "pip>=7.0.0" -TEST_NEW_REQ = "pyhelloworld3==1.0.0" +TEST_EXIST_REQ = 'pip>=7.0.0' +TEST_NEW_REQ = 'pyhelloworld3==1.0.0' TEST_ZIP_REQ = 'file://{}#{}' \ .format(os.path.join(RESOURCE_DIR, 'pyhelloworld3.zip'), TEST_NEW_REQ) @@ -48,8 +48,8 @@ class TestPackageUtilInstallPackage(unittest.TestCase): self.assertEqual( mock_subprocess.call_args, call([ - mock_sys.executable, '-m', 'pip', - 'install', '--quiet', TEST_NEW_REQ + mock_sys.executable, '-m', 'pip', 'install', '--quiet', + TEST_NEW_REQ ]) ) @@ -67,8 +67,8 @@ class TestPackageUtilInstallPackage(unittest.TestCase): self.assertEqual( mock_subprocess.call_args, call([ - mock_sys.executable, '-m', 'pip', 'install', - '--quiet', TEST_NEW_REQ, '--upgrade' + mock_sys.executable, '-m', 'pip', 'install', '--quiet', + TEST_NEW_REQ, '--upgrade' ]) ) @@ -96,9 +96,8 @@ class TestPackageUtilInstallPackage(unittest.TestCase): @patch('homeassistant.util.package._LOGGER') @patch('homeassistant.util.package.sys') - def test_install_error( - self, mock_sys, mock_logger, mock_exists, mock_subprocess - ): + def test_install_error(self, mock_sys, mock_logger, mock_exists, + mock_subprocess): """Test an install with a target.""" mock_exists.return_value = False mock_subprocess.side_effect = [subprocess.SubprocessError] @@ -112,13 +111,13 @@ class TestPackageUtilCheckPackageExists(unittest.TestCase): """Test for homeassistant.util.package module.""" def test_check_package_global(self): - """Test for a globally-installed package""" + """Test for a globally-installed package.""" installed_package = list(pkg_resources.working_set)[0].project_name self.assertTrue(package.check_package_exists(installed_package, None)) def test_check_package_local(self): - """Test for a locally-installed package""" + """Test for a locally-installed package.""" lib_dir = get_python_lib() installed_package = list(pkg_resources.working_set)[0].project_name @@ -127,5 +126,5 @@ class TestPackageUtilCheckPackageExists(unittest.TestCase): ) def test_check_package_zip(self): - """Test for an installed zip package""" + """Test for an installed zip package.""" self.assertFalse(package.check_package_exists(TEST_ZIP_REQ, None)) From 2b37b4251be1c3d89314115e68f83b09847663eb Mon Sep 17 00:00:00 2001 From: henryk Date: Thu, 20 Oct 2016 20:04:11 +0200 Subject: [PATCH 120/147] Fritzbox - Only report a MAC address if it's really there (#3964) Fixes 'Neither mac or device id passed in' --- homeassistant/components/device_tracker/fritz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index 0e8ed512072..5832fa425be 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -79,7 +79,7 @@ class FritzBoxScanner(object): self._update_info() active_hosts = [] for known_host in self.last_results: - if known_host['status'] == '1': + if known_host['status'] == '1' and known_host.get('mac'): active_hosts.append(known_host['mac']) return active_hosts From fee01fccccbdda02e9d837e0b705d2a3385a0585 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Thu, 20 Oct 2016 14:14:50 -0400 Subject: [PATCH 121/147] Added unit test to the Yahoo Finance sensor (#3943) --- .coveragerc | 1 - tests/components/sensor/test_yahoo_finance.py | 41 +++++++++ tests/fixtures/yahoo_finance.json | 85 +++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/components/sensor/test_yahoo_finance.py create mode 100644 tests/fixtures/yahoo_finance.json diff --git a/.coveragerc b/.coveragerc index a57f5ac99b7..c7bc78b7b31 100644 --- a/.coveragerc +++ b/.coveragerc @@ -283,7 +283,6 @@ omit = homeassistant/components/sensor/vasttrafik.py homeassistant/components/sensor/worldclock.py homeassistant/components/sensor/xbox_live.py - homeassistant/components/sensor/yahoo_finance.py homeassistant/components/sensor/yweather.py homeassistant/components/switch/acer_projector.py homeassistant/components/switch/anel_pwrctrl.py diff --git a/tests/components/sensor/test_yahoo_finance.py b/tests/components/sensor/test_yahoo_finance.py new file mode 100644 index 00000000000..5cbbf50dcab --- /dev/null +++ b/tests/components/sensor/test_yahoo_finance.py @@ -0,0 +1,41 @@ +"""The tests for the Yahoo Finance platform.""" +import json + +import unittest +from unittest.mock import patch + +import homeassistant.components.sensor as sensor +from homeassistant.bootstrap import setup_component +from tests.common import ( + get_test_home_assistant, load_fixture, assert_setup_component) + +VALID_CONFIG = { + 'platform': 'yahoo_finance', + 'symbol': 'YHOO' +} + + +class TestYahooFinanceSetup(unittest.TestCase): + """Test the Yahoo Finance platform.""" + + def setUp(self): + """Initialize values for this testcase class.""" + self.hass = get_test_home_assistant() + self.config = VALID_CONFIG + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + @patch('yahoo_finance.Base._request', + return_value=json.loads(load_fixture('yahoo_finance.json'))) + def test_default_setup(self, m): # pylint: disable=invalid-name + """Test the default setup.""" + with assert_setup_component(1, sensor.DOMAIN): + assert setup_component(self.hass, sensor.DOMAIN, { + 'sensor': VALID_CONFIG}) + + state = self.hass.states.get('sensor.yahoo_stock') + self.assertEqual("41.69", state.attributes.get('open')) + self.assertEqual("41.79", state.attributes.get('prev_close')) + self.assertEqual("YHOO", state.attributes.get('unit_of_measurement')) diff --git a/tests/fixtures/yahoo_finance.json b/tests/fixtures/yahoo_finance.json new file mode 100644 index 00000000000..8d59e80db96 --- /dev/null +++ b/tests/fixtures/yahoo_finance.json @@ -0,0 +1,85 @@ + { + "symbol": "YHOO", + "Ask": "42.42", + "AverageDailyVolume": "11397600", + "Bid": "42.41", + "AskRealtime": null, + "BidRealtime": null, + "BookValue": "29.83", + "Change_PercentChange": "+0.62 - +1.48%", + "Change": "+0.62", + "Commission": null, + "Currency": "USD", + "ChangeRealtime": null, + "AfterHoursChangeRealtime": null, + "DividendShare": null, + "LastTradeDate": "10/18/2016", + "TradeDate": null, + "EarningsShare": "-5.18", + "ErrorIndicationreturnedforsymbolchangedinvalid": null, + "EPSEstimateCurrentYear": "0.49", + "EPSEstimateNextYear": "0.57", + "EPSEstimateNextQuarter": "0.17", + "DaysLow": "41.86", + "DaysHigh": "42.42", + "YearLow": "26.15", + "YearHigh": "44.92", + "HoldingsGainPercent": null, + "AnnualizedGain": null, + "HoldingsGain": null, + "HoldingsGainPercentRealtime": null, + "HoldingsGainRealtime": null, + "MoreInfo": null, + "OrderBookRealtime": null, + "MarketCapitalization": "40.37B", + "MarketCapRealtime": null, + "EBITDA": "151.08M", + "ChangeFromYearLow": "16.26", + "PercentChangeFromYearLow": "+62.18%", + "LastTradeRealtimeWithTime": null, + "ChangePercentRealtime": null, + "ChangeFromYearHigh": "-2.51", + "PercebtChangeFromYearHigh": "-5.59%", + "LastTradeWithTime": "9:41am - 42.41", + "LastTradePriceOnly": "42.41", + "HighLimit": null, + "LowLimit": null, + "DaysRange": "41.86 - 42.42", + "DaysRangeRealtime": null, + "FiftydayMovingAverage": "43.16", + "TwoHundreddayMovingAverage": "39.26", + "ChangeFromTwoHundreddayMovingAverage": "3.15", + "PercentChangeFromTwoHundreddayMovingAverage": "+8.03%", + "ChangeFromFiftydayMovingAverage": "-0.75", + "PercentChangeFromFiftydayMovingAverage": "-1.74%", + "Name": "Yahoo! Inc.", + "Notes": null, + "Open": "41.69", + "PreviousClose": "41.79", + "PricePaid": null, + "ChangeinPercent": "+1.48%", + "PriceSales": "8.13", + "PriceBook": "1.40", + "ExDividendDate": null, + "PERatio": null, + "DividendPayDate": null, + "PERatioRealtime": null, + "PEGRatio": "-24.57", + "PriceEPSEstimateCurrentYear": "86.55", + "PriceEPSEstimateNextYear": "74.40", + "Symbol": "YHOO", + "SharesOwned": null, + "ShortRatio": "5.05", + "LastTradeTime": "9:41am", + "TickerTrend": null, + "OneyrTargetPrice": "43.64", + "Volume": "946198", + "HoldingsValue": null, + "HoldingsValueRealtime": null, + "YearRange": "26.15 - 44.92", + "DaysValueChange": null, + "DaysValueChangeRealtime": null, + "StockExchange": "NMS", + "DividendYield": null, + "PercentChange": "+1.48%" +} From a05fb4cef87d3489ceaeda64c9da964095dd9179 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 20 Oct 2016 20:56:26 +0200 Subject: [PATCH 122/147] Upgrade pymysensors to 0.8 (#3954) --- homeassistant/components/mysensors.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 0c13347ebd1..be5a19bf7c0 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -42,7 +42,7 @@ GATEWAYS = None MQTT_COMPONENT = 'mqtt' REQUIREMENTS = [ 'https://github.com/theolind/pymysensors/archive/' - '8ce98b7fb56f7921a808eb66845ce8b2c455c81e.zip#pymysensors==0.7.1'] + '0b705119389be58332f17753c53167f551254b6c.zip#pymysensors==0.8'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ diff --git a/requirements_all.txt b/requirements_all.txt index d17775487fc..03f35a03a37 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ https://github.com/robbiet480/pygtfs/archive/00546724e4bbcb3053110d844ca44e22462 https://github.com/sander76/powerviewApi/archive/246e782d60d5c0addcc98d7899a0186f9d5640b0.zip#powerviewApi==0.3.15 # homeassistant.components.mysensors -https://github.com/theolind/pymysensors/archive/8ce98b7fb56f7921a808eb66845ce8b2c455c81e.zip#pymysensors==0.7.1 +https://github.com/theolind/pymysensors/archive/0b705119389be58332f17753c53167f551254b6c.zip#pymysensors==0.8 # homeassistant.components.alarm_control_panel.simplisafe https://github.com/w1ll1am23/simplisafe-python/archive/586fede0e85fd69e56e516aaa8e97eb644ca8866.zip#simplisafe-python==0.0.1 From c70722dbaef64cb00dbf6793d84b5530856a35cb Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Thu, 20 Oct 2016 21:30:44 +0200 Subject: [PATCH 123/147] Updater component with basic system reporting (#3781) --- homeassistant/components/updater.py | 106 +++++++++++++++++++++------- requirements_all.txt | 3 + tests/components/test_updater.py | 85 +++++++++++++++------- 3 files changed, 142 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index ec91149a87d..76dd9d8c09e 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -4,62 +4,118 @@ Support to check for available updates. For more details about this component, please refer to the documentation at https://home-assistant.io/components/updater/ """ +from datetime import datetime, timedelta import logging +import json +import platform +import uuid +# pylint: disable=no-name-in-module,import-error +from distutils.version import StrictVersion import requests import voluptuous as vol from homeassistant.const import __version__ as CURRENT_VERSION from homeassistant.const import ATTR_FRIENDLY_NAME +import homeassistant.util.dt as dt_util from homeassistant.helpers import event +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) - +UPDATER_URL = 'https://updater.home-assistant.io/' DOMAIN = 'updater' - ENTITY_ID = 'updater.updater' +ATTR_RELEASE_NOTES = 'release_notes' +UPDATER_UUID_FILE = '.uuid' +CONF_OPT_OUT = 'opt_out' -PYPI_URL = 'https://pypi.python.org/pypi/homeassistant/json' +REQUIREMENTS = ['distro==1.0.0'] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({}), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: { + vol.Optional(CONF_OPT_OUT, default=False): cv.boolean +}}, extra=vol.ALLOW_EXTRA) + + +def _create_uuid(hass, filename=UPDATER_UUID_FILE): + """Create UUID and save it in a file.""" + with open(hass.config.path(filename), 'w') as fptr: + _uuid = uuid.uuid4().hex + fptr.write(json.dumps({"uuid": _uuid})) + return _uuid + + +def _load_uuid(hass, filename=UPDATER_UUID_FILE): + """Load UUID from a file, or return None.""" + try: + with open(hass.config.path(filename)) as fptr: + jsonf = json.loads(fptr.read()) + return uuid.UUID(jsonf['uuid'], version=4).hex + except (ValueError, AttributeError): + return None + except FileNotFoundError: + return _create_uuid(hass, filename) def setup(hass, config): """Setup the updater component.""" if 'dev' in CURRENT_VERSION: - _LOGGER.warning("Updater not supported in development version") + # This component only makes sense in release versions + _LOGGER.warning('Updater not supported in development version') return False - def check_newest_version(_=None): - """Check if a new version is available and report if one is.""" - newest = get_newest_version() - - if newest != CURRENT_VERSION and newest is not None: - hass.states.set( - ENTITY_ID, newest, {ATTR_FRIENDLY_NAME: 'Update available'}) + huuid = None if config.get(CONF_OPT_OUT) else _load_uuid(hass) + # Update daily, start 1 hour after startup + _dt = datetime.now() + timedelta(hours=1) event.track_time_change( - hass, check_newest_version, hour=[0, 12], minute=0, second=0) - - check_newest_version() + hass, lambda _: check_newest_version(hass, huuid), + hour=_dt.hour, minute=_dt.minute, second=_dt.second) return True -def get_newest_version(): - """Get the newest Home Assistant version from PyPI.""" - try: - req = requests.get(PYPI_URL) +def check_newest_version(hass, huuid): + """Check if a new version is available and report if one is.""" + newest, releasenotes = get_newest_version(huuid) - return req.json()['info']['version'] + if newest is not None: + if StrictVersion(newest) > StrictVersion(CURRENT_VERSION): + hass.states.set( + ENTITY_ID, newest, {ATTR_FRIENDLY_NAME: 'Update Available', + ATTR_RELEASE_NOTES: releasenotes} + ) + + +def get_newest_version(huuid): + """Get the newest Home Assistant version.""" + info_object = {'uuid': huuid, 'version': CURRENT_VERSION, + 'timezone': dt_util.DEFAULT_TIME_ZONE.zone, + 'os_name': platform.system(), "arch": platform.machine(), + 'python_version': platform.python_version()} + + if platform.system() == 'Windows': + info_object['os_version'] = platform.win32_ver()[0] + elif platform.system() == 'Darwin': + info_object['os_version'] = platform.mac_ver()[0] + elif platform.system() == 'Linux': + import distro + linux_dist = distro.linux_distribution(full_distribution_name=False) + info_object['distribution'] = linux_dist[0] + info_object['os_version'] = linux_dist[1] + + if not huuid: + info_object = {} + + try: + req = requests.post(UPDATER_URL, json=info_object) + res = req.json() + return (res['version'], res['release-notes']) except requests.RequestException: - _LOGGER.exception("Could not contact PyPI to check for updates") + _LOGGER.exception('Could not contact HASS Update to check for updates') return None except ValueError: - _LOGGER.exception("Received invalid response from PyPI") + _LOGGER.exception('Received invalid response from HASS Update') return None except KeyError: - _LOGGER.exception("Response from PyPI did not include version") + _LOGGER.exception('Response from HASS Update did not include version') return None diff --git a/requirements_all.txt b/requirements_all.txt index 03f35a03a37..4d05b474fe7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -79,6 +79,9 @@ concord232==0.14 # homeassistant.components.media_player.directv directpy==0.1 +# homeassistant.components.updater +distro==1.0.0 + # homeassistant.components.notify.xmpp dnspython3==1.15.0 diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index ec958a0d264..74fc7fc8cd4 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -1,13 +1,16 @@ """The tests for the Updater component.""" +from datetime import datetime, timedelta import unittest from unittest.mock import patch +import os import requests from homeassistant.bootstrap import setup_component from homeassistant.components import updater -import homeassistant.util.dt as dt_util -from tests.common import fire_time_changed, get_test_home_assistant + +from tests.common import ( + assert_setup_component, fire_time_changed, get_test_home_assistant) NEW_VERSION = '10000.0' @@ -18,65 +21,93 @@ MOCK_CURRENT_VERSION = '10.0' class TestUpdater(unittest.TestCase): """Test the Updater component.""" - def setUp(self): # pylint: disable=invalid-name + hass = None + + def setup_method(self, _): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - def tearDown(self): # pylint: disable=invalid-name + def teardown_method(self, _): """Stop everything that was started.""" self.hass.stop() @patch('homeassistant.components.updater.get_newest_version') - def test_new_version_shows_entity_on_start(self, mock_get_newest_version): + def test_new_version_shows_entity_on_start( # pylint: disable=invalid-name + self, mock_get_newest_version): """Test if new entity is created if new version is available.""" - mock_get_newest_version.return_value = NEW_VERSION + mock_get_newest_version.return_value = (NEW_VERSION, '') updater.CURRENT_VERSION = MOCK_CURRENT_VERSION - self.assertTrue(setup_component(self.hass, updater.DOMAIN, { - 'updater': {} - })) + with assert_setup_component(1) as config: + setup_component(self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) + _dt = datetime.now() + timedelta(hours=1) + assert config['updater'] == {'opt_out': False} + + for secs in [-1, 0, 1]: + fire_time_changed(self.hass, _dt + timedelta(seconds=secs)) + self.hass.block_till_done() self.assertTrue(self.hass.states.is_state( updater.ENTITY_ID, NEW_VERSION)) @patch('homeassistant.components.updater.get_newest_version') - def test_no_entity_on_same_version(self, mock_get_newest_version): + def test_no_entity_on_same_version( # pylint: disable=invalid-name + self, mock_get_newest_version): """Test if no entity is created if same version.""" - mock_get_newest_version.return_value = MOCK_CURRENT_VERSION + mock_get_newest_version.return_value = (MOCK_CURRENT_VERSION, '') updater.CURRENT_VERSION = MOCK_CURRENT_VERSION - self.assertTrue(setup_component(self.hass, updater.DOMAIN, { - 'updater': {} - })) + with assert_setup_component(1) as config: + assert setup_component( + self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) + _dt = datetime.now() + timedelta(hours=1) + assert config['updater'] == {'opt_out': False} self.assertIsNone(self.hass.states.get(updater.ENTITY_ID)) - mock_get_newest_version.return_value = NEW_VERSION + mock_get_newest_version.return_value = (NEW_VERSION, '') - fire_time_changed( - self.hass, dt_util.utcnow().replace(hour=0, minute=0, second=0)) - - self.hass.block_till_done() + for secs in [-1, 0, 1]: + fire_time_changed(self.hass, _dt + timedelta(seconds=secs)) + self.hass.block_till_done() self.assertTrue(self.hass.states.is_state( updater.ENTITY_ID, NEW_VERSION)) - @patch('homeassistant.components.updater.requests.get') - def test_errors_while_fetching_new_version(self, mock_get): + @patch('homeassistant.components.updater.requests.post') + def test_errors_while_fetching_new_version( # pylint: disable=invalid-name + self, mock_get): """Test for errors while fetching the new version.""" mock_get.side_effect = requests.RequestException - self.assertIsNone(updater.get_newest_version()) + uuid = '0000' + self.assertIsNone(updater.get_newest_version(uuid)) mock_get.side_effect = ValueError - self.assertIsNone(updater.get_newest_version()) + self.assertIsNone(updater.get_newest_version(uuid)) mock_get.side_effect = KeyError - self.assertIsNone(updater.get_newest_version()) + self.assertIsNone(updater.get_newest_version(uuid)) def test_updater_disabled_on_dev(self): """Test if the updater component is disabled on dev.""" updater.CURRENT_VERSION = MOCK_CURRENT_VERSION + 'dev' - self.assertFalse(setup_component(self.hass, updater.DOMAIN, { - 'updater': {} - })) + with assert_setup_component(1) as config: + assert not setup_component( + self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert config['updater'] == {'opt_out': False} + + def test_uuid_function(self): + """Test if the uuid function works.""" + path = self.hass.config.path(updater.UPDATER_UUID_FILE) + try: + # pylint: disable=protected-access + uuid = updater._load_uuid(self.hass) + assert os.path.isfile(path) + uuid2 = updater._load_uuid(self.hass) + assert uuid == uuid2 + os.remove(path) + uuid2 = updater._load_uuid(self.hass) + assert uuid != uuid2 + finally: + os.remove(path) From 0c0b02eb3d48d2b21326c535e568a3c1ed50e36d Mon Sep 17 00:00:00 2001 From: Jason Carter Date: Fri, 21 Oct 2016 01:35:25 -0400 Subject: [PATCH 124/147] Moving updates out of state property (#3966) --- .../components/alarm_control_panel/nx584.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index 45857f3ef29..8e3b327aecb 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -60,6 +60,7 @@ class NX584Alarm(alarm.AlarmControlPanel): # talk to the API and trigger a requests exception for setup_platform() # to catch self._alarm.list_zones() + self._state = STATE_UNKNOWN @property def should_poll(self): @@ -79,16 +80,20 @@ class NX584Alarm(alarm.AlarmControlPanel): @property def state(self): """Return the state of the device.""" + return self._state + + def update(self): + """Process new events from panel.""" try: part = self._alarm.list_partitions()[0] zones = self._alarm.list_zones() except requests.exceptions.ConnectionError as ex: _LOGGER.error('Unable to connect to %(host)s: %(reason)s', dict(host=self._url, reason=ex)) - return STATE_UNKNOWN + self._state = STATE_UNKNOWN except IndexError: _LOGGER.error('nx584 reports no partitions') - return STATE_UNKNOWN + self._state = STATE_UNKNOWN bypassed = False for zone in zones: @@ -100,11 +105,11 @@ class NX584Alarm(alarm.AlarmControlPanel): break if not part['armed']: - return STATE_ALARM_DISARMED + self._state = STATE_ALARM_DISARMED elif bypassed: - return STATE_ALARM_ARMED_HOME + self._state = STATE_ALARM_ARMED_HOME else: - return STATE_ALARM_ARMED_AWAY + self._state = STATE_ALARM_ARMED_AWAY def alarm_disarm(self, code=None): """Send disarm command.""" From 1ceac8407da7fe8d0989ce338aad1cc91c55429f Mon Sep 17 00:00:00 2001 From: Dustin S Date: Fri, 21 Oct 2016 01:43:39 -0400 Subject: [PATCH 125/147] Adding security contexts to the resources. (#3840) --- docs/swagger.yaml | 62 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b0d765be361..488d6bddd46 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2,7 +2,7 @@ swagger: '2.0' info: title: Home Assistant description: Home Assistant REST API - version: "1.0.0" + version: "1.0.1" # the domain of the service host: localhost:8123 @@ -12,17 +12,17 @@ schemes: - https securityDefinitions: - api_key: - type: apiKey - description: API password - name: api_password - in: query - - # api_key: + #api_key: # type: apiKey # description: API password - # name: x-ha-access - # in: header + # name: api_password + # in: query + + api_key: + type: apiKey + description: API password + name: x-ha-access + in: header # will be prefixed to all paths basePath: /api @@ -38,6 +38,8 @@ paths: description: Returns message if API is up and running. tags: - Core + security: + - api_key: [] responses: 200: description: API is up and running @@ -53,6 +55,8 @@ paths: description: Returns the current configuration as JSON. tags: - Core + security: + - api_key: [] responses: 200: description: Current configuration @@ -81,6 +85,8 @@ paths: summary: Returns all data needed to bootstrap Home Assistant. tags: - Core + security: + - api_key: [] responses: 200: description: Bootstrap information @@ -96,6 +102,8 @@ paths: description: Returns an array of event objects. Each event object contain event name and listener count. tags: - Events + security: + - api_key: [] responses: 200: description: Events @@ -113,6 +121,8 @@ paths: description: Returns an array of service objects. Each object contains the domain and which services it contains. tags: - Services + security: + - api_key: [] responses: 200: description: Services @@ -130,6 +140,8 @@ paths: description: Returns an array of state changes in the past. Each object contains further detail for the entities. tags: - State + security: + - api_key: [] responses: 200: description: State changes @@ -148,6 +160,8 @@ paths: Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes. tags: - State + security: + - api_key: [] responses: 200: description: States @@ -166,6 +180,8 @@ paths: Returns a state object for specified entity_id. tags: - State + security: + - api_key: [] parameters: - name: entity_id in: path @@ -223,6 +239,8 @@ paths: Retrieve all errors logged during the current session of Home Assistant as a plaintext response. tags: - Core + security: + - api_key: [] produces: - text/plain responses: @@ -239,6 +257,8 @@ paths: Returns the data (image) from the specified camera entity_id. tags: - Camera + security: + - api_key: [] produces: - image/jpeg parameters: @@ -262,6 +282,8 @@ paths: Fires an event with event_type tags: - Events + security: + - api_key: [] consumes: - application/json parameters: @@ -286,6 +308,8 @@ paths: Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first. tags: - Services + security: + - api_key: [] consumes: - application/json parameters: @@ -317,6 +341,8 @@ paths: Render a Home Assistant template. tags: - Template + security: + - api_key: [] consumes: - application/json produces: @@ -338,6 +364,8 @@ paths: Setup event forwarding to another Home Assistant instance. tags: - Core + security: + - api_key: [] consumes: - application/json parameters: @@ -376,6 +404,8 @@ paths: tags: - Core - Events + security: + - api_key: [] produces: - text/event-stream parameters: @@ -420,8 +450,16 @@ definitions: location_name: type: string unit_system: - type: string - description: The system for measurement units + type: object + properties: + length: + type: string + mass: + type: string + temperature: + type: string + volume: + type: string time_zone: type: string version: From 62b8e54235ff09691abfa7e91846f8e143ba605c Mon Sep 17 00:00:00 2001 From: AlucardZero Date: Fri, 21 Oct 2016 01:51:00 -0400 Subject: [PATCH 126/147] Upgrade SoCo to 0.12 (#3951) Changelog: https://github.com/SoCo/SoCo/releases/tag/v0.12 Backwards Compatability changes: Dropped support for Python 3.2 Methods relating to the music library (get_artists, get_album_artists, get_albums and others) have been moved to the music_library module. Instead of device.get_album_artists(), please now use device.music_library.get_album_artists() etc. Old code will continue to work for the moment, but will raise deprecation warnings Made a hard deprecation of the Spotify plugin since the API it relied on has been deprecated and it therefore no longer worked Dropped pylint checks for Python 2.6 --- homeassistant/components/media_player/sonos.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 29ed4b9b90a..533b385f0fa 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -21,7 +21,7 @@ from homeassistant.const import ( from homeassistant.config import load_yaml_config_file import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['SoCo==0.11.1'] +REQUIREMENTS = ['SoCo==0.12'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 4d05b474fe7..0e933faae4f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -23,7 +23,7 @@ PyMata==2.13 # RPi.GPIO==0.6.1 # homeassistant.components.media_player.sonos -SoCo==0.11.1 +SoCo==0.12 # homeassistant.components.notify.twitter TwitterAPI==2.4.2 From 9f6d1c4e7b6e291e35d92f08e6491ed88226e6e5 Mon Sep 17 00:00:00 2001 From: Teemu Mikkonen Date: Fri, 21 Oct 2016 09:01:30 +0300 Subject: [PATCH 127/147] Device tracker: SNMPv3 (#3961) * Add initial SNMPv3 support for better security * Fixed indentation errors * Fixed flake8 E128 on row 65 * Disabled warning about too many instance-attributes * Removed extra code, added Inclusive to make better config validation --- .../components/device_tracker/snmp.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/device_tracker/snmp.py index 56f9eb4aae6..33c89110da0 100644 --- a/homeassistant/components/device_tracker/snmp.py +++ b/homeassistant/components/device_tracker/snmp.py @@ -23,11 +23,17 @@ _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pysnmp==4.3.2'] CONF_COMMUNITY = "community" +CONF_AUTHKEY = "authkey" +CONF_PRIVKEY = "privkey" CONF_BASEOID = "baseoid" +DEFAULT_COMMUNITY = "public" + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_COMMUNITY): cv.string, + vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string, + vol.Inclusive(CONF_AUTHKEY, "keys"): cv.string, + vol.Inclusive(CONF_PRIVKEY, "keys"): cv.string, vol.Required(CONF_BASEOID): cv.string }) @@ -43,13 +49,24 @@ def get_scanner(hass, config): class SnmpScanner(object): """Queries any SNMP capable Access Point for connected devices.""" + # pylint: disable=too-many-instance-attributes def __init__(self, config): """Initialize the scanner.""" from pysnmp.entity.rfc3413.oneliner import cmdgen + from pysnmp.entity import config as cfg self.snmp = cmdgen.CommandGenerator() self.host = cmdgen.UdpTransportTarget((config[CONF_HOST], 161)) - self.community = cmdgen.CommunityData(config[CONF_COMMUNITY]) + if CONF_AUTHKEY not in config or CONF_PRIVKEY not in config: + self.auth = cmdgen.CommunityData(config[CONF_COMMUNITY]) + else: + self.auth = cmdgen.UsmUserData( + config[CONF_COMMUNITY], + config[CONF_AUTHKEY], + config[CONF_PRIVKEY], + authProtocol=cfg.usmHMACSHAAuthProtocol, + privProtocol=cfg.usmAesCfb128Protocol + ) self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID]) self.lock = threading.Lock() @@ -95,7 +112,7 @@ class SnmpScanner(object): devices = [] errindication, errstatus, errindex, restable = self.snmp.nextCmd( - self.community, self.host, self.baseoid) + self.auth, self.host, self.baseoid) if errindication: _LOGGER.error("SNMPLIB error: %s", errindication) From 2d89c3ecf40179251194ec729cb7ac969cd17514 Mon Sep 17 00:00:00 2001 From: Nick Vella Date: Fri, 21 Oct 2016 17:47:21 +1100 Subject: [PATCH 128/147] Telstra SMS API notification component (#3949) * Telstra API component * import PLATFORM_SCHEMA * Exclude Telstra notify component in coveragerc * fix authentication issues * Include title in SMS if it's provided * pass lint * Fix many code styling issues * Confirm credentials are correct on component setup --- .coveragerc | 1 + homeassistant/components/notify/telstra.py | 109 +++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 homeassistant/components/notify/telstra.py diff --git a/.coveragerc b/.coveragerc index c7bc78b7b31..a57accaa188 100644 --- a/.coveragerc +++ b/.coveragerc @@ -217,6 +217,7 @@ omit = homeassistant/components/notify/smtp.py homeassistant/components/notify/syslog.py homeassistant/components/notify/telegram.py + homeassistant/components/notify/telstra.py homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twitter.py homeassistant/components/notify/xmpp.py diff --git a/homeassistant/components/notify/telstra.py b/homeassistant/components/notify/telstra.py new file mode 100644 index 00000000000..2bd76989eaa --- /dev/null +++ b/homeassistant/components/notify/telstra.py @@ -0,0 +1,109 @@ +""" +Telstra API platform for notify component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.telstra/ +""" +import logging + +import requests +import voluptuous as vol + +from homeassistant.components.notify import (BaseNotificationService, + ATTR_TITLE, + PLATFORM_SCHEMA) +from homeassistant.const import CONTENT_TYPE_JSON +import homeassistant.helpers.config_validation as cv + +CONF_CONSUMER_KEY = 'consumer_key' +CONF_CONSUMER_SECRET = 'consumer_secret' +CONF_PHONE_NUMBER = 'phone_number' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_CONSUMER_KEY): cv.string, + vol.Required(CONF_CONSUMER_SECRET): cv.string, + vol.Required(CONF_PHONE_NUMBER): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config): + """Get the Telstra SMS API notification service.""" + consumer_key = config.get(CONF_CONSUMER_KEY) + consumer_secret = config.get(CONF_CONSUMER_SECRET) + phone_number = config.get(CONF_PHONE_NUMBER) + + # Attempt an initial authentication to confirm credentials + if _authenticate(consumer_key, consumer_secret) is False: + _LOGGER.exception('Error obtaining authorization from Telstra API') + return None + + return TelstraNotificationService(consumer_key, + consumer_secret, + phone_number) + + +# pylint: disable=too-few-public-methods, too-many-arguments +class TelstraNotificationService(BaseNotificationService): + """Implementation of a notification service for the Telstra SMS API.""" + + def __init__(self, consumer_key, consumer_secret, phone_number): + """Initialize the service.""" + self._consumer_key = consumer_key + self._consumer_secret = consumer_secret + self._phone_number = phone_number + + def send_message(self, message="", **kwargs): + """Send a message to a user.""" + title = kwargs.get(ATTR_TITLE) + + # Retrieve authorization first + token_response = _authenticate(self._consumer_key, + self._consumer_secret) + if token_response is False: + _LOGGER.exception('Error obtaining authorization from Telstra API') + return + + # Send the SMS + if title: + text = '{} {}'.format(title, message) + else: + text = message + + message_data = { + 'to': self._phone_number, + 'body': text + } + message_resource = 'https://api.telstra.com/v1/sms/messages' + message_headers = { + 'Content-Type': CONTENT_TYPE_JSON, + 'Authorization': 'Bearer ' + token_response['access_token'] + } + message_response = requests.post(message_resource, + headers=message_headers, + json=message_data, + timeout=10) + + if message_response.status_code != 202: + _LOGGER.exception("Failed to send SMS. Status code: %d", + message_response.status_code) + + +def _authenticate(consumer_key, consumer_secret): + """Authenticate with the Telstra API.""" + token_data = { + 'client_id': consumer_key, + 'client_secret': consumer_secret, + 'grant_type': 'client_credentials', + 'scope': 'SMS' + } + token_resource = 'https://api.telstra.com/v1/oauth/token' + token_response = requests.get(token_resource, + params=token_data, + timeout=10).json() + + if 'error' in token_response: + return False + + return token_response From 54a64fb8d914ee406cfcadd4ffcac24b07c0db91 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Fri, 21 Oct 2016 22:41:17 +0200 Subject: [PATCH 129/147] Add support for verisure file camera. (#3952) --- homeassistant/components/camera/verisure.py | 102 ++++++++++++++++++++ homeassistant/components/verisure.py | 22 ++++- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/camera/verisure.py diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/camera/verisure.py new file mode 100644 index 00000000000..cc98dc5f363 --- /dev/null +++ b/homeassistant/components/camera/verisure.py @@ -0,0 +1,102 @@ +""" +Camera that loads a picture from a local file. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.verisure/ +""" +import logging +import os + +from homeassistant.components.camera import Camera +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.components.verisure import HUB as hub +from homeassistant.components.verisure import CONF_SMARTCAM + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Camera.""" + if not int(hub.config.get(CONF_SMARTCAM, 1)): + return False + directory_path = hass.config.config_dir + if not os.access(directory_path, os.R_OK): + _LOGGER.error("file path %s is not readable", directory_path) + return False + hub.update_smartcam() + smartcams = [] + smartcams.extend([ + VerisureSmartcam(hass, value.deviceLabel, directory_path) + for value in hub.smartcam_status.values()]) + add_devices(smartcams) + + +class VerisureSmartcam(Camera): + """Local camera.""" + + def __init__(self, hass, device_id, directory_path): + """Initialize Verisure File Camera component.""" + super().__init__() + + self._device_id = device_id + self._directory_path = directory_path + self._image = None + self._image_id = None + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + self.delete_image) + + def camera_image(self): + """Return image response.""" + self.check_imagelist() + if not self._image: + _LOGGER.debug('No image to display') + return + _LOGGER.debug('Trying to open %s', self._image) + with open(self._image, 'rb') as file: + return file.read() + + def check_imagelist(self): + """Check the contents of the image list.""" + hub.update_smartcam_imagelist() + if (self._device_id not in hub.smartcam_dict or + not hub.smartcam_dict[self._device_id]): + return + images = hub.smartcam_dict[self._device_id] + new_image_id = images[0] + _LOGGER.debug('self._device_id=%s, self._images=%s, ' + 'self._new_image_id=%s', self._device_id, + images, new_image_id) + if (new_image_id == '-1' or + self._image_id == new_image_id): + _LOGGER.debug('The image is the same, or loading image_id') + return + _LOGGER.debug('Download new image %s', new_image_id) + hub.my_pages.smartcam.download_image(self._device_id, + new_image_id, + self._directory_path) + if self._image_id: + _LOGGER.debug('Old image_id=%s', self._image_id) + self.delete_image(self) + + else: + _LOGGER.debug('No old image, only new %s', new_image_id) + + self._image_id = new_image_id + self._image = os.path.join(self._directory_path, + '{}{}'.format( + self._image_id, + '.jpg')) + + def delete_image(self, event): + """Delete an old image.""" + remove_image = os.path.join(self._directory_path, + '{}{}'.format( + self._image_id, + '.jpg')) + _LOGGER.debug('Deleting old image %s', remove_image) + os.remove(remove_image) + + @property + def name(self): + """Return the name of this camera.""" + return hub.smartcam_status[self._device_id].location diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 878d9a8d6a2..0c760d899c8 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -27,7 +27,7 @@ CONF_LOCKS = 'locks' CONF_MOUSE = 'mouse' CONF_SMARTPLUGS = 'smartplugs' CONF_THERMOMETERS = 'thermometers' - +CONF_SMARTCAM = 'smartcam' DOMAIN = 'verisure' HUB = None @@ -43,6 +43,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_MOUSE, default=True): cv.boolean, vol.Optional(CONF_SMARTPLUGS, default=True): cv.boolean, vol.Optional(CONF_THERMOMETERS, default=True): cv.boolean, + vol.Optional(CONF_SMARTCAM, default=True): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) @@ -55,7 +56,8 @@ def setup(hass, config): if not HUB.login(): return False - for component in ('sensor', 'switch', 'alarm_control_panel', 'lock'): + for component in ('sensor', 'switch', 'alarm_control_panel', 'lock', + 'camera'): discovery.load_platform(hass, component, DOMAIN, {}, config) return True @@ -72,6 +74,8 @@ class VerisureHub(object): self.climate_status = {} self.mouse_status = {} self.smartplug_status = {} + self.smartcam_status = {} + self.smartcam_dict = {} self.config = domain_config self._verisure = verisure @@ -133,6 +137,20 @@ class VerisureHub(object): self.my_pages.smartplug.get, self.smartplug_status) + @Throttle(timedelta(seconds=30)) + def update_smartcam(self): + """Update the status of the smartcam.""" + self.update_component( + self.my_pages.smartcam.get, + self.smartcam_status) + + @Throttle(timedelta(seconds=30)) + def update_smartcam_imagelist(self): + """Update the imagelist for the camera.""" + _LOGGER.debug('Running update imagelist') + self.smartcam_dict = self.my_pages.smartcam.get_imagelist() + _LOGGER.debug('New dict: %s', self.smartcam_dict) + @property def available(self): """Return True if hub is available.""" From 9f7e16766934e801ce9bd63067421becfe24122e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 22 Oct 2016 05:23:29 +0200 Subject: [PATCH 130/147] Fix test using libsodium and SECRET_KEY (#3975) * Move test to class with custom config setups and with config validation. --- .../device_tracker/test_owntracks.py | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 38aae9021ec..2a269a65212 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -2,18 +2,16 @@ import json import os import unittest +from collections import defaultdict from unittest.mock import patch -from collections import defaultdict +from tests.common import (assert_setup_component, fire_mqtt_message, + get_test_home_assistant, mock_mqtt_component) +import homeassistant.components.device_tracker.owntracks as owntracks from homeassistant.bootstrap import setup_component from homeassistant.components import device_tracker -from homeassistant.const import (STATE_NOT_HOME, CONF_PLATFORM) -import homeassistant.components.device_tracker.owntracks as owntracks - -from tests.common import ( - assert_setup_component, get_test_home_assistant, mock_mqtt_component, - fire_mqtt_message) +from homeassistant.const import CONF_PLATFORM, STATE_NOT_HOME USER = 'greg' DEVICE = 'phone' @@ -684,23 +682,6 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): new_wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) self.assertTrue(wayp == new_wayp) - try: - import libnacl - except (ImportError, OSError): - libnacl = None - - @unittest.skipUnless(libnacl, "libnacl/libsodium is not installed") - def test_encrypted_payload_libsodium(self): - """Test sending encrypted message payload.""" - self.assertTrue(device_tracker.setup(self.hass, { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'owntracks', - CONF_SECRET: SECRET_KEY, - }})) - - self.send_message(LOCATION_TOPIC, ENCRYPTED_LOCATION_MESSAGE) - self.assert_location_latitude(2.0) - class TestDeviceTrackerOwnTrackConfigs(BaseMQTT): """Test the OwnTrack sensor.""" @@ -803,3 +784,21 @@ class TestDeviceTrackerOwnTrackConfigs(BaseMQTT): }}}) self.send_message(LOCATION_TOPIC, MOCK_ENCRYPTED_LOCATION_MESSAGE) self.assert_location_latitude(None) + + try: + import libnacl + except (ImportError, OSError): + libnacl = None + + @unittest.skipUnless(libnacl, "libnacl/libsodium is not installed") + def test_encrypted_payload_libsodium(self): + """Test sending encrypted message payload.""" + with assert_setup_component(1, device_tracker.DOMAIN): + assert setup_component(self.hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + CONF_PLATFORM: 'owntracks', + CONF_SECRET: SECRET_KEY, + }}) + + self.send_message(LOCATION_TOPIC, ENCRYPTED_LOCATION_MESSAGE) + self.assert_location_latitude(2.0) From 6e903fd429fad3b51b5f610b3d16ab8ed3f3f34c Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sat, 22 Oct 2016 05:40:23 +0200 Subject: [PATCH 131/147] Updater component - rename opt_out to reporting (#3979) * Updater component - rename opt_out to reporting * Fix tests --- homeassistant/components/updater.py | 6 +++--- tests/components/test_updater.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index 76dd9d8c09e..f1467f0e4cb 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -27,12 +27,12 @@ DOMAIN = 'updater' ENTITY_ID = 'updater.updater' ATTR_RELEASE_NOTES = 'release_notes' UPDATER_UUID_FILE = '.uuid' -CONF_OPT_OUT = 'opt_out' +CONF_REPORTING = 'reporting' REQUIREMENTS = ['distro==1.0.0'] CONFIG_SCHEMA = vol.Schema({DOMAIN: { - vol.Optional(CONF_OPT_OUT, default=False): cv.boolean + vol.Optional(CONF_REPORTING, default=True): cv.boolean }}, extra=vol.ALLOW_EXTRA) @@ -63,7 +63,7 @@ def setup(hass, config): _LOGGER.warning('Updater not supported in development version') return False - huuid = None if config.get(CONF_OPT_OUT) else _load_uuid(hass) + huuid = _load_uuid(hass) if config.get(CONF_REPORTING) else None # Update daily, start 1 hour after startup _dt = datetime.now() + timedelta(hours=1) diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index 74fc7fc8cd4..8333a721beb 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -41,7 +41,7 @@ class TestUpdater(unittest.TestCase): with assert_setup_component(1) as config: setup_component(self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) _dt = datetime.now() + timedelta(hours=1) - assert config['updater'] == {'opt_out': False} + assert config['updater'] == {'reporting': True} for secs in [-1, 0, 1]: fire_time_changed(self.hass, _dt + timedelta(seconds=secs)) @@ -61,7 +61,7 @@ class TestUpdater(unittest.TestCase): assert setup_component( self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) _dt = datetime.now() + timedelta(hours=1) - assert config['updater'] == {'opt_out': False} + assert config['updater'] == {'reporting': True} self.assertIsNone(self.hass.states.get(updater.ENTITY_ID)) @@ -95,7 +95,7 @@ class TestUpdater(unittest.TestCase): with assert_setup_component(1) as config: assert not setup_component( self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) - assert config['updater'] == {'opt_out': False} + assert config['updater'] == {'reporting': True} def test_uuid_function(self): """Test if the uuid function works.""" From da7837af734e53b106a8a98571717908968ac416 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 21 Oct 2016 20:52:58 -0700 Subject: [PATCH 132/147] Update frontend --- homeassistant/components/frontend/version.py | 6 +++--- .../frontend/www_static/frontend.html | 2 +- .../frontend/www_static/frontend.html.gz | Bin 128228 -> 128256 bytes .../www_static/home-assistant-polymer | 2 +- .../panels/ha-panel-dev-service.html | 2 +- .../panels/ha-panel-dev-service.html.gz | Bin 2842 -> 17418 bytes .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 2327 -> 2329 bytes 8 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 12dcfb6ca55..8ccf0af6145 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -2,12 +2,12 @@ FINGERPRINTS = { "core.js": "5ed5e063d66eb252b5b288738c9c2d16", - "frontend.html": "7d56d6bc46a61004c76838518ff92209", + "frontend.html": "0b226e89047d24f1af8d070990f6c079", "mdi.html": "46a76f877ac9848899b8ed382427c16f", - "micromarkdown-js.html": "c31103ca5b81380b230376c70f323d6c", + "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", "panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002", "panels/ha-panel-dev-info.html": "ec613406ce7e20d93754233d55625c8a", - "panels/ha-panel-dev-service.html": "c7974458ebc33412d95497e99b785e12", + "panels/ha-panel-dev-service.html": "d33657c964041d3ebf114e90a922a15e", "panels/ha-panel-dev-state.html": "65e5f791cc467561719bf591f1386054", "panels/ha-panel-dev-template.html": "d23943fa0370f168714da407c90091a2", "panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295", diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 45b10022f7f..297ada699c1 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2,4 +2,4 @@ },_distributeDirtyRoots:function(){for(var e,t=this.shadyRoot._dirtyRoots,o=0,i=t.length;o0?~setTimeout(e,t):(this._twiddle.textContent=this._twiddleContent++,this._callbacks.push(e),this._currVal++)},cancel:function(e){if(e<0)clearTimeout(~e);else{var t=e-this._lastVal;if(t>=0){if(!this._callbacks[t])throw"invalid async handle: "+e;this._callbacks[t]=null}}},_atEndOfMicrotask:function(){for(var e=this._callbacks.length,t=0;t \ No newline at end of file +this.currentTarget=t,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},i=window.Element.prototype.animate;window.Element.prototype.animate=function(n,r){var o=i.call(this,n,r);o._cancelHandlers=[],o.oncancel=null;var a=o.cancel;o.cancel=function(){a.call(this);var i=new e(this,null,t()),n=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){n.forEach(function(t){t.call(i.target,i)})},0)};var s=o.addEventListener;o.addEventListener=function(t,e){"function"==typeof e&&"cancel"==t?this._cancelHandlers.push(e):s.call(this,t,e)};var u=o.removeEventListener;return o.removeEventListener=function(t,e){if("cancel"==t){var i=this._cancelHandlers.indexOf(e);i>=0&&this._cancelHandlers.splice(i,1)}else u.call(this,t,e)},o}}}(),function(t){var e=document.documentElement,i=null,n=!1;try{var r=getComputedStyle(e).getPropertyValue("opacity"),o="0"==r?"1":"0";i=e.animate({opacity:[o,o]},{duration:1}),i.currentTime=0,n=getComputedStyle(e).getPropertyValue("opacity")==o}catch(t){}finally{i&&i.cancel()}if(!n){var a=window.Element.prototype.animate;window.Element.prototype.animate=function(e,i){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&e[Symbol.iterator]&&(e=Array.from(e)),Array.isArray(e)||null===e||(e=t.convertToArrayForm(e)),a.call(this,e,i)}}}(c),!function(t,e,i){function n(t){var i=e.timeline;i.currentTime=t,i._discardAnimations(),0==i._animations.length?o=!1:requestAnimationFrame(n)}var r=window.requestAnimationFrame;window.requestAnimationFrame=function(t){return r(function(i){e.timeline._updateAnimationsPromises(),t(i),e.timeline._updateAnimationsPromises()})},e.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},e.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){e.animationsWithPromises=e.animationsWithPromises.filter(function(t){return t._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(t){return"finished"!=t.playState&&"idle"!=t.playState})},_play:function(t){var i=new e.Animation(t,this);return this._animations.push(i),e.restartWebAnimationsNextTick(),i._updatePromises(),i._animation.play(),i._updatePromises(),i},play:function(t){return t&&t.remove(),this._play(t)}};var o=!1;e.restartWebAnimationsNextTick=function(){o||(o=!0,requestAnimationFrame(n))};var a=new e.AnimationTimeline;e.timeline=a;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return a}})}catch(t){}try{window.document.timeline=a}catch(t){}}(c,e,f),function(t,e,i){e.animationsWithPromises=[],e.Animation=function(e,i){if(this.id="",e&&e._id&&(this.id=e._id),this.effect=e,e&&(e._animation=this),!i)throw new Error("Animation with null timeline is not supported");this._timeline=i,this._sequenceNumber=t.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},e.Animation.prototype={_updatePromises:function(){var t=this._oldPlayState,e=this.playState;return this._readyPromise&&e!==t&&("idle"==e?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==t?this._resolveReadyPromise():"pending"==e&&(this._readyPromise=void 0)),this._finishedPromise&&e!==t&&("idle"==e?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==e?this._resolveFinishedPromise():"finished"==t&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var t,i,n,r,o=!!this._animation;o&&(t=this.playbackRate,i=this._paused,n=this.startTime,r=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=e.newUnderlyingAnimationForKeyframeEffect(this.effect),e.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=e.newUnderlyingAnimationForGroup(this.effect),e.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&e.bindAnimationForCustomEffect(this),o&&(1!=t&&(this.playbackRate=t),null!==n?this.startTime=n:null!==r?this.currentTime=r:null!==this._holdTime&&(this.currentTime=this._holdTime),i&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var t=this.effect._timing.delay;this._childAnimations.forEach(function(i){this._arrangeChildren(i,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i.effect))}.bind(this))}},_setExternalAnimation:function(t){if(this.effect&&this._isGroup)for(var e=0;e \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 163a4eabdff2529cc710210be430c9afe230f8bf..3beed8e689bf725bfead5e238941af0db6a63c18 100644 GIT binary patch delta 45343 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

9}u49xz3tiy*u^+8FXB-2^-|Bns&lwticE$4r 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 Tmm$>g=>Gu-jOVKaxdR9QzEpvI delta 45320 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 diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 78c7756aa18..51bfd5b5967 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 78c7756aa18b7abcaf8f531209d8d9b910527f27 +Subproject commit 51bfd5b596766da35d99d09a4a92da32a514b299 diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html index 4725aa4a729..458e136f7b1 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html @@ -1 +1 @@ -

\ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz index cdfed733269eebef744f0afb5f822096ffd39ee6..458b49d4f44a7700c09d6c248238ed075e352d52 100644 GIT binary patch literal 17418 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 N|3ARW@Y#e40RT4|0MY;e literal 2842 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 diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index 545fbc20797..08fe313e5d8 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -1 +1 @@ -"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","0a642be9e1b7fb02b86a2e52fc88a8a7"],["/frontend/panels/dev-event-550bf85345c454274a40d15b2795a002.html","6977c253b5b4da588d50b0aaa50b21f4"],["/frontend/panels/dev-info-ec613406ce7e20d93754233d55625c8a.html","8e28a4c617fd6963b45103d5e5c80617"],["/frontend/panels/dev-service-c7974458ebc33412d95497e99b785e12.html","3a551b1ea5fd8b64dee7b1a458d9ffde"],["/frontend/panels/dev-state-65e5f791cc467561719bf591f1386054.html","78158786a6597ef86c3fd6f4985cde92"],["/frontend/panels/dev-template-d23943fa0370f168714da407c90091a2.html","2cf2426a6aa4ee9c1df74926dc475bc8"],["/frontend/panels/map-49ab2d6f180f8bdea7cffaa66b8a5d3e.html","6e6c9c74e0b2424b62d4cc55b8e89be3"],["/static/core-5ed5e063d66eb252b5b288738c9c2d16.js","59dabb570c57dd421d5197009bf1d07f"],["/static/frontend-7d56d6bc46a61004c76838518ff92209.html","d986914a99641b4ce8b0d4b842ffc71f"],["/static/mdi-46a76f877ac9848899b8ed382427c16f.html","a846c4082dd5cffd88ac72cbe943e691"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]],cacheName="sw-precache-v2--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},createCacheKey=function(e,t,a,n){var c=new URL(e);return n&&c.toString().match(n)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,t){var a=new URL(e);return a.search=a.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),a.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),c=createCacheKey(n,hashParamName,a,!1);return[n.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(a){if(!t.has(a))return e.add(new Request(a,{credentials:"same-origin"}))}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(a){return Promise.all(a.map(function(a){if(!t.has(a.url))return e.delete(a)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,a=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(a);var n="index.html";!t&&n&&(a=addDirectoryIndex(a,n),t=urlsToCacheKeys.has(a));var c="/";!t&&c&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(a=new URL(c,self.location).toString(),t=urlsToCacheKeys.has(a)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url,t&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var a,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 From fae620f3b39e9eb3c9847953d57dd9b4dce5eab0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 22 Oct 2016 06:14:35 +0200 Subject: [PATCH 133/147] Migrate to voluptuous (#3748) --- homeassistant/components/binary_sensor/tcp.py | 19 +- homeassistant/components/sensor/tcp.py | 77 ++--- tests/components/binary_sensor/test_tcp.py | 86 ++--- tests/components/sensor/test_tcp.py | 295 ++++++++++-------- 4 files changed, 253 insertions(+), 224 deletions(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index dcf4c3dff7e..12a96a5492f 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -7,21 +7,20 @@ https://home-assistant.io/components/binary_sensor.tcp/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.sensor.tcp import Sensor, CONF_VALUE_ON - +from homeassistant.components.sensor.tcp import ( + TcpSensor, CONF_VALUE_ON, PLATFORM_SCHEMA) _LOGGER = logging.getLogger(__name__) - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Create the binary sensor.""" - if not BinarySensor.validate_config(config): - return False - - add_entities((BinarySensor(hass, config),)) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({}) -class BinarySensor(BinarySensorDevice, Sensor): +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the TCP binary sensor.""" + add_devices([TcpBinarySensor(hass, config)]) + + +class TcpBinarySensor(BinarySensorDevice, TcpSensor): """A binary sensor which is on when its state == CONF_VALUE_ON.""" required = (CONF_VALUE_ON,) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 7a3c4e9bfc3..ab27e1e580f 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -8,33 +8,46 @@ import logging import socket import select -from homeassistant.const import CONF_NAME, CONF_HOST +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_PORT, CONF_PAYLOAD, CONF_TIMEOUT, + CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.helpers.template import Template - -CONF_PORT = "port" -CONF_TIMEOUT = "timeout" -CONF_PAYLOAD = "payload" -CONF_UNIT = "unit" -CONF_VALUE_TEMPLATE = "value_template" -CONF_VALUE_ON = "value_on" -CONF_BUFFER_SIZE = "buffer_size" - -DEFAULT_TIMEOUT = 10 -DEFAULT_BUFFER_SIZE = 1024 +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +CONF_BUFFER_SIZE = 'buffer_size' +CONF_VALUE_ON = 'value_on' -def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup the TCP Sensor.""" - if not Sensor.validate_config(config): - return False - add_entities((Sensor(hass, config),)) +DEFAULT_BUFFER_SIZE = 1024 +DEFAULT_NAME = 'TCP Sensor' +DEFAULT_TIMEOUT = 10 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_PAYLOAD): cv.string, + vol.Optional(CONF_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE): + cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_ON): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, +}) -class Sensor(Entity): +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the TCP Sensor.""" + add_devices([TcpSensor(hass, config)]) + + +class TcpSensor(Entity): """Implementation of a TCP socket based sensor.""" required = tuple() @@ -49,37 +62,25 @@ class Sensor(Entity): self._hass = hass self._config = { CONF_NAME: config.get(CONF_NAME), - CONF_HOST: config[CONF_HOST], - CONF_PORT: config[CONF_PORT], - CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), - CONF_PAYLOAD: config[CONF_PAYLOAD], - CONF_UNIT: config.get(CONF_UNIT), + CONF_HOST: config.get(CONF_HOST), + CONF_PORT: config.get(CONF_PORT), + CONF_TIMEOUT: config.get(CONF_TIMEOUT), + CONF_PAYLOAD: config.get(CONF_PAYLOAD), + CONF_UNIT_OF_MEASUREMENT: config.get(CONF_UNIT_OF_MEASUREMENT), CONF_VALUE_TEMPLATE: value_template, CONF_VALUE_ON: config.get(CONF_VALUE_ON), - CONF_BUFFER_SIZE: config.get( - CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), + CONF_BUFFER_SIZE: config.get(CONF_BUFFER_SIZE), } self._state = None self.update() - @classmethod - def validate_config(cls, config): - """Ensure the configuration has all of the necessary values.""" - always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) - for key in always_required + tuple(cls.required): - if key not in config: - _LOGGER.error( - "You must provide %r to create any TCP entity.", key) - return False - return True - @property def name(self): """Return the name of this sensor.""" name = self._config[CONF_NAME] if name is not None: return name - return super(Sensor, self).name + return super(TcpSensor, self).name @property def state(self): @@ -89,7 +90,7 @@ class Sensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return self._config[CONF_UNIT] + return self._config[CONF_UNIT_OF_MEASUREMENT] def update(self): """Get the latest value for this sensor.""" diff --git a/tests/components/binary_sensor/test_tcp.py b/tests/components/binary_sensor/test_tcp.py index ea06d69bebc..156ebe2c355 100644 --- a/tests/components/binary_sensor/test_tcp.py +++ b/tests/components/binary_sensor/test_tcp.py @@ -1,59 +1,65 @@ """The tests for the TCP binary sensor platform.""" -from copy import copy +import unittest from unittest.mock import patch, Mock -from homeassistant.components.sensor import tcp +from homeassistant.bootstrap import setup_component from homeassistant.components.binary_sensor import tcp as bin_tcp -from tests.common import get_test_home_assistant +from homeassistant.components.sensor import tcp +from tests.common import (get_test_home_assistant, assert_setup_component) from tests.components.sensor import test_tcp -@patch('homeassistant.components.sensor.tcp.Sensor.update') -def test_setup_platform_valid_config(mock_update): - """Should check the supplied config and call add_entities with Sensor.""" - add_entities = Mock() - ret = bin_tcp.setup_platform(None, test_tcp.TEST_CONFIG, add_entities) - assert ret is None, "setup_platform() should return None if successful." - assert add_entities.called - assert isinstance(add_entities.call_args[0][0][0], bin_tcp.BinarySensor) - - -def test_setup_platform_invalid_config(): - """Should check the supplied config and return False if it is invalid.""" - config = copy(test_tcp.TEST_CONFIG) - del config[tcp.CONF_HOST] - assert bin_tcp.setup_platform(None, config, None) is False - - -class TestTCPBinarySensor(): +class TestTCPBinarySensor(unittest.TestCase): """Test the TCP Binary Sensor.""" - def setup_class(cls): + def setup_method(self, method): """Setup things to be run when tests are started.""" - cls.hass = get_test_home_assistant() + self.hass = get_test_home_assistant() - def teardown_class(cls): + def teardown_method(self, method): """Stop down everything that was started.""" - cls.hass.stop() + self.hass.stop() - def test_requires_additional_values(self): - """Should require the additional config values specified.""" - config = copy(test_tcp.TEST_CONFIG) - for key in bin_tcp.BinarySensor.required: - del config[key] - assert len(config) != len(test_tcp.TEST_CONFIG) - assert not bin_tcp.BinarySensor.validate_config(config) + def test_setup_platform_valid_config(self): + """Check a valid configuration.""" + with assert_setup_component(0, 'binary_sensor'): + assert setup_component( + self.hass, 'binary_sensor', test_tcp.TEST_CONFIG) - @patch('homeassistant.components.sensor.tcp.Sensor.update') + def test_setup_platform_invalid_config(self): + """Check the invalid configuration.""" + with assert_setup_component(0): + assert setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { + 'platform': 'tcp', + 'porrt': 1234, + } + }) + + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + def test_setup_platform_devices(self, mock_update): + """Check the supplied config and call add_devices with sensor.""" + add_devices = Mock() + ret = bin_tcp.setup_platform(None, test_tcp.TEST_CONFIG, add_devices) + assert ret is None + assert add_devices.called + assert isinstance( + add_devices.call_args[0][0][0], bin_tcp.TcpBinarySensor) + + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_is_on_true(self, mock_update): - """Should return True if _state is the same as value_on.""" - sensor = bin_tcp.BinarySensor(self.hass, test_tcp.TEST_CONFIG) - sensor._state = test_tcp.TEST_CONFIG[tcp.CONF_VALUE_ON] + """Check the return that _state is value_on.""" + sensor = bin_tcp.TcpBinarySensor( + self.hass, test_tcp.TEST_CONFIG['sensor']) + sensor._state = test_tcp.TEST_CONFIG['sensor'][tcp.CONF_VALUE_ON] + print(sensor._state) assert sensor.is_on - @patch('homeassistant.components.sensor.tcp.Sensor.update') + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_is_on_false(self, mock_update): - """Should return False if _state is not the same as value_on.""" - sensor = bin_tcp.BinarySensor(self.hass, test_tcp.TEST_CONFIG) - sensor._state = "%s abc" % test_tcp.TEST_CONFIG[tcp.CONF_VALUE_ON] + """Check the return that _state is not the same as value_on.""" + sensor = bin_tcp.TcpBinarySensor( + self.hass, test_tcp.TEST_CONFIG['sensor']) + sensor._state = '{} abc'.format( + test_tcp.TEST_CONFIG['sensor'][tcp.CONF_VALUE_ON]) assert not sensor.is_on diff --git a/tests/components/sensor/test_tcp.py b/tests/components/sensor/test_tcp.py index a20c01eee52..d12eccccc63 100644 --- a/tests/components/sensor/test_tcp.py +++ b/tests/components/sensor/test_tcp.py @@ -1,247 +1,270 @@ """The tests for the TCP sensor platform.""" import socket +import unittest from copy import copy from uuid import uuid4 from unittest.mock import patch, Mock +from tests.common import (get_test_home_assistant, assert_setup_component) +from homeassistant.bootstrap import setup_component from homeassistant.components.sensor import tcp from homeassistant.helpers.entity import Entity -from tests.common import get_test_home_assistant - TEST_CONFIG = { - tcp.CONF_NAME: "test_name", - tcp.CONF_HOST: "test_host", - tcp.CONF_PORT: 12345, - tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, - tcp.CONF_PAYLOAD: "test_payload", - tcp.CONF_UNIT: "test_unit", - tcp.CONF_VALUE_TEMPLATE: "test_template", - tcp.CONF_VALUE_ON: "test_on", - tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 + 'sensor': { + 'platform': 'tcp', + tcp.CONF_NAME: 'test_name', + tcp.CONF_HOST: 'test_host', + tcp.CONF_PORT: 12345, + tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, + tcp.CONF_PAYLOAD: 'test_payload', + tcp.CONF_UNIT_OF_MEASUREMENT: 'test_unit', + tcp.CONF_VALUE_TEMPLATE: 'test_template', + tcp.CONF_VALUE_ON: 'test_on', + tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 + }, } + KEYS_AND_DEFAULTS = { - tcp.CONF_NAME: None, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, - tcp.CONF_UNIT: None, + tcp.CONF_UNIT_OF_MEASUREMENT: None, tcp.CONF_VALUE_TEMPLATE: None, tcp.CONF_VALUE_ON: None, tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE } -@patch('homeassistant.components.sensor.tcp.Sensor.update') -def test_setup_platform_valid_config(mock_update): - """Should check the supplied config and call add_entities with Sensor.""" - add_entities = Mock() - ret = tcp.setup_platform(None, TEST_CONFIG, add_entities) - assert ret is None, "setup_platform() should return None if successful." - assert add_entities.called - assert isinstance(add_entities.call_args[0][0][0], tcp.Sensor) - - -def test_setup_platform_invalid_config(): - """Should check the supplied config and return False if it is invalid.""" - config = copy(TEST_CONFIG) - del config[tcp.CONF_HOST] - assert tcp.setup_platform(None, config, None) is False - - -class TestTCPSensor(): +class TestTCPSensor(unittest.TestCase): """Test the TCP Sensor.""" - def setup_class(cls): + def setup_method(self, method): """Setup things to be run when tests are started.""" - cls.hass = get_test_home_assistant() + self.hass = get_test_home_assistant() - def teardown_class(cls): + def teardown_method(self, method): """Stop everything that was started.""" - cls.hass.stop() + self.hass.stop() - @patch('homeassistant.components.sensor.tcp.Sensor.update') + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + def test_setup_platform_valid_config(self, mock_update): + """Check a valid configuration and call add_devices with sensor.""" + with assert_setup_component(0, 'sensor'): + assert setup_component(self.hass, 'sensor', TEST_CONFIG) + + add_devices = Mock() + tcp.setup_platform(None, TEST_CONFIG['sensor'], add_devices) + assert add_devices.called + assert isinstance(add_devices.call_args[0][0][0], tcp.TcpSensor) + + def test_setup_platform_invalid_config(self): + """Check an invalid configuration.""" + with assert_setup_component(0): + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'tcp', + 'porrt': 1234, + } + }) + + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_name(self, mock_update): - """Should return the name if set in the config.""" - sensor = tcp.Sensor(self.hass, TEST_CONFIG) - assert sensor.name == TEST_CONFIG[tcp.CONF_NAME] + """Return the name if set in the configuration.""" + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) + assert sensor.name == TEST_CONFIG['sensor'][tcp.CONF_NAME] - @patch('homeassistant.components.sensor.tcp.Sensor.update') + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_name_not_set(self, mock_update): - """Should return the superclass name property if not set in config.""" - config = copy(TEST_CONFIG) + """Return the superclass name property if not set in configuration.""" + config = copy(TEST_CONFIG['sensor']) del config[tcp.CONF_NAME] entity = Entity() - sensor = tcp.Sensor(self.hass, config) + sensor = tcp.TcpSensor(self.hass, config) assert sensor.name == entity.name - @patch('homeassistant.components.sensor.tcp.Sensor.update') + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_state(self, mock_update): - """Should return the contents of _state.""" - sensor = tcp.Sensor(self.hass, TEST_CONFIG) + """Return the contents of _state.""" + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) uuid = str(uuid4()) sensor._state = uuid assert sensor.state == uuid - @patch('homeassistant.components.sensor.tcp.Sensor.update') + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_unit_of_measurement(self, mock_update): - """Should return the configured unit of measurement.""" - sensor = tcp.Sensor(self.hass, TEST_CONFIG) - assert sensor.unit_of_measurement == TEST_CONFIG[tcp.CONF_UNIT] + """Return the configured unit of measurement.""" + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) + assert sensor.unit_of_measurement == \ + TEST_CONFIG['sensor'][tcp.CONF_UNIT_OF_MEASUREMENT] - @patch("homeassistant.components.sensor.tcp.Sensor.update") + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_config_valid_keys(self, *args): - """Should store valid keys in _config.""" - sensor = tcp.Sensor(self.hass, TEST_CONFIG) - for key in TEST_CONFIG: + """Store valid keys in _config.""" + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) + del TEST_CONFIG['sensor']['platform'] + + for key in TEST_CONFIG['sensor']: assert key in sensor._config def test_validate_config_valid_keys(self): - """Should return True when provided with the correct keys.""" - assert tcp.Sensor.validate_config(TEST_CONFIG) + """Return True when provided with the correct keys.""" + with assert_setup_component(0, 'sensor'): + assert setup_component(self.hass, 'sensor', TEST_CONFIG) - @patch("homeassistant.components.sensor.tcp.Sensor.update") + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_config_invalid_keys(self, mock_update): """Shouldn't store invalid keys in _config.""" - config = copy(TEST_CONFIG) + config = copy(TEST_CONFIG['sensor']) config.update({ - "a": "test_a", - "b": "test_b", - "c": "test_c" + 'a': 'test_a', + 'b': 'test_b', + 'c': 'test_c' }) - sensor = tcp.Sensor(self.hass, config) - for invalid_key in "abc": + sensor = tcp.TcpSensor(self.hass, config) + for invalid_key in 'abc': assert invalid_key not in sensor._config def test_validate_config_invalid_keys(self): """Test with invalid keys plus some extra.""" - config = copy(TEST_CONFIG) + config = copy(TEST_CONFIG['sensor']) config.update({ - "a": "test_a", - "b": "test_b", - "c": "test_c" + 'a': 'test_a', + 'b': 'test_b', + 'c': 'test_c' }) - assert tcp.Sensor.validate_config(config) + with assert_setup_component(0, 'sensor'): + assert setup_component(self.hass, 'sensor', {'tcp': config}) - @patch("homeassistant.components.sensor.tcp.Sensor.update") + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_config_uses_defaults(self, mock_update): - """Should use defaults where appropriate.""" - config = copy(TEST_CONFIG) - for key in KEYS_AND_DEFAULTS.keys(): + """Check if defaults were set.""" + config = copy(TEST_CONFIG['sensor']) + + for key in KEYS_AND_DEFAULTS: del config[key] - sensor = tcp.Sensor(self.hass, config) + + with assert_setup_component(1) as result_config: + assert setup_component(self.hass, 'sensor', { + 'sensor': config, + }) + + sensor = tcp.TcpSensor(self.hass, result_config['sensor'][0]) + for key, default in KEYS_AND_DEFAULTS.items(): assert sensor._config[key] == default def test_validate_config_missing_defaults(self): - """Should return True when defaulted keys are not provided.""" - config = copy(TEST_CONFIG) - for key in KEYS_AND_DEFAULTS.keys(): + """Return True when defaulted keys are not provided.""" + config = copy(TEST_CONFIG['sensor']) + + for key in KEYS_AND_DEFAULTS: del config[key] - assert tcp.Sensor.validate_config(config) + + with assert_setup_component(0, 'sensor'): + assert setup_component(self.hass, 'sensor', {'tcp': config}) def test_validate_config_missing_required(self): - """Should return False when required config items are missing.""" - for key in TEST_CONFIG: + """Return False when required config items are missing.""" + for key in TEST_CONFIG['sensor']: if key in KEYS_AND_DEFAULTS: continue - config = copy(TEST_CONFIG) + config = copy(TEST_CONFIG['sensor']) del config[key] - assert not tcp.Sensor.validate_config(config), ( - "validate_config() should have returned False since %r was not" - "provided." % key) + with assert_setup_component(0, 'sensor'): + assert setup_component(self.hass, 'sensor', {'tcp': config}) - @patch("homeassistant.components.sensor.tcp.Sensor.update") + @patch('homeassistant.components.sensor.tcp.TcpSensor.update') def test_init_calls_update(self, mock_update): - """Should call update() method during __init__().""" - tcp.Sensor(self.hass, TEST_CONFIG) + """Call update() method during __init__().""" + tcp.TcpSensor(self.hass, TEST_CONFIG) assert mock_update.called - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_connects_to_host_and_port(self, mock_select, mock_socket): - """Should connect to the configured host and port.""" - tcp.Sensor(self.hass, TEST_CONFIG) + """Connect to the configured host and port.""" + tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) mock_socket = mock_socket().__enter__() assert mock_socket.connect.mock_calls[0][1] == (( - TEST_CONFIG[tcp.CONF_HOST], - TEST_CONFIG[tcp.CONF_PORT]),) + TEST_CONFIG['sensor'][tcp.CONF_HOST], + TEST_CONFIG['sensor'][tcp.CONF_PORT]),) - @patch("socket.socket.connect", side_effect=socket.error()) + @patch('socket.socket.connect', side_effect=socket.error()) def test_update_returns_if_connecting_fails(self, *args): - """Should return if connecting to host fails.""" - with patch("homeassistant.components.sensor.tcp.Sensor.update"): - sensor = tcp.Sensor(self.hass, TEST_CONFIG) + """Return if connecting to host fails.""" + with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None - @patch("socket.socket.connect") - @patch("socket.socket.send", side_effect=socket.error()) + @patch('socket.socket.connect') + @patch('socket.socket.send', side_effect=socket.error()) def test_update_returns_if_sending_fails(self, *args): - """Should return if sending fails.""" - with patch("homeassistant.components.sensor.tcp.Sensor.update"): - sensor = tcp.Sensor(self.hass, TEST_CONFIG) + """Return if sending fails.""" + with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None - @patch("socket.socket.connect") - @patch("socket.socket.send") - @patch("select.select", return_value=(False, False, False)) + @patch('socket.socket.connect') + @patch('socket.socket.send') + @patch('select.select', return_value=(False, False, False)) def test_update_returns_if_select_fails(self, *args): - """Should return if select fails to return a socket.""" - with patch("homeassistant.components.sensor.tcp.Sensor.update"): - sensor = tcp.Sensor(self.hass, TEST_CONFIG) + """Return if select fails to return a socket.""" + with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_sends_payload(self, mock_select, mock_socket): - """Should send the configured payload as bytes.""" - tcp.Sensor(self.hass, TEST_CONFIG) + """Send the configured payload as bytes.""" + tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) mock_socket = mock_socket().__enter__() mock_socket.send.assert_called_with( - TEST_CONFIG[tcp.CONF_PAYLOAD].encode() + TEST_CONFIG['sensor'][tcp.CONF_PAYLOAD].encode() ) - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_calls_select_with_timeout(self, mock_select, mock_socket): - """Should provide the timeout argument to select.""" - tcp.Sensor(self.hass, TEST_CONFIG) + """Provide the timeout argument to select.""" + tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) mock_socket = mock_socket().__enter__() mock_select.assert_called_with( - [mock_socket], [], [], TEST_CONFIG[tcp.CONF_TIMEOUT]) + [mock_socket], [], [], TEST_CONFIG['sensor'][tcp.CONF_TIMEOUT]) - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_receives_packet_and_sets_as_state( self, mock_select, mock_socket): """Test the response from the socket and set it as the state.""" - test_value = "test_value" + test_value = 'test_value' mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() - config = copy(TEST_CONFIG) + config = copy(TEST_CONFIG['sensor']) del config[tcp.CONF_VALUE_TEMPLATE] - sensor = tcp.Sensor(self.hass, config) + sensor = tcp.TcpSensor(self.hass, config) assert sensor._state == test_value - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_renders_value_in_template(self, mock_select, mock_socket): - """Should render the value in the provided template.""" - test_value = "test_value" + """Render the value in the provided template.""" + test_value = 'test_value' mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() - config = copy(TEST_CONFIG) - config[tcp.CONF_VALUE_TEMPLATE] = "{{ value }} {{ 1+1 }}" - sensor = tcp.Sensor(self.hass, config) - assert sensor._state == "%s 2" % test_value + config = copy(TEST_CONFIG['sensor']) + config[tcp.CONF_VALUE_TEMPLATE] = '{{ value }} {{ 1+1 }}' + sensor = tcp.TcpSensor(self.hass, config) + assert sensor._state == '%s 2' % test_value - @patch("socket.socket") - @patch("select.select", return_value=(True, False, False)) + @patch('socket.socket') + @patch('select.select', return_value=(True, False, False)) def test_update_returns_if_template_render_fails( self, mock_select, mock_socket): - """Should return None if rendering the template fails.""" - test_value = "test_value" + """Return None if rendering the template fails.""" + test_value = 'test_value' mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() - config = copy(TEST_CONFIG) + config = copy(TEST_CONFIG['sensor']) config[tcp.CONF_VALUE_TEMPLATE] = "{{ this won't work" - sensor = tcp.Sensor(self.hass, config) + sensor = tcp.TcpSensor(self.hass, config) assert sensor.update() is None From cb475070029fba78b0e2e14fbd210df1d60b629f Mon Sep 17 00:00:00 2001 From: Hugo Dupras Date: Sat, 22 Oct 2016 06:14:45 +0200 Subject: [PATCH 134/147] Add support for Neato Connected robot as a switch (#3935) * Add support for Neato Connected robot as a switch Signed-off-by: Hugo D. (jabesq) * Add checklist items Signed-off-by: Hugo D. (jabesq) * Add missing docstring Signed-off-by: Hugo D. (jabesq) * [Neato] Add update function to retrieve robot state * Add docstring for update function + catch exception when retrieving state * Change type of HTTPError when updating state * Fix pylint errors --- .coveragerc | 1 + homeassistant/components/switch/neato.py | 148 +++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 152 insertions(+) create mode 100644 homeassistant/components/switch/neato.py diff --git a/.coveragerc b/.coveragerc index a57accaa188..303a7907329 100644 --- a/.coveragerc +++ b/.coveragerc @@ -292,6 +292,7 @@ omit = homeassistant/components/switch/edimax.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/mystrom.py + homeassistant/components/switch/neato.py homeassistant/components/switch/netio.py homeassistant/components/switch/orvibo.py homeassistant/components/switch/pulseaudio_loopback.py diff --git a/homeassistant/components/switch/neato.py b/homeassistant/components/switch/neato.py new file mode 100644 index 00000000000..c5ff4bae861 --- /dev/null +++ b/homeassistant/components/switch/neato.py @@ -0,0 +1,148 @@ +""" +Support for Neato Connected Vaccums. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.neato/ +""" +import time +import logging +from datetime import timedelta +from urllib.error import HTTPError +from requests.exceptions import HTTPError as req_HTTPError + +import voluptuous as vol + +from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, STATE_OFF, + STATE_ON, STATE_UNAVAILABLE) +from homeassistant.helpers.entity import ToggleEntity +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.1.zip' + '#pybotvac==0.0.1'] + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) + +MIN_TIME_TO_WAIT = timedelta(seconds=10) +MIN_TIME_TO_LOCK_UPDATE = 10 + +SWITCH_TYPES = { + 'clean': ['Clean'] +} + +DOMAIN = 'neato' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Neato platform.""" + from pybotvac import Account + + try: + auth = Account(config[CONF_USERNAME], config[CONF_PASSWORD]) + except HTTPError: + _LOGGER.error("Unable to connect to Neato API") + return False + + dev = [] + for robot in auth.robots: + for type_name in SWITCH_TYPES: + dev.append(NeatoConnectedSwitch(robot, type_name)) + add_devices(dev) + + +class NeatoConnectedSwitch(ToggleEntity): + """Neato Connected Switch (clean).""" + + def __init__(self, robot, switch_type): + """Initialize the Neato Connected switch.""" + self.type = switch_type + self.robot = robot + self.lock = False + self.last_lock_time = None + self.graceful_state = False + self._state = None + + def lock_update(self): + """Lock the update since Neato clean takes some time to start.""" + if self.is_update_locked(): + return + self.lock = True + self.last_lock_time = time.time() + + def reset_update_lock(self): + """Reset the update lock.""" + self.lock = False + self.last_lock_time = None + + def set_graceful_lock(self, state): + """Set the graceful state.""" + self.graceful_state = state + self.reset_update_lock() + self.lock_update() + + def is_update_locked(self): + """Check if the update method is locked.""" + if self.last_lock_time is None: + return False + + if time.time() - self.last_lock_time >= MIN_TIME_TO_LOCK_UPDATE: + self.last_lock_time = None + return False + + return True + + @property + def state(self): + """Return the state.""" + if not self._state: + return STATE_UNAVAILABLE + if not self._state['availableCommands']['start'] and \ + not self._state['availableCommands']['stop'] and \ + not self._state['availableCommands']['pause'] and \ + not self._state['availableCommands']['resume'] and \ + not self._state['availableCommands']['goToBase']: + return STATE_UNAVAILABLE + return STATE_ON if self.is_on else STATE_OFF + + @property + def name(self): + """Return the name of the sensor.""" + return self.robot.name + ' ' + SWITCH_TYPES[self.type][0] + + @property + def is_on(self): + """Return true if device is on.""" + if self.is_update_locked(): + return self.graceful_state + if self._state['action'] == 1 and self._state['state'] == 2: + return True + return False + + def turn_on(self, **kwargs): + """Turn the device on.""" + self.set_graceful_lock(True) + self.robot.start_cleaning() + + def turn_off(self, **kwargs): + """Turn the device off (Return Robot to base).""" + self.robot.pause_cleaning() + time.sleep(1) + self.robot.send_to_base() + + def update(self): + """Refresh Robot state from Neato API.""" + try: + self._state = self.robot.state + except req_HTTPError: + _LOGGER.error("Unable to retrieve to Robot State.") + self._state = None + return False diff --git a/requirements_all.txt b/requirements_all.txt index 0e933faae4f..00953ca1e46 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -189,6 +189,9 @@ https://github.com/gadgetreactor/pyHS100/archive/ef85f939fd5b07064a0f34dfa673fa7 # homeassistant.components.netatmo https://github.com/jabesq/netatmo-api-python/archive/v0.6.0.zip#lnetatmo==0.6.0 +# homeassistant.components.switch.neato +https://github.com/jabesq/pybotvac/archive/v0.0.1.zip#pybotvac==0.0.1 + # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 From 1d2d338cd019897759f0ebb9c763b7d3fe771de2 Mon Sep 17 00:00:00 2001 From: Hydreliox Date: Sat, 22 Oct 2016 06:34:22 +0200 Subject: [PATCH 135/147] Add Bbox Router bandwidth as sensors (#3956) * Add Bbox Routeur bandwidth as sensors Add possibility to monitor max and currently used bandwidth of your xdsl connection for Bbox Routeur * Minor Fixes Unit constant get back into the main sensor file * Unused round removed --- .coveragerc | 1 + homeassistant/components/sensor/bbox.py | 151 ++++++++++++++++++++++++ requirements_all.txt | 1 + 3 files changed, 153 insertions(+) create mode 100644 homeassistant/components/sensor/bbox.py diff --git a/.coveragerc b/.coveragerc index 303a7907329..8916c8fd251 100644 --- a/.coveragerc +++ b/.coveragerc @@ -226,6 +226,7 @@ omit = homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/arwn.py + homeassistant/components/sensor/bbox.py homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bom.py homeassistant/components/sensor/coinmarketcap.py diff --git a/homeassistant/components/sensor/bbox.py b/homeassistant/components/sensor/bbox.py new file mode 100644 index 00000000000..c79fa904c5d --- /dev/null +++ b/homeassistant/components/sensor/bbox.py @@ -0,0 +1,151 @@ +""" +Support for Bbox Bouygues Modem Router. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.bbox/ +""" +import logging +from datetime import timedelta +import requests +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_NAME, CONF_MONITORED_VARIABLES, + ATTR_ATTRIBUTION) +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) + +REQUIREMENTS = ['pybbox==0.0.5-alpha'] + +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = "Powered by Bouygues Telecom" +DEFAULT_NAME = 'Bbox' + +# Bandwidth units +BANDWIDTH_MEGABITS_SECONDS = 'Mb/s' # type: str + +# Sensor types are defined like so: +# Name, unit, icon +SENSOR_TYPES = { + 'down_max_bandwidth': ['Maximum Download Bandwidth', + BANDWIDTH_MEGABITS_SECONDS, 'mdi:download'], + 'up_max_bandwidth': ['Maximum Upload Bandwidth', + BANDWIDTH_MEGABITS_SECONDS, 'mdi:upload'], + 'current_down_bandwidth': ['Currently Used Download Bandwidth', + BANDWIDTH_MEGABITS_SECONDS, 'mdi:download'], + 'current_up_bandwidth': ['Currently Used Upload Bandwidth', + BANDWIDTH_MEGABITS_SECONDS, 'mdi:upload'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_VARIABLES): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +# pylint: disable=too-many-arguments +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Bbox sensor.""" + # Create a data fetcher to support all of the configured sensors. Then make + # the first call to init the data. + try: + bbox_data = BboxData() + bbox_data.update() + except requests.exceptions.HTTPError as error: + _LOGGER.error(error) + return False + + name = config.get(CONF_NAME) + + sensors = [] + for variable in config[CONF_MONITORED_VARIABLES]: + sensors.append(BboxSensor(bbox_data, variable, name)) + + add_devices(sensors) + + +class BboxSensor(Entity): + """Implementation of a Bbox sensor.""" + + def __init__(self, bbox_data, sensor_type, name): + """Initialize the sensor.""" + self.client_name = name + self.type = sensor_type + self._name = SENSOR_TYPES[sensor_type][0] + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self._icon = SENSOR_TYPES[sensor_type][2] + self.bbox_data = bbox_data + self._state = None + + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return '{} {}'.format(self.client_name, self._name) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + } + + def update(self): + """Get the latest data from Bbox and update the state.""" + self.bbox_data.update() + if self.type == 'down_max_bandwidth': + self._state = round( + self.bbox_data.data['rx']['maxBandwidth'] / 1000, 2) + elif self.type == 'up_max_bandwidth': + self._state = round( + self.bbox_data.data['tx']['maxBandwidth'] / 1000, 2) + elif self.type == 'current_down_bandwidth': + self._state = round(self.bbox_data.data['rx']['bandwidth'] / 1000, + 2) + elif self.type == 'current_up_bandwidth': + self._state = round(self.bbox_data.data['tx']['bandwidth'] / 1000, + 2) + + +# pylint: disable=too-few-public-methods +class BboxData(object): + """Get data from the Bbox.""" + + def __init__(self): + """Initialize the data object.""" + self.data = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from the Bbox.""" + import pybbox + + try: + box = pybbox.Bbox() + self.data = box.get_ip_stats() + except requests.exceptions.HTTPError as error: + _LOGGER.error(error) + self.data = None + return False diff --git a/requirements_all.txt b/requirements_all.txt index 00953ca1e46..48506711f8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -339,6 +339,7 @@ pyasn1-modules==0.0.8 pyasn1==0.1.9 # homeassistant.components.device_tracker.bbox +# homeassistant.components.sensor.bbox pybbox==0.0.5-alpha # homeassistant.components.device_tracker.bluetooth_tracker From 8d375e2d4712ba19238b96c9c4c2545e2836c066 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sat, 22 Oct 2016 06:41:27 +0200 Subject: [PATCH 136/147] Improve known_device.yaml writing (#3955) * Better known_device.yaml writing * yaml dump --- .../components/device_tracker/__init__.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 5ea3690852a..87b628b050b 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -14,6 +14,7 @@ import threading from typing import Any, Sequence, Callable import voluptuous as vol +import yaml from homeassistant.bootstrap import ( prepare_setup_platform, log_exception) @@ -420,7 +421,12 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): }) try: result = [] - devices = load_yaml_config_file(path) + try: + devices = load_yaml_config_file(path) + except HomeAssistantError as err: + _LOGGER.error('Unable to load %s: %s', path, str(err)) + return [] + for dev_id, device in devices.items(): try: device = dev_schema(device) @@ -463,14 +469,15 @@ def update_config(path: str, dev_id: str, device: Device): """Add device to YAML configuration file.""" with open(path, 'a') as out: out.write('\n') - out.write('{}:\n'.format(device.dev_id)) - for key, value in (('name', device.name), ('mac', device.mac), - ('picture', device.config_picture), - ('track', 'yes' if device.track else 'no'), - (CONF_AWAY_HIDE, - 'yes' if device.away_hide else 'no')): - out.write(' {}: {}\n'.format(key, '' if value is None else value)) + device = {device.dev_id: { + 'name': device.name, + 'mac': device.mac, + 'picture': device.config_picture, + 'track': device.track, + CONF_AWAY_HIDE: device.away_hide + }} + yaml.dump(device, out, default_flow_style=False) def get_gravatar_for_email(email: str): From 4f6ed09a9929e228fb9544fea4220c0aa09c1e70 Mon Sep 17 00:00:00 2001 From: Georgi Kirichkov Date: Sat, 22 Oct 2016 07:45:36 +0300 Subject: [PATCH 137/147] Adds energy monitoring capabilities to the TP-Link HS110 (#3917) * Adds energy monitoring capabilities to the TP-Link HS110 Energy monitoring works only on the HS110 model * Reverts to using GadgetReactor's module * Updates requirements_all.txt * Refactors tplink switch to use attribute caching * Update tplink.py --- homeassistant/components/switch/tplink.py | 37 +++++++++++++++++++++-- requirements_all.txt | 6 ++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py index 7937a4c77ac..06c67dcf5ea 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -12,13 +12,18 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_HOST, CONF_NAME) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['https://github.com/gadgetreactor/pyHS100/archive/' - 'ef85f939fd5b07064a0f34dfa673fa7d6140bd95.zip#pyHS100==0.1.2'] +REQUIREMENTS = ['https://github.com/GadgetReactor/pyHS100/archive/' + '1f771b7d8090a91c6a58931532e42730b021cbde.zip#pyHS100==0.2.0'] _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'TPLink Switch HS100' +ATTR_CURRENT_CONSUMPTION = 'Current consumption' +ATTR_TOTAL_CONSUMPTION = 'Total consumption' +ATTR_VOLTAGE = 'Voltage' +ATTR_CURRENT = 'Current' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -42,6 +47,11 @@ class SmartPlugSwitch(SwitchDevice): """Initialize the switch.""" self.smartplug = smartplug self._name = name + self._state = None + self._emeter_present = (smartplug.model == 110) + _LOGGER.debug("Setting up TP-Link Smart Plug HS%i", smartplug.model) + # Set up emeter cache + self._emeter_params = {} @property def name(self): @@ -51,7 +61,7 @@ class SmartPlugSwitch(SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return self.smartplug.state == 'ON' + return self._state == 'ON' def turn_on(self, **kwargs): """Turn the switch on.""" @@ -60,3 +70,24 @@ class SmartPlugSwitch(SwitchDevice): def turn_off(self): """Turn the switch off.""" self.smartplug.state = 'OFF' + + @property + def device_state_attributes(self): + """Return the state attributes of the device.""" + return self._emeter_params + + def update(self): + """Update the TP-Link switch's state.""" + self._state = self.smartplug.state + + if self._emeter_present: + emeter_readings = self.smartplug.get_emeter_realtime() + + self._emeter_params[ATTR_CURRENT_CONSUMPTION] \ + = "%.1f W" % emeter_readings["power"] + self._emeter_params[ATTR_TOTAL_CONSUMPTION] \ + = "%.2f kW" % emeter_readings["total"] + self._emeter_params[ATTR_VOLTAGE] \ + = "%.2f V" % emeter_readings["voltage"] + self._emeter_params[ATTR_CURRENT] \ + = "%.1f A" % emeter_readings["current"] diff --git a/requirements_all.txt b/requirements_all.txt index 48506711f8d..a3af772aeab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -154,6 +154,9 @@ hikvision==0.4 # homeassistant.components.light.flux_led https://github.com/Danielhiversen/flux_led/archive/0.7.zip#flux_led==0.8 +# homeassistant.components.switch.tplink +https://github.com/GadgetReactor/pyHS100/archive/1f771b7d8090a91c6a58931532e42730b021cbde.zip#pyHS100==0.2.0 + # homeassistant.components.switch.dlink https://github.com/LinuxChristian/pyW215/archive/v0.3.5.zip#pyW215==0.3.5 @@ -183,9 +186,6 @@ https://github.com/danieljkemp/onkyo-eiscp/archive/python3.zip#onkyo-eiscp==0.9. # homeassistant.components.device_tracker.fritz # https://github.com/deisi/fritzconnection/archive/b5c14515e1c8e2652b06b6316a7f3913df942841.zip#fritzconnection==0.4.6 -# homeassistant.components.switch.tplink -https://github.com/gadgetreactor/pyHS100/archive/ef85f939fd5b07064a0f34dfa673fa7d6140bd95.zip#pyHS100==0.1.2 - # homeassistant.components.netatmo https://github.com/jabesq/netatmo-api-python/archive/v0.6.0.zip#lnetatmo==0.6.0 From 754d536974fccd0768b90938d79071ba0910714a Mon Sep 17 00:00:00 2001 From: dasos Date: Sat, 22 Oct 2016 06:37:35 +0100 Subject: [PATCH 138/147] Work better with password-protected Squeezebox / LMS servers (#3953) * Work better with password-protected Squeezebox / LMS servers, including getting file art. Refactored to only have a single method calling the telent lib. (Should make it easier to convert to the more appropriate JSON interface) * Update squeezebox.py * Update squeezebox.py * Update squeezebox.py * Update squeezebox.py * Update squeezebox.py --- .../components/media_player/squeezebox.py | 114 +++++++++++------- 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 9df91ceb276..4f994461a26 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -40,6 +40,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the squeezebox platform.""" + import socket + username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -50,11 +52,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): host = config.get(CONF_HOST) port = config.get(CONF_PORT) - # Only add a media server once - if host in KNOWN_DEVICES: + # Get IP of host, to prevent duplication of same host (different DNS names) + try: + ipaddr = socket.gethostbyname(host) + except (OSError) as error: + _LOGGER.error("Could not communicate with %s:%d: %s", + host, port, error) return False - KNOWN_DEVICES.append(host) + # Combine it with port to allow multiple servers at the same host + key = "{}:{}".format(ipaddr, port) + + # Only add a media server once + if key in KNOWN_DEVICES: + return False + KNOWN_DEVICES.append(key) + + _LOGGER.debug("Creating LMS object for %s", key) lms = LogitechMediaServer(host, port, username, password) if not lms.init_success: @@ -97,30 +111,12 @@ class LogitechMediaServer(object): def query(self, *parameters): """Send request and await response from server.""" - try: - telnet = telnetlib.Telnet(self.host, self.port) - if self._username and self._password: - telnet.write('login {username} {password}\n'.format( - username=self._username, - password=self._password).encode('UTF-8')) - telnet.read_until(b'\n', timeout=3) - message = '{}\n'.format(' '.join(parameters)) - telnet.write(message.encode('UTF-8')) - response = telnet.read_until(b'\n', timeout=3)\ - .decode('UTF-8')\ - .split(' ')[-1]\ - .strip() - telnet.write(b'exit\n') - return urllib.parse.unquote(response) - except (OSError, ConnectionError) as error: - _LOGGER.error("Could not communicate with %s:%d: %s", - self.host, - self.port, - error) - return None + response = urllib.parse.unquote(self.get(' '.join(parameters))) + + return response.split(' ')[-1].strip() def get_player_status(self, player): - """Get ithe status of a player.""" + """Get the status of a player.""" # (title) : Song title # Requested Information # a (artist): Artist name 'artist' @@ -129,25 +125,50 @@ class LogitechMediaServer(object): # l (album): Album, including the server's "(N of M)" tags = 'adKl' new_status = {} + response = self.get('{player} status - 1 tags:{tags}\n' + .format(player=player, tags=tags)) + + if not response: + return {} + + response = response.split(' ') + + for item in response: + parts = urllib.parse.unquote(item).partition(':') + new_status[parts[0]] = parts[2] + return new_status + + def get(self, command): + """Abstract out the telnet connection.""" try: telnet = telnetlib.Telnet(self.host, self.port) - telnet.write('{player} status - 1 tags:{tags}\n'.format( - player=player, - tags=tags - ).encode('UTF-8')) + + if self._username and self._password: + _LOGGER.debug("Logging in") + + telnet.write('login {username} {password}\n'.format( + username=self._username, + password=self._password).encode('UTF-8')) + telnet.read_until(b'\n', timeout=3) + + _LOGGER.debug("About to send message: %s", command) + message = '{}\n'.format(command) + telnet.write(message.encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ .decode('UTF-8')\ - .split(' ') + telnet.write(b'exit\n') - for item in response: - parts = urllib.parse.unquote(item).partition(':') - new_status[parts[0]] = parts[2] - except (OSError, ConnectionError) as error: + _LOGGER.debug("Response: %s", response) + + return response + + except (OSError, ConnectionError, EOFError) as error: _LOGGER.error("Could not communicate with %s:%d: %s", self.host, self.port, error) - return new_status + return None # pylint: disable=too-many-instance-attributes @@ -228,11 +249,22 @@ class SqueezeBoxDevice(MediaPlayerDevice): media_url = ('/music/current/cover.jpg?player={player}').format( player=self._id) - base_url = 'http://{server}:{port}/'.format( - server=self._lms.host, - port=self._lms.http_port) + # pylint: disable=protected-access + if self._lms._username: + base_url = 'http://{username}:{password}@{server}:{port}/'.format( + username=self._lms._username, + password=self._lms._password, + server=self._lms.host, + port=self._lms.http_port) + else: + base_url = 'http://{server}:{port}/'.format( + server=self._lms.host, + port=self._lms.http_port) - return urllib.parse.urljoin(base_url, media_url) + url = urllib.parse.urljoin(base_url, media_url) + + _LOGGER.debug("Media image url: %s", url) + return url @property def media_title(self): @@ -337,7 +369,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): """ Replace the current play list with the uri. - Telnet Command Strucutre: + Telnet Command Structure: playlist play <fadeInSecs> The "playlist play" command puts the specified song URL, @@ -361,7 +393,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): """ Add a items to the existing playlist. - Telnet Command Strucutre: + Telnet Command Structure: <playerid> playlist add <item> The "playlist add" command adds the specified song URL, playlist or From 6e5a3c0a949894a407764e5543aceb467e205128 Mon Sep 17 00:00:00 2001 From: Brent Hughes <bah2830@users.noreply.github.com> Date: Sat, 22 Oct 2016 01:18:13 -0500 Subject: [PATCH 139/147] Fixed statsd stopping if state is not numeric or only attributes changed (#3981) * Fixed statsd stopping if attribute changed by not the state * Fixed tests which exposed a new bug. * Fixed another issue. whoops --- homeassistant/components/statsd.py | 19 +++++++++++-------- tests/components/test_statsd.py | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd.py index 3a99a65fe6a..d85bc1e030c 100644 --- a/homeassistant/components/statsd.py +++ b/homeassistant/components/statsd.py @@ -61,18 +61,20 @@ def setup(hass, config): try: _state = state_helper.state_as_number(state) except ValueError: - return + # Set the state to none and continue for any numeric attributes. + _state = None states = dict(state.attributes) - _LOGGER.debug('Sending %s.%s', state.entity_id, _state) + _LOGGER.debug('Sending %s', state.entity_id) if show_attribute_flag is True: - statsd_client.gauge( - "%s.state" % state.entity_id, - _state, - sample_rate - ) + if isinstance(_state, (float, int)): + statsd_client.gauge( + "%s.state" % state.entity_id, + _state, + sample_rate + ) # Send attribute values for key, value in states.items(): @@ -81,7 +83,8 @@ def setup(hass, config): statsd_client.gauge(stat, value, sample_rate) else: - statsd_client.gauge(state.entity_id, _state, sample_rate) + if isinstance(_state, (float, int)): + statsd_client.gauge(state.entity_id, _state, sample_rate) # Increment the count statsd_client.incr(state.entity_id, rate=sample_rate) diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py index 696617a92eb..ccc494fbc24 100644 --- a/tests/components/test_statsd.py +++ b/tests/components/test_statsd.py @@ -114,7 +114,7 @@ class TestStatsd(unittest.TestCase): handler_method(mock.MagicMock(data={ 'new_state': ha.State('domain.test', invalid, {})})) self.assertFalse(mock_client.return_value.gauge.called) - self.assertFalse(mock_client.return_value.incr.called) + self.assertTrue(mock_client.return_value.incr.called) @mock.patch('statsd.StatsClient') def test_event_listener_attr_details(self, mock_client): @@ -162,4 +162,4 @@ class TestStatsd(unittest.TestCase): handler_method(mock.MagicMock(data={ 'new_state': ha.State('domain.test', invalid, {})})) self.assertFalse(mock_client.return_value.gauge.called) - self.assertFalse(mock_client.return_value.incr.called) + self.assertTrue(mock_client.return_value.incr.called) From ea91d24eb2defdb3900e42680f65efa673e93e60 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny <me@robbiet.us> Date: Fri, 21 Oct 2016 23:20:15 -0700 Subject: [PATCH 140/147] HA iOS support (#3752) * Initial commit of the iOS component and platform * Allow extra * Add battery to identify, a new function to get devices, and load the upcoming sensor * Add iOS sensor platform, currently for battery state & level * Add discoverability for the iOS app * Convert single quote to double quotes * Load all required components and platforms when loading the iOS component for the best experience * Unify quote style to double * Change to hass_ios * Update push URL, add support for logging based on status code, log rate limit updates * Block iOS from coverage checks for now... --- .coveragerc | 3 + homeassistant/components/discovery.py | 4 +- homeassistant/components/ios.py | 323 +++++++++++++++++++++++++ homeassistant/components/notify/ios.py | 87 +++++++ homeassistant/components/sensor/ios.py | 112 +++++++++ requirements_all.txt | 2 +- 6 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/ios.py create mode 100644 homeassistant/components/notify/ios.py create mode 100644 homeassistant/components/sensor/ios.py diff --git a/.coveragerc b/.coveragerc index 8916c8fd251..15fa27dd1c0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -31,6 +31,9 @@ omit = homeassistant/components/insteon_hub.py homeassistant/components/*/insteon_hub.py + homeassistant/components/ios.py + homeassistant/components/*/ios.py + homeassistant/components/isy994.py homeassistant/components/*/isy994.py diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index fa48be04e74..32e1bbd5f6a 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -14,15 +14,17 @@ import voluptuous as vol from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.helpers.discovery import load_platform, discover -REQUIREMENTS = ['netdisco==0.7.1'] +REQUIREMENTS = ['netdisco==0.7.2'] DOMAIN = 'discovery' SCAN_INTERVAL = 300 # seconds SERVICE_NETGEAR = 'netgear_router' SERVICE_WEMO = 'belkin_wemo' +SERVICE_HASS_IOS_APP = 'hass_ios' SERVICE_HANDLERS = { + SERVICE_HASS_IOS_APP: ('ios', None), SERVICE_NETGEAR: ('device_tracker', None), SERVICE_WEMO: ('wemo', None), 'philips_hue': ('light', 'hue'), diff --git a/homeassistant/components/ios.py b/homeassistant/components/ios.py new file mode 100644 index 00000000000..0793417fab3 --- /dev/null +++ b/homeassistant/components/ios.py @@ -0,0 +1,323 @@ +""" +Native Home Assistant iOS app component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/ios/ +""" +import os +import json +import logging + +import voluptuous as vol +from voluptuous.humanize import humanize_error + +from homeassistant.helpers import config_validation as cv + +import homeassistant.loader as loader + +from homeassistant.helpers import discovery + +from homeassistant.components.http import HomeAssistantView + +from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR, + HTTP_BAD_REQUEST) + +from homeassistant.components.notify import DOMAIN as NotifyDomain + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "ios" + +DEPENDENCIES = ["http"] + +CONF_PUSH = "push" +CONF_PUSH_CATEGORIES = "categories" +CONF_PUSH_CATEGORIES_NAME = "name" +CONF_PUSH_CATEGORIES_IDENTIFIER = "identifier" +CONF_PUSH_CATEGORIES_ACTIONS = "actions" + +CONF_PUSH_ACTIONS_IDENTIFIER = "identifier" +CONF_PUSH_ACTIONS_TITLE = "title" +CONF_PUSH_ACTIONS_ACTIVATION_MODE = "activationMode" +CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED = "authenticationRequired" +CONF_PUSH_ACTIONS_DESTRUCTIVE = "destructive" +CONF_PUSH_ACTIONS_BEHAVIOR = "behavior" +CONF_PUSH_ACTIONS_CONTEXT = "context" +CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE = "textInputButtonTitle" +CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER = "textInputPlaceholder" + +ATTR_FOREGROUND = "foreground" +ATTR_BACKGROUND = "background" + +ACTIVATION_MODES = [ATTR_FOREGROUND, ATTR_BACKGROUND] + +ATTR_DEFAULT_BEHAVIOR = "default" +ATTR_TEXT_INPUT_BEHAVIOR = "textInput" + +BEHAVIORS = [ATTR_DEFAULT_BEHAVIOR, ATTR_TEXT_INPUT_BEHAVIOR] + +ATTR_DEVICE = "device" +ATTR_PUSH_TOKEN = "pushToken" +ATTR_APP = "app" +ATTR_PERMISSIONS = "permissions" +ATTR_PUSH_ID = "pushId" +ATTR_DEVICE_ID = "deviceId" +ATTR_PUSH_SOUNDS = "pushSounds" +ATTR_BATTERY = "battery" + +ATTR_DEVICE_NAME = "name" +ATTR_DEVICE_LOCALIZED_MODEL = "localizedModel" +ATTR_DEVICE_MODEL = "model" +ATTR_DEVICE_PERMANENT_ID = "permanentID" +ATTR_DEVICE_SYSTEM_VERSION = "systemVersion" +ATTR_DEVICE_TYPE = "type" +ATTR_DEVICE_SYSTEM_NAME = "systemName" + +ATTR_APP_BUNDLE_IDENTIFER = "bundleIdentifer" +ATTR_APP_BUILD_NUMBER = "buildNumber" +ATTR_APP_VERSION_NUMBER = "versionNumber" + +ATTR_LOCATION_PERMISSION = "location" +ATTR_NOTIFICATIONS_PERMISSION = "notifications" + +PERMISSIONS = [ATTR_LOCATION_PERMISSION, ATTR_NOTIFICATIONS_PERMISSION] + +ATTR_BATTERY_STATE = "state" +ATTR_BATTERY_LEVEL = "level" + +ATTR_BATTERY_STATE_UNPLUGGED = "Unplugged" +ATTR_BATTERY_STATE_CHARGING = "Charging" +ATTR_BATTERY_STATE_FULL = "Full" +ATTR_BATTERY_STATE_UNKNOWN = "Unknown" + +BATTERY_STATES = [ATTR_BATTERY_STATE_UNPLUGGED, ATTR_BATTERY_STATE_CHARGING, + ATTR_BATTERY_STATE_FULL, ATTR_BATTERY_STATE_UNKNOWN] + +ATTR_DEVICES = "devices" + +ACTION_SCHEMA = vol.Schema({ + vol.Required(CONF_PUSH_ACTIONS_IDENTIFIER): vol.Upper, + vol.Required(CONF_PUSH_ACTIONS_TITLE): cv.string, + vol.Optional(CONF_PUSH_ACTIONS_ACTIVATION_MODE, + default=ATTR_BACKGROUND): vol.In(ACTIVATION_MODES), + vol.Optional(CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED, + default=False): cv.boolean, + vol.Optional(CONF_PUSH_ACTIONS_DESTRUCTIVE, + default=False): cv.boolean, + vol.Optional(CONF_PUSH_ACTIONS_BEHAVIOR, + default=ATTR_DEFAULT_BEHAVIOR): vol.In(BEHAVIORS), + vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE): cv.string, + vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER): cv.string, +}, extra=vol.ALLOW_EXTRA) + +ACTION_SCHEMA_LIST = vol.All(cv.ensure_list, [ACTION_SCHEMA]) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: { + CONF_PUSH: { + CONF_PUSH_CATEGORIES: vol.All(cv.ensure_list, [{ + vol.Required(CONF_PUSH_CATEGORIES_NAME): cv.string, + vol.Required(CONF_PUSH_CATEGORIES_IDENTIFIER): vol.Upper, + vol.Required(CONF_PUSH_CATEGORIES_ACTIONS): ACTION_SCHEMA_LIST + }]) + } + } +}, extra=vol.ALLOW_EXTRA) + +IDENTIFY_DEVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_DEVICE_NAME): cv.string, + vol.Required(ATTR_DEVICE_LOCALIZED_MODEL): cv.string, + vol.Required(ATTR_DEVICE_MODEL): cv.string, + vol.Required(ATTR_DEVICE_PERMANENT_ID): cv.string, + vol.Required(ATTR_DEVICE_SYSTEM_VERSION): cv.string, + vol.Required(ATTR_DEVICE_TYPE): cv.string, + vol.Required(ATTR_DEVICE_SYSTEM_NAME): cv.string, +}, extra=vol.ALLOW_EXTRA) + +IDENTIFY_DEVICE_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_DEVICE_SCHEMA) + +IDENTIFY_APP_SCHEMA = vol.Schema({ + vol.Required(ATTR_APP_BUNDLE_IDENTIFER): cv.string, + vol.Required(ATTR_APP_BUILD_NUMBER): cv.positive_int, + vol.Required(ATTR_APP_VERSION_NUMBER): cv.positive_int +}, extra=vol.ALLOW_EXTRA) + +IDENTIFY_APP_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_APP_SCHEMA) + +IDENTIFY_BATTERY_SCHEMA = vol.Schema({ + vol.Required(ATTR_BATTERY_LEVEL): cv.positive_int, + vol.Required(ATTR_BATTERY_STATE): vol.In(BATTERY_STATES) +}, extra=vol.ALLOW_EXTRA) + +IDENTIFY_BATTERY_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_BATTERY_SCHEMA) + +IDENTIFY_SCHEMA = vol.Schema({ + vol.Required(ATTR_DEVICE): IDENTIFY_DEVICE_SCHEMA_CONTAINER, + vol.Required(ATTR_BATTERY): IDENTIFY_BATTERY_SCHEMA_CONTAINER, + vol.Required(ATTR_PUSH_TOKEN): cv.string, + vol.Required(ATTR_APP): IDENTIFY_APP_SCHEMA_CONTAINER, + vol.Required(ATTR_PERMISSIONS): vol.All(cv.ensure_list, + [vol.In(PERMISSIONS)]), + vol.Required(ATTR_PUSH_ID): cv.string, + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Optional(ATTR_PUSH_SOUNDS): list +}, extra=vol.ALLOW_EXTRA) + +CONFIGURATION_FILE = "ios.conf" + +CONFIG_FILE = {ATTR_DEVICES: {}} + +CONFIG_FILE_PATH = "" + + +def _load_config(filename): + """Load configuration.""" + if not os.path.isfile(filename): + return {} + + try: + with open(filename, "r") as fdesc: + inp = fdesc.read() + + # In case empty file + if not inp: + return {} + + return json.loads(inp) + except (IOError, ValueError) as error: + _LOGGER.error("Reading config file %s failed: %s", filename, error) + return None + + +def _save_config(filename, config): + """Save configuration.""" + try: + with open(filename, "w") as fdesc: + fdesc.write(json.dumps(config)) + except (IOError, TypeError) as error: + _LOGGER.error("Saving config file failed: %s", error) + return False + return True + + +def devices_with_push(): + """Return a dictionary of push enabled targets.""" + targets = {} + for device_name, device in CONFIG_FILE[ATTR_DEVICES].items(): + if device.get(ATTR_PUSH_ID) is not None: + targets[device_name] = device.get(ATTR_PUSH_ID) + return targets + + +def enabled_push_ids(): + """Return a list of push enabled target push IDs.""" + push_ids = list() + # pylint: disable=unused-variable + for device_name, device in CONFIG_FILE[ATTR_DEVICES].items(): + if device.get(ATTR_PUSH_ID) is not None: + push_ids.append(device.get(ATTR_PUSH_ID)) + return push_ids + + +def devices(): + """Return a dictionary of all identified devices.""" + return CONFIG_FILE[ATTR_DEVICES] + + +def device_name_for_push_id(push_id): + """Return the device name for the push ID.""" + for device_name, device in CONFIG_FILE[ATTR_DEVICES].items(): + if device.get(ATTR_PUSH_ID) is push_id: + return device_name + return None + + +def setup(hass, config): + """Setup the iOS component.""" + # pylint: disable=global-statement, import-error + global CONFIG_FILE + global CONFIG_FILE_PATH + + CONFIG_FILE_PATH = hass.config.path(CONFIGURATION_FILE) + + CONFIG_FILE = _load_config(CONFIG_FILE_PATH) + + if CONFIG_FILE == {}: + CONFIG_FILE[ATTR_DEVICES] = {} + + device_tracker = loader.get_component("device_tracker") + if device_tracker.DOMAIN not in hass.config.components: + device_tracker.setup(hass, {}) + # Need this to enable requirements checking in the app. + hass.config.components.append(device_tracker.DOMAIN) + + if "notify.ios" not in hass.config.components: + notify = loader.get_component("notify.ios") + notify.get_service(hass, {}) + # Need this to enable requirements checking in the app. + if NotifyDomain not in hass.config.components: + hass.config.components.append(NotifyDomain) + + zeroconf = loader.get_component("zeroconf") + if zeroconf.DOMAIN not in hass.config.components: + zeroconf.setup(hass, config) + # Need this to enable requirements checking in the app. + hass.config.components.append(zeroconf.DOMAIN) + + discovery.load_platform(hass, "sensor", DOMAIN, {}, config) + + hass.wsgi.register_view(iOSIdentifyDeviceView(hass)) + + if config.get(DOMAIN) is not None: + app_config = config[DOMAIN] + if app_config.get(CONF_PUSH) is not None: + push_config = app_config[CONF_PUSH] + hass.wsgi.register_view(iOSPushConfigView(hass, push_config)) + + return True + + +# pylint: disable=invalid-name +class iOSPushConfigView(HomeAssistantView): + """A view that provides the push categories configuration.""" + + url = "/api/ios/push" + name = "api:ios:push" + + def __init__(self, hass, push_config): + """Init the view.""" + super().__init__(hass) + self.push_config = push_config + + def get(self, request): + """Handle the GET request for the push configuration.""" + return self.json(self.push_config) + + +class iOSIdentifyDeviceView(HomeAssistantView): + """A view that accepts device identification requests.""" + + url = "/api/ios/identify" + name = "api:ios:identify" + + def __init__(self, hass): + """Init the view.""" + super().__init__(hass) + + def post(self, request): + """Handle the POST request for device identification.""" + try: + data = IDENTIFY_SCHEMA(request.json) + except vol.Invalid as ex: + return self.json_message(humanize_error(request.json, ex), + HTTP_BAD_REQUEST) + + name = data.get(ATTR_DEVICE_ID) + + CONFIG_FILE[ATTR_DEVICES][name] = data + + if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE): + return self.json_message("Error saving device.", + HTTP_INTERNAL_SERVER_ERROR) + + return self.json({"status": "registered"}) diff --git a/homeassistant/components/notify/ios.py b/homeassistant/components/notify/ios.py new file mode 100644 index 00000000000..cb85ab8f753 --- /dev/null +++ b/homeassistant/components/notify/ios.py @@ -0,0 +1,87 @@ +""" +iOS push notification platform for notify component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.ios/ +""" +import logging +from datetime import datetime, timezone +import requests + +from homeassistant.components import ios + +import homeassistant.util.dt as dt_util + +from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_MESSAGE, + ATTR_DATA, BaseNotificationService) + +_LOGGER = logging.getLogger(__name__) + +PUSH_URL = "https://ios-push.home-assistant.io/push" + +DEPENDENCIES = ["ios"] + + +def get_service(hass, config): + """Get the iOS notification service.""" + if "notify.ios" not in hass.config.components: + # Need this to enable requirements checking in the app. + hass.config.components.append("notify.ios") + + return iOSNotificationService() + + +# pylint: disable=too-few-public-methods, too-many-arguments, invalid-name +class iOSNotificationService(BaseNotificationService): + """Implement the notification service for iOS.""" + + def __init__(self): + """Initialize the service.""" + + @property + def targets(self): + """Return a dictionary of registered targets.""" + return ios.devices_with_push() + + def send_message(self, message="", **kwargs): + """Send a message to the Lambda APNS gateway.""" + data = {ATTR_MESSAGE: message} + + if kwargs.get(ATTR_TITLE) is not None: + # Remove default title from notifications. + if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + + targets = kwargs.get(ATTR_TARGET) + + if not targets: + targets = ios.enabled_push_ids() + + if kwargs.get(ATTR_DATA) is not None: + data[ATTR_DATA] = kwargs.get(ATTR_DATA) + + for target in targets: + data[ATTR_TARGET] = target + + req = requests.post(PUSH_URL, json=data, timeout=10) + + if req.status_code is not 201: + message = req.json()["message"] + if req.status_code is 429: + _LOGGER.warning(message) + elif req.status_code is 400 or 500: + _LOGGER.error(message) + + if req.status_code in (201, 429): + rate_limits = req.json()["rateLimits"] + resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"]) + resetsAtTime = resetsAt - datetime.now(timezone.utc) + rate_limit_msg = ("iOS push notification rate limits for %s: " + "%d sent, %d allowed, %d errors, " + "resets in %s") + _LOGGER.info(rate_limit_msg, + ios.device_name_for_push_id(target), + rate_limits["successful"], + rate_limits["maximum"], rate_limits["errors"], + str(resetsAtTime).split(".")[0]) diff --git a/homeassistant/components/sensor/ios.py b/homeassistant/components/sensor/ios.py new file mode 100644 index 00000000000..c4c8f1eba69 --- /dev/null +++ b/homeassistant/components/sensor/ios.py @@ -0,0 +1,112 @@ +""" +Support for Home Assistant iOS app sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.ios/ +""" +from homeassistant.components import ios +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ["ios"] + +SENSOR_TYPES = { + "level": ["Battery Level", "%"], + "state": ["Battery State", None] +} + +DEFAULT_ICON = "mdi:battery" + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the iOS sensor.""" + if discovery_info is None: + return + dev = list() + for device_name, device in ios.devices().items(): + for sensor_type in ("level", "state"): + dev.append(IOSSensor(sensor_type, device_name, device)) + + add_devices(dev) + + +class IOSSensor(Entity): + """Representation of an iOS sensor.""" + + def __init__(self, sensor_type, device_name, device): + """Initialize the sensor.""" + self._device_name = device_name + self._name = device_name + " " + SENSOR_TYPES[sensor_type][0] + self._device = device + self.type = sensor_type + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self.update() + + @property + def name(self): + """Return the name of the iOS sensor.""" + device_name = self._device[ios.ATTR_DEVICE][ios.ATTR_DEVICE_NAME] + return "{} {}".format(device_name, SENSOR_TYPES[self.type][0]) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unique_id(self): + """Return the unique ID of this sensor.""" + return "sensor_ios_battery_{}_{}".format(self.type, self._device_name) + + @property + def unit_of_measurement(self): + """Return the unit of measurement this sensor expresses itself in.""" + return self._unit_of_measurement + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + device = self._device[ios.ATTR_DEVICE] + device_battery = self._device[ios.ATTR_BATTERY] + return { + "Battery State": device_battery[ios.ATTR_BATTERY_STATE], + "Battery Level": device_battery[ios.ATTR_BATTERY_LEVEL], + "Device Type": device[ios.ATTR_DEVICE_TYPE], + "Device Name": device[ios.ATTR_DEVICE_NAME], + "Device Version": device[ios.ATTR_DEVICE_SYSTEM_VERSION], + } + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + device_battery = self._device[ios.ATTR_BATTERY] + battery_state = device_battery[ios.ATTR_BATTERY_STATE] + battery_level = device_battery[ios.ATTR_BATTERY_LEVEL] + rounded_level = round(battery_level, -1) + returning_icon = DEFAULT_ICON + if battery_state == ios.ATTR_BATTERY_STATE_FULL: + returning_icon = DEFAULT_ICON + elif battery_state == ios.ATTR_BATTERY_STATE_CHARGING: + # Why is MDI missing 10, 50, 70? + if rounded_level in (20, 30, 40, 60, 80, 90, 100): + returning_icon = "{}-charging-{}".format(DEFAULT_ICON, + str(rounded_level)) + else: + returning_icon = "{}-charging".format(DEFAULT_ICON) + elif battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED: + if rounded_level < 10: + returning_icon = "{}-outline".format(DEFAULT_ICON) + elif battery_level == 100: + returning_icon = DEFAULT_ICON + else: + returning_icon = "{}-{}".format(DEFAULT_ICON, + str(rounded_level)) + elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN: + returning_icon = "{}-unknown".format(DEFAULT_ICON) + + return returning_icon + + def update(self): + """Get the latest state of the sensor.""" + self._device = ios.devices().get(self._device_name) + self._state = self._device[ios.ATTR_BATTERY][self.type] diff --git a/requirements_all.txt b/requirements_all.txt index a3af772aeab..01a0acdeebd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -277,7 +277,7 @@ mficlient==0.3.0 miflora==0.1.9 # homeassistant.components.discovery -netdisco==0.7.1 +netdisco==0.7.2 # homeassistant.components.sensor.neurio_energy neurio==0.2.10 From ca6fa1313e376c3d7b6d1890e85608522794ebfe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen <paulus@paulusschoutsen.nl> Date: Fri, 21 Oct 2016 23:30:40 -0700 Subject: [PATCH 141/147] Fix updater, add new fields (#3982) * Fix updater * Add Docker and virtualenv checks * Add log line informing user of update/analytics * Remove str --- homeassistant/components/updater.py | 10 +++++++++- tests/components/test_updater.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index f1467f0e4cb..40899af9803 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -9,6 +9,7 @@ import logging import json import platform import uuid +import os # pylint: disable=no-name-in-module,import-error from distutils.version import StrictVersion @@ -63,6 +64,7 @@ def setup(hass, config): _LOGGER.warning('Updater not supported in development version') return False + config = config.get(DOMAIN, {}) huuid = _load_uuid(hass) if config.get(CONF_REPORTING) else None # Update daily, start 1 hour after startup @@ -91,7 +93,9 @@ def get_newest_version(huuid): info_object = {'uuid': huuid, 'version': CURRENT_VERSION, 'timezone': dt_util.DEFAULT_TIME_ZONE.zone, 'os_name': platform.system(), "arch": platform.machine(), - 'python_version': platform.python_version()} + 'python_version': platform.python_version(), + 'virtualenv': (os.environ.get('VIRTUAL_ENV') is not None), + 'docker': False} if platform.system() == 'Windows': info_object['os_version'] = platform.win32_ver()[0] @@ -102,6 +106,7 @@ def get_newest_version(huuid): linux_dist = distro.linux_distribution(full_distribution_name=False) info_object['distribution'] = linux_dist[0] info_object['os_version'] = linux_dist[1] + info_object['docker'] = os.path.isfile('/.dockerenv') if not huuid: info_object = {} @@ -109,6 +114,9 @@ def get_newest_version(huuid): try: req = requests.post(UPDATER_URL, json=info_object) res = req.json() + _LOGGER.info(('The latest version is %s. ' + 'Information submitted includes %s'), + res['version'], info_object) return (res['version'], res['release-notes']) except requests.RequestException: _LOGGER.exception('Could not contact HASS Update to check for updates') diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index 8333a721beb..7cc2ba8d962 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -5,6 +5,7 @@ from unittest.mock import patch import os import requests +import requests_mock from homeassistant.bootstrap import setup_component from homeassistant.components import updater @@ -111,3 +112,19 @@ class TestUpdater(unittest.TestCase): assert uuid != uuid2 finally: os.remove(path) + + @requests_mock.Mocker() + def test_reporting_false_works(self, m): + """Test we do not send any data.""" + m.post(updater.UPDATER_URL, + json={'version': '0.15', + 'release-notes': 'https://home-assistant.io'}) + + response = updater.get_newest_version(None) + + assert response == ('0.15', 'https://home-assistant.io') + + history = m.request_history + + assert len(history) == 1 + assert history[0].json() == {} From 57777ef79a97ddb7877e03dc59a03b3958352d4c Mon Sep 17 00:00:00 2001 From: Eric Hagan <ehagan@gmail.com> Date: Sat, 22 Oct 2016 04:05:00 -0500 Subject: [PATCH 142/147] Adds support for Pioneer AVR interface port number (#3878) * Adds support for Pioneer AVR interface port number https://community.home-assistant.io/t/support-for-pioneer-avr/503 telnetlib supports a port number so adding port as an optional config element with a default of 23 resolves this. * Adds timeout to Pioneer AVR timeout in telnetlib defaults to socket._GLOBAL_DEFAULT_TIMEOUT which is not a value, but rather a bare Object used for comparison. telnetlib says the following about the timeout optional argument: "The optional timeout parameter specifies a timeout in seconds for blocking operations like the connection attempt (if not specified, the global default timeout setting will be used)." From the documentation for sockets: "Sockets are by default always created in blocking mode" Catching connect and timeout errors, logging to debug and continuing. * Catches timeout exceptions, logs and continues. --- .../components/media_player/pioneer.py | 47 +++++++++++++++---- homeassistant/helpers/config_validation.py | 19 ++++++++ tests/helpers/test_config_validation.py | 20 ++++++++ 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/media_player/pioneer.py b/homeassistant/components/media_player/pioneer.py index 599edf08b37..8930057857d 100644 --- a/homeassistant/components/media_player/pioneer.py +++ b/homeassistant/components/media_player/pioneer.py @@ -13,12 +13,15 @@ from homeassistant.components.media_player import ( SUPPORT_PAUSE, SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( - CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_NAME) + CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_NAME, CONF_PORT, + CONF_TIMEOUT) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Pioneer AVR' +DEFAULT_PORT = 23 # telnet default. Some Pioneer AVRs use 8102 +DEFAULT_TIMEOUT = None SUPPORT_PIONEER = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE @@ -29,12 +32,17 @@ MAX_SOURCE_NUMBERS = 60 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.socket_timeout, }) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Pioneer platform.""" - pioneer = PioneerDevice(config.get(CONF_NAME), config.get(CONF_HOST)) + pioneer = PioneerDevice(config.get(CONF_NAME), + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(CONF_TIMEOUT)) if pioneer.update(): add_devices([pioneer]) @@ -48,10 +56,12 @@ class PioneerDevice(MediaPlayerDevice): # pylint: disable=too-many-public-methods, abstract-method # pylint: disable=too-many-instance-attributes - def __init__(self, name, host): + def __init__(self, name, host, port, timeout): """Initialize the Pioneer device.""" self._name = name self._host = host + self._port = port + self._timeout = timeout self._pwstate = 'PWR1' self._volume = 0 self._muted = False @@ -62,7 +72,11 @@ class PioneerDevice(MediaPlayerDevice): @classmethod def telnet_request(cls, telnet, command, expected_prefix): """Execute `command` and return the response.""" - telnet.write(command.encode("ASCII") + b"\r") + try: + telnet.write(command.encode("ASCII") + b"\r") + except telnetlib.socket.timeout: + _LOGGER.debug("Pioneer command %s timed out", command) + return None # The receiver will randomly send state change updates, make sure # we get the response we are looking for @@ -76,19 +90,32 @@ class PioneerDevice(MediaPlayerDevice): def telnet_command(self, command): """Establish a telnet connection and sends `command`.""" - telnet = telnetlib.Telnet(self._host) - telnet.write(command.encode("ASCII") + b"\r") - telnet.read_very_eager() # skip response - telnet.close() + try: + try: + telnet = telnetlib.Telnet(self._host, + self._port, + self._timeout) + except ConnectionRefusedError: + _LOGGER.debug("Pioneer %s refused connection", self._name) + return + telnet.write(command.encode("ASCII") + b"\r") + telnet.read_very_eager() # skip response + telnet.close() + except telnetlib.socket.timeout: + _LOGGER.debug( + "Pioneer %s command %s timed out", self._name, command) def update(self): """Get the latest details from the device.""" try: - telnet = telnetlib.Telnet(self._host) + telnet = telnetlib.Telnet(self._host, self._port, self._timeout) except ConnectionRefusedError: + _LOGGER.debug("Pioneer %s refused connection", self._name) return False - self._pwstate = self.telnet_request(telnet, "?P", "PWR") + pwstate = self.telnet_request(telnet, "?P", "PWR") + if pwstate: + self._pwstate = pwstate volume_str = self.telnet_request(telnet, "?V", "VOL") self._volume = int(volume_str[3:]) / MAX_VOLUME if volume_str else None diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index dcbbbb4b3db..4c6efe11001 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -3,6 +3,7 @@ from collections import OrderedDict from datetime import timedelta import os from urllib.parse import urlparse +from socket import _GLOBAL_DEFAULT_TIMEOUT from typing import Any, Union, TypeVar, Callable, Sequence, Dict @@ -306,6 +307,24 @@ def time_zone(value): weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)]) +def socket_timeout(value): + """Validate timeout float > 0.0. + + None coerced to socket._GLOBAL_DEFAULT_TIMEOUT bare object. + """ + if value is None: + return _GLOBAL_DEFAULT_TIMEOUT + else: + try: + float_value = float(value) + if float_value > 0.0: + return float_value + raise vol.Invalid('Invalid socket timeout value.' + ' float > 0.0 required.') + except Exception as _: + raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_)) + + # pylint: disable=no-value-for-parameter def url(value: Any) -> str: """Validate an URL.""" diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 287219aa669..3ff9755bba2 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -3,6 +3,7 @@ from collections import OrderedDict from datetime import timedelta import enum import os +from socket import _GLOBAL_DEFAULT_TIMEOUT import pytest import voluptuous as vol @@ -436,3 +437,22 @@ def test_enum(): schema('value3') TestEnum['value1'] + + +def test_socket_timeout(): + """Test socket timeout validator.""" + TEST_CONF_TIMEOUT = 'timeout' + + schema = vol.Schema( + {vol.Required(TEST_CONF_TIMEOUT, default=None): cv.socket_timeout}) + + with pytest.raises(vol.Invalid): + schema({TEST_CONF_TIMEOUT: 0.0}) + + with pytest.raises(vol.Invalid): + schema({TEST_CONF_TIMEOUT: -1}) + + assert _GLOBAL_DEFAULT_TIMEOUT == schema({TEST_CONF_TIMEOUT: + None})[TEST_CONF_TIMEOUT] + + assert 1.0 == schema({TEST_CONF_TIMEOUT: 1})[TEST_CONF_TIMEOUT] From 02afc986685a1b8d00daeb0e1898eef6bf6d5615 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen <turbokongen@hotmail.com> Date: Sat, 22 Oct 2016 14:08:24 +0200 Subject: [PATCH 143/147] Prevent zwave from firing event at shutdown (#3987) --- homeassistant/components/zwave/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 93802487f25..33dfa690632 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -430,7 +430,8 @@ def setup(hass, config): """Stop Z-Wave network.""" _LOGGER.info("Stopping ZWave network.") NETWORK.stop() - hass.bus.fire(const.EVENT_NETWORK_STOP) + if hass.state == 'RUNNING': + hass.bus.fire(const.EVENT_NETWORK_STOP) def rename_node(service): """Rename a node.""" From 0fce5ccc7f7fe2cfed6b1172c0e9fc6bcd02b542 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen <paulus@paulusschoutsen.nl> Date: Sat, 22 Oct 2016 11:24:06 -0700 Subject: [PATCH 144/147] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 2 +- .../frontend/www_static/frontend.html.gz | Bin 128256 -> 128945 bytes .../www_static/home-assistant-polymer | 2 +- .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 2329 -> 2327 bytes 6 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 8ccf0af6145..1c437dedd5d 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -2,7 +2,7 @@ FINGERPRINTS = { "core.js": "5ed5e063d66eb252b5b288738c9c2d16", - "frontend.html": "0b226e89047d24f1af8d070990f6c079", + "frontend.html": "0a4c2c6e86a0a78c2ff3e03842de609d", "mdi.html": "46a76f877ac9848899b8ed382427c16f", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", "panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002", diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 297ada699c1..58990a9c766 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2,4 +2,4 @@ },_distributeDirtyRoots:function(){for(var e,t=this.shadyRoot._dirtyRoots,o=0,i=t.length;o<i&&(e=t[o]);o++)e._distributeContent();this.shadyRoot._dirtyRoots=[]},_finishDistribute:function(){if(this._useContent){if(this.shadyRoot._distributionClean=!0,h.hasInsertionPoint(this.shadyRoot))this._composeTree(),d(this.shadyRoot);else if(this.shadyRoot._hasDistributed){var e=this._composeNode(this);this._updateChildNodes(this,e)}else u.Composed.clearChildNodes(this),this.appendChild(this.shadyRoot);this.shadyRoot._hasDistributed||a(this),this.shadyRoot._hasDistributed=!0}},elementMatches:function(e,t){return t=t||this,h.matchesSelector.call(t,e)},_resetDistribution:function(){for(var e=u.Logical.getChildNodes(this),o=0;o<e.length;o++){var i=e[o];i._destinationInsertionPoints&&(i._destinationInsertionPoints=void 0),n(i)&&t(i)}for(var s=this.shadyRoot,r=s._insertionPoints,d=0;d<r.length;d++)r[d]._distributedNodes=[]},_collectPool:function(){for(var e=[],t=u.Logical.getChildNodes(this),o=0;o<t.length;o++){var i=t[o];n(i)?e.push.apply(e,i._distributedNodes):e.push(i)}return e},_distributePool:function(e,t){for(var i,n=e._insertionPoints,s=0,r=n.length;s<r&&(i=n[s]);s++)this._distributeInsertionPoint(i,t),o(i,this)},_distributeInsertionPoint:function(t,o){for(var i,n=!1,s=0,r=o.length;s<r;s++)i=o[s],i&&this._matchesContentSelect(i,t)&&(e(i,t),o[s]=void 0,n=!0);if(!n)for(var d=u.Logical.getChildNodes(t),a=0;a<d.length;a++)e(d[a],t)},_composeTree:function(){this._updateChildNodes(this,this._composeNode(this));for(var e,t,o=this.shadyRoot._insertionPoints,i=0,n=o.length;i<n&&(e=o[i]);i++)t=u.Logical.getParentNode(e),t._useContent||t===this||t===this.shadyRoot||this._updateChildNodes(t,this._composeNode(t))},_composeNode:function(e){for(var t=[],o=u.Logical.getChildNodes(e.shadyRoot||e),s=0;s<o.length;s++){var r=o[s];if(n(r))for(var d=r._distributedNodes,a=0;a<d.length;a++){var l=d[a];i(r,l)&&t.push(l)}else t.push(r)}return t},_updateChildNodes:function(e,t){for(var o,i=u.Composed.getChildNodes(e),n=Polymer.ArraySplice.calculateSplices(t,i),s=0,r=0;s<n.length&&(o=n[s]);s++){for(var d,a=0;a<o.removed.length&&(d=o.removed[a]);a++)u.Composed.getParentNode(d)===e&&u.Composed.removeChild(e,d),i.splice(o.index+r,1);r-=o.addedCount}for(var o,l,s=0;s<n.length&&(o=n[s]);s++)for(l=i[o.index],a=o.index,d;a<o.index+o.addedCount;a++)d=t[a],u.Composed.insertBefore(e,d,l),i.splice(a,0,d)},_matchesContentSelect:function(e,t){var o=t.getAttribute("select");if(!o)return!0;if(o=o.trim(),!o)return!0;if(!(e instanceof Element))return!1;var i=/^(:not\()?[*.#[a-zA-Z_|]/;return!!i.test(o)&&this.elementMatches(o,e)},_elementAdd:function(){},_elementRemove:function(){}});var c=window.CustomElements&&!CustomElements.useNative}(),Polymer.Settings.useShadow&&Polymer.Base._addFeature({_poolContent:function(){},_beginDistribute:function(){},distributeContent:function(){},_distributeContent:function(){},_finishDistribute:function(){},_createLocalRoot:function(){this.createShadowRoot(),this.shadowRoot.appendChild(this.root),this.root=this.shadowRoot}}),Polymer.Async={_currVal:0,_lastVal:0,_callbacks:[],_twiddleContent:0,_twiddle:document.createTextNode(""),run:function(e,t){return t>0?~setTimeout(e,t):(this._twiddle.textContent=this._twiddleContent++,this._callbacks.push(e),this._currVal++)},cancel:function(e){if(e<0)clearTimeout(~e);else{var t=e-this._lastVal;if(t>=0){if(!this._callbacks[t])throw"invalid async handle: "+e;this._callbacks[t]=null}}},_atEndOfMicrotask:function(){for(var e=this._callbacks.length,t=0;t<e;t++){var o=this._callbacks[t];if(o)try{o()}catch(e){throw t++,this._callbacks.splice(0,t),this._lastVal+=t,this._twiddle.textContent=this._twiddleContent++,e}}this._callbacks.splice(0,e),this._lastVal+=e}},new window.MutationObserver(function(){Polymer.Async._atEndOfMicrotask()}).observe(Polymer.Async._twiddle,{characterData:!0}),Polymer.Debounce=function(){function e(e,t,i){return e?e.stop():e=new o(this),e.go(t,i),e}var t=Polymer.Async,o=function(e){this.context=e;var t=this;this.boundComplete=function(){t.complete()}};return o.prototype={go:function(e,o){var i;this.finish=function(){t.cancel(i)},i=t.run(this.boundComplete,o),this.callback=e},stop:function(){this.finish&&(this.finish(),this.finish=null,this.callback=null)},complete:function(){if(this.finish){var e=this.callback;this.stop(),e.call(this.context)}}},e}(),Polymer.Base._addFeature({_setupDebouncers:function(){this._debouncers={}},debounce:function(e,t,o){return this._debouncers[e]=Polymer.Debounce.call(this,this._debouncers[e],t,o)},isDebouncerActive:function(e){var t=this._debouncers[e];return!(!t||!t.finish)},flushDebouncer:function(e){var t=this._debouncers[e];t&&t.complete()},cancelDebouncer:function(e){var t=this._debouncers[e];t&&t.stop()}}),Polymer.DomModule=document.createElement("dom-module"),Polymer.Base._addFeature({_registerFeatures:function(){this._prepIs(),this._prepBehaviors(),this._prepConstructor(),this._prepTemplate(),this._prepShady(),this._prepPropertyInfo()},_prepBehavior:function(e){this._addHostAttributes(e.hostAttributes)},_initFeatures:function(){this._registerHost(),this._template&&(this._poolContent(),this._beginHosting(),this._stampTemplate(),this._endHosting()),this._marshalHostAttributes(),this._setupDebouncers(),this._marshalBehaviors(),this._tryReady()},_marshalBehavior:function(e){}})</script><script>Polymer.nar=[],Polymer.Annotations={parseAnnotations:function(e){var t=[],n=e._content||e.content;return this._parseNodeAnnotations(n,t,e.hasAttribute("strip-whitespace")),t},_parseNodeAnnotations:function(e,t,n){return e.nodeType===Node.TEXT_NODE?this._parseTextNodeAnnotation(e,t):this._parseElementAnnotations(e,t,n)},_bindingRegex:function(){var e="(?:[a-zA-Z_$][\\w.:$\\-*]*)",t="(?:[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)",n="(?:'(?:[^'\\\\]|\\\\.)*')",r='(?:"(?:[^"\\\\]|\\\\.)*")',s="(?:"+n+"|"+r+")",i="(?:"+e+"|"+t+"|"+s+"\\s*)",o="(?:"+i+"(?:,\\s*"+i+")*)",a="(?:\\(\\s*(?:"+o+"?)\\)\\s*)",l="("+e+"\\s*"+a+"?)",c="(\\[\\[|{{)\\s*",h="(?:]]|}})",u="(?:(!)\\s*)?",f=c+u+l+h;return new RegExp(f,"g")}(),_parseBindings:function(e){for(var t,n=this._bindingRegex,r=[],s=0;null!==(t=n.exec(e));){t.index>s&&r.push({literal:e.slice(s,t.index)});var i,o,a,l=t[1][0],c=Boolean(t[2]),h=t[3].trim();"{"==l&&(a=h.indexOf("::"))>0&&(o=h.substring(a+2),h=h.substring(0,a),i=!0),r.push({compoundIndex:r.length,value:h,mode:l,negate:c,event:o,customEvent:i}),s=n.lastIndex}if(s&&s<e.length){var u=e.substring(s);u&&r.push({literal:u})}if(r.length)return r},_literalFromParts:function(e){for(var t="",n=0;n<e.length;n++){var r=e[n].literal;t+=r||""}return t},_parseTextNodeAnnotation:function(e,t){var n=this._parseBindings(e.textContent);if(n){e.textContent=this._literalFromParts(n)||" ";var r={bindings:[{kind:"text",name:"textContent",parts:n,isCompound:1!==n.length}]};return t.push(r),r}},_parseElementAnnotations:function(e,t,n){var r={bindings:[],events:[]};return"content"===e.localName&&(t._hasContent=!0),this._parseChildNodesAnnotations(e,r,t,n),e.attributes&&(this._parseNodeAttributeAnnotations(e,r,t),this.prepElement&&this.prepElement(e)),(r.bindings.length||r.events.length||r.id)&&t.push(r),r},_parseChildNodesAnnotations:function(e,t,n,r){if(e.firstChild)for(var s=e.firstChild,i=0;s;){var o=s.nextSibling;if("template"!==s.localName||s.hasAttribute("preserve-content")||this._parseTemplate(s,i,n,t),"slot"==s.localName&&(s=this._replaceSlotWithContent(s)),s.nodeType===Node.TEXT_NODE){for(var a=o;a&&a.nodeType===Node.TEXT_NODE;)s.textContent+=a.textContent,o=a.nextSibling,e.removeChild(a),a=o;r&&!s.textContent.trim()&&(e.removeChild(s),i--)}if(s.parentNode){var l=this._parseNodeAnnotations(s,n,r);l&&(l.parent=t,l.index=i)}s=o,i++}},_replaceSlotWithContent:function(e){for(var t=e.ownerDocument.createElement("content");e.firstChild;)t.appendChild(e.firstChild);for(var n=e.attributes,r=0;r<n.length;r++){var s=n[r];t.setAttribute(s.name,s.value)}var i=e.getAttribute("name"),o=i?"[slot='"+i+"']":":not([slot])";return t.setAttribute("select",o),e.parentNode.replaceChild(t,e),t},_parseTemplate:function(e,t,n,r){var s=document.createDocumentFragment();s._notes=this.parseAnnotations(e),s.appendChild(e.content),n.push({bindings:Polymer.nar,events:Polymer.nar,templateContent:s,parent:r,index:t})},_parseNodeAttributeAnnotations:function(e,t){for(var n,r=Array.prototype.slice.call(e.attributes),s=r.length-1;n=r[s];s--){var i,o=n.name,a=n.value;"on-"===o.slice(0,3)?(e.removeAttribute(o),t.events.push({name:o.slice(3),value:a})):(i=this._parseNodeAttributeAnnotation(e,o,a))?t.bindings.push(i):"id"===o&&(t.id=a)}},_parseNodeAttributeAnnotation:function(e,t,n){var r=this._parseBindings(n);if(r){var s=t,i="property";"$"==t[t.length-1]&&(t=t.slice(0,-1),i="attribute");var o=this._literalFromParts(r);o&&"attribute"==i&&e.setAttribute(t,o),"input"===e.localName&&"value"===s&&e.setAttribute(s,""),e.removeAttribute(s);var a=Polymer.CaseMap.dashToCamelCase(t);return"property"===i&&(t=a),{kind:i,name:t,propertyName:a,parts:r,literal:o,isCompound:1!==r.length}}},findAnnotatedNode:function(e,t){var n=t.parent&&Polymer.Annotations.findAnnotatedNode(e,t.parent);if(!n)return e;for(var r=n.firstChild,s=0;r;r=r.nextSibling)if(t.index===s++)return r}},function(){function e(e,t){return e.replace(a,function(e,r,s,i){return r+"'"+n(s.replace(/["']/g,""),t)+"'"+i})}function t(t,r){for(var s in l)for(var i,o,a,c=l[s],u=0,f=c.length;u<f&&(i=c[u]);u++)"*"!==s&&t.localName!==s||(o=t.attributes[i],a=o&&o.value,a&&a.search(h)<0&&(o.value="style"===i?e(a,r):n(a,r)))}function n(e,t){if(e&&c.test(e))return e;var n=s(t);return n.href=e,n.href||e}function r(e,t){return i||(i=document.implementation.createHTMLDocument("temp"),o=i.createElement("base"),i.head.appendChild(o)),o.href=t,n(e,i)}function s(e){return e.__urlResolver||(e.__urlResolver=e.createElement("a"))}var i,o,a=/(url\()([^)]*)(\))/g,l={"*":["href","src","style","url"],form:["action"]},c=/(^\/)|(^#)|(^[\w-\d]*:)/,h=/\{\{|\[\[/;Polymer.ResolveUrl={resolveCss:e,resolveAttrs:t,resolveUrl:r}}(),Polymer.Path={root:function(e){var t=e.indexOf(".");return t===-1?e:e.slice(0,t)},isDeep:function(e){return e.indexOf(".")!==-1},isAncestor:function(e,t){return 0===e.indexOf(t+".")},isDescendant:function(e,t){return 0===t.indexOf(e+".")},translate:function(e,t,n){return t+n.slice(e.length)},matches:function(e,t,n){return e===n||this.isAncestor(e,n)||Boolean(t)&&this.isDescendant(e,n)}},Polymer.Base._addFeature({_prepAnnotations:function(){if(this._template){var e=this;Polymer.Annotations.prepElement=function(t){e._prepElement(t)},this._template._content&&this._template._content._notes?this._notes=this._template._content._notes:(this._notes=Polymer.Annotations.parseAnnotations(this._template),this._processAnnotations(this._notes)),Polymer.Annotations.prepElement=null}else this._notes=[]},_processAnnotations:function(e){for(var t=0;t<e.length;t++){for(var n=e[t],r=0;r<n.bindings.length;r++)for(var s=n.bindings[r],i=0;i<s.parts.length;i++){var o=s.parts[i];if(!o.literal){var a=this._parseMethod(o.value);a?o.signature=a:o.model=Polymer.Path.root(o.value)}}if(n.templateContent){this._processAnnotations(n.templateContent._notes);var l=n.templateContent._parentProps=this._discoverTemplateParentProps(n.templateContent._notes),c=[];for(var h in l){var u="_parent_"+h;c.push({index:n.index,kind:"property",name:u,propertyName:u,parts:[{mode:"{",model:h,value:h}]})}n.bindings=n.bindings.concat(c)}}},_discoverTemplateParentProps:function(e){for(var t,n={},r=0;r<e.length&&(t=e[r]);r++){for(var s,i=0,o=t.bindings;i<o.length&&(s=o[i]);i++)for(var a,l=0,c=s.parts;l<c.length&&(a=c[l]);l++)if(a.signature){for(var h=a.signature.args,u=0;u<h.length;u++){var f=h[u].model;f&&(n[f]=!0)}a.signature.dynamicFn&&(n[a.signature.method]=!0)}else a.model&&(n[a.model]=!0);if(t.templateContent){var p=t.templateContent._parentProps;Polymer.Base.mixin(n,p)}}return n},_prepElement:function(e){Polymer.ResolveUrl.resolveAttrs(e,this._template.ownerDocument)},_findAnnotatedNode:Polymer.Annotations.findAnnotatedNode,_marshalAnnotationReferences:function(){this._template&&(this._marshalIdNodes(),this._marshalAnnotatedNodes(),this._marshalAnnotatedListeners())},_configureAnnotationReferences:function(){for(var e=this._notes,t=this._nodes,n=0;n<e.length;n++){var r=e[n],s=t[n];this._configureTemplateContent(r,s),this._configureCompoundBindings(r,s)}},_configureTemplateContent:function(e,t){e.templateContent&&(t._content=e.templateContent)},_configureCompoundBindings:function(e,t){for(var n=e.bindings,r=0;r<n.length;r++){var s=n[r];if(s.isCompound){for(var i=t.__compoundStorage__||(t.__compoundStorage__={}),o=s.parts,a=new Array(o.length),l=0;l<o.length;l++)a[l]=o[l].literal;var c=s.name;i[c]=a,s.literal&&"property"==s.kind&&(t._configValue?t._configValue(c,s.literal):t[c]=s.literal)}}},_marshalIdNodes:function(){this.$={};for(var e,t=0,n=this._notes.length;t<n&&(e=this._notes[t]);t++)e.id&&(this.$[e.id]=this._findAnnotatedNode(this.root,e))},_marshalAnnotatedNodes:function(){if(this._notes&&this._notes.length){for(var e=new Array(this._notes.length),t=0;t<this._notes.length;t++)e[t]=this._findAnnotatedNode(this.root,this._notes[t]);this._nodes=e}},_marshalAnnotatedListeners:function(){for(var e,t=0,n=this._notes.length;t<n&&(e=this._notes[t]);t++)if(e.events&&e.events.length)for(var r,s=this._findAnnotatedNode(this.root,e),i=0,o=e.events;i<o.length&&(r=o[i]);i++)this.listen(s,r.name,r.value)}}),Polymer.Base._addFeature({listeners:{},_listenListeners:function(e){var t,n,r;for(r in e)r.indexOf(".")<0?(t=this,n=r):(n=r.split("."),t=this.$[n[0]],n=n[1]),this.listen(t,n,e[r])},listen:function(e,t,n){var r=this._recallEventHandler(this,t,e,n);r||(r=this._createEventHandler(e,t,n)),r._listening||(this._listen(e,t,r),r._listening=!0)},_boundListenerKey:function(e,t){return e+":"+t},_recordEventHandler:function(e,t,n,r,s){var i=e.__boundListeners;i||(i=e.__boundListeners=new WeakMap);var o=i.get(n);o||(o={},i.set(n,o));var a=this._boundListenerKey(t,r);o[a]=s},_recallEventHandler:function(e,t,n,r){var s=e.__boundListeners;if(s){var i=s.get(n);if(i){var o=this._boundListenerKey(t,r);return i[o]}}},_createEventHandler:function(e,t,n){var r=this,s=function(e){r[n]?r[n](e,e.detail):r._warn(r._logf("_createEventHandler","listener method `"+n+"` not defined"))};return s._listening=!1,this._recordEventHandler(r,t,e,n,s),s},unlisten:function(e,t,n){var r=this._recallEventHandler(this,t,e,n);r&&(this._unlisten(e,t,r),r._listening=!1)},_listen:function(e,t,n){e.addEventListener(t,n)},_unlisten:function(e,t,n){e.removeEventListener(t,n)}}),function(){"use strict";function e(e){for(var t,n=g?["click"]:m,r=0;r<n.length;r++)t=n[r],e?document.addEventListener(t,P,!0):document.removeEventListener(t,P,!0)}function t(){S.mouse.mouseIgnoreJob||e(!0);var t=function(){e(),S.mouse.target=null,S.mouse.mouseIgnoreJob=null};S.mouse.mouseIgnoreJob=Polymer.Debounce(S.mouse.mouseIgnoreJob,t,d)}function n(e){var t=e.type;if(m.indexOf(t)===-1)return!1;if("mousemove"===t){var n=void 0===e.buttons?1:e.buttons;return e instanceof window.MouseEvent&&!v&&(n=y[e.which]||0),Boolean(1&n)}var r=void 0===e.button?0:e.button;return 0===r}function r(e){if("click"===e.type){if(0===e.detail)return!0;var t=C.findOriginalTarget(e),n=t.getBoundingClientRect(),r=e.pageX,s=e.pageY;return!(r>=n.left&&r<=n.right&&s>=n.top&&s<=n.bottom)}return!1}function s(e){for(var t,n=Polymer.dom(e).path,r="auto",s=0;s<n.length;s++)if(t=n[s],t[u]){r=t[u];break}return r}function i(e,t,n){e.movefn=t,e.upfn=n,document.addEventListener("mousemove",t),document.addEventListener("mouseup",n)}function o(e){document.removeEventListener("mousemove",e.movefn),document.removeEventListener("mouseup",e.upfn),e.movefn=null,e.upfn=null}var a=Polymer.DomApi.wrap,l="string"==typeof document.head.style.touchAction,c="__polymerGestures",h="__polymerGesturesHandled",u="__polymerGesturesTouchAction",f=25,p=5,_=2,d=2500,m=["mousedown","mousemove","mouseup","click"],y=[0,1,4,2],v=function(){try{return 1===new MouseEvent("test",{buttons:1}).buttons}catch(e){return!1}}(),g=navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/),P=function(e){var t=e.sourceCapabilities;if((!t||t.firesTouchEvents)&&(e[h]={skip:!0},"click"===e.type)){for(var n=Polymer.dom(e).path,r=0;r<n.length;r++)if(n[r]===S.mouse.target)return;e.preventDefault(),e.stopPropagation()}},S={mouse:{target:null,mouseIgnoreJob:null},touch:{x:0,y:0,id:-1,scrollDecided:!1}},C={gestures:{},recognizers:[],deepTargetFind:function(e,t){for(var n=document.elementFromPoint(e,t),r=n;r&&r.shadowRoot;)r=r.shadowRoot.elementFromPoint(e,t),r&&(n=r);return n},findOriginalTarget:function(e){return e.path?e.path[0]:e.target},handleNative:function(e){var n,r=e.type,s=a(e.currentTarget),i=s[c];if(i){var o=i[r];if(o){if(!e[h]&&(e[h]={},"touch"===r.slice(0,5))){var u=e.changedTouches[0];if("touchstart"===r&&1===e.touches.length&&(S.touch.id=u.identifier),S.touch.id!==u.identifier)return;l||"touchstart"!==r&&"touchmove"!==r||C.handleTouchAction(e),"touchend"===r&&(S.mouse.target=Polymer.dom(e).rootTarget,t())}if(n=e[h],!n.skip){for(var f,p=C.recognizers,_=0;_<p.length;_++)f=p[_],o[f.name]&&!n[f.name]&&f.flow&&f.flow.start.indexOf(e.type)>-1&&f.reset&&f.reset();for(_=0,f;_<p.length;_++)f=p[_],o[f.name]&&!n[f.name]&&(n[f.name]=!0,f[r](e))}}}},handleTouchAction:function(e){var t=e.changedTouches[0],n=e.type;if("touchstart"===n)S.touch.x=t.clientX,S.touch.y=t.clientY,S.touch.scrollDecided=!1;else if("touchmove"===n){if(S.touch.scrollDecided)return;S.touch.scrollDecided=!0;var r=s(e),i=!1,o=Math.abs(S.touch.x-t.clientX),a=Math.abs(S.touch.y-t.clientY);e.cancelable&&("none"===r?i=!0:"pan-x"===r?i=a>o:"pan-y"===r&&(i=o>a)),i?e.preventDefault():C.prevent("track")}},add:function(e,t,n){e=a(e);var r=this.gestures[t],s=r.deps,i=r.name,o=e[c];o||(e[c]=o={});for(var l,h,u=0;u<s.length;u++)l=s[u],g&&m.indexOf(l)>-1&&"click"!==l||(h=o[l],h||(o[l]=h={_count:0}),0===h._count&&e.addEventListener(l,this.handleNative),h[i]=(h[i]||0)+1,h._count=(h._count||0)+1);e.addEventListener(t,n),r.touchAction&&this.setTouchAction(e,r.touchAction)},remove:function(e,t,n){e=a(e);var r=this.gestures[t],s=r.deps,i=r.name,o=e[c];if(o)for(var l,h,u=0;u<s.length;u++)l=s[u],h=o[l],h&&h[i]&&(h[i]=(h[i]||1)-1,h._count=(h._count||1)-1,0===h._count&&e.removeEventListener(l,this.handleNative));e.removeEventListener(t,n)},register:function(e){this.recognizers.push(e);for(var t=0;t<e.emits.length;t++)this.gestures[e.emits[t]]=e},findRecognizerByEvent:function(e){for(var t,n=0;n<this.recognizers.length;n++){t=this.recognizers[n];for(var r,s=0;s<t.emits.length;s++)if(r=t.emits[s],r===e)return t}return null},setTouchAction:function(e,t){l&&(e.style.touchAction=t),e[u]=t},fire:function(e,t,n){var r=Polymer.Base.fire(t,n,{node:e,bubbles:!0,cancelable:!0});if(r.defaultPrevented){var s=n.preventer||n.sourceEvent;s&&s.preventDefault&&s.preventDefault()}},prevent:function(e){var t=this.findRecognizerByEvent(e);t.info&&(t.info.prevent=!0)},resetMouseCanceller:function(){S.mouse.mouseIgnoreJob&&S.mouse.mouseIgnoreJob.complete()}};C.register({name:"downup",deps:["mousedown","touchstart","touchend"],flow:{start:["mousedown","touchstart"],end:["mouseup","touchend"]},emits:["down","up"],info:{movefn:null,upfn:null},reset:function(){o(this.info)},mousedown:function(e){if(n(e)){var t=C.findOriginalTarget(e),r=this,s=function(e){n(e)||(r.fire("up",t,e),o(r.info))},a=function(e){n(e)&&r.fire("up",t,e),o(r.info)};i(this.info,s,a),this.fire("down",t,e)}},touchstart:function(e){this.fire("down",C.findOriginalTarget(e),e.changedTouches[0],e)},touchend:function(e){this.fire("up",C.findOriginalTarget(e),e.changedTouches[0],e)},fire:function(e,t,n,r){C.fire(t,e,{x:n.clientX,y:n.clientY,sourceEvent:n,preventer:r,prevent:function(e){return C.prevent(e)}})}}),C.register({name:"track",touchAction:"none",deps:["mousedown","touchstart","touchmove","touchend"],flow:{start:["mousedown","touchstart"],end:["mouseup","touchend"]},emits:["track"],info:{x:0,y:0,state:"start",started:!1,moves:[],addMove:function(e){this.moves.length>_&&this.moves.shift(),this.moves.push(e)},movefn:null,upfn:null,prevent:!1},reset:function(){this.info.state="start",this.info.started=!1,this.info.moves=[],this.info.x=0,this.info.y=0,this.info.prevent=!1,o(this.info)},hasMovedEnough:function(e,t){if(this.info.prevent)return!1;if(this.info.started)return!0;var n=Math.abs(this.info.x-e),r=Math.abs(this.info.y-t);return n>=p||r>=p},mousedown:function(e){if(n(e)){var t=C.findOriginalTarget(e),r=this,s=function(e){var s=e.clientX,i=e.clientY;r.hasMovedEnough(s,i)&&(r.info.state=r.info.started?"mouseup"===e.type?"end":"track":"start","start"===r.info.state&&C.prevent("tap"),r.info.addMove({x:s,y:i}),n(e)||(r.info.state="end",o(r.info)),r.fire(t,e),r.info.started=!0)},a=function(e){r.info.started&&s(e),o(r.info)};i(this.info,s,a),this.info.x=e.clientX,this.info.y=e.clientY}},touchstart:function(e){var t=e.changedTouches[0];this.info.x=t.clientX,this.info.y=t.clientY},touchmove:function(e){var t=C.findOriginalTarget(e),n=e.changedTouches[0],r=n.clientX,s=n.clientY;this.hasMovedEnough(r,s)&&("start"===this.info.state&&C.prevent("tap"),this.info.addMove({x:r,y:s}),this.fire(t,n),this.info.state="track",this.info.started=!0)},touchend:function(e){var t=C.findOriginalTarget(e),n=e.changedTouches[0];this.info.started&&(this.info.state="end",this.info.addMove({x:n.clientX,y:n.clientY}),this.fire(t,n,e))},fire:function(e,t,n){var r,s=this.info.moves[this.info.moves.length-2],i=this.info.moves[this.info.moves.length-1],o=i.x-this.info.x,a=i.y-this.info.y,l=0;return s&&(r=i.x-s.x,l=i.y-s.y),C.fire(e,"track",{state:this.info.state,x:t.clientX,y:t.clientY,dx:o,dy:a,ddx:r,ddy:l,sourceEvent:t,preventer:n,hover:function(){return C.deepTargetFind(t.clientX,t.clientY)}})}}),C.register({name:"tap",deps:["mousedown","click","touchstart","touchend"],flow:{start:["mousedown","touchstart"],end:["click","touchend"]},emits:["tap"],info:{x:NaN,y:NaN,prevent:!1},reset:function(){this.info.x=NaN,this.info.y=NaN,this.info.prevent=!1},save:function(e){this.info.x=e.clientX,this.info.y=e.clientY},mousedown:function(e){n(e)&&this.save(e)},click:function(e){n(e)&&this.forward(e)},touchstart:function(e){this.save(e.changedTouches[0],e)},touchend:function(e){this.forward(e.changedTouches[0],e)},forward:function(e,t){var n=Math.abs(e.clientX-this.info.x),s=Math.abs(e.clientY-this.info.y),i=C.findOriginalTarget(e);(isNaN(n)||isNaN(s)||n<=f&&s<=f||r(e))&&(this.info.prevent||C.fire(i,"tap",{x:e.clientX,y:e.clientY,sourceEvent:e,preventer:t}))}});var E={x:"pan-x",y:"pan-y",none:"none",all:"auto"};Polymer.Base._addFeature({_setupGestures:function(){this.__polymerGestures=null},_listen:function(e,t,n){C.gestures[t]?C.add(e,t,n):e.addEventListener(t,n)},_unlisten:function(e,t,n){C.gestures[t]?C.remove(e,t,n):e.removeEventListener(t,n)},setScrollDirection:function(e,t){t=t||this,C.setTouchAction(t,E[e]||"auto")}}),Polymer.Gestures=C}(),function(){"use strict";if(Polymer.Base._addFeature({$$:function(e){return Polymer.dom(this.root).querySelector(e)},toggleClass:function(e,t,n){n=n||this,1==arguments.length&&(t=!n.classList.contains(e)),t?Polymer.dom(n).classList.add(e):Polymer.dom(n).classList.remove(e)},toggleAttribute:function(e,t,n){n=n||this,1==arguments.length&&(t=!n.hasAttribute(e)),t?Polymer.dom(n).setAttribute(e,""):Polymer.dom(n).removeAttribute(e)},classFollows:function(e,t,n){n&&Polymer.dom(n).classList.remove(e),t&&Polymer.dom(t).classList.add(e)},attributeFollows:function(e,t,n){n&&Polymer.dom(n).removeAttribute(e),t&&Polymer.dom(t).setAttribute(e,"")},getEffectiveChildNodes:function(){return Polymer.dom(this).getEffectiveChildNodes()},getEffectiveChildren:function(){var e=Polymer.dom(this).getEffectiveChildNodes();return e.filter(function(e){return e.nodeType===Node.ELEMENT_NODE})},getEffectiveTextContent:function(){for(var e,t=this.getEffectiveChildNodes(),n=[],r=0;e=t[r];r++)e.nodeType!==Node.COMMENT_NODE&&n.push(Polymer.dom(e).textContent);return n.join("")},queryEffectiveChildren:function(e){var t=Polymer.dom(this).queryDistributedElements(e);return t&&t[0]},queryAllEffectiveChildren:function(e){return Polymer.dom(this).queryDistributedElements(e)},getContentChildNodes:function(e){var t=Polymer.dom(this.root).querySelector(e||"content");return t?Polymer.dom(t).getDistributedNodes():[]},getContentChildren:function(e){return this.getContentChildNodes(e).filter(function(e){return e.nodeType===Node.ELEMENT_NODE})},fire:function(e,t,n){n=n||Polymer.nob;var r=n.node||this;t=null===t||void 0===t?{}:t;var s=void 0===n.bubbles||n.bubbles,i=Boolean(n.cancelable),o=n._useCache,a=this._getEvent(e,s,i,o);return a.detail=t,o&&(this.__eventCache[e]=null),r.dispatchEvent(a),o&&(this.__eventCache[e]=a),a},__eventCache:{},_getEvent:function(e,t,n,r){var s=r&&this.__eventCache[e];return s&&s.bubbles==t&&s.cancelable==n||(s=new Event(e,{bubbles:Boolean(t),cancelable:n})),s},async:function(e,t){var n=this;return Polymer.Async.run(function(){e.call(n)},t)},cancelAsync:function(e){Polymer.Async.cancel(e)},arrayDelete:function(e,t){var n;if(Array.isArray(e)){if(n=e.indexOf(t),n>=0)return e.splice(n,1)}else{var r=this._get(e);if(n=r.indexOf(t),n>=0)return this.splice(e,n,1)}},transform:function(e,t){t=t||this,t.style.webkitTransform=e,t.style.transform=e},translate3d:function(e,t,n,r){r=r||this,this.transform("translate3d("+e+","+t+","+n+")",r)},importHref:function(e,t,n,r){var s=document.createElement("link");s.rel="import",s.href=e;var i=Polymer.Base.importHref.imported=Polymer.Base.importHref.imported||{},o=i[s.href],a=o||s,l=this;if(t){var c=function(e){return e.target.__firedLoad=!0,e.target.removeEventListener("load",c),t.call(l,e)};a.addEventListener("load",c)}if(n){var h=function(e){return e.target.__firedError=!0,e.target.removeEventListener("error",h),n.call(l,e)};a.addEventListener("error",h)}return o?(o.__firedLoad&&o.dispatchEvent(new Event("load")),o.__firedError&&o.dispatchEvent(new Event("error"))):(i[s.href]=s,r=Boolean(r),r&&s.setAttribute("async",""),document.head.appendChild(s)),a},create:function(e,t){var n=document.createElement(e);if(t)for(var r in t)n[r]=t[r];return n},isLightDescendant:function(e){return this!==e&&this.contains(e)&&Polymer.dom(this).getOwnerRoot()===Polymer.dom(e).getOwnerRoot()},isLocalDescendant:function(e){return this.root===Polymer.dom(e).getOwnerRoot()}}),!Polymer.Settings.useNativeCustomElements){var e=Polymer.Base.importHref;Polymer.Base.importHref=function(t,n,r,s){CustomElements.ready=!1;var i=function(e){if(CustomElements.upgradeDocumentTree(document),CustomElements.ready=!0,n)return n.call(this,e)};return e.call(this,t,i,r,s)}}}(),Polymer.Bind={prepareModel:function(e){Polymer.Base.mixin(e,this._modelApi)},_modelApi:{_notifyChange:function(e,t,n){n=void 0===n?this[e]:n,t=t||Polymer.CaseMap.camelToDashCase(e)+"-changed",this.fire(t,{value:n},{bubbles:!1,cancelable:!1,_useCache:!0})},_propertySetter:function(e,t,n,r){var s=this.__data__[e];return s===t||s!==s&&t!==t||(this.__data__[e]=t,"object"==typeof t&&this._clearPath(e),this._propertyChanged&&this._propertyChanged(e,t,s),n&&this._effectEffects(e,t,n,s,r)),s},__setProperty:function(e,t,n,r){r=r||this;var s=r._propertyEffects&&r._propertyEffects[e];s?r._propertySetter(e,t,s,n):r[e]!==t&&(r[e]=t)},_effectEffects:function(e,t,n,r,s){for(var i,o=0,a=n.length;o<a&&(i=n[o]);o++)i.fn.call(this,e,this[e],i.effect,r,s)},_clearPath:function(e){for(var t in this.__data__)Polymer.Path.isDescendant(e,t)&&(this.__data__[t]=void 0)}},ensurePropertyEffects:function(e,t){e._propertyEffects||(e._propertyEffects={});var n=e._propertyEffects[t];return n||(n=e._propertyEffects[t]=[]),n},addPropertyEffect:function(e,t,n,r){var s=this.ensurePropertyEffects(e,t),i={kind:n,effect:r,fn:Polymer.Bind["_"+n+"Effect"]};return s.push(i),i},createBindings:function(e){var t=e._propertyEffects;if(t)for(var n in t){var r=t[n];r.sort(this._sortPropertyEffects),this._createAccessors(e,n,r)}},_sortPropertyEffects:function(){var e={compute:0,annotation:1,annotatedComputation:2,reflect:3,notify:4,observer:5,complexObserver:6,function:7};return function(t,n){return e[t.kind]-e[n.kind]}}(),_createAccessors:function(e,t,n){var r={get:function(){return this.__data__[t]}},s=function(e){this._propertySetter(t,e,n)},i=e.getPropertyInfo&&e.getPropertyInfo(t);i&&i.readOnly?i.computed||(e["_set"+this.upper(t)]=s):r.set=s,Object.defineProperty(e,t,r)},upper:function(e){return e[0].toUpperCase()+e.substring(1)},_addAnnotatedListener:function(e,t,n,r,s,i){e._bindListeners||(e._bindListeners=[]);var o=this._notedListenerFactory(n,r,Polymer.Path.isDeep(r),i),a=s||Polymer.CaseMap.camelToDashCase(n)+"-changed";e._bindListeners.push({index:t,property:n,path:r,changedFn:o,event:a})},_isEventBogus:function(e,t){return e.path&&e.path[0]!==t},_notedListenerFactory:function(e,t,n,r){return function(s,i,o){if(o){var a=Polymer.Path.translate(e,t,o);this._notifyPath(a,i)}else i=s[e],r&&(i=!i),n?this.__data__[t]!=i&&this.set(t,i):this[t]=i}},prepareInstance:function(e){e.__data__=Object.create(null)},setupBindListeners:function(e){for(var t,n=e._bindListeners,r=0,s=n.length;r<s&&(t=n[r]);r++){var i=e._nodes[t.index];this._addNotifyListener(i,e,t.event,t.changedFn)}},_addNotifyListener:function(e,t,n,r){e.addEventListener(n,function(e){return t._notifyListener(r,e)})}},Polymer.Base.extend(Polymer.Bind,{_shouldAddListener:function(e){return e.name&&"attribute"!=e.kind&&"text"!=e.kind&&!e.isCompound&&"{"===e.parts[0].mode},_annotationEffect:function(e,t,n){e!=n.value&&(t=this._get(n.value),this.__data__[n.value]=t),this._applyEffectValue(n,t)},_reflectEffect:function(e,t,n){this.reflectPropertyToAttribute(e,n.attribute,t)},_notifyEffect:function(e,t,n,r,s){s||this._notifyChange(e,n.event,t)},_functionEffect:function(e,t,n,r,s){n.call(this,e,t,r,s)},_observerEffect:function(e,t,n,r){var s=this[n.method];s?s.call(this,t,r):this._warn(this._logf("_observerEffect","observer method `"+n.method+"` not defined"))},_complexObserverEffect:function(e,t,n){var r=this[n.method];if(r){var s=Polymer.Bind._marshalArgs(this.__data__,n,e,t);s&&r.apply(this,s)}else n.dynamicFn||this._warn(this._logf("_complexObserverEffect","observer method `"+n.method+"` not defined"))},_computeEffect:function(e,t,n){var r=this[n.method];if(r){var s=Polymer.Bind._marshalArgs(this.__data__,n,e,t);if(s){var i=r.apply(this,s);this.__setProperty(n.name,i)}}else n.dynamicFn||this._warn(this._logf("_computeEffect","compute method `"+n.method+"` not defined"))},_annotatedComputationEffect:function(e,t,n){var r=this._rootDataHost||this,s=r[n.method];if(s){var i=Polymer.Bind._marshalArgs(this.__data__,n,e,t);if(i){var o=s.apply(r,i);this._applyEffectValue(n,o)}}else n.dynamicFn||r._warn(r._logf("_annotatedComputationEffect","compute method `"+n.method+"` not defined"))},_marshalArgs:function(e,t,n,r){for(var s=[],i=t.args,o=i.length>1||t.dynamicFn,a=0,l=i.length;a<l;a++){var c,h=i[a],u=h.name;if(h.literal?c=h.value:n===u?c=r:(c=e[u],void 0===c&&h.structured&&(c=Polymer.Base._get(u,e))),o&&void 0===c)return;if(h.wildcard){var f=Polymer.Path.isAncestor(n,u);s[a]={path:f?n:u,value:f?r:c,base:c}}else s[a]=c}return s}}),Polymer.Base._addFeature({_addPropertyEffect:function(e,t,n){var r=Polymer.Bind.addPropertyEffect(this,e,t,n);r.pathFn=this["_"+r.kind+"PathEffect"]},_prepEffects:function(){Polymer.Bind.prepareModel(this),this._addAnnotationEffects(this._notes)},_prepBindings:function(){Polymer.Bind.createBindings(this)},_addPropertyEffects:function(e){if(e)for(var t in e){var n=e[t];if(n.observer&&this._addObserverEffect(t,n.observer),n.computed&&(n.readOnly=!0,this._addComputedEffect(t,n.computed)),n.notify&&this._addPropertyEffect(t,"notify",{event:Polymer.CaseMap.camelToDashCase(t)+"-changed"}),n.reflectToAttribute){var r=Polymer.CaseMap.camelToDashCase(t);"-"===r[0]?this._warn(this._logf("_addPropertyEffects","Property "+t+" cannot be reflected to attribute "+r+' because "-" is not a valid starting attribute name. Use a lowercase first letter for the property instead.')):this._addPropertyEffect(t,"reflect",{attribute:r})}n.readOnly&&Polymer.Bind.ensurePropertyEffects(this,t)}},_addComputedEffect:function(e,t){for(var n,r=this._parseMethod(t),s=r.dynamicFn,i=0;i<r.args.length&&(n=r.args[i]);i++)this._addPropertyEffect(n.model,"compute",{method:r.method,args:r.args,trigger:n,name:e,dynamicFn:s});s&&this._addPropertyEffect(r.method,"compute",{method:r.method,args:r.args,trigger:null,name:e,dynamicFn:s})},_addObserverEffect:function(e,t){this._addPropertyEffect(e,"observer",{method:t,property:e})},_addComplexObserverEffects:function(e){if(e)for(var t,n=0;n<e.length&&(t=e[n]);n++)this._addComplexObserverEffect(t)},_addComplexObserverEffect:function(e){var t=this._parseMethod(e);if(!t)throw new Error("Malformed observer expression '"+e+"'");for(var n,r=t.dynamicFn,s=0;s<t.args.length&&(n=t.args[s]);s++)this._addPropertyEffect(n.model,"complexObserver",{method:t.method,args:t.args,trigger:n,dynamicFn:r});r&&this._addPropertyEffect(t.method,"complexObserver",{method:t.method,args:t.args,trigger:null,dynamicFn:r})},_addAnnotationEffects:function(e){for(var t,n=0;n<e.length&&(t=e[n]);n++)for(var r,s=t.bindings,i=0;i<s.length&&(r=s[i]);i++)this._addAnnotationEffect(r,n)},_addAnnotationEffect:function(e,t){Polymer.Bind._shouldAddListener(e)&&Polymer.Bind._addAnnotatedListener(this,t,e.name,e.parts[0].value,e.parts[0].event,e.parts[0].negate);for(var n=0;n<e.parts.length;n++){var r=e.parts[n];r.signature?this._addAnnotatedComputationEffect(e,r,t):r.literal||("attribute"===e.kind&&"-"===e.name[0]?this._warn(this._logf("_addAnnotationEffect","Cannot set attribute "+e.name+' because "-" is not a valid attribute starting character')):this._addPropertyEffect(r.model,"annotation",{kind:e.kind,index:t,name:e.name,propertyName:e.propertyName,value:r.value,isCompound:e.isCompound,compoundIndex:r.compoundIndex,event:r.event,customEvent:r.customEvent,negate:r.negate}))}},_addAnnotatedComputationEffect:function(e,t,n){var r=t.signature;if(r.static)this.__addAnnotatedComputationEffect("__static__",n,e,t,null);else{for(var s,i=0;i<r.args.length&&(s=r.args[i]);i++)s.literal||this.__addAnnotatedComputationEffect(s.model,n,e,t,s);r.dynamicFn&&this.__addAnnotatedComputationEffect(r.method,n,e,t,null)}},__addAnnotatedComputationEffect:function(e,t,n,r,s){this._addPropertyEffect(e,"annotatedComputation",{index:t,isCompound:n.isCompound,compoundIndex:r.compoundIndex,kind:n.kind,name:n.name,negate:r.negate,method:r.signature.method,args:r.signature.args,trigger:s,dynamicFn:r.signature.dynamicFn})},_parseMethod:function(e){var t=e.match(/([^\s]+?)\(([\s\S]*)\)/);if(t){var n={method:t[1],static:!0};if(this.getPropertyInfo(n.method)!==Polymer.nob&&(n.static=!1,n.dynamicFn=!0),t[2].trim()){var r=t[2].replace(/\\,/g,",").split(",");return this._parseArgs(r,n)}return n.args=Polymer.nar,n}},_parseArgs:function(e,t){return t.args=e.map(function(e){var n=this._parseArg(e);return n.literal||(t.static=!1),n},this),t},_parseArg:function(e){var t=e.trim().replace(/,/g,",").replace(/\\(.)/g,"$1"),n={name:t},r=t[0];switch("-"===r&&(r=t[1]),r>="0"&&r<="9"&&(r="#"),r){case"'":case'"':n.value=t.slice(1,-1),n.literal=!0;break;case"#":n.value=Number(t),n.literal=!0}return n.literal||(n.model=Polymer.Path.root(t),n.structured=Polymer.Path.isDeep(t),n.structured&&(n.wildcard=".*"==t.slice(-2),n.wildcard&&(n.name=t.slice(0,-2)))),n},_marshalInstanceEffects:function(){Polymer.Bind.prepareInstance(this),this._bindListeners&&Polymer.Bind.setupBindListeners(this)},_applyEffectValue:function(e,t){var n=this._nodes[e.index],r=e.name;if(t=this._computeFinalAnnotationValue(n,r,t,e),"attribute"==e.kind)this.serializeValueToAttribute(t,r,n);else{var s=n._propertyInfo&&n._propertyInfo[r];if(s&&s.readOnly)return;this.__setProperty(r,t,!1,n)}},_computeFinalAnnotationValue:function(e,t,n,r){if(r.negate&&(n=!n),r.isCompound){var s=e.__compoundStorage__[t];s[r.compoundIndex]=n,n=s.join("")}return"attribute"!==r.kind&&("className"===t&&(n=this._scopeElementClass(e,n)),("textContent"===t||"input"==e.localName&&"value"==t)&&(n=void 0==n?"":n)),n},_executeStaticEffects:function(){this._propertyEffects&&this._propertyEffects.__static__&&this._effectEffects("__static__",null,this._propertyEffects.__static__)}}),function(){var e=Polymer.Settings.usePolyfillProto;Polymer.Base._addFeature({_setupConfigure:function(e){if(this._config={},this._handlers=[],this._aboveConfig=null,e)for(var t in e)void 0!==e[t]&&(this._config[t]=e[t])},_marshalAttributes:function(){this._takeAttributesToModel(this._config)},_attributeChangedImpl:function(e){var t=this._clientsReadied?this:this._config;this._setAttributeToProperty(t,e)},_configValue:function(e,t){var n=this._propertyInfo[e];n&&n.readOnly||(this._config[e]=t)},_beforeClientsReady:function(){this._configure()},_configure:function(){this._configureAnnotationReferences(),this._configureInstanceProperties(),this._aboveConfig=this.mixin({},this._config);for(var e={},t=0;t<this.behaviors.length;t++)this._configureProperties(this.behaviors[t].properties,e);this._configureProperties(this.properties,e),this.mixin(e,this._aboveConfig),this._config=e,this._clients&&this._clients.length&&this._distributeConfig(this._config)},_configureInstanceProperties:function(){for(var t in this._propertyEffects)!e&&this.hasOwnProperty(t)&&(this._configValue(t,this[t]), delete this[t])},_configureProperties:function(e,t){for(var n in e){var r=e[n];if(void 0!==r.value){var s=r.value;"function"==typeof s&&(s=s.call(this,this._config)),t[n]=s}}},_distributeConfig:function(e){var t=this._propertyEffects;if(t)for(var n in e){var r=t[n];if(r)for(var s,i=0,o=r.length;i<o&&(s=r[i]);i++)if("annotation"===s.kind){var a=this._nodes[s.effect.index],l=s.effect.propertyName,c="attribute"==s.effect.kind,h=a._propertyEffects&&a._propertyEffects[l];if(a._configValue&&(h||!c)){var u=n===s.effect.value?e[n]:this._get(s.effect.value,e);u=this._computeFinalAnnotationValue(a,l,u,s.effect),c&&(u=a.deserialize(this.serialize(u),a._propertyInfo[l].type)),a._configValue(l,u)}}}},_afterClientsReady:function(){this._executeStaticEffects(),this._applyConfig(this._config,this._aboveConfig),this._flushHandlers()},_applyConfig:function(e,t){for(var n in e)void 0===this[n]&&this.__setProperty(n,e[n],n in t)},_notifyListener:function(e,t){if(!Polymer.Bind._isEventBogus(t,t.target)){var n,r;if(t.detail&&(n=t.detail.value,r=t.detail.path),this._clientsReadied)return e.call(this,t.target,n,r);this._queueHandler([e,t.target,n,r])}},_queueHandler:function(e){this._handlers.push(e)},_flushHandlers:function(){for(var e,t=this._handlers,n=0,r=t.length;n<r&&(e=t[n]);n++)e[0].call(this,e[1],e[2],e[3]);this._handlers=[]}})}(),function(){"use strict";var e=Polymer.Path;Polymer.Base._addFeature({notifyPath:function(e,t,n){var r={},s=this._get(e,this,r);1===arguments.length&&(t=s),r.path&&this._notifyPath(r.path,t,n)},_notifyPath:function(e,t,n){var r=this._propertySetter(e,t);if(r!==t&&(r===r||t===t))return this._pathEffector(e,t),n||this._notifyPathUp(e,t),!0},_getPathParts:function(e){if(Array.isArray(e)){for(var t=[],n=0;n<e.length;n++)for(var r=e[n].toString().split("."),s=0;s<r.length;s++)t.push(r[s]);return t}return e.toString().split(".")},set:function(e,t,n){var r,s=n||this,i=this._getPathParts(e),o=i[i.length-1];if(i.length>1){for(var a=0;a<i.length-1;a++){var l=i[a];if(r&&"#"==l[0]?s=Polymer.Collection.get(r).getItem(l):(s=s[l],r&&parseInt(l,10)==l&&(i[a]=Polymer.Collection.get(r).getKey(s))),!s)return;r=Array.isArray(s)?s:null}if(r){var c,h,u=Polymer.Collection.get(r);"#"==o[0]?(h=o,c=u.getItem(h),o=r.indexOf(c),u.setItem(h,t)):parseInt(o,10)==o&&(c=s[o],h=u.getKey(c),i[a]=h,u.setItem(h,t))}s[o]=t,n||this._notifyPath(i.join("."),t)}else s[e]=t},get:function(e,t){return this._get(e,t)},_get:function(e,t,n){for(var r,s=t||this,i=this._getPathParts(e),o=0;o<i.length;o++){if(!s)return;var a=i[o];r&&"#"==a[0]?s=Polymer.Collection.get(r).getItem(a):(s=s[a],n&&r&&parseInt(a,10)==a&&(i[o]=Polymer.Collection.get(r).getKey(s))),r=Array.isArray(s)?s:null}return n&&(n.path=i.join(".")),s},_pathEffector:function(t,n){var r=e.root(t),s=this._propertyEffects&&this._propertyEffects[r];if(s)for(var i,o=0;o<s.length&&(i=s[o]);o++){var a=i.pathFn;a&&a.call(this,t,n,i.effect)}this._boundPaths&&this._notifyBoundPaths(t,n)},_annotationPathEffect:function(t,n,r){if(e.matches(r.value,!1,t))Polymer.Bind._annotationEffect.call(this,t,n,r);else if(!r.negate&&e.isDescendant(r.value,t)){var s=this._nodes[r.index];if(s&&s._notifyPath){var i=e.translate(r.value,r.name,t);s._notifyPath(i,n,!0)}}},_complexObserverPathEffect:function(t,n,r){e.matches(r.trigger.name,r.trigger.wildcard,t)&&Polymer.Bind._complexObserverEffect.call(this,t,n,r)},_computePathEffect:function(t,n,r){e.matches(r.trigger.name,r.trigger.wildcard,t)&&Polymer.Bind._computeEffect.call(this,t,n,r)},_annotatedComputationPathEffect:function(t,n,r){e.matches(r.trigger.name,r.trigger.wildcard,t)&&Polymer.Bind._annotatedComputationEffect.call(this,t,n,r)},linkPaths:function(e,t){this._boundPaths=this._boundPaths||{},t?this._boundPaths[e]=t:this.unlinkPaths(e)},unlinkPaths:function(e){this._boundPaths&&delete this._boundPaths[e]},_notifyBoundPaths:function(t,n){for(var r in this._boundPaths){var s=this._boundPaths[r];e.isDescendant(r,t)?this._notifyPath(e.translate(r,s,t),n):e.isDescendant(s,t)&&this._notifyPath(e.translate(s,r,t),n)}},_notifyPathUp:function(t,n){var r=e.root(t),s=Polymer.CaseMap.camelToDashCase(r),i=s+this._EVENT_CHANGED;this.fire(i,{path:t,value:n},{bubbles:!1,_useCache:!0})},_EVENT_CHANGED:"-changed",notifySplices:function(e,t){var n={},r=this._get(e,this,n);this._notifySplices(r,n.path,t)},_notifySplices:function(e,t,n){var r={keySplices:Polymer.Collection.applySplices(e,n),indexSplices:n},s=t+".splices";this._notifyPath(s,r),this._notifyPath(t+".length",e.length),this.__data__[s]={keySplices:null,indexSplices:null}},_notifySplice:function(e,t,n,r,s){this._notifySplices(e,t,[{index:n,addedCount:r,removed:s,object:e,type:"splice"}])},push:function(e){var t={},n=this._get(e,this,t),r=Array.prototype.slice.call(arguments,1),s=n.length,i=n.push.apply(n,r);return r.length&&this._notifySplice(n,t.path,s,r.length,[]),i},pop:function(e){var t={},n=this._get(e,this,t),r=Boolean(n.length),s=Array.prototype.slice.call(arguments,1),i=n.pop.apply(n,s);return r&&this._notifySplice(n,t.path,n.length,0,[i]),i},splice:function(e,t){var n={},r=this._get(e,this,n);t=t<0?r.length-Math.floor(-t):Math.floor(t),t||(t=0);var s=Array.prototype.slice.call(arguments,1),i=r.splice.apply(r,s),o=Math.max(s.length-2,0);return(o||i.length)&&this._notifySplice(r,n.path,t,o,i),i},shift:function(e){var t={},n=this._get(e,this,t),r=Boolean(n.length),s=Array.prototype.slice.call(arguments,1),i=n.shift.apply(n,s);return r&&this._notifySplice(n,t.path,0,0,[i]),i},unshift:function(e){var t={},n=this._get(e,this,t),r=Array.prototype.slice.call(arguments,1),s=n.unshift.apply(n,r);return r.length&&this._notifySplice(n,t.path,0,r.length,[]),s},prepareModelNotifyPath:function(e){this.mixin(e,{fire:Polymer.Base.fire,_getEvent:Polymer.Base._getEvent,__eventCache:Polymer.Base.__eventCache,notifyPath:Polymer.Base.notifyPath,_get:Polymer.Base._get,_EVENT_CHANGED:Polymer.Base._EVENT_CHANGED,_notifyPath:Polymer.Base._notifyPath,_notifyPathUp:Polymer.Base._notifyPathUp,_pathEffector:Polymer.Base._pathEffector,_annotationPathEffect:Polymer.Base._annotationPathEffect,_complexObserverPathEffect:Polymer.Base._complexObserverPathEffect,_annotatedComputationPathEffect:Polymer.Base._annotatedComputationPathEffect,_computePathEffect:Polymer.Base._computePathEffect,_notifyBoundPaths:Polymer.Base._notifyBoundPaths,_getPathParts:Polymer.Base._getPathParts})}})}(),Polymer.Base._addFeature({resolveUrl:function(e){var t=Polymer.DomModule.import(this.is),n="";if(t){var r=t.getAttribute("assetpath")||"";n=Polymer.ResolveUrl.resolveUrl(r,t.ownerDocument.baseURI)}return Polymer.ResolveUrl.resolveUrl(e,n)}}),Polymer.CssParse=function(){return{parse:function(e){return e=this._clean(e),this._parseCss(this._lex(e),e)},_clean:function(e){return e.replace(this._rx.comments,"").replace(this._rx.port,"")},_lex:function(e){for(var t={start:0,end:e.length},n=t,r=0,s=e.length;r<s;r++)switch(e[r]){case this.OPEN_BRACE:n.rules||(n.rules=[]);var i=n,o=i.rules[i.rules.length-1];n={start:r+1,parent:i,previous:o},i.rules.push(n);break;case this.CLOSE_BRACE:n.end=r+1,n=n.parent||t}return t},_parseCss:function(e,t){var n=t.substring(e.start,e.end-1);if(e.parsedCssText=e.cssText=n.trim(),e.parent){var r=e.previous?e.previous.end:e.parent.start;n=t.substring(r,e.start-1),n=this._expandUnicodeEscapes(n),n=n.replace(this._rx.multipleSpaces," "),n=n.substring(n.lastIndexOf(";")+1);var s=e.parsedSelector=e.selector=n.trim();e.atRule=0===s.indexOf(this.AT_START),e.atRule?0===s.indexOf(this.MEDIA_START)?e.type=this.types.MEDIA_RULE:s.match(this._rx.keyframesRule)&&(e.type=this.types.KEYFRAMES_RULE,e.keyframesName=e.selector.split(this._rx.multipleSpaces).pop()):0===s.indexOf(this.VAR_START)?e.type=this.types.MIXIN_RULE:e.type=this.types.STYLE_RULE}var i=e.rules;if(i)for(var o,a=0,l=i.length;a<l&&(o=i[a]);a++)this._parseCss(o,t);return e},_expandUnicodeEscapes:function(e){return e.replace(/\\([0-9a-f]{1,6})\s/gi,function(){for(var e=arguments[1],t=6-e.length;t--;)e="0"+e;return"\\"+e})},stringify:function(e,t,n){n=n||"";var r="";if(e.cssText||e.rules){var s=e.rules;if(s&&!this._hasMixinRules(s))for(var i,o=0,a=s.length;o<a&&(i=s[o]);o++)r=this.stringify(i,t,r);else r=t?e.cssText:this.removeCustomProps(e.cssText),r=r.trim(),r&&(r=" "+r+"\n")}return r&&(e.selector&&(n+=e.selector+" "+this.OPEN_BRACE+"\n"),n+=r,e.selector&&(n+=this.CLOSE_BRACE+"\n\n")),n},_hasMixinRules:function(e){return 0===e[0].selector.indexOf(this.VAR_START)},removeCustomProps:function(e){return e=this.removeCustomPropAssignment(e),this.removeCustomPropApply(e)},removeCustomPropAssignment:function(e){return e.replace(this._rx.customProp,"").replace(this._rx.mixinProp,"")},removeCustomPropApply:function(e){return e.replace(this._rx.mixinApply,"").replace(this._rx.varApply,"")},types:{STYLE_RULE:1,KEYFRAMES_RULE:7,MEDIA_RULE:4,MIXIN_RULE:1e3},OPEN_BRACE:"{",CLOSE_BRACE:"}",_rx:{comments:/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,port:/@import[^;]*;/gim,customProp:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,mixinProp:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,mixinApply:/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,varApply:/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,keyframesRule:/^@[^\s]*keyframes/,multipleSpaces:/\s+/g},VAR_START:"--",MEDIA_START:"@media",AT_START:"@"}}(),Polymer.StyleUtil=function(){var e=Polymer.Settings;return{NATIVE_VARIABLES:Polymer.Settings.useNativeCSSProperties,MODULE_STYLES_SELECTOR:"style, link[rel=import][type~=css], template",INCLUDE_ATTR:"include",toCssText:function(e,t){return"string"==typeof e&&(e=this.parser.parse(e)),t&&this.forEachRule(e,t),this.parser.stringify(e,this.NATIVE_VARIABLES)},forRulesInStyles:function(e,t,n){if(e)for(var r,s=0,i=e.length;s<i&&(r=e[s]);s++)this.forEachRuleInStyle(r,t,n)},forActiveRulesInStyles:function(e,t,n){if(e)for(var r,s=0,i=e.length;s<i&&(r=e[s]);s++)this.forEachRuleInStyle(r,t,n,!0)},rulesForStyle:function(e){return!e.__cssRules&&e.textContent&&(e.__cssRules=this.parser.parse(e.textContent)),e.__cssRules},isKeyframesSelector:function(e){return e.parent&&e.parent.type===this.ruleTypes.KEYFRAMES_RULE},forEachRuleInStyle:function(e,t,n,r){var s,i,o=this.rulesForStyle(e);t&&(s=function(n){t(n,e)}),n&&(i=function(t){n(t,e)}),this.forEachRule(o,s,i,r)},forEachRule:function(e,t,n,r){if(e){var s=!1;if(r&&e.type===this.ruleTypes.MEDIA_RULE){var i=e.selector.match(this.rx.MEDIA_MATCH);i&&(window.matchMedia(i[1]).matches||(s=!0))}e.type===this.ruleTypes.STYLE_RULE?t(e):n&&e.type===this.ruleTypes.KEYFRAMES_RULE?n(e):e.type===this.ruleTypes.MIXIN_RULE&&(s=!0);var o=e.rules;if(o&&!s)for(var a,l=0,c=o.length;l<c&&(a=o[l]);l++)this.forEachRule(a,t,n,r)}},applyCss:function(e,t,n,r){var s=this.createScopeStyle(e,t);return this.applyStyle(s,n,r)},applyStyle:function(e,t,n){t=t||document.head;var r=n&&n.nextSibling||t.firstChild;return this.__lastHeadApplyNode=e,t.insertBefore(e,r)},createScopeStyle:function(e,t){var n=document.createElement("style");return t&&n.setAttribute("scope",t),n.textContent=e,n},__lastHeadApplyNode:null,applyStylePlaceHolder:function(e){var t=document.createComment(" Shady DOM styles for "+e+" "),n=this.__lastHeadApplyNode?this.__lastHeadApplyNode.nextSibling:null,r=document.head;return r.insertBefore(t,n||r.firstChild),this.__lastHeadApplyNode=t,t},cssFromModules:function(e,t){for(var n=e.trim().split(" "),r="",s=0;s<n.length;s++)r+=this.cssFromModule(n[s],t);return r},cssFromModule:function(e,t){var n=Polymer.DomModule.import(e);return n&&!n._cssText&&(n._cssText=this.cssFromElement(n)),!n&&t&&console.warn("Could not find style data in module named",e),n&&n._cssText||""},cssFromElement:function(e){for(var t,n="",r=e.content||e,s=Polymer.TreeApi.arrayCopy(r.querySelectorAll(this.MODULE_STYLES_SELECTOR)),i=0;i<s.length;i++)if(t=s[i],"template"===t.localName)t.hasAttribute("preserve-content")||(n+=this.cssFromElement(t));else if("style"===t.localName){var o=t.getAttribute(this.INCLUDE_ATTR);o&&(n+=this.cssFromModules(o,!0)),t=t.__appliedElement||t,t.parentNode.removeChild(t),n+=this.resolveCss(t.textContent,e.ownerDocument)}else t.import&&t.import.body&&(n+=this.resolveCss(t.import.body.textContent,t.import));return n},isTargetedBuild:function(t){return e.useNativeShadow?"shadow"===t:"shady"===t},cssBuildTypeForModule:function(e){var t=Polymer.DomModule.import(e);if(t)return this.getCssBuildType(t)},getCssBuildType:function(e){return e.getAttribute("css-build")},_findMatchingParen:function(e,t){for(var n=0,r=t,s=e.length;r<s;r++)switch(e[r]){case"(":n++;break;case")":if(0===--n)return r}return-1},processVariableAndFallback:function(e,t){var n=e.indexOf("var(");if(n===-1)return t(e,"","","");var r=this._findMatchingParen(e,n+3),s=e.substring(n+4,r),i=e.substring(0,n),o=this.processVariableAndFallback(e.substring(r+1),t),a=s.indexOf(",");if(a===-1)return t(i,s.trim(),"",o);var l=s.substring(0,a).trim(),c=s.substring(a+1).trim();return t(i,l,c,o)},rx:{VAR_ASSIGN:/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi,MIXIN_MATCH:/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi,VAR_CONSUMED:/(--[\w-]+)\s*([:,;)]|$)/gi,ANIMATION_MATCH:/(animation\s*:)|(animation-name\s*:)/,MEDIA_MATCH:/@media[^(]*(\([^)]*\))/,IS_VAR:/^--/,BRACKETED:/\{[^}]*\}/g,HOST_PREFIX:"(?:^|[^.#[:])",HOST_SUFFIX:"($|[.:[\\s>+~])"},resolveCss:Polymer.ResolveUrl.resolveCss,parser:Polymer.CssParse,ruleTypes:Polymer.CssParse.types}}(),Polymer.StyleTransformer=function(){var e=Polymer.StyleUtil,t=Polymer.Settings,n={dom:function(e,t,n,r){this._transformDom(e,t||"",n,r)},_transformDom:function(e,t,n,r){e.setAttribute&&this.element(e,t,n,r);for(var s=Polymer.dom(e).childNodes,i=0;i<s.length;i++)this._transformDom(s[i],t,n,r)},element:function(e,t,n,s){if(n)s?e.removeAttribute(r):e.setAttribute(r,t);else if(t)if(e.classList)s?(e.classList.remove(r),e.classList.remove(t)):(e.classList.add(r),e.classList.add(t));else if(e.getAttribute){var i=e.getAttribute(g);s?i&&e.setAttribute(g,i.replace(r,"").replace(t,"")):e.setAttribute(g,(i?i+" ":"")+r+" "+t)}},elementStyles:function(n,r){var s,i=n._styles,o="",a=n.__cssBuild,l=t.useNativeShadow||"shady"===a;if(l){var h=this;s=function(e){e.selector=h._slottedToContent(e.selector),e.selector=e.selector.replace(c,":host > *"),r&&r(e)}}for(var u,f=0,p=i.length;f<p&&(u=i[f]);f++){var _=e.rulesForStyle(u);o+=l?e.toCssText(_,s):this.css(_,n.is,n.extends,r,n._scopeCssViaAttr)+"\n\n"}return o.trim()},css:function(t,n,r,s,i){var o=this._calcHostScope(n,r);n=this._calcElementScope(n,i);var a=this;return e.toCssText(t,function(e){e.isScoped||(a.rule(e,n,o),e.isScoped=!0),s&&s(e,n,o)})},_calcElementScope:function(e,t){return e?t?m+e+y:d+e:""},_calcHostScope:function(e,t){return t?"[is="+e+"]":e},rule:function(e,t,n){this._transformRule(e,this._transformComplexSelector,t,n)},_transformRule:function(e,t,n,r){e.selector=e.transformedSelector=this._transformRuleCss(e,t,n,r)},_transformRuleCss:function(t,n,r,s){var o=t.selector.split(i);if(!e.isKeyframesSelector(t))for(var a,l=0,c=o.length;l<c&&(a=o[l]);l++)o[l]=n.call(this,a,r,s);return o.join(i)},_transformComplexSelector:function(e,t,n){var r=!1,s=!1,a=this;return e=e.trim(),e=this._slottedToContent(e),e=e.replace(c,":host > *"),e=e.replace(P,l+" $1"),e=e.replace(o,function(e,i,o){if(r)o=o.replace(_," ");else{var l=a._transformCompoundSelector(o,i,t,n);r=r||l.stop,s=s||l.hostContext,i=l.combinator,o=l.value}return i+o}),s&&(e=e.replace(f,function(e,t,r,s){return t+r+" "+n+s+i+" "+t+n+r+s})),e},_transformCompoundSelector:function(e,t,n,r){var s=e.search(_),i=!1;e.indexOf(u)>=0?i=!0:e.indexOf(l)>=0?e=this._transformHostSelector(e,r):0!==s&&(e=n?this._transformSimpleSelector(e,n):e),e.indexOf(p)>=0&&(t="");var o;return s>=0&&(e=e.replace(_," "),o=!0),{value:e,combinator:t,stop:o,hostContext:i}},_transformSimpleSelector:function(e,t){var n=e.split(v);return n[0]+=t,n.join(v)},_transformHostSelector:function(e,t){var n=e.match(h),r=n&&n[2].trim()||"";if(r){if(r[0].match(a))return e.replace(h,function(e,n,r){return t+r});var s=r.split(a)[0];return s===t?r:S}return e.replace(l,t)},documentRule:function(e){e.selector=e.parsedSelector,this.normalizeRootSelector(e),t.useNativeShadow||this._transformRule(e,this._transformDocumentSelector)},normalizeRootSelector:function(e){e.selector=e.selector.replace(c,"html")},_transformDocumentSelector:function(e){return e.match(_)?this._transformComplexSelector(e,s):this._transformSimpleSelector(e.trim(),s)},_slottedToContent:function(e){return e.replace(C,p+"> $1")},SCOPE_NAME:"style-scope"},r=n.SCOPE_NAME,s=":not(["+r+"]):not(."+r+")",i=",",o=/(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=\[])+)/g,a=/[[.:#*]/,l=":host",c=":root",h=/(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,u=":host-context",f=/(.*)(?::host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/,p="::content",_=/::content|::shadow|\/deep\//,d=".",m="["+r+"~=",y="]",v=":",g="class",P=new RegExp("^("+p+")"),S="should_not_match",C=/(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;return n}(),Polymer.StyleExtends=function(){var e=Polymer.StyleUtil;return{hasExtends:function(e){return Boolean(e.match(this.rx.EXTEND))},transform:function(t){var n=e.rulesForStyle(t),r=this;return e.forEachRule(n,function(e){if(r._mapRuleOntoParent(e),e.parent)for(var t;t=r.rx.EXTEND.exec(e.cssText);){var n=t[1],s=r._findExtendor(n,e);s&&r._extendRule(e,s)}e.cssText=e.cssText.replace(r.rx.EXTEND,"")}),e.toCssText(n,function(e){e.selector.match(r.rx.STRIP)&&(e.cssText="")},!0)},_mapRuleOntoParent:function(e){if(e.parent){for(var t,n=e.parent.map||(e.parent.map={}),r=e.selector.split(","),s=0;s<r.length;s++)t=r[s],n[t.trim()]=e;return n}},_findExtendor:function(e,t){return t.parent&&t.parent.map&&t.parent.map[e]||this._findExtendor(e,t.parent)},_extendRule:function(e,t){e.parent!==t.parent&&this._cloneAndAddRuleToParent(t,e.parent),e.extends=e.extends||[],e.extends.push(t),t.selector=t.selector.replace(this.rx.STRIP,""),t.selector=(t.selector&&t.selector+",\n")+e.selector,t.extends&&t.extends.forEach(function(t){this._extendRule(e,t)},this)},_cloneAndAddRuleToParent:function(e,t){e=Object.create(e),e.parent=t,e.extends&&(e.extends=e.extends.slice()),t.rules.push(e)},rx:{EXTEND:/@extends\(([^)]*)\)\s*?;/gim,STRIP:/%[^,]*$/}}}(),Polymer.ApplyShim=function(){"use strict";function e(e,t){e=e.trim(),m[e]={properties:t,dependants:{}}}function t(e){return e=e.trim(),m[e]}function n(e,t){var n=_.exec(t);return n&&(t=n[1]?y._getInitialValueForProperty(e):"apply-shim-inherit"),t}function r(e){for(var t,r,s,i,o=e.split(";"),a={},l=0;l<o.length;l++)s=o[l],s&&(i=s.split(":"),i.length>1&&(t=i[0].trim(),r=n(t,i.slice(1).join(":")),a[t]=r));return a}function s(e){var t=y.__currentElementProto,n=t&&t.is;for(var r in e.dependants)r!==n&&(e.dependants[r].__applyShimInvalid=!0)}function i(n,i,o,a){if(o&&c.processVariableAndFallback(o,function(e,n){n&&t(n)&&(a="@apply "+n+";")}),!a)return n;var h=l(a),u=n.slice(0,n.indexOf("--")),f=r(h),p=f,_=t(i),m=_&&_.properties;m?(p=Object.create(m),p=Polymer.Base.mixin(p,f)):e(i,p);var y,v,g=[],P=!1;for(y in p)v=f[y],void 0===v&&(v="initial"),!m||y in m||(P=!0),g.push(i+d+y+": "+v);return P&&s(_),_&&(_.properties=p),o&&(u=n+";"+u),u+g.join("; ")+";"}function o(e,t,n){return"var("+t+",var("+n+"))"}function a(n,r){n=n.replace(p,"");var s=[],i=t(n);if(i||(e(n,{}),i=t(n)),i){var o=y.__currentElementProto;o&&(i.dependants[o.is]=o);var a,l,c;for(a in i.properties)c=r&&r[a],l=[a,": var(",n,d,a],c&&l.push(",",c),l.push(")"),s.push(l.join(""))}return s.join("; ")}function l(e){for(var t;t=h.exec(e);){var n=t[0],s=t[1],i=t.index,o=i+n.indexOf("@apply"),l=i+n.length,c=e.slice(0,o),u=e.slice(l),f=r(c),p=a(s,f);e=[c,p,u].join(""),h.lastIndex=i+p.length}return e}var c=Polymer.StyleUtil,h=c.rx.MIXIN_MATCH,u=c.rx.VAR_ASSIGN,f=/var\(\s*(--[^,]*),\s*(--[^)]*)\)/g,p=/;\s*/m,_=/^\s*(initial)|(inherit)\s*$/,d="_-_",m={},y={_measureElement:null,_map:m,_separator:d,transform:function(e,t){this.__currentElementProto=t,c.forRulesInStyles(e,this._boundFindDefinitions),c.forRulesInStyles(e,this._boundFindApplications),t&&(t.__applyShimInvalid=!1),this.__currentElementProto=null},_findDefinitions:function(e){var t=e.parsedCssText;t=t.replace(f,o),t=t.replace(u,i),e.cssText=t,":root"===e.selector&&(e.selector=":host > *")},_findApplications:function(e){e.cssText=l(e.cssText)},transformRule:function(e){this._findDefinitions(e),this._findApplications(e)},_getInitialValueForProperty:function(e){return this._measureElement||(this._measureElement=document.createElement("meta"),this._measureElement.style.all="initial",document.head.appendChild(this._measureElement)),window.getComputedStyle(this._measureElement).getPropertyValue(e)}};return y._boundTransformRule=y.transformRule.bind(y),y._boundFindDefinitions=y._findDefinitions.bind(y),y._boundFindApplications=y._findApplications.bind(y),y}(),function(){var e=Polymer.Base._prepElement,t=Polymer.Settings.useNativeShadow,n=Polymer.StyleUtil,r=Polymer.StyleTransformer,s=Polymer.StyleExtends,i=Polymer.ApplyShim,o=Polymer.Settings;Polymer.Base._addFeature({_prepElement:function(t){this._encapsulateStyle&&"shady"!==this.__cssBuild&&r.element(t,this.is,this._scopeCssViaAttr),e.call(this,t)},_prepStyles:function(){void 0===this._encapsulateStyle&&(this._encapsulateStyle=!t),t||(this._scopeStyle=n.applyStylePlaceHolder(this.is)),this.__cssBuild=n.cssBuildTypeForModule(this.is)},_prepShimStyles:function(){if(this._template){var e=n.isTargetedBuild(this.__cssBuild);if(o.useNativeCSSProperties&&"shadow"===this.__cssBuild&&e)return;this._styles=this._styles||this._collectStyles(),o.useNativeCSSProperties&&!this.__cssBuild&&i.transform(this._styles,this);var s=o.useNativeCSSProperties&&e?this._styles.length&&this._styles[0].textContent.trim():r.elementStyles(this);this._prepStyleProperties(),!this._needsStyleProperties()&&s&&n.applyCss(s,this.is,t?this._template.content:null,this._scopeStyle)}else this._styles=[]},_collectStyles:function(){var e=[],t="",r=this.styleModules;if(r)for(var i,o=0,a=r.length;o<a&&(i=r[o]);o++)t+=n.cssFromModule(i);t+=n.cssFromModule(this.is);var l=this._template&&this._template.parentNode;if(!this._template||l&&l.id.toLowerCase()===this.is||(t+=n.cssFromElement(this._template)),t){var c=document.createElement("style");c.textContent=t,s.hasExtends(c.textContent)&&(t=s.transform(c)),e.push(c)}return e},_elementAdd:function(e){this._encapsulateStyle&&(e.__styleScoped?e.__styleScoped=!1:r.dom(e,this.is,this._scopeCssViaAttr))},_elementRemove:function(e){this._encapsulateStyle&&r.dom(e,this.is,this._scopeCssViaAttr,!0)},scopeSubtree:function(e,n){if(!t){var r=this,s=function(e){if(e.nodeType===Node.ELEMENT_NODE){var t=e.getAttribute("class");e.setAttribute("class",r._scopeElementClass(e,t));for(var n,s=e.querySelectorAll("*"),i=0;i<s.length&&(n=s[i]);i++)t=n.getAttribute("class"),n.setAttribute("class",r._scopeElementClass(n,t))}};if(s(e),n){var i=new MutationObserver(function(e){for(var t,n=0;n<e.length&&(t=e[n]);n++)if(t.addedNodes)for(var r=0;r<t.addedNodes.length;r++)s(t.addedNodes[r])});return i.observe(e,{childList:!0,subtree:!0}),i}}}})}(),Polymer.StyleProperties=function(){"use strict";function e(e,t){var n=parseInt(e/32),r=1<<e%32;t[n]=(t[n]||0)|r}var t=Polymer.DomApi.matchesSelector,n=Polymer.StyleUtil,r=Polymer.StyleTransformer,s=navigator.userAgent.match("Trident"),i=Polymer.Settings;return{decorateStyles:function(e,t){var s=this,i={},o=[],a=0,l=r._calcHostScope(t.is,t.extends);n.forRulesInStyles(e,function(e,r){s.decorateRule(e),e.index=a++,s.whenHostOrRootRule(t,e,r,function(r){if(e.parent.type===n.ruleTypes.MEDIA_RULE&&(t.__notStyleScopeCacheable=!0),r.isHost){var s=r.selector.split(" ").some(function(e){return 0===e.indexOf(l)&&e.length!==l.length});t.__notStyleScopeCacheable=t.__notStyleScopeCacheable||s}}),s.collectPropertiesInCssText(e.propertyInfo.cssText,i)},function(e){o.push(e)}),e._keyframes=o;var c=[];for(var h in i)c.push(h);return c},decorateRule:function(e){if(e.propertyInfo)return e.propertyInfo;var t={},n={},r=this.collectProperties(e,n);return r&&(t.properties=n,e.rules=null),t.cssText=this.collectCssText(e),e.propertyInfo=t,t},collectProperties:function(e,t){var n=e.propertyInfo;if(!n){for(var r,s,i,o=this.rx.VAR_ASSIGN,a=e.parsedCssText;r=o.exec(a);)s=(r[2]||r[3]).trim(),"inherit"!==s&&(t[r[1].trim()]=s),i=!0;return i}if(n.properties)return Polymer.Base.mixin(t,n.properties),!0},collectCssText:function(e){return this.collectConsumingCssText(e.parsedCssText)},collectConsumingCssText:function(e){return e.replace(this.rx.BRACKETED,"").replace(this.rx.VAR_ASSIGN,"")},collectPropertiesInCssText:function(e,t){for(var n;n=this.rx.VAR_CONSUMED.exec(e);){var r=n[1];":"!==n[2]&&(t[r]=!0)}},reify:function(e){for(var t,n=Object.getOwnPropertyNames(e),r=0;r<n.length;r++)t=n[r],e[t]=this.valueForProperty(e[t],e)},valueForProperty:function(e,t){if(e)if(e.indexOf(";")>=0)e=this.valueForProperties(e,t);else{var r=this,s=function(e,n,s,i){var o=r.valueForProperty(t[n],t);return o&&"initial"!==o?"apply-shim-inherit"===o&&(o="inherit"):o=r.valueForProperty(t[s]||s,t)||s,e+(o||"")+i};e=n.processVariableAndFallback(e,s)}return e&&e.trim()||""},valueForProperties:function(e,t){for(var n,r,s=e.split(";"),i=0;i<s.length;i++)if(n=s[i]){if(this.rx.MIXIN_MATCH.lastIndex=0,r=this.rx.MIXIN_MATCH.exec(n))n=this.valueForProperty(t[r[1]],t);else{var o=n.indexOf(":");if(o!==-1){var a=n.substring(o);a=a.trim(),a=this.valueForProperty(a,t)||a,n=n.substring(0,o)+a}}s[i]=n&&n.lastIndexOf(";")===n.length-1?n.slice(0,-1):n||""}return s.join(";")},applyProperties:function(e,t){var n="";e.propertyInfo||this.decorateRule(e),e.propertyInfo.cssText&&(n=this.valueForProperties(e.propertyInfo.cssText,t)),e.cssText=n},applyKeyframeTransforms:function(e,t){var n=e.cssText,r=e.cssText;if(null==e.hasAnimations&&(e.hasAnimations=this.rx.ANIMATION_MATCH.test(n)),e.hasAnimations){var s;if(null==e.keyframeNamesToTransform){e.keyframeNamesToTransform=[];for(var i in t)s=t[i],r=s(n),n!==r&&(n=r,e.keyframeNamesToTransform.push(i))}else{for(var o=0;o<e.keyframeNamesToTransform.length;++o)s=t[e.keyframeNamesToTransform[o]],n=s(n);r=n}}e.cssText=r},propertyDataFromStyles:function(r,s){var i={},o=this,a=[];return n.forActiveRulesInStyles(r,function(n){n.propertyInfo||o.decorateRule(n);var r=n.transformedSelector||n.parsedSelector;s&&n.propertyInfo.properties&&r&&t.call(s,r)&&(o.collectProperties(n,i),e(n.index,a))}),{properties:i,key:a}},_rootSelector:/:root|:host\s*>\s*\*/,_checkRoot:function(e,t){return Boolean(t.match(this._rootSelector))||"html"===e&&t.indexOf("html")>-1},whenHostOrRootRule:function(e,t,n,s){if(t.propertyInfo||self.decorateRule(t),t.propertyInfo.properties){var o=e.is?r._calcHostScope(e.is,e.extends):"html",a=t.parsedSelector,l=this._checkRoot(o,a),c=!l&&0===a.indexOf(":host"),h=e.__cssBuild||n.__cssBuild;if("shady"===h&&(l=a===o+" > *."+o||a.indexOf("html")>-1,c=!l&&0===a.indexOf(o)),l||c){var u=o;c&&(i.useNativeShadow&&!t.transformedSelector&&(t.transformedSelector=r._transformRuleCss(t,r._transformComplexSelector,e.is,o)),u=t.transformedSelector||t.parsedSelector),l&&"html"===o&&(u=t.transformedSelector||t.parsedSelector),s({selector:u,isHost:c,isRoot:l})}}},hostAndRootPropertiesForScope:function(e){var r={},s={},i=this;return n.forActiveRulesInStyles(e._styles,function(n,o){i.whenHostOrRootRule(e,n,o,function(o){var a=e._element||e;t.call(a,o.selector)&&(o.isHost?i.collectProperties(n,r):i.collectProperties(n,s))})}),{rootProps:s,hostProps:r}},transformStyles:function(e,t,n){var s=this,o=r._calcHostScope(e.is,e.extends),a=e.extends?"\\"+o.slice(0,-1)+"\\]":o,l=new RegExp(this.rx.HOST_PREFIX+a+this.rx.HOST_SUFFIX),c=this._elementKeyframeTransforms(e,n);return r.elementStyles(e,function(r){s.applyProperties(r,t),i.useNativeShadow||Polymer.StyleUtil.isKeyframesSelector(r)||!r.cssText||(s.applyKeyframeTransforms(r,c),s._scopeSelector(r,l,o,e._scopeCssViaAttr,n))})},_elementKeyframeTransforms:function(e,t){var n=e._styles._keyframes,r={};if(!i.useNativeShadow&&n)for(var s=0,o=n[s];s<n.length;o=n[++s])this._scopeKeyframes(o,t),r[o.keyframesName]=this._keyframesRuleTransformer(o);return r},_keyframesRuleTransformer:function(e){return function(t){return t.replace(e.keyframesNameRx,e.transformedKeyframesName)}},_scopeKeyframes:function(e,t){e.keyframesNameRx=new RegExp(e.keyframesName,"g"),e.transformedKeyframesName=e.keyframesName+"-"+t,e.transformedSelector=e.transformedSelector||e.selector,e.selector=e.transformedSelector.replace(e.keyframesName,e.transformedKeyframesName)},_scopeSelector:function(e,t,n,s,i){e.transformedSelector=e.transformedSelector||e.selector;for(var o,a=e.transformedSelector,l=s?"["+r.SCOPE_NAME+"~="+i+"]":"."+i,c=a.split(","),h=0,u=c.length;h<u&&(o=c[h]);h++)c[h]=o.match(t)?o.replace(n,l):l+" "+o;e.selector=c.join(",")},applyElementScopeSelector:function(e,t,n,s){var i=s?e.getAttribute(r.SCOPE_NAME):e.getAttribute("class")||"",o=n?i.replace(n,t):(i?i+" ":"")+this.XSCOPE_NAME+" "+t;i!==o&&(s?e.setAttribute(r.SCOPE_NAME,o):e.setAttribute("class",o))},applyElementStyle:function(e,t,r,o){var a=o?o.textContent||"":this.transformStyles(e,t,r),l=e._customStyle;return l&&!i.useNativeShadow&&l!==o&&(l._useCount--,l._useCount<=0&&l.parentNode&&l.parentNode.removeChild(l)),i.useNativeShadow?e._customStyle?(e._customStyle.textContent=a,o=e._customStyle):a&&(o=n.applyCss(a,r,e.root,e._scopeStyle)):o?o.parentNode||(s&&a.indexOf("@media")>-1&&(o.textContent=a),n.applyStyle(o,null,e._scopeStyle)):a&&(o=n.applyCss(a,r,null,e._scopeStyle)),o&&(o._useCount=o._useCount||0,e._customStyle!=o&&o._useCount++,e._customStyle=o),o},mixinCustomStyle:function(e,t){var n;for(var r in t)n=t[r],(n||0===n)&&(e[r]=n)},updateNativeStyleProperties:function(e,t){var n=e.__customStyleProperties;if(n)for(var r=0;r<n.length;r++)e.style.removeProperty(n[r]);var s=[];for(var i in t)null!==t[i]&&(e.style.setProperty(i,t[i]),s.push(i));e.__customStyleProperties=s},rx:n.rx,XSCOPE_NAME:"x-scope"}}(),function(){Polymer.StyleCache=function(){this.cache={}},Polymer.StyleCache.prototype={MAX:100,store:function(e,t,n,r){t.keyValues=n,t.styles=r;var s=this.cache[e]=this.cache[e]||[];s.push(t),s.length>this.MAX&&s.shift()},retrieve:function(e,t,n){var r=this.cache[e];if(r)for(var s,i=r.length-1;i>=0;i--)if(s=r[i],n===s.styles&&this._objectsEqual(t,s.keyValues))return s},clear:function(){this.cache={}},_objectsEqual:function(e,t){var n,r;for(var s in e)if(n=e[s],r=t[s],!("object"==typeof n&&n?this._objectsStrictlyEqual(n,r):n===r))return!1;return!Array.isArray(e)||e.length===t.length},_objectsStrictlyEqual:function(e,t){return this._objectsEqual(e,t)&&this._objectsEqual(t,e)}}}(),Polymer.StyleDefaults=function(){var e=Polymer.StyleProperties,t=Polymer.StyleCache,n=Polymer.Settings.useNativeCSSProperties,r={_styles:[],_properties:null,customStyle:{},_styleCache:new t,_element:Polymer.DomApi.wrap(document.documentElement),addStyle:function(e){this._styles.push(e),this._properties=null},get _styleProperties(){return this._properties||(e.decorateStyles(this._styles,this),this._styles._scopeStyleProperties=null,this._properties=e.hostAndRootPropertiesForScope(this).rootProps,e.mixinCustomStyle(this._properties,this.customStyle),e.reify(this._properties)),this._properties},hasStyleProperties:function(){return Boolean(this._properties)},_needsStyleProperties:function(){},_computeStyleProperties:function(){return this._styleProperties},updateStyles:function(t){this._properties=null,t&&Polymer.Base.mixin(this.customStyle,t),this._styleCache.clear();for(var r,s=0;s<this._styles.length;s++)r=this._styles[s],r=r.__importElement||r,r._apply();n&&e.updateNativeStyleProperties(document.documentElement,this.customStyle)}};return r}(),function(){"use strict";var e=Polymer.Base.serializeValueToAttribute,t=Polymer.StyleProperties,n=Polymer.StyleTransformer,r=Polymer.StyleDefaults,s=Polymer.Settings.useNativeShadow,i=Polymer.Settings.useNativeCSSProperties;Polymer.Base._addFeature({_prepStyleProperties:function(){i||(this._ownStylePropertyNames=this._styles&&this._styles.length?t.decorateStyles(this._styles,this):null); },customStyle:null,getComputedStyleValue:function(e){return!i&&this._styleProperties&&this._styleProperties[e]||getComputedStyle(this).getPropertyValue(e)},_setupStyleProperties:function(){this.customStyle={},this._styleCache=null,this._styleProperties=null,this._scopeSelector=null,this._ownStyleProperties=null,this._customStyle=null},_needsStyleProperties:function(){return Boolean(!i&&this._ownStylePropertyNames&&this._ownStylePropertyNames.length)},_validateApplyShim:function(){if(this.__applyShimInvalid){Polymer.ApplyShim.transform(this._styles,this.__proto__);var e=n.elementStyles(this);if(s){var t=this._template.content.querySelector("style");t&&(t.textContent=e)}else{var r=this._scopeStyle&&this._scopeStyle.nextSibling;r&&(r.textContent=e)}}},_beforeAttached:function(){this._scopeSelector&&!this.__stylePropertiesInvalid||!this._needsStyleProperties()||(this.__stylePropertiesInvalid=!1,this._updateStyleProperties())},_findStyleHost:function(){for(var e,t=this;e=Polymer.dom(t).getOwnerRoot();){if(Polymer.isInstance(e.host))return e.host;t=e.host}return r},_updateStyleProperties:function(){var e,n=this._findStyleHost();n._styleProperties||n._computeStyleProperties(),n._styleCache||(n._styleCache=new Polymer.StyleCache);var r=t.propertyDataFromStyles(n._styles,this),i=!this.__notStyleScopeCacheable;i&&(r.key.customStyle=this.customStyle,e=n._styleCache.retrieve(this.is,r.key,this._styles));var a=Boolean(e);a?this._styleProperties=e._styleProperties:this._computeStyleProperties(r.properties),this._computeOwnStyleProperties(),a||(e=o.retrieve(this.is,this._ownStyleProperties,this._styles));var l=Boolean(e)&&!a,c=this._applyStyleProperties(e);a||(c=c&&s?c.cloneNode(!0):c,e={style:c,_scopeSelector:this._scopeSelector,_styleProperties:this._styleProperties},i&&(r.key.customStyle={},this.mixin(r.key.customStyle,this.customStyle),n._styleCache.store(this.is,e,r.key,this._styles)),l||o.store(this.is,Object.create(e),this._ownStyleProperties,this._styles))},_computeStyleProperties:function(e){var n=this._findStyleHost();n._styleProperties||n._computeStyleProperties();var r=Object.create(n._styleProperties),s=t.hostAndRootPropertiesForScope(this);this.mixin(r,s.hostProps),e=e||t.propertyDataFromStyles(n._styles,this).properties,this.mixin(r,e),this.mixin(r,s.rootProps),t.mixinCustomStyle(r,this.customStyle),t.reify(r),this._styleProperties=r},_computeOwnStyleProperties:function(){for(var e,t={},n=0;n<this._ownStylePropertyNames.length;n++)e=this._ownStylePropertyNames[n],t[e]=this._styleProperties[e];this._ownStyleProperties=t},_scopeCount:0,_applyStyleProperties:function(e){var n=this._scopeSelector;this._scopeSelector=e?e._scopeSelector:this.is+"-"+this.__proto__._scopeCount++;var r=t.applyElementStyle(this,this._styleProperties,this._scopeSelector,e&&e.style);return s||t.applyElementScopeSelector(this,this._scopeSelector,n,this._scopeCssViaAttr),r},serializeValueToAttribute:function(t,n,r){if(r=r||this,"class"===n&&!s){var i=r===this?this.domHost||this.dataHost:this;i&&(t=i._scopeElementClass(r,t))}r=this.shadyRoot&&this.shadyRoot._hasDistributed?Polymer.dom(r):r,e.call(this,t,n,r)},_scopeElementClass:function(e,t){return s||this._scopeCssViaAttr||(t=(t?t+" ":"")+a+" "+this.is+(e._scopeSelector?" "+l+" "+e._scopeSelector:"")),t},updateStyles:function(e){e&&this.mixin(this.customStyle,e),i?t.updateNativeStyleProperties(this,this.customStyle):(this.isAttached?this._needsStyleProperties()?this._updateStyleProperties():this._styleProperties=null:this.__stylePropertiesInvalid=!0,this._styleCache&&this._styleCache.clear(),this._updateRootStyles())},_updateRootStyles:function(e){e=e||this.root;for(var t,n=Polymer.dom(e)._query(function(e){return e.shadyRoot||e.shadowRoot}),r=0,s=n.length;r<s&&(t=n[r]);r++)t.updateStyles&&t.updateStyles()}}),Polymer.updateStyles=function(e){r.updateStyles(e),Polymer.Base._updateRootStyles(document)};var o=new Polymer.StyleCache;Polymer.customStyleCache=o;var a=n.SCOPE_NAME,l=t.XSCOPE_NAME}(),Polymer.Base._addFeature({_registerFeatures:function(){this._prepIs(),this._prepConstructor(),this._prepStyles()},_finishRegisterFeatures:function(){this._prepTemplate(),this._prepShimStyles(),this._prepAnnotations(),this._prepEffects(),this._prepBehaviors(),this._prepPropertyInfo(),this._prepBindings(),this._prepShady()},_prepBehavior:function(e){this._addPropertyEffects(e.properties),this._addComplexObserverEffects(e.observers),this._addHostAttributes(e.hostAttributes)},_initFeatures:function(){this._setupGestures(),this._setupConfigure(),this._setupStyleProperties(),this._setupDebouncers(),this._setupShady(),this._registerHost(),this._template&&(this._validateApplyShim(),this._poolContent(),this._beginHosting(),this._stampTemplate(),this._endHosting(),this._marshalAnnotationReferences()),this._marshalInstanceEffects(),this._marshalBehaviors(),this._marshalHostAttributes(),this._marshalAttributes(),this._tryReady()},_marshalBehavior:function(e){e.listeners&&this._listenListeners(e.listeners)}}),function(){var e,t=Polymer.StyleProperties,n=Polymer.StyleUtil,r=Polymer.CssParse,s=Polymer.StyleDefaults,i=Polymer.StyleTransformer,o=Polymer.ApplyShim,a=Polymer.Debounce,l=Polymer.Settings;Polymer({is:"custom-style",extends:"style",_template:null,properties:{include:String},ready:function(){this.__appliedElement=this.__appliedElement||this,this.__cssBuild=n.getCssBuildType(this),this.__appliedElement!==this&&(this.__appliedElement.__cssBuild=this.__cssBuild),this._tryApply()},attached:function(){this._tryApply()},_tryApply:function(){if(!this._appliesToDocument&&this.parentNode&&"dom-module"!==this.parentNode.localName){this._appliesToDocument=!0;var e=this.__appliedElement;if(l.useNativeCSSProperties||(this.__needsUpdateStyles=s.hasStyleProperties(),s.addStyle(e)),e.textContent||this.include)this._apply(!0);else{var t=this,n=new MutationObserver(function(){n.disconnect(),t._apply(!0)});n.observe(e,{childList:!0})}}},_updateStyles:function(){Polymer.updateStyles()},_apply:function(e){var t=this.__appliedElement;if(this.include&&(t.textContent=n.cssFromModules(this.include,!0)+t.textContent),t.textContent){var r=this.__cssBuild,s=n.isTargetedBuild(r);if(!l.useNativeCSSProperties||!s){var a=n.rulesForStyle(t);if(s||(n.forEachRule(a,function(e){i.documentRule(e)}),l.useNativeCSSProperties&&!r&&o.transform([t])),l.useNativeCSSProperties)t.textContent=n.toCssText(a);else{var c=this,h=function(){c._flushCustomProperties()};e?Polymer.RenderStatus.whenReady(h):h()}}}},_flushCustomProperties:function(){this.__needsUpdateStyles?(this.__needsUpdateStyles=!1,e=a(e,this._updateStyles)):this._applyCustomProperties()},_applyCustomProperties:function(){var e=this.__appliedElement;this._computeStyleProperties();var s=this._styleProperties,i=n.rulesForStyle(e);i&&(e.textContent=n.toCssText(i,function(e){var n=e.cssText=e.parsedCssText;e.propertyInfo&&e.propertyInfo.cssText&&(n=r.removeCustomPropAssignment(n),e.cssText=t.valueForProperties(n,s))}))}})}(),Polymer.Templatizer={properties:{__hideTemplateChildren__:{observer:"_showHideChildren"}},_instanceProps:Polymer.nob,_parentPropPrefix:"_parent_",templatize:function(e){if(this._templatized=e,e._content||(e._content=e.content),e._content._ctor)return this.ctor=e._content._ctor,void this._prepParentProperties(this.ctor.prototype,e);var t=Object.create(Polymer.Base);this._customPrepAnnotations(t,e),this._prepParentProperties(t,e),t._prepEffects(),this._customPrepEffects(t),t._prepBehaviors(),t._prepPropertyInfo(),t._prepBindings(),t._notifyPathUp=this._notifyPathUpImpl,t._scopeElementClass=this._scopeElementClassImpl,t.listen=this._listenImpl,t._showHideChildren=this._showHideChildrenImpl,t.__setPropertyOrig=this.__setProperty,t.__setProperty=this.__setPropertyImpl;var n=this._constructorImpl,r=function(e,t){n.call(this,e,t)};r.prototype=t,t.constructor=r,e._content._ctor=r,this.ctor=r},_getRootDataHost:function(){return this.dataHost&&this.dataHost._rootDataHost||this.dataHost},_showHideChildrenImpl:function(e){for(var t=this._children,n=0;n<t.length;n++){var r=t[n];Boolean(e)!=Boolean(r.__hideTemplateChildren__)&&(r.nodeType===Node.TEXT_NODE?e?(r.__polymerTextContent__=r.textContent,r.textContent=""):r.textContent=r.__polymerTextContent__:r.style&&(e?(r.__polymerDisplay__=r.style.display,r.style.display="none"):r.style.display=r.__polymerDisplay__)),r.__hideTemplateChildren__=e}},__setPropertyImpl:function(e,t,n,r){r&&r.__hideTemplateChildren__&&"textContent"==e&&(e="__polymerTextContent__"),this.__setPropertyOrig(e,t,n,r)},_debounceTemplate:function(e){Polymer.dom.addDebouncer(this.debounce("_debounceTemplate",e))},_flushTemplates:function(){Polymer.dom.flush()},_customPrepEffects:function(e){var t=e._parentProps;for(var n in t)e._addPropertyEffect(n,"function",this._createHostPropEffector(n));for(n in this._instanceProps)e._addPropertyEffect(n,"function",this._createInstancePropEffector(n))},_customPrepAnnotations:function(e,t){e._template=t;var n=t._content;if(!n._notes){var r=e._rootDataHost;r&&(Polymer.Annotations.prepElement=function(){r._prepElement()}),n._notes=Polymer.Annotations.parseAnnotations(t),Polymer.Annotations.prepElement=null,this._processAnnotations(n._notes)}e._notes=n._notes,e._parentProps=n._parentProps},_prepParentProperties:function(e,t){var n=this._parentProps=e._parentProps;if(this._forwardParentProp&&n){var r,s=e._parentPropProto;if(!s){for(r in this._instanceProps)delete n[r];s=e._parentPropProto=Object.create(null),t!=this&&(Polymer.Bind.prepareModel(s),Polymer.Base.prepareModelNotifyPath(s));for(r in n){var i=this._parentPropPrefix+r,o=[{kind:"function",effect:this._createForwardPropEffector(r),fn:Polymer.Bind._functionEffect},{kind:"notify",fn:Polymer.Bind._notifyEffect,effect:{event:Polymer.CaseMap.camelToDashCase(i)+"-changed"}}];Polymer.Bind._createAccessors(s,i,o)}}var a=this;t!=this&&(Polymer.Bind.prepareInstance(t),t._forwardParentProp=function(e,t){a._forwardParentProp(e,t)}),this._extendTemplate(t,s),t._pathEffector=function(e,t,n){return a._pathEffectorImpl(e,t,n)}}},_createForwardPropEffector:function(e){return function(t,n){this._forwardParentProp(e,n)}},_createHostPropEffector:function(e){var t=this._parentPropPrefix;return function(n,r){this.dataHost._templatized[t+e]=r}},_createInstancePropEffector:function(e){return function(t,n,r,s){s||this.dataHost._forwardInstanceProp(this,e,n)}},_extendTemplate:function(e,t){var n=Object.getOwnPropertyNames(t);t._propertySetter&&(e._propertySetter=t._propertySetter);for(var r,s=0;s<n.length&&(r=n[s]);s++){var i=e[r],o=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,o),void 0!==i&&e._propertySetter(r,i)}},_showHideChildren:function(e){},_forwardInstancePath:function(e,t,n){},_forwardInstanceProp:function(e,t,n){},_notifyPathUpImpl:function(e,t){var n=this.dataHost,r=Polymer.Path.root(e);n._forwardInstancePath.call(n,this,e,t),r in n._parentProps&&n._templatized._notifyPath(n._parentPropPrefix+e,t)},_pathEffectorImpl:function(e,t,n){if(this._forwardParentPath&&0===e.indexOf(this._parentPropPrefix)){var r=e.substring(this._parentPropPrefix.length),s=Polymer.Path.root(r);s in this._parentProps&&this._forwardParentPath(r,t)}Polymer.Base._pathEffector.call(this._templatized,e,t,n)},_constructorImpl:function(e,t){this._rootDataHost=t._getRootDataHost(),this._setupConfigure(e),this._registerHost(t),this._beginHosting(),this.root=this.instanceTemplate(this._template),this.root.__noContent=!this._notes._hasContent,this.root.__styleScoped=!0,this._endHosting(),this._marshalAnnotatedNodes(),this._marshalInstanceEffects(),this._marshalAnnotatedListeners();for(var n=[],r=this.root.firstChild;r;r=r.nextSibling)n.push(r),r._templateInstance=this;this._children=n,t.__hideTemplateChildren__&&this._showHideChildren(!0),this._tryReady()},_listenImpl:function(e,t,n){var r=this,s=this._rootDataHost,i=s._createEventHandler(e,t,n),o=function(e){e.model=r,i(e)};s._listen(e,t,o)},_scopeElementClassImpl:function(e,t){var n=this._rootDataHost;return n?n._scopeElementClass(e,t):t},stamp:function(e){if(e=e||{},this._parentProps){var t=this._templatized;for(var n in this._parentProps)void 0===e[n]&&(e[n]=t[this._parentPropPrefix+n])}return new this.ctor(e,this)},modelForElement:function(e){for(var t;e;)if(t=e._templateInstance){if(t.dataHost==this)return t;e=t.dataHost}else e=e.parentNode}},Polymer({is:"dom-template",extends:"template",_template:null,behaviors:[Polymer.Templatizer],ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polymer.Collection=function(e){Polymer._collections.set(e,this),this.userArray=e,this.store=e.slice(),this.initMap()},Polymer.Collection.prototype={constructor:Polymer.Collection,initMap:function(){for(var e=this.omap=new WeakMap,t=this.pmap={},n=this.store,r=0;r<n.length;r++){var s=n[r];s&&"object"==typeof s?e.set(s,r):t[s]=r}},add:function(e){var t=this.store.push(e)-1;return e&&"object"==typeof e?this.omap.set(e,t):this.pmap[e]=t,"#"+t},removeKey:function(e){(e=this._parseKey(e))&&(this._removeFromMap(this.store[e]),delete this.store[e])},_removeFromMap:function(e){e&&"object"==typeof e?this.omap.delete(e):delete this.pmap[e]},remove:function(e){var t=this.getKey(e);return this.removeKey(t),t},getKey:function(e){var t;if(t=e&&"object"==typeof e?this.omap.get(e):this.pmap[e],void 0!=t)return"#"+t},getKeys:function(){return Object.keys(this.store).map(function(e){return"#"+e})},_parseKey:function(e){if(e&&"#"==e[0])return e.slice(1)},setItem:function(e,t){if(e=this._parseKey(e)){var n=this.store[e];n&&this._removeFromMap(n),t&&"object"==typeof t?this.omap.set(t,e):this.pmap[t]=e,this.store[e]=t}},getItem:function(e){if(e=this._parseKey(e))return this.store[e]},getItems:function(){var e=[],t=this.store;for(var n in t)e.push(t[n]);return e},_applySplices:function(e){for(var t,n,r={},s=0;s<e.length&&(n=e[s]);s++){n.addedKeys=[];for(var i=0;i<n.removed.length;i++)t=this.getKey(n.removed[i]),r[t]=r[t]?null:-1;for(i=0;i<n.addedCount;i++){var o=this.userArray[n.index+i];t=this.getKey(o),t=void 0===t?this.add(o):t,r[t]=r[t]?null:1,n.addedKeys.push(t)}}var a=[],l=[];for(t in r)r[t]<0&&(this.removeKey(t),a.push(t)),r[t]>0&&l.push(t);return[{removed:a,added:l}]}},Polymer.Collection.get=function(e){return Polymer._collections.get(e)||new Polymer.Collection(e)},Polymer.Collection.applySplices=function(e,t){var n=Polymer._collections.get(e);return n?n._applySplices(t):null},Polymer({is:"dom-repeat",extends:"template",_template:null,properties:{items:{type:Array},as:{type:String,value:"item"},indexAs:{type:String,value:"index"},sort:{type:Function,observer:"_sortChanged"},filter:{type:Function,observer:"_filterChanged"},observe:{type:String,observer:"_observeChanged"},delay:Number,renderedItemCount:{type:Number,notify:!0,readOnly:!0},initialCount:{type:Number,observer:"_initializeChunking"},targetFramerate:{type:Number,value:20},_targetFrameTime:{type:Number,computed:"_computeFrameTime(targetFramerate)"}},behaviors:[Polymer.Templatizer],observers:["_itemsChanged(items.*)"],created:function(){this._instances=[],this._pool=[],this._limit=1/0;var e=this;this._boundRenderChunk=function(){e._renderChunk()}},detached:function(){this.__isDetached=!0;for(var e=0;e<this._instances.length;e++)this._detachInstance(e)},attached:function(){if(this.__isDetached){this.__isDetached=!1;for(var e=Polymer.dom(Polymer.dom(this).parentNode),t=0;t<this._instances.length;t++)this._attachInstance(t,e)}},ready:function(){this._instanceProps={__key__:!0},this._instanceProps[this.as]=!0,this._instanceProps[this.indexAs]=!0,this.ctor||this.templatize(this)},_sortChanged:function(e){var t=this._getRootDataHost();this._sortFn=e&&("function"==typeof e?e:function(){return t[e].apply(t,arguments)}),this._needFullRefresh=!0,this.items&&this._debounceTemplate(this._render)},_filterChanged:function(e){var t=this._getRootDataHost();this._filterFn=e&&("function"==typeof e?e:function(){return t[e].apply(t,arguments)}),this._needFullRefresh=!0,this.items&&this._debounceTemplate(this._render)},_computeFrameTime:function(e){return Math.ceil(1e3/e)},_initializeChunking:function(){this.initialCount&&(this._limit=this.initialCount,this._chunkCount=this.initialCount,this._lastChunkTime=performance.now())},_tryRenderChunk:function(){this.items&&this._limit<this.items.length&&this.debounce("renderChunk",this._requestRenderChunk)},_requestRenderChunk:function(){requestAnimationFrame(this._boundRenderChunk)},_renderChunk:function(){var e=performance.now(),t=this._targetFrameTime/(e-this._lastChunkTime);this._chunkCount=Math.round(this._chunkCount*t)||1,this._limit+=this._chunkCount,this._lastChunkTime=e,this._debounceTemplate(this._render)},_observeChanged:function(){this._observePaths=this.observe&&this.observe.replace(".*",".").split(" ")},_itemsChanged:function(e){if("items"==e.path)Array.isArray(this.items)?this.collection=Polymer.Collection.get(this.items):this.items?this._error(this._logf("dom-repeat","expected array for `items`, found",this.items)):this.collection=null,this._keySplices=[],this._indexSplices=[],this._needFullRefresh=!0,this._initializeChunking(),this._debounceTemplate(this._render);else if("items.splices"==e.path)this._keySplices=this._keySplices.concat(e.value.keySplices),this._indexSplices=this._indexSplices.concat(e.value.indexSplices),this._debounceTemplate(this._render);else{var t=e.path.slice(6);this._forwardItemPath(t,e.value),this._checkObservedPaths(t)}},_checkObservedPaths:function(e){if(this._observePaths){e=e.substring(e.indexOf(".")+1);for(var t=this._observePaths,n=0;n<t.length;n++)if(0===e.indexOf(t[n]))return this._needFullRefresh=!0,void(this.delay?this.debounce("render",this._render,this.delay):this._debounceTemplate(this._render))}},render:function(){this._needFullRefresh=!0,this._debounceTemplate(this._render),this._flushTemplates()},_render:function(){this._needFullRefresh?(this._applyFullRefresh(),this._needFullRefresh=!1):this._keySplices.length&&(this._sortFn?this._applySplicesUserSort(this._keySplices):this._filterFn?this._applyFullRefresh():this._applySplicesArrayOrder(this._indexSplices)),this._keySplices=[],this._indexSplices=[];for(var e=this._keyToInstIdx={},t=this._instances.length-1;t>=0;t--){var n=this._instances[t];n.isPlaceholder&&t<this._limit?n=this._insertInstance(t,n.__key__):!n.isPlaceholder&&t>=this._limit&&(n=this._downgradeInstance(t,n.__key__)),e[n.__key__]=t,n.isPlaceholder||n.__setProperty(this.indexAs,t,!0)}this._pool.length=0,this._setRenderedItemCount(this._instances.length),this.fire("dom-change"),this._tryRenderChunk()},_applyFullRefresh:function(){var e,t=this.collection;if(this._sortFn)e=t?t.getKeys():[];else{e=[];var n=this.items;if(n)for(var r=0;r<n.length;r++)e.push(t.getKey(n[r]))}var s=this;for(this._filterFn&&(e=e.filter(function(e){return s._filterFn(t.getItem(e))})),this._sortFn&&e.sort(function(e,n){return s._sortFn(t.getItem(e),t.getItem(n))}),r=0;r<e.length;r++){var i=e[r],o=this._instances[r];o?(o.__key__=i,!o.isPlaceholder&&r<this._limit&&o.__setProperty(this.as,t.getItem(i),!0)):r<this._limit?this._insertInstance(r,i):this._insertPlaceholder(r,i)}for(var a=this._instances.length-1;a>=r;a--)this._detachAndRemoveInstance(a)},_numericSort:function(e,t){return e-t},_applySplicesUserSort:function(e){for(var t,n,r=this.collection,s={},i=0;i<e.length&&(n=e[i]);i++){for(var o=0;o<n.removed.length;o++)t=n.removed[o],s[t]=s[t]?null:-1;for(o=0;o<n.added.length;o++)t=n.added[o],s[t]=s[t]?null:1}var a=[],l=[];for(t in s)s[t]===-1&&a.push(this._keyToInstIdx[t]),1===s[t]&&l.push(t);if(a.length)for(a.sort(this._numericSort),i=a.length-1;i>=0;i--){var c=a[i];void 0!==c&&this._detachAndRemoveInstance(c)}var h=this;if(l.length){this._filterFn&&(l=l.filter(function(e){return h._filterFn(r.getItem(e))})),l.sort(function(e,t){return h._sortFn(r.getItem(e),r.getItem(t))});var u=0;for(i=0;i<l.length;i++)u=this._insertRowUserSort(u,l[i])}},_insertRowUserSort:function(e,t){for(var n=this.collection,r=n.getItem(t),s=this._instances.length-1,i=-1;e<=s;){var o=e+s>>1,a=this._instances[o].__key__,l=this._sortFn(n.getItem(a),r);if(l<0)e=o+1;else{if(!(l>0)){i=o;break}s=o-1}}return i<0&&(i=s+1),this._insertPlaceholder(i,t),i},_applySplicesArrayOrder:function(e){for(var t,n=0;n<e.length&&(t=e[n]);n++){for(var r=0;r<t.removed.length;r++)this._detachAndRemoveInstance(t.index);for(r=0;r<t.addedKeys.length;r++)this._insertPlaceholder(t.index+r,t.addedKeys[r])}},_detachInstance:function(e){var t=this._instances[e];if(!t.isPlaceholder){for(var n=0;n<t._children.length;n++){var r=t._children[n];Polymer.dom(t.root).appendChild(r)}return t}},_attachInstance:function(e,t){var n=this._instances[e];n.isPlaceholder||t.insertBefore(n.root,this)},_detachAndRemoveInstance:function(e){var t=this._detachInstance(e);t&&this._pool.push(t),this._instances.splice(e,1)},_insertPlaceholder:function(e,t){this._instances.splice(e,0,{isPlaceholder:!0,__key__:t})},_stampInstance:function(e,t){var n={__key__:t};return n[this.as]=this.collection.getItem(t),n[this.indexAs]=e,this.stamp(n)},_insertInstance:function(e,t){var n=this._pool.pop();n?(n.__setProperty(this.as,this.collection.getItem(t),!0),n.__setProperty("__key__",t,!0)):n=this._stampInstance(e,t);var r=this._instances[e+1],s=r&&!r.isPlaceholder?r._children[0]:this,i=Polymer.dom(this).parentNode;return Polymer.dom(i).insertBefore(n.root,s),this._instances[e]=n,n},_downgradeInstance:function(e,t){var n=this._detachInstance(e);return n&&this._pool.push(n),n={isPlaceholder:!0,__key__:t},this._instances[e]=n,n},_showHideChildren:function(e){for(var t=0;t<this._instances.length;t++)this._instances[t]._showHideChildren(e)},_forwardInstanceProp:function(e,t,n){if(t==this.as){var r;r=this._sortFn||this._filterFn?this.items.indexOf(this.collection.getItem(e.__key__)):e[this.indexAs],this.set("items."+r,n)}},_forwardInstancePath:function(e,t,n){0===t.indexOf(this.as+".")&&this._notifyPath("items."+e.__key__+"."+t.slice(this.as.length+1),n)},_forwardParentProp:function(e,t){for(var n,r=this._instances,s=0;s<r.length&&(n=r[s]);s++)n.isPlaceholder||n.__setProperty(e,t,!0)},_forwardParentPath:function(e,t){for(var n,r=this._instances,s=0;s<r.length&&(n=r[s]);s++)n.isPlaceholder||n._notifyPath(e,t,!0)},_forwardItemPath:function(e,t){if(this._keyToInstIdx){var n=e.indexOf("."),r=e.substring(0,n<0?e.length:n),s=this._keyToInstIdx[r],i=this._instances[s];i&&!i.isPlaceholder&&(n>=0?(e=this.as+"."+e.substring(n+1),i._notifyPath(e,t,!0)):i.__setProperty(this.as,t,!0))}},itemForElement:function(e){var t=this.modelForElement(e);return t&&t[this.as]},keyForElement:function(e){var t=this.modelForElement(e);return t&&t.__key__},indexForElement:function(e){var t=this.modelForElement(e);return t&&t[this.indexAs]}}),Polymer({is:"array-selector",_template:null,properties:{items:{type:Array,observer:"clearSelection"},multi:{type:Boolean,value:!1,observer:"clearSelection"},selected:{type:Object,notify:!0},selectedItem:{type:Object,notify:!0},toggle:{type:Boolean,value:!1}},clearSelection:function(){if(Array.isArray(this.selected))for(var e=0;e<this.selected.length;e++)this.unlinkPaths("selected."+e);else this.unlinkPaths("selected"),this.unlinkPaths("selectedItem");this.multi?this.selected&&!this.selected.length||(this.selected=[],this._selectedColl=Polymer.Collection.get(this.selected)):(this.selected=null,this._selectedColl=null),this.selectedItem=null},isSelected:function(e){return this.multi?void 0!==this._selectedColl.getKey(e):this.selected==e},deselect:function(e){if(this.multi){if(this.isSelected(e)){var t=this._selectedColl.getKey(e);this.arrayDelete("selected",e),this.unlinkPaths("selected."+t)}}else this.selected=null,this.selectedItem=null,this.unlinkPaths("selected"),this.unlinkPaths("selectedItem")},select:function(e){var t=Polymer.Collection.get(this.items),n=t.getKey(e);if(this.multi)if(this.isSelected(e))this.toggle&&this.deselect(e);else{this.push("selected",e);var r=this._selectedColl.getKey(e);this.linkPaths("selected."+r,"items."+n)}else this.toggle&&e==this.selected?this.deselect():(this.selected=e,this.selectedItem=e,this.linkPaths("selected","items."+n),this.linkPaths("selectedItem","items."+n))}}),Polymer({is:"dom-if",extends:"template",_template:null,properties:{if:{type:Boolean,value:!1,observer:"_queueRender"},restamp:{type:Boolean,value:!1,observer:"_queueRender"}},behaviors:[Polymer.Templatizer],_queueRender:function(){this._debounceTemplate(this._render)},detached:function(){this.parentNode&&(this.parentNode.nodeType!=Node.DOCUMENT_FRAGMENT_NODE||Polymer.Settings.hasShadow&&this.parentNode instanceof ShadowRoot)||this._teardownInstance()},attached:function(){this.if&&this.ctor&&this.async(this._ensureInstance)},render:function(){this._flushTemplates()},_render:function(){this.if?(this.ctor||this.templatize(this),this._ensureInstance(),this._showHideChildren()):this.restamp&&this._teardownInstance(),!this.restamp&&this._instance&&this._showHideChildren(),this.if!=this._lastIf&&(this.fire("dom-change"),this._lastIf=this.if)},_ensureInstance:function(){var e=Polymer.dom(this).parentNode;if(e){var t=Polymer.dom(e);if(this._instance){var n=this._instance._children;if(n&&n.length){var r=Polymer.dom(this).previousSibling;if(r!==n[n.length-1])for(var s,i=0;i<n.length&&(s=n[i]);i++)t.insertBefore(s,this)}}else{this._instance=this.stamp();var o=this._instance.root;t.insertBefore(o,this)}}},_teardownInstance:function(){if(this._instance){var e=this._instance._children;if(e&&e.length)for(var t,n=Polymer.dom(Polymer.dom(e[0]).parentNode),r=0;r<e.length&&(t=e[r]);r++)n.removeChild(t);this._instance=null}},_showHideChildren:function(){var e=this.__hideTemplateChildren__||!this.if;this._instance&&this._instance._showHideChildren(e)},_forwardParentProp:function(e,t){this._instance&&this._instance.__setProperty(e,t,!0)},_forwardParentPath:function(e,t){this._instance&&this._instance._notifyPath(e,t,!0)}}),Polymer({is:"dom-bind",extends:"template",_template:null,created:function(){var e=this;Polymer.RenderStatus.whenReady(function(){"loading"==document.readyState?document.addEventListener("DOMContentLoaded",function(){e._markImportsReady()}):e._markImportsReady()})},_ensureReady:function(){this._readied||this._readySelf()},_markImportsReady:function(){this._importsReady=!0,this._ensureReady()},_registerFeatures:function(){this._prepConstructor()},_insertChildren:function(){var e=Polymer.dom(Polymer.dom(this).parentNode);e.insertBefore(this.root,this)},_removeChildren:function(){if(this._children)for(var e=0;e<this._children.length;e++)this.root.appendChild(this._children[e])},_initFeatures:function(){},_scopeElementClass:function(e,t){return this.dataHost?this.dataHost._scopeElementClass(e,t):t},_configureInstanceProperties:function(){},_prepConfigure:function(){var e={};for(var t in this._propertyEffects)e[t]=this[t];var n=this._setupConfigure;this._setupConfigure=function(){n.call(this,e)}},attached:function(){this._importsReady&&this.render()},detached:function(){this._removeChildren()},render:function(){this._ensureReady(),this._children||(this._template=this,this._prepAnnotations(),this._prepEffects(),this._prepBehaviors(),this._prepConfigure(),this._prepBindings(),this._prepPropertyInfo(),Polymer.Base._initFeatures.call(this),this._children=Polymer.TreeApi.arrayCopyChildNodes(this.root)),this._insertChildren(),this.fire("dom-change")}})</script><style>[hidden]{display:none!important}</style><style is="custom-style">:root{--layout:{display:-ms-flexbox;display:-webkit-flex;display:flex};--layout-inline:{display:-ms-inline-flexbox;display:-webkit-inline-flex;display:inline-flex};--layout-horizontal:{@apply(--layout);-ms-flex-direction:row;-webkit-flex-direction:row;flex-direction:row};--layout-horizontal-reverse:{@apply(--layout);-ms-flex-direction:row-reverse;-webkit-flex-direction:row-reverse;flex-direction:row-reverse};--layout-vertical:{@apply(--layout);-ms-flex-direction:column;-webkit-flex-direction:column;flex-direction:column};--layout-vertical-reverse:{@apply(--layout);-ms-flex-direction:column-reverse;-webkit-flex-direction:column-reverse;flex-direction:column-reverse};--layout-wrap:{-ms-flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-wrap:wrap};--layout-wrap-reverse:{-ms-flex-wrap:wrap-reverse;-webkit-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse};--layout-flex-auto:{-ms-flex:1 1 auto;-webkit-flex:1 1 auto;flex:1 1 auto};--layout-flex-none:{-ms-flex:none;-webkit-flex:none;flex:none};--layout-flex:{-ms-flex:1 1 0px;-webkit-flex:1;flex:1;-webkit-flex-basis:0px;flex-basis:0px};--layout-flex-2:{-ms-flex:2;-webkit-flex:2;flex:2};--layout-flex-3:{-ms-flex:3;-webkit-flex:3;flex:3};--layout-flex-4:{-ms-flex:4;-webkit-flex:4;flex:4};--layout-flex-5:{-ms-flex:5;-webkit-flex:5;flex:5};--layout-flex-6:{-ms-flex:6;-webkit-flex:6;flex:6};--layout-flex-7:{-ms-flex:7;-webkit-flex:7;flex:7};--layout-flex-8:{-ms-flex:8;-webkit-flex:8;flex:8};--layout-flex-9:{-ms-flex:9;-webkit-flex:9;flex:9};--layout-flex-10:{-ms-flex:10;-webkit-flex:10;flex:10};--layout-flex-11:{-ms-flex:11;-webkit-flex:11;flex:11};--layout-flex-12:{-ms-flex:12;-webkit-flex:12;flex:12};--layout-start:{-ms-flex-align:start;-webkit-align-items:flex-start;align-items:flex-start};--layout-center:{-ms-flex-align:center;-webkit-align-items:center;align-items:center};--layout-end:{-ms-flex-align:end;-webkit-align-items:flex-end;align-items:flex-end};--layout-baseline:{-ms-flex-align:baseline;-webkit-align-items:baseline;align-items:baseline};--layout-start-justified:{-ms-flex-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start};--layout-center-justified:{-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center};--layout-end-justified:{-ms-flex-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end};--layout-around-justified:{-ms-flex-pack:distribute;-webkit-justify-content:space-around;justify-content:space-around};--layout-justified:{-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between};--layout-center-center:{@apply(--layout-center);@apply(--layout-center-justified)};--layout-self-start:{-ms-align-self:flex-start;-webkit-align-self:flex-start;align-self:flex-start};--layout-self-center:{-ms-align-self:center;-webkit-align-self:center;align-self:center};--layout-self-end:{-ms-align-self:flex-end;-webkit-align-self:flex-end;align-self:flex-end};--layout-self-stretch:{-ms-align-self:stretch;-webkit-align-self:stretch;align-self:stretch};--layout-self-baseline:{-ms-align-self:baseline;-webkit-align-self:baseline;align-self:baseline};--layout-start-aligned:{-ms-flex-line-pack:start;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start};--layout-end-aligned:{-ms-flex-line-pack:end;-ms-align-content:flex-end;-webkit-align-content:flex-end;align-content:flex-end};--layout-center-aligned:{-ms-flex-line-pack:center;-ms-align-content:center;-webkit-align-content:center;align-content:center};--layout-between-aligned:{-ms-flex-line-pack:justify;-ms-align-content:space-between;-webkit-align-content:space-between;align-content:space-between};--layout-around-aligned:{-ms-flex-line-pack:distribute;-ms-align-content:space-around;-webkit-align-content:space-around;align-content:space-around};--layout-block:{display:block};--layout-invisible:{visibility:hidden!important};--layout-relative:{position:relative};--layout-fit:{position:absolute;top:0;right:0;bottom:0;left:0};--layout-scroll:{-webkit-overflow-scrolling:touch;overflow:auto};--layout-fullbleed:{margin:0;height:100vh};--layout-fixed-top:{position:fixed;top:0;left:0;right:0};--layout-fixed-right:{position:fixed;top:0;right:0;bottom:0};--layout-fixed-bottom:{position:fixed;right:0;bottom:0;left:0};--layout-fixed-left:{position:fixed;top:0;bottom:0;left:0};}</style><script>Polymer.PaperSpinnerBehavior={listeners:{animationend:"__reset",webkitAnimationEnd:"__reset"},properties:{active:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"__activeChanged"},alt:{type:String,value:"loading",observer:"__altChanged"},__coolingDown:{type:Boolean,value:!1}},__computeContainerClasses:function(e,t){return[e||t?"active":"",t?"cooldown":""].join(" ")},__activeChanged:function(e,t){this.__setAriaHidden(!e),this.__coolingDown=!e&&t},__altChanged:function(e){e===this.getPropertyInfo("alt").value?this.alt=this.getAttribute("aria-label")||e:(this.__setAriaHidden(""===e),this.setAttribute("aria-label",e))},__setAriaHidden:function(e){var t="aria-hidden";e?this.setAttribute(t,"true"):this.removeAttribute(t)},__reset:function(){this.active=!1,this.__coolingDown=!1}}</script><dom-module id="paper-spinner-styles" assetpath="../bower_components/paper-spinner/"><template><style>:host{display:inline-block;position:relative;width:28px;height:28px;--paper-spinner-container-rotation-duration:1568ms;--paper-spinner-expand-contract-duration:1333ms;--paper-spinner-full-cycle-duration:5332ms;--paper-spinner-cooldown-duration:400ms}#spinnerContainer{width:100%;height:100%;direction:ltr}#spinnerContainer.active{-webkit-animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite;animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;white-space:nowrap;border-color:var(--paper-spinner-color,--google-blue-500)}.layer-1{border-color:var(--paper-spinner-layer-1-color,--google-blue-500)}.layer-2{border-color:var(--paper-spinner-layer-2-color,--google-red-500)}.layer-3{border-color:var(--paper-spinner-layer-3-color,--google-yellow-500)}.layer-4{border-color:var(--paper-spinner-layer-4-color,--google-green-500)}.active .spinner-layer{-webkit-animation-name:fill-unfill-rotate;-webkit-animation-duration:var(--paper-spinner-full-cycle-duration);-webkit-animation-timing-function:cubic-bezier(.4,0,.2,1);-webkit-animation-iteration-count:infinite;animation-name:fill-unfill-rotate;animation-duration:var(--paper-spinner-full-cycle-duration);animation-timing-function:cubic-bezier(.4,0,.2,1);animation-iteration-count:infinite;opacity:1}.active .spinner-layer.layer-1{-webkit-animation-name:fill-unfill-rotate,layer-1-fade-in-out;animation-name:fill-unfill-rotate,layer-1-fade-in-out}.active .spinner-layer.layer-2{-webkit-animation-name:fill-unfill-rotate,layer-2-fade-in-out;animation-name:fill-unfill-rotate,layer-2-fade-in-out}.active .spinner-layer.layer-3{-webkit-animation-name:fill-unfill-rotate,layer-3-fade-in-out;animation-name:fill-unfill-rotate,layer-3-fade-in-out}.active .spinner-layer.layer-4{-webkit-animation-name:fill-unfill-rotate,layer-4-fade-in-out;animation-name:fill-unfill-rotate,layer-4-fade-in-out}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@-webkit-keyframes layer-1-fade-in-out{0%{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}to{opacity:1}}@keyframes layer-1-fade-in-out{0%{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}to{opacity:1}}@-webkit-keyframes layer-2-fade-in-out{0%{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}to{opacity:0}}@keyframes layer-2-fade-in-out{0%{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}to{opacity:0}}@-webkit-keyframes layer-3-fade-in-out{0%{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}to{opacity:0}}@keyframes layer-3-fade-in-out{0%{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}to{opacity:0}}@-webkit-keyframes layer-4-fade-in-out{0%{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}to{opacity:0}}@keyframes layer-4-fade-in-out{0%{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}to{opacity:0}}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.spinner-layer::after{left:45%;width:10%;border-top-style:solid}.circle-clipper::after,.spinner-layer::after{content:'';box-sizing:border-box;position:absolute;top:0;border-width:var(--paper-spinner-stroke-width,3px);border-color:inherit;border-radius:50%}.circle-clipper::after{bottom:0;width:200%;border-style:solid;border-bottom-color:transparent!important}.circle-clipper.left::after{left:0;border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right::after{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper::after,.active .gap-patch::after{-webkit-animation-duration:var(--paper-spinner-expand-contract-duration);-webkit-animation-timing-function:cubic-bezier(.4,0,.2,1);-webkit-animation-iteration-count:infinite;animation-duration:var(--paper-spinner-expand-contract-duration);animation-timing-function:cubic-bezier(.4,0,.2,1);animation-iteration-count:infinite}.active .circle-clipper.left::after{-webkit-animation-name:left-spin;animation-name:left-spin}.active .circle-clipper.right::after{-webkit-animation-name:right-spin;animation-name:right-spin}@-webkit-keyframes left-spin{0%{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{0%{transform:rotate(130deg)}50%{transform:rotate(-5deg)}to{transform:rotate(130deg)}}@-webkit-keyframes right-spin{0%{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{0%{transform:rotate(-130deg)}50%{transform:rotate(5deg)}to{transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite,fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(.4,0,.2,1);animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite,fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(.4,0,.2,1)}@-webkit-keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}</style></template></dom-module><dom-module id="paper-spinner" assetpath="../bower_components/paper-spinner/"><template strip-whitespace=""><style include="paper-spinner-styles"></style><div id="spinnerContainer" class-name="[[__computeContainerClasses(active, __coolingDown)]]"><div class="spinner-layer layer-1"><div class="circle-clipper left"></div><div class="circle-clipper right"></div></div><div class="spinner-layer layer-2"><div class="circle-clipper left"></div><div class="circle-clipper right"></div></div><div class="spinner-layer layer-3"><div class="circle-clipper left"></div><div class="circle-clipper right"></div></div><div class="spinner-layer layer-4"><div class="circle-clipper left"></div><div class="circle-clipper right"></div></div></div></template><script>Polymer({is:"paper-spinner",behaviors:[Polymer.PaperSpinnerBehavior]})</script></dom-module><style>@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Thin.ttf) format("truetype");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-ThinItalic.ttf) format("truetype");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Light.ttf) format("truetype");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-LightItalic.ttf) format("truetype");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Medium.ttf) format("truetype");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-MediumItalic.ttf) format("truetype");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-BoldItalic.ttf) format("truetype");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-Black.ttf) format("truetype");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(/static/fonts/roboto/Roboto-BlackItalic.ttf) format("truetype");font-weight:900;font-style:italic}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Thin.ttf) format("truetype");font-weight:100;font-style:normal}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-ThinItalic.ttf) format("truetype");font-weight:100;font-style:italic}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Light.ttf) format("truetype");font-weight:300;font-style:normal}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-LightItalic.ttf) format("truetype");font-weight:300;font-style:italic}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Medium.ttf) format("truetype");font-weight:500;font-style:normal}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-MediumItalic.ttf) format("truetype");font-weight:500;font-style:italic}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:"Roboto Mono";src:url(/static/fonts/robotomono/RobotoMono-BoldItalic.ttf) format("truetype");font-weight:700;font-style:italic}</style><style is="custom-style">:root{--paper-font-common-base:{font-family:Roboto,Noto,sans-serif;-webkit-font-smoothing:antialiased};--paper-font-common-code:{font-family:'Roboto Mono',Consolas,Menlo,monospace;-webkit-font-smoothing:antialiased};--paper-font-common-expensive-kerning:{text-rendering:optimizeLegibility};--paper-font-common-nowrap:{white-space:nowrap;overflow:hidden;text-overflow:ellipsis};--paper-font-display4:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:112px;font-weight:300;letter-spacing:-.044em;line-height:120px};--paper-font-display3:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:56px;font-weight:400;letter-spacing:-.026em;line-height:60px};--paper-font-display2:{@apply(--paper-font-common-base);font-size:45px;font-weight:400;letter-spacing:-.018em;line-height:48px};--paper-font-display1:{@apply(--paper-font-common-base);font-size:34px;font-weight:400;letter-spacing:-.01em;line-height:40px};--paper-font-headline:{@apply(--paper-font-common-base);font-size:24px;font-weight:400;letter-spacing:-.012em;line-height:32px};--paper-font-title:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:20px;font-weight:500;line-height:28px};--paper-font-subhead:{@apply(--paper-font-common-base);font-size:16px;font-weight:400;line-height:24px};--paper-font-body2:{@apply(--paper-font-common-base);font-size:14px;font-weight:500;line-height:24px};--paper-font-body1:{@apply(--paper-font-common-base);font-size:14px;font-weight:400;line-height:20px};--paper-font-caption:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:12px;font-weight:400;letter-spacing:0.011em;line-height:20px};--paper-font-menu:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:13px;font-weight:500;line-height:24px};--paper-font-button:{@apply(--paper-font-common-base);@apply(--paper-font-common-nowrap);font-size:14px;font-weight:500;letter-spacing:0.018em;line-height:24px;text-transform:uppercase};--paper-font-code2:{@apply(--paper-font-common-code);font-size:14px;font-weight:700;line-height:20px};--paper-font-code1:{@apply(--paper-font-common-code);font-size:14px;font-weight:500;line-height:20px};}</style><dom-module id="iron-flex" assetpath="../bower_components/iron-flex-layout/"><template><style>.layout.horizontal,.layout.vertical{display:-ms-flexbox;display:-webkit-flex;display:flex}.layout.inline{display:-ms-inline-flexbox;display:-webkit-inline-flex;display:inline-flex}.layout.horizontal{-ms-flex-direction:row;-webkit-flex-direction:row;flex-direction:row}.layout.vertical{-ms-flex-direction:column;-webkit-flex-direction:column;flex-direction:column}.layout.wrap{-ms-flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-wrap:wrap}.layout.center,.layout.center-center{-ms-flex-align:center;-webkit-align-items:center;align-items:center}.layout.center-center,.layout.center-justified{-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}.flex{-ms-flex:1 1 0px;-webkit-flex:1;flex:1;-webkit-flex-basis:0px;flex-basis:0px}.flex-auto{-ms-flex:1 1 auto;-webkit-flex:1 1 auto;flex:1 1 auto}.flex-none{-ms-flex:none;-webkit-flex:none;flex:none}</style></template></dom-module><dom-module id="iron-flex-reverse" assetpath="../bower_components/iron-flex-layout/"><template><style>.layout.horizontal-reverse,.layout.vertical-reverse{display:-ms-flexbox;display:-webkit-flex;display:flex}.layout.horizontal-reverse{-ms-flex-direction:row-reverse;-webkit-flex-direction:row-reverse;flex-direction:row-reverse}.layout.vertical-reverse{-ms-flex-direction:column-reverse;-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.layout.wrap-reverse{-ms-flex-wrap:wrap-reverse;-webkit-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}</style></template></dom-module><dom-module id="iron-flex-alignment" assetpath="../bower_components/iron-flex-layout/"><template><style>.layout.start{-ms-flex-align:start;-webkit-align-items:flex-start;align-items:flex-start}.layout.center,.layout.center-center{-ms-flex-align:center;-webkit-align-items:center;align-items:center}.layout.end{-ms-flex-align:end;-webkit-align-items:flex-end;align-items:flex-end}.layout.baseline{-ms-flex-align:baseline;-webkit-align-items:baseline;align-items:baseline}.layout.start-justified{-ms-flex-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout.center-center,.layout.center-justified{-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}.layout.end-justified{-ms-flex-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout.around-justified{-ms-flex-pack:distribute;-webkit-justify-content:space-around;justify-content:space-around}.layout.justified{-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.self-start{-ms-align-self:flex-start;-webkit-align-self:flex-start;align-self:flex-start}.self-center{-ms-align-self:center;-webkit-align-self:center;align-self:center}.self-end{-ms-align-self:flex-end;-webkit-align-self:flex-end;align-self:flex-end}.self-stretch{-ms-align-self:stretch;-webkit-align-self:stretch;align-self:stretch}.self-baseline{-ms-align-self:baseline;-webkit-align-self:baseline;align-self:baseline}; .layout.start-aligned{-ms-flex-line-pack:start;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout.end-aligned{-ms-flex-line-pack:end;-ms-align-content:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout.center-aligned{-ms-flex-line-pack:center;-ms-align-content:center;-webkit-align-content:center;align-content:center}.layout.between-aligned{-ms-flex-line-pack:justify;-ms-align-content:space-between;-webkit-align-content:space-between;align-content:space-between}.layout.around-aligned{-ms-flex-line-pack:distribute;-ms-align-content:space-around;-webkit-align-content:space-around;align-content:space-around}</style></template></dom-module><dom-module id="iron-flex-factors" assetpath="../bower_components/iron-flex-layout/"><template><style>.flex,.flex-1{-ms-flex:1 1 0px;-webkit-flex:1;flex:1;-webkit-flex-basis:0px;flex-basis:0px}.flex-2{-ms-flex:2;-webkit-flex:2;flex:2}.flex-3{-ms-flex:3;-webkit-flex:3;flex:3}.flex-4{-ms-flex:4;-webkit-flex:4;flex:4}.flex-5{-ms-flex:5;-webkit-flex:5;flex:5}.flex-6{-ms-flex:6;-webkit-flex:6;flex:6}.flex-7{-ms-flex:7;-webkit-flex:7;flex:7}.flex-8{-ms-flex:8;-webkit-flex:8;flex:8}.flex-9{-ms-flex:9;-webkit-flex:9;flex:9}.flex-10{-ms-flex:10;-webkit-flex:10;flex:10}.flex-11{-ms-flex:11;-webkit-flex:11;flex:11}.flex-12{-ms-flex:12;-webkit-flex:12;flex:12}</style></template></dom-module><dom-module id="iron-positioning" assetpath="../bower_components/iron-flex-layout/"><template><style>.block{display:block}[hidden]{display:none!important}.invisible{visibility:hidden!important}.relative{position:relative}.fit{position:absolute;top:0;right:0;bottom:0;left:0}body.fullbleed{margin:0;height:100vh}.scroll{-webkit-overflow-scrolling:touch;overflow:auto}.fixed-bottom,.fixed-left,.fixed-right,.fixed-top{position:fixed}.fixed-top{top:0;left:0;right:0}.fixed-right{top:0;right:0;bottom:0}.fixed-bottom{right:0;bottom:0;left:0}.fixed-left{top:0;bottom:0;left:0}</style></template></dom-module><script>!function(n){"use strict";function t(n,t){for(var e=[],r=0,u=n.length;r<u;r++)e.push(n[r].substr(0,t));return e}function e(n){return function(t,e,r){var u=r[n].indexOf(e.charAt(0).toUpperCase()+e.substr(1).toLowerCase());~u&&(t.month=u)}}function r(n,t){for(n=String(n),t=t||2;n.length<t;)n="0"+n;return n}var u={},o=/d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g,a=/\d\d?/,i=/\d{3}/,s=/\d{4}/,m=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,d=function(){},f=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],c=["January","February","March","April","May","June","July","August","September","October","November","December"],h=t(c,3),l=t(f,3);u.i18n={dayNamesShort:l,dayNames:f,monthNamesShort:h,monthNames:c,amPm:["am","pm"],DoFn:function(n){return n+["th","st","nd","rd"][n%10>3?0:(n-n%10!==10)*n%10]}};var M={D:function(n){return n.getDate()},DD:function(n){return r(n.getDate())},Do:function(n,t){return t.DoFn(n.getDate())},d:function(n){return n.getDay()},dd:function(n){return r(n.getDay())},ddd:function(n,t){return t.dayNamesShort[n.getDay()]},dddd:function(n,t){return t.dayNames[n.getDay()]},M:function(n){return n.getMonth()+1},MM:function(n){return r(n.getMonth()+1)},MMM:function(n,t){return t.monthNamesShort[n.getMonth()]},MMMM:function(n,t){return t.monthNames[n.getMonth()]},YY:function(n){return String(n.getFullYear()).substr(2)},YYYY:function(n){return n.getFullYear()},h:function(n){return n.getHours()%12||12},hh:function(n){return r(n.getHours()%12||12)},H:function(n){return n.getHours()},HH:function(n){return r(n.getHours())},m:function(n){return n.getMinutes()},mm:function(n){return r(n.getMinutes())},s:function(n){return n.getSeconds()},ss:function(n){return r(n.getSeconds())},S:function(n){return Math.round(n.getMilliseconds()/100)},SS:function(n){return r(Math.round(n.getMilliseconds()/10),2)},SSS:function(n){return r(n.getMilliseconds(),3)},a:function(n,t){return n.getHours()<12?t.amPm[0]:t.amPm[1]},A:function(n,t){return n.getHours()<12?t.amPm[0].toUpperCase():t.amPm[1].toUpperCase()},ZZ:function(n){var t=n.getTimezoneOffset();return(t>0?"-":"+")+r(100*Math.floor(Math.abs(t)/60)+Math.abs(t)%60,4)}},g={D:[a,function(n,t){n.day=t}],Do:[new RegExp(a.source+m.source),function(n,t){n.day=parseInt(t,10)}],M:[a,function(n,t){n.month=t-1}],YY:[a,function(n,t){var e=new Date,r=+(""+e.getFullYear()).substr(0,2);n.year=""+(t>68?r-1:r)+t}],h:[a,function(n,t){n.hour=t}],m:[a,function(n,t){n.minute=t}],s:[a,function(n,t){n.second=t}],YYYY:[s,function(n,t){n.year=t}],S:[/\d/,function(n,t){n.millisecond=100*t}],SS:[/\d{2}/,function(n,t){n.millisecond=10*t}],SSS:[i,function(n,t){n.millisecond=t}],d:[a,d],ddd:[m,d],MMM:[m,e("monthNamesShort")],MMMM:[m,e("monthNames")],a:[m,function(n,t,e){var r=t.toLowerCase();r===e.amPm[0]?n.isPm=!1:r===e.amPm[1]&&(n.isPm=!0)}],ZZ:[/[\+\-]\d\d:?\d\d/,function(n,t){var e,r=(t+"").match(/([\+\-]|\d\d)/gi);r&&(e=+(60*r[1])+parseInt(r[2],10),n.timezoneOffset="+"===r[0]?e:-e)}]};g.dd=g.d,g.dddd=g.ddd,g.DD=g.D,g.mm=g.m,g.hh=g.H=g.HH=g.h,g.MM=g.M,g.ss=g.s,g.A=g.a,u.masks={default:"ddd MMM DD YYYY HH:mm:ss",shortDate:"M/D/YY",mediumDate:"MMM D, YYYY",longDate:"MMMM D, YYYY",fullDate:"dddd, MMMM D, YYYY",shortTime:"HH:mm",mediumTime:"HH:mm:ss",longTime:"HH:mm:ss.SSS"},u.format=function(n,t,e){var r=e||u.i18n;if("number"==typeof n&&(n=new Date(n)),"[object Date]"!==Object.prototype.toString.call(n)||isNaN(n.getTime()))throw new Error("Invalid Date in fecha.format");return t=u.masks[t]||t||u.masks.default,t.replace(o,function(t){return t in M?M[t](n,r):t.slice(1,t.length-1)})},u.parse=function(n,t,e){var r=e||u.i18n;if("string"!=typeof t)throw new Error("Invalid format in fecha.parse");if(t=u.masks[t]||t,n.length>1e3)return!1;var a=!0,i={};if(t.replace(o,function(t){if(g[t]){var e=g[t],u=n.search(e[0]);~u?n.replace(e[0],function(t){return e[1](i,t,r),n=n.substr(u+t.length),t}):a=!1}return g[t]?"":t.slice(1,t.length-1)}),!a)return!1;var s=new Date;i.isPm===!0&&null!=i.hour&&12!==+i.hour?i.hour=+i.hour+12:i.isPm===!1&&12===+i.hour&&(i.hour=0);var m;return null!=i.timezoneOffset?(i.minute=+(i.minute||0)-+i.timezoneOffset,m=new Date(Date.UTC(i.year||s.getFullYear(),i.month||0,i.day||1,i.hour||0,i.minute||0,i.second||0,i.millisecond||0))):m=new Date(i.year||s.getFullYear(),i.month||0,i.day||1,i.hour||0,i.minute||0,i.second||0,i.millisecond||0),m},"undefined"!=typeof module&&module.exports?module.exports=u:"function"==typeof define&&define.amd?define(function(){return u}):n.fecha=u}(this)</script><script>function toLocaleStringSupportsOptions(){try{(new Date).toLocaleString("i")}catch(e){return"RangeError"===e.name}return!1}function toLocaleDateStringSupportsOptions(){try{(new Date).toLocaleDateString("i")}catch(e){return"RangeError"===e.name}return!1}function toLocaleTimeStringSupportsOptions(){try{(new Date).toLocaleTimeString("i")}catch(e){return"RangeError"===e.name}return!1}window.hassUtil=window.hassUtil||{},window.hassUtil.DEFAULT_ICON="mdi:bookmark",window.hassUtil.OFF_STATES=["off","closed","unlocked"],window.hassUtil.DOMAINS_WITH_CARD=["climate","cover","configurator","hvac","input_select","input_slider","media_player","rollershutter","scene","script","thermostat","weblink"],window.hassUtil.DOMAINS_WITH_MORE_INFO=["light","group","sun","climate","configurator","cover","thermostat","script","media_player","camera","updater","alarm_control_panel","lock","hvac","automation"],window.hassUtil.DOMAINS_WITH_NO_HISTORY=["camera","configurator","scene"],window.hassUtil.HIDE_MORE_INFO=["input_select","scene","script","input_slider"],window.hassUtil.attributeClassNames=function(e,t){return e?t.map(function(t){return t in e.attributes?"has-"+t:""}).join(" "):""},window.hassUtil.canToggle=function(e,t){return e.reactor.evaluate(e.serviceGetters.canToggleEntity(t))},window.hassUtil.dynamicContentUpdater=function(e,t,i){var n,r=Polymer.dom(e);r.lastChild&&r.lastChild.tagName===t?n=r.lastChild:(r.lastChild&&r.removeChild(r.lastChild),n=document.createElement(t)),Object.keys(i).forEach(function(e){n[e]=i[e]}),null===n.parentNode&&r.appendChild(n)},window.fecha.masks.haDateTime=window.fecha.masks.shortTime+" "+window.fecha.masks.mediumDate,toLocaleStringSupportsOptions()?window.hassUtil.formatDateTime=function(e){return e.toLocaleString(navigator.language,{year:"numeric",month:"long",day:"numeric",hour:"numeric",minute:"2-digit"})}:window.hassUtil.formatDateTime=function(e){return window.fecha.format(e,"haDateTime")},toLocaleDateStringSupportsOptions()?window.hassUtil.formatDate=function(e){return e.toLocaleDateString(navigator.language,{year:"numeric",month:"long",day:"numeric"})}:window.hassUtil.formatDate=function(e){return window.fecha.format(e,"mediumDate")},toLocaleTimeStringSupportsOptions()?window.hassUtil.formatTime=function(e){return e.toLocaleTimeString(navigator.language,{hour:"numeric",minute:"2-digit"})}:window.hassUtil.formatTime=function(e){return window.fecha.format(e,"shortTime")},window.hassUtil.relativeTime=function(e){var t,i=Math.abs(new Date-e)/1e3,n=new Date>e?"%s ago":"in %s",r=window.hassUtil.relativeTime.tests;for(t=0;t<r.length;t+=2){if(i<r[t])return i=Math.floor(i),n.replace("%s",1===i?"1 "+r[t+1]:i+" "+r[t+1]+"s");i/=r[t]}return i=Math.floor(i),n.replace("%s",1===i?"1 week":i+" weeks")},window.hassUtil.relativeTime.tests=[60,"second",60,"minute",24,"hour",7,"day"],window.hassUtil.stateCardType=function(e,t){return"unavailable"===t.state?"display":window.hassUtil.DOMAINS_WITH_CARD.indexOf(t.domain)!==-1?t.domain:window.hassUtil.canToggle(e,t.entityId)?"toggle":"display"},window.hassUtil.stateMoreInfoType=function(e){return window.hassUtil.DOMAINS_WITH_MORE_INFO.indexOf(e.domain)!==-1?e.domain:window.hassUtil.HIDE_MORE_INFO.indexOf(e.domain)!==-1?"hidden":"default"},window.hassUtil.domainIcon=function(e,t){switch(e){case"alarm_control_panel":return t&&"disarmed"===t?"mdi:bell-outline":"mdi:bell";case"automation":return"mdi:playlist-play";case"binary_sensor":return t&&"off"===t?"mdi:radiobox-blank":"mdi:checkbox-marked-circle";case"camera":return"mdi:video";case"climate":return"mdi:nest-thermostat";case"configurator":return"mdi:settings";case"conversation":return"mdi:text-to-speech";case"cover":return t&&"open"===t?"mdi:window-open":"mdi:window-closed";case"device_tracker":return"mdi:account";case"fan":return"mdi:fan";case"garage_door":return"mdi:glassdoor";case"group":return"mdi:google-circles-communities";case"homeassistant":return"mdi:home";case"hvac":return"mdi:air-conditioner";case"input_boolean":return"mdi:drawing";case"input_select":return"mdi:format-list-bulleted";case"input_slider":return"mdi:ray-vertex";case"light":return"mdi:lightbulb";case"lock":return t&&"unlocked"===t?"mdi:lock-open":"mdi:lock";case"media_player":return t&&"off"!==t&&"idle"!==t?"mdi:cast-connected":"mdi:cast";case"notify":return"mdi:comment-alert";case"proximity":return"mdi:apple-safari";case"rollershutter":return t&&"open"===t?"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 "+e+" ("+t+")"),window.hassUtil.DEFAULT_ICON}},window.hassUtil.binarySensorIcon=function(e){var t=e.state&&"off"===e.state;switch(e.attributes.sensor_class){case"connectivity":return t?"mdi:server-network-off":"mdi:server-network";case"light":return t?"mdi:brightness-5":"mdi:brightness-7";case"moisture":return t?"mdi:water-off":"mdi:water";case"motion":return t?"mdi:walk":"mdi:run";case"occupancy":return t?"mdi:home":"mdi:home-outline";case"opening":return t?"mdi:crop-square":"mdi:exit-to-app";case"sound":return t?"mdi:music-note-off":"mdi:music-note";case"vibration":return t?"mdi:crop-portrait":"mdi:vibrate";case"gas":case"power":case"safety":case"smoke":return t?"mdi:verified":"mdi:alert";default:return t?"mdi:radiobox-blank":"mdi:checkbox-marked-circle"}},window.hassUtil.stateIcon=function(e){var t;if(!e)return window.hassUtil.DEFAULT_ICON;if(e.attributes.icon)return e.attributes.icon;if(t=e.attributes.unit_of_measurement,t&&"sensor"===e.domain){if("°C"===t||"°F"===t)return"mdi:thermometer";if("Mice"===t)return"mdi:mouse-variant"}else if("binary_sensor"===e.domain)return window.hassUtil.binarySensorIcon(e);return window.hassUtil.domainIcon(e.domain,e.state)}</script><script>window.hassBehavior={attached:function(){var e=this.hass;if(!e)throw new Error("No hass property found on "+this.nodeName);this.nuclearUnwatchFns=Object.keys(this.properties).reduce(function(t,r){var n;if(!("bindNuclear"in this.properties[r]))return t;if(n=this.properties[r].bindNuclear(e),!n)throw new Error("Undefined getter specified for key "+r+" on "+this.nodeName);return this[r]=e.reactor.evaluate(n),t.concat(e.reactor.observe(n,function(e){this[r]=e}.bind(this)))}.bind(this),[])},detached:function(){for(;this.nuclearUnwatchFns.length;)this.nuclearUnwatchFns.shift()()}}</script><style is="custom-style">:root{--primary-text-color:var(--light-theme-text-color);--primary-background-color:var(--light-theme-background-color);--secondary-text-color:var(--light-theme-secondary-color);--disabled-text-color:var(--light-theme-disabled-color);--divider-color:var(--light-theme-divider-color);--error-color:var(--paper-deep-orange-a700);--primary-color:var(--paper-indigo-500);--light-primary-color:var(--paper-indigo-100);--dark-primary-color:var(--paper-indigo-700);--accent-color:var(--paper-pink-a200);--light-accent-color:var(--paper-pink-a100);--dark-accent-color:var(--paper-pink-a400);--light-theme-background-color:#ffffff;--light-theme-base-color:#000000;--light-theme-text-color:var(--paper-grey-900);--light-theme-secondary-color:#737373;--light-theme-disabled-color:#9b9b9b;--light-theme-divider-color:#dbdbdb;--dark-theme-background-color:var(--paper-grey-900);--dark-theme-base-color:#ffffff;--dark-theme-text-color:#ffffff;--dark-theme-secondary-color:#bcbcbc;--dark-theme-disabled-color:#646464;--dark-theme-divider-color:#3c3c3c;--text-primary-color:var(--dark-theme-text-color);--default-primary-color:var(--primary-color)}</style><script>!function(){var e={},t={},i=null;Polymer.IronMeta=Polymer({is:"iron-meta",properties:{type:{type:String,value:"default",observer:"_typeChanged"},key:{type:String,observer:"_keyChanged"},value:{type:Object,notify:!0,observer:"_valueChanged"},self:{type:Boolean,observer:"_selfChanged"},list:{type:Array,notify:!0}},hostAttributes:{hidden:!0},factoryImpl:function(e){if(e)for(var t in e)switch(t){case"type":case"key":case"value":this[t]=e[t]}},created:function(){this._metaDatas=e,this._metaArrays=t},_keyChanged:function(e,t){this._resetRegistration(t)},_valueChanged:function(e){this._resetRegistration(this.key)},_selfChanged:function(e){e&&(this.value=this)},_typeChanged:function(i){this._unregisterKey(this.key),e[i]||(e[i]={}),this._metaData=e[i],t[i]||(t[i]=[]),this.list=t[i],this._registerKeyValue(this.key,this.value)},byKey:function(e){return this._metaData&&this._metaData[e]},_resetRegistration:function(e){this._unregisterKey(e),this._registerKeyValue(this.key,this.value)},_unregisterKey:function(e){this._unregister(e,this._metaData,this.list)},_registerKeyValue:function(e,t){this._register(e,t,this._metaData,this.list)},_register:function(e,t,i,a){e&&i&&void 0!==t&&(i[e]=t,a.push(t))},_unregister:function(e,t,i){if(e&&t&&e in t){var a=t[e];delete t[e],this.arrayDelete(i,a)}}}),Polymer.IronMeta.getIronMeta=function(){return null===i&&(i=new Polymer.IronMeta),i},Polymer.IronMetaQuery=Polymer({is:"iron-meta-query",properties:{type:{type:String,value:"default",observer:"_typeChanged"},key:{type:String,observer:"_keyChanged"},value:{type:Object,notify:!0,readOnly:!0},list:{type:Array,notify:!0}},factoryImpl:function(e){if(e)for(var t in e)switch(t){case"type":case"key":this[t]=e[t]}},created:function(){this._metaDatas=e,this._metaArrays=t},_keyChanged:function(e){this._setValue(this._metaData&&this._metaData[e])},_typeChanged:function(i){this._metaData=e[i],this.list=t[i],this.key&&this._keyChanged(this.key)},byKey:function(e){return this._metaData&&this._metaData[e]}})}()</script><script>Polymer.IronValidatableBehaviorMeta=null,Polymer.IronValidatableBehavior={properties:{validator:{type:String},invalid:{notify:!0,reflectToAttribute:!0,type:Boolean,value:!1},_validatorMeta:{type:Object},validatorType:{type:String,value:"validator"},_validator:{type:Object,computed:"__computeValidator(validator)"}},observers:["_invalidChanged(invalid)"],registered:function(){Polymer.IronValidatableBehaviorMeta=new Polymer.IronMeta({type:"validator"})},_invalidChanged:function(){this.invalid?this.setAttribute("aria-invalid","true"):this.removeAttribute("aria-invalid")},hasValidator:function(){return null!=this._validator},validate:function(a){return this.invalid=!this._getValidity(a),!this.invalid},_getValidity:function(a){return!this.hasValidator()||this._validator.validate(a)},__computeValidator:function(){return Polymer.IronValidatableBehaviorMeta&&Polymer.IronValidatableBehaviorMeta.byKey(this.validator)}}</script><script>Polymer.IronFormElementBehavior={properties:{name:{type:String},value:{notify:!0,type:String},required:{type:Boolean,value:!1},_parentForm:{type:Object}},attached:function(){this.fire("iron-form-element-register")},detached:function(){this._parentForm&&this._parentForm.fire("iron-form-element-unregister",{target:this})}}</script><script>Polymer.IronCheckedElementBehaviorImpl={properties:{checked:{type:Boolean,value:!1,reflectToAttribute:!0,notify:!0,observer:"_checkedChanged"},toggles:{type:Boolean,value:!0,reflectToAttribute:!0},value:{type:String,value:"on",observer:"_valueChanged"}},observers:["_requiredChanged(required)"],created:function(){this._hasIronCheckedElementBehavior=!0},_getValidity:function(e){return this.disabled||!this.required||this.checked},_requiredChanged:function(){this.required?this.setAttribute("aria-required","true"):this.removeAttribute("aria-required")},_checkedChanged:function(){this.active=this.checked,this.fire("iron-change")},_valueChanged:function(){void 0!==this.value&&null!==this.value||(this.value="on")}},Polymer.IronCheckedElementBehavior=[Polymer.IronFormElementBehavior,Polymer.IronValidatableBehavior,Polymer.IronCheckedElementBehaviorImpl]</script><script>!function(){"use strict";function e(e,t){var n="";if(e){var i=e.toLowerCase();" "===i||v.test(i)?n="space":f.test(i)?n="esc":1==i.length?t&&!u.test(i)||(n=i):n=c.test(i)?i.replace("arrow",""):"multiply"==i?"*":i}return n}function t(e){var t="";return e&&(e in o?t=o[e]:h.test(e)?(e=parseInt(e.replace("U+","0x"),16),t=String.fromCharCode(e).toLowerCase()):t=e.toLowerCase()),t}function n(e){var t="";return Number(e)&&(t=e>=65&&e<=90?String.fromCharCode(32+e):e>=112&&e<=123?"f"+(e-112):e>=48&&e<=57?String(e-48):e>=96&&e<=105?String(e-96):d[e]),t}function i(i,r){return i.key?e(i.key,r):i.detail&&i.detail.key?e(i.detail.key,r):t(i.keyIdentifier)||n(i.keyCode)||""}function r(e,t){var n=i(t,e.hasModifiers);return n===e.key&&(!e.hasModifiers||!!t.shiftKey==!!e.shiftKey&&!!t.ctrlKey==!!e.ctrlKey&&!!t.altKey==!!e.altKey&&!!t.metaKey==!!e.metaKey)}function s(e){return 1===e.length?{combo:e,key:e,event:"keydown"}:e.split("+").reduce(function(e,t){var n=t.split(":"),i=n[0],r=n[1];return i in y?(e[y[i]]=!0,e.hasModifiers=!0):(e.key=i,e.event=r||"keydown"),e},{combo:e.split(":").shift()})}function a(e){return e.trim().split(" ").map(function(e){return s(e)})}var o={"U+0008":"backspace","U+0009":"tab","U+001B":"esc","U+0020":"space","U+007F":"del"},d={8:"backspace",9:"tab",13:"enter",27:"esc",33:"pageup",34:"pagedown",35:"end",36:"home",32:"space",37:"left",38:"up",39:"right",40:"down",46:"del",106:"*"},y={shift:"shiftKey",ctrl:"ctrlKey",alt:"altKey",meta:"metaKey"},u=/[a-z0-9*]/,h=/U\+/,c=/^arrow/,v=/^space(bar)?/,f=/^escape$/;Polymer.IronA11yKeysBehavior={properties:{keyEventTarget:{type:Object,value:function(){return this}},stopKeyboardEventPropagation:{type:Boolean,value:!1},_boundKeyHandlers:{type:Array,value:function(){return[]}},_imperativeKeyBindings:{type:Object,value:function(){return{}}}},observers:["_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)"],keyBindings:{},registered:function(){this._prepKeyBindings()},attached:function(){this._listenKeyEventListeners()},detached:function(){this._unlistenKeyEventListeners()},addOwnKeyBinding:function(e,t){this._imperativeKeyBindings[e]=t,this._prepKeyBindings(),this._resetKeyEventListeners()},removeOwnKeyBindings:function(){this._imperativeKeyBindings={},this._prepKeyBindings(),this._resetKeyEventListeners()},keyboardEventMatchesKeys:function(e,t){for(var n=a(t),i=0;i<n.length;++i)if(r(n[i],e))return!0;return!1},_collectKeyBindings:function(){var e=this.behaviors.map(function(e){return e.keyBindings});return e.indexOf(this.keyBindings)===-1&&e.push(this.keyBindings),e},_prepKeyBindings:function(){this._keyBindings={},this._collectKeyBindings().forEach(function(e){for(var t in e)this._addKeyBinding(t,e[t])},this);for(var e in this._imperativeKeyBindings)this._addKeyBinding(e,this._imperativeKeyBindings[e]);for(var t in this._keyBindings)this._keyBindings[t].sort(function(e,t){var n=e[0].hasModifiers,i=t[0].hasModifiers;return n===i?0:n?-1:1})},_addKeyBinding:function(e,t){a(e).forEach(function(e){this._keyBindings[e.event]=this._keyBindings[e.event]||[],this._keyBindings[e.event].push([e,t])},this)},_resetKeyEventListeners:function(){this._unlistenKeyEventListeners(),this.isAttached&&this._listenKeyEventListeners()},_listenKeyEventListeners:function(){this.keyEventTarget&&Object.keys(this._keyBindings).forEach(function(e){var t=this._keyBindings[e],n=this._onKeyBindingEvent.bind(this,t);this._boundKeyHandlers.push([this.keyEventTarget,e,n]),this.keyEventTarget.addEventListener(e,n)},this)},_unlistenKeyEventListeners:function(){for(var e,t,n,i;this._boundKeyHandlers.length;)e=this._boundKeyHandlers.pop(),t=e[0],n=e[1],i=e[2],t.removeEventListener(n,i)},_onKeyBindingEvent:function(e,t){if(this.stopKeyboardEventPropagation&&t.stopPropagation(),!t.defaultPrevented)for(var n=0;n<e.length;n++){var i=e[n][0],s=e[n][1];if(r(i,t)&&(this._triggerKeyHandler(i,s,t),t.defaultPrevented))return}},_triggerKeyHandler:function(e,t,n){var i=Object.create(e);i.keyboardEvent=n;var r=new CustomEvent(e.event,{detail:i,cancelable:!0});this[t].call(this,r),r.defaultPrevented&&n.preventDefault()}}}()</script><script>Polymer.IronControlState={properties:{focused:{type:Boolean,value:!1,notify:!0,readOnly:!0,reflectToAttribute:!0},disabled:{type:Boolean,value:!1,notify:!0,observer:"_disabledChanged",reflectToAttribute:!0},_oldTabIndex:{type:Number},_boundFocusBlurHandler:{type:Function,value:function(){return this._focusBlurHandler.bind(this)}}},observers:["_changedControlState(focused, disabled)"],ready:function(){this.addEventListener("focus",this._boundFocusBlurHandler,!0),this.addEventListener("blur",this._boundFocusBlurHandler,!0)},_focusBlurHandler:function(e){if(e.target===this)this._setFocused("focus"===e.type);else if(!this.shadowRoot){var t=Polymer.dom(e).localTarget;this.isLightDescendant(t)||this.fire(e.type,{sourceEvent:e},{node:this,bubbles:e.bubbles,cancelable:e.cancelable})}},_disabledChanged:function(e,t){this.setAttribute("aria-disabled",e?"true":"false"),this.style.pointerEvents=e?"none":"",e?(this._oldTabIndex=this.tabIndex,this._setFocused(!1),this.tabIndex=-1,this.blur()):void 0!==this._oldTabIndex&&(this.tabIndex=this._oldTabIndex)},_changedControlState:function(){this._controlStateChanged&&this._controlStateChanged()}}</script><script>Polymer.IronButtonStateImpl={properties:{pressed:{type:Boolean,readOnly:!0,value:!1,reflectToAttribute:!0,observer:"_pressedChanged"},toggles:{type:Boolean,value:!1,reflectToAttribute:!0},active:{type:Boolean,value:!1,notify:!0,reflectToAttribute:!0},pointerDown:{type:Boolean,readOnly:!0,value:!1},receivedFocusFromKeyboard:{type:Boolean,readOnly:!0},ariaActiveAttribute:{type:String,value:"aria-pressed",observer:"_ariaActiveAttributeChanged"}},listeners:{down:"_downHandler",up:"_upHandler",tap:"_tapHandler"},observers:["_detectKeyboardFocus(focused)","_activeChanged(active, ariaActiveAttribute)"],keyBindings:{"enter:keydown":"_asyncClick","space:keydown":"_spaceKeyDownHandler","space:keyup":"_spaceKeyUpHandler"},_mouseEventRe:/^mouse/,_tapHandler:function(){this.toggles?this._userActivate(!this.active):this.active=!1},_detectKeyboardFocus:function(e){this._setReceivedFocusFromKeyboard(!this.pointerDown&&e)},_userActivate:function(e){this.active!==e&&(this.active=e,this.fire("change"))},_downHandler:function(e){this._setPointerDown(!0),this._setPressed(!0),this._setReceivedFocusFromKeyboard(!1)},_upHandler:function(){this._setPointerDown(!1),this._setPressed(!1)},_spaceKeyDownHandler:function(e){var t=e.detail.keyboardEvent,i=Polymer.dom(t).localTarget;this.isLightDescendant(i)||(t.preventDefault(),t.stopImmediatePropagation(),this._setPressed(!0))},_spaceKeyUpHandler:function(e){var t=e.detail.keyboardEvent,i=Polymer.dom(t).localTarget;this.isLightDescendant(i)||(this.pressed&&this._asyncClick(),this._setPressed(!1))},_asyncClick:function(){this.async(function(){this.click()},1)},_pressedChanged:function(e){this._changedButtonState()},_ariaActiveAttributeChanged:function(e,t){t&&t!=e&&this.hasAttribute(t)&&this.removeAttribute(t)},_activeChanged:function(e,t){this.toggles?this.setAttribute(this.ariaActiveAttribute,e?"true":"false"):this.removeAttribute(this.ariaActiveAttribute),this._changedButtonState()},_controlStateChanged:function(){this.disabled?this._setPressed(!1):this._changedButtonState()},_changedButtonState:function(){this._buttonStateChanged&&this._buttonStateChanged()}},Polymer.IronButtonState=[Polymer.IronA11yKeysBehavior,Polymer.IronButtonStateImpl]</script><dom-module id="paper-ripple" assetpath="../bower_components/paper-ripple/"><template><style>:host{display:block;position:absolute;border-radius:inherit;overflow:hidden;top:0;left:0;right:0;bottom:0;pointer-events:none}:host([animating]){-webkit-transform:translate(0,0);transform:translate3d(0,0,0)}#background,#waves,.wave,.wave-container{pointer-events:none;position:absolute;top:0;left:0;width:100%;height:100%}#background,.wave{opacity:0}#waves,.wave{overflow:hidden}.wave,.wave-container{border-radius:50%}:host(.circle) #background,:host(.circle) #waves{border-radius:50%}:host(.circle) .wave-container{overflow:hidden}</style><div id="background"></div><div id="waves"></div></template></dom-module><script>!function(){function t(t){this.element=t,this.width=this.boundingRect.width,this.height=this.boundingRect.height,this.size=Math.max(this.width,this.height)}function i(t){this.element=t,this.color=window.getComputedStyle(t).color,this.wave=document.createElement("div"),this.waveContainer=document.createElement("div"),this.wave.style.backgroundColor=this.color,this.wave.classList.add("wave"),this.waveContainer.classList.add("wave-container"),Polymer.dom(this.waveContainer).appendChild(this.wave),this.resetInteractionState()}var e={distance:function(t,i,e,n){var s=t-e,o=i-n;return Math.sqrt(s*s+o*o)},now:window.performance&&window.performance.now?window.performance.now.bind(window.performance):Date.now};t.prototype={get boundingRect(){return this.element.getBoundingClientRect()},furthestCornerDistanceFrom:function(t,i){var n=e.distance(t,i,0,0),s=e.distance(t,i,this.width,0),o=e.distance(t,i,0,this.height),a=e.distance(t,i,this.width,this.height);return Math.max(n,s,o,a)}},i.MAX_RADIUS=300,i.prototype={get recenters(){return this.element.recenters},get center(){return this.element.center},get mouseDownElapsed(){var t;return this.mouseDownStart?(t=e.now()-this.mouseDownStart,this.mouseUpStart&&(t-=this.mouseUpElapsed),t):0},get mouseUpElapsed(){return this.mouseUpStart?e.now()-this.mouseUpStart:0},get mouseDownElapsedSeconds(){return this.mouseDownElapsed/1e3},get mouseUpElapsedSeconds(){return this.mouseUpElapsed/1e3},get mouseInteractionSeconds(){return this.mouseDownElapsedSeconds+this.mouseUpElapsedSeconds},get initialOpacity(){return this.element.initialOpacity},get opacityDecayVelocity(){return this.element.opacityDecayVelocity},get radius(){var t=this.containerMetrics.width*this.containerMetrics.width,e=this.containerMetrics.height*this.containerMetrics.height,n=1.1*Math.min(Math.sqrt(t+e),i.MAX_RADIUS)+5,s=1.1-.2*(n/i.MAX_RADIUS),o=this.mouseInteractionSeconds/s,a=n*(1-Math.pow(80,-o));return Math.abs(a)},get opacity(){return this.mouseUpStart?Math.max(0,this.initialOpacity-this.mouseUpElapsedSeconds*this.opacityDecayVelocity):this.initialOpacity},get outerOpacity(){var t=.3*this.mouseUpElapsedSeconds,i=this.opacity;return Math.max(0,Math.min(t,i))},get isOpacityFullyDecayed(){return this.opacity<.01&&this.radius>=Math.min(this.maxRadius,i.MAX_RADIUS)},get isRestingAtMaxRadius(){return this.opacity>=this.initialOpacity&&this.radius>=Math.min(this.maxRadius,i.MAX_RADIUS)},get isAnimationComplete(){return this.mouseUpStart?this.isOpacityFullyDecayed:this.isRestingAtMaxRadius},get translationFraction(){return Math.min(1,this.radius/this.containerMetrics.size*2/Math.sqrt(2))},get xNow(){return this.xEnd?this.xStart+this.translationFraction*(this.xEnd-this.xStart):this.xStart},get yNow(){return this.yEnd?this.yStart+this.translationFraction*(this.yEnd-this.yStart):this.yStart},get isMouseDown(){return this.mouseDownStart&&!this.mouseUpStart},resetInteractionState:function(){this.maxRadius=0,this.mouseDownStart=0,this.mouseUpStart=0,this.xStart=0,this.yStart=0,this.xEnd=0,this.yEnd=0,this.slideDistance=0,this.containerMetrics=new t(this.element)},draw:function(){var t,i,e;this.wave.style.opacity=this.opacity,t=this.radius/(this.containerMetrics.size/2),i=this.xNow-this.containerMetrics.width/2,e=this.yNow-this.containerMetrics.height/2,this.waveContainer.style.webkitTransform="translate("+i+"px, "+e+"px)",this.waveContainer.style.transform="translate3d("+i+"px, "+e+"px, 0)",this.wave.style.webkitTransform="scale("+t+","+t+")",this.wave.style.transform="scale3d("+t+","+t+",1)"},downAction:function(t){var i=this.containerMetrics.width/2,n=this.containerMetrics.height/2;this.resetInteractionState(),this.mouseDownStart=e.now(),this.center?(this.xStart=i,this.yStart=n,this.slideDistance=e.distance(this.xStart,this.yStart,this.xEnd,this.yEnd)):(this.xStart=t?t.detail.x-this.containerMetrics.boundingRect.left:this.containerMetrics.width/2,this.yStart=t?t.detail.y-this.containerMetrics.boundingRect.top:this.containerMetrics.height/2),this.recenters&&(this.xEnd=i,this.yEnd=n,this.slideDistance=e.distance(this.xStart,this.yStart,this.xEnd,this.yEnd)),this.maxRadius=this.containerMetrics.furthestCornerDistanceFrom(this.xStart,this.yStart),this.waveContainer.style.top=(this.containerMetrics.height-this.containerMetrics.size)/2+"px",this.waveContainer.style.left=(this.containerMetrics.width-this.containerMetrics.size)/2+"px",this.waveContainer.style.width=this.containerMetrics.size+"px",this.waveContainer.style.height=this.containerMetrics.size+"px"},upAction:function(t){this.isMouseDown&&(this.mouseUpStart=e.now())},remove:function(){Polymer.dom(this.waveContainer.parentNode).removeChild(this.waveContainer)}},Polymer({is:"paper-ripple",behaviors:[Polymer.IronA11yKeysBehavior],properties:{initialOpacity:{type:Number,value:.25},opacityDecayVelocity:{type:Number,value:.8},recenters:{type:Boolean,value:!1},center:{type:Boolean,value:!1},ripples:{type:Array,value:function(){return[]}},animating:{type:Boolean,readOnly:!0,reflectToAttribute:!0,value:!1},holdDown:{type:Boolean,value:!1,observer:"_holdDownChanged"},noink:{type:Boolean,value:!1},_animating:{type:Boolean},_boundAnimate:{type:Function,value:function(){return this.animate.bind(this)}}},get target(){return this.keyEventTarget},keyBindings:{"enter:keydown":"_onEnterKeydown","space:keydown":"_onSpaceKeydown","space:keyup":"_onSpaceKeyup"},attached:function(){11==this.parentNode.nodeType?this.keyEventTarget=Polymer.dom(this).getOwnerRoot().host:this.keyEventTarget=this.parentNode;var t=this.keyEventTarget;this.listen(t,"up","uiUpAction"),this.listen(t,"down","uiDownAction")},detached:function(){this.unlisten(this.keyEventTarget,"up","uiUpAction"),this.unlisten(this.keyEventTarget,"down","uiDownAction"),this.keyEventTarget=null},get shouldKeepAnimating(){for(var t=0;t<this.ripples.length;++t)if(!this.ripples[t].isAnimationComplete)return!0;return!1},simulatedRipple:function(){this.downAction(null),this.async(function(){this.upAction()},1)},uiDownAction:function(t){this.noink||this.downAction(t)},downAction:function(t){if(!(this.holdDown&&this.ripples.length>0)){var i=this.addRipple();i.downAction(t),this._animating||(this._animating=!0,this.animate())}},uiUpAction:function(t){this.noink||this.upAction(t)},upAction:function(t){this.holdDown||(this.ripples.forEach(function(i){i.upAction(t)}),this._animating=!0,this.animate())},onAnimationComplete:function(){this._animating=!1,this.$.background.style.backgroundColor=null,this.fire("transitionend")},addRipple:function(){var t=new i(this);return Polymer.dom(this.$.waves).appendChild(t.waveContainer),this.$.background.style.backgroundColor=t.color,this.ripples.push(t),this._setAnimating(!0),t},removeRipple:function(t){var i=this.ripples.indexOf(t);i<0||(this.ripples.splice(i,1),t.remove(),this.ripples.length||this._setAnimating(!1))},animate:function(){if(this._animating){var t,i;for(t=0;t<this.ripples.length;++t)i=this.ripples[t],i.draw(),this.$.background.style.opacity=i.outerOpacity,i.isOpacityFullyDecayed&&!i.isRestingAtMaxRadius&&this.removeRipple(i);this.shouldKeepAnimating||0!==this.ripples.length?window.requestAnimationFrame(this._boundAnimate):this.onAnimationComplete()}},_onEnterKeydown:function(){this.uiDownAction(),this.async(this.uiUpAction,1)},_onSpaceKeydown:function(){this.uiDownAction()},_onSpaceKeyup:function(){this.uiUpAction()},_holdDownChanged:function(t,i){void 0!==i&&(t?this.downAction():this.upAction())}})}()</script><script>Polymer.PaperRippleBehavior={properties:{noink:{type:Boolean,observer:"_noinkChanged"},_rippleContainer:{type:Object}},_buttonStateChanged:function(){this.focused&&this.ensureRipple()},_downHandler:function(e){Polymer.IronButtonStateImpl._downHandler.call(this,e),this.pressed&&this.ensureRipple(e)},ensureRipple:function(e){if(!this.hasRipple()){this._ripple=this._createRipple(),this._ripple.noink=this.noink;var i=this._rippleContainer||this.root;if(i&&Polymer.dom(i).appendChild(this._ripple),e){var n=Polymer.dom(this._rippleContainer||this),t=Polymer.dom(e).rootTarget;n.deepContains(t)&&this._ripple.uiDownAction(e)}}},getRipple:function(){return this.ensureRipple(),this._ripple},hasRipple:function(){return Boolean(this._ripple)},_createRipple:function(){return document.createElement("paper-ripple")},_noinkChanged:function(e){this.hasRipple()&&(this._ripple.noink=e)}}</script><script>Polymer.PaperInkyFocusBehaviorImpl={observers:["_focusedChanged(receivedFocusFromKeyboard)"],_focusedChanged:function(e){e&&this.ensureRipple(),this.hasRipple()&&(this._ripple.holdDown=e)},_createRipple:function(){var e=Polymer.PaperRippleBehavior._createRipple();return e.id="ink",e.setAttribute("center",""),e.classList.add("circle"),e}},Polymer.PaperInkyFocusBehavior=[Polymer.IronButtonState,Polymer.IronControlState,Polymer.PaperRippleBehavior,Polymer.PaperInkyFocusBehaviorImpl]</script><script>Polymer.PaperCheckedElementBehaviorImpl={_checkedChanged:function(){Polymer.IronCheckedElementBehaviorImpl._checkedChanged.call(this),this.hasRipple()&&(this.checked?this._ripple.setAttribute("checked",""):this._ripple.removeAttribute("checked"))},_buttonStateChanged:function(){Polymer.PaperRippleBehavior._buttonStateChanged.call(this),this.disabled||this.isAttached&&(this.checked=this.active)}},Polymer.PaperCheckedElementBehavior=[Polymer.PaperInkyFocusBehavior,Polymer.IronCheckedElementBehavior,Polymer.PaperCheckedElementBehaviorImpl]</script><dom-module id="paper-checkbox" assetpath="../bower_components/paper-checkbox/"><template strip-whitespace=""><style>:host{display:inline-block;white-space:nowrap;cursor:pointer;--calculated-paper-checkbox-size:var(--paper-checkbox-size, 18px);--calculated-paper-checkbox-ink-size:var(--paper-checkbox-ink-size, -1px);@apply(--paper-font-common-base);line-height:0;-webkit-tap-highlight-color:transparent}:host([hidden]){display:none!important}:host(:focus){outline:0}.hidden{display:none}#checkboxContainer{display:inline-block;position:relative;width:var(--calculated-paper-checkbox-size);height:var(--calculated-paper-checkbox-size);min-width:var(--calculated-paper-checkbox-size);margin:var(--paper-checkbox-margin,initial);vertical-align:var(--paper-checkbox-vertical-align,middle);background-color:var(--paper-checkbox-unchecked-background-color,transparent)}#ink{position:absolute;top:calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size))/ 2);left:calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size))/ 2);width:var(--calculated-paper-checkbox-ink-size);height:var(--calculated-paper-checkbox-ink-size);color:var(--paper-checkbox-unchecked-ink-color,var(--primary-text-color));opacity:.6;pointer-events:none}:host-context([dir=rtl]) #ink{right:calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size))/ 2);left:auto}#ink[checked]{color:var(--paper-checkbox-checked-ink-color,var(--primary-color))}#checkbox{position:relative;box-sizing:border-box;height:100%;border:solid 2px;border-color:var(--paper-checkbox-unchecked-color,var(--primary-text-color));border-radius:2px;pointer-events:none;-webkit-transition:background-color 140ms,border-color 140ms;transition:background-color 140ms,border-color 140ms}#checkbox.checked #checkmark{-webkit-animation:checkmark-expand 140ms ease-out forwards;animation:checkmark-expand 140ms ease-out forwards}@-webkit-keyframes checkmark-expand{0%{-webkit-transform:scale(0,0) rotate(45deg)}100%{-webkit-transform:scale(1,1) rotate(45deg)}}@keyframes checkmark-expand{0%{transform:scale(0,0) rotate(45deg)}100%{transform:scale(1,1) rotate(45deg)}}#checkbox.checked{background-color:var(--paper-checkbox-checked-color,var(--primary-color));border-color:var(--paper-checkbox-checked-color,var(--primary-color))}#checkmark{position:absolute;width:36%;height:70%;border-style:solid;border-top:none;border-left:none;border-right-width:calc(2/15 * var(--calculated-paper-checkbox-size));border-bottom-width:calc(2/15 * var(--calculated-paper-checkbox-size));border-color:var(--paper-checkbox-checkmark-color,#fff);-webkit-transform-origin:97% 86%;transform-origin:97% 86%;box-sizing:content-box}:host-context([dir=rtl]) #checkmark{-webkit-transform-origin:50% 14%;transform-origin:50% 14%}#checkboxLabel{position:relative;display:inline-block;vertical-align:middle;padding-left:var(--paper-checkbox-label-spacing,8px);white-space:normal;line-height:normal;color:var(--paper-checkbox-label-color,var(--primary-text-color));@apply(--paper-checkbox-label)}:host([checked]) #checkboxLabel{color:var(--paper-checkbox-label-checked-color,var(--paper-checkbox-label-color,var(--primary-text-color)));@apply(--paper-checkbox-label-checked)}:host-context([dir=rtl]) #checkboxLabel{padding-right:var(--paper-checkbox-label-spacing,8px);padding-left:0}#checkboxLabel[hidden]{display:none}:host([disabled]) #checkbox{opacity:.5;border-color:var(--paper-checkbox-unchecked-color,var(--primary-text-color))}:host([disabled][checked]) #checkbox{background-color:var(--paper-checkbox-unchecked-color,var(--primary-text-color));opacity:.5}:host([disabled]) #checkboxLabel{opacity:.65}#checkbox.invalid:not(.checked){border-color:var(--paper-checkbox-error-color,var(--error-color))}</style><div id="checkboxContainer"><div id="checkbox" class$="[[_computeCheckboxClass(checked, invalid)]]"><div id="checkmark" class$="[[_computeCheckmarkClass(checked)]]"></div></div></div><div id="checkboxLabel"><content></content></div></template><script>Polymer({is:"paper-checkbox",behaviors:[Polymer.PaperCheckedElementBehavior],hostAttributes:{role:"checkbox","aria-checked":!1,tabindex:0},properties:{ariaActiveAttribute:{type:String,value:"aria-checked"}},attached:function(){var e=this.getComputedStyleValue("--calculated-paper-checkbox-ink-size");if("-1px"===e){var t=parseFloat(this.getComputedStyleValue("--calculated-paper-checkbox-size")),a=Math.floor(8/3*t);a%2!==t%2&&a++,this.customStyle["--paper-checkbox-ink-size"]=a+"px",this.updateStyles()}},_computeCheckboxClass:function(e,t){var a="";return e&&(a+="checked "),t&&(a+="invalid"),a},_computeCheckmarkClass:function(e){return e?"":"hidden"},_createRipple:function(){return this._rippleContainer=this.$.checkboxContainer,Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this)}})</script></dom-module><script>Polymer.PaperButtonBehaviorImpl={properties:{elevation:{type:Number,reflectToAttribute:!0,readOnly:!0}},observers:["_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)","_computeKeyboardClass(receivedFocusFromKeyboard)"],hostAttributes:{role:"button",tabindex:"0",animated:!0},_calculateElevation:function(){var e=1;this.disabled?e=0:this.active||this.pressed?e=4:this.receivedFocusFromKeyboard&&(e=3),this._setElevation(e)},_computeKeyboardClass:function(e){this.toggleClass("keyboard-focus",e)},_spaceKeyDownHandler:function(e){Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this,e),this.hasRipple()&&this.getRipple().ripples.length<1&&this._ripple.uiDownAction()},_spaceKeyUpHandler:function(e){Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this,e),this.hasRipple()&&this._ripple.uiUpAction()}},Polymer.PaperButtonBehavior=[Polymer.IronButtonState,Polymer.IronControlState,Polymer.PaperRippleBehavior,Polymer.PaperButtonBehaviorImpl]</script><style is="custom-style">:root{--shadow-transition:{transition:box-shadow .28s cubic-bezier(.4,0,.2,1)};--shadow-none:{box-shadow:none};--shadow-elevation-2dp:{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)};--shadow-elevation-3dp:{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12),0 3px 3px -2px rgba(0,0,0,.4)};--shadow-elevation-4dp:{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.4)};--shadow-elevation-6dp:{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4)};--shadow-elevation-8dp:{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4)};--shadow-elevation-12dp:{box-shadow:0 12px 16px 1px rgba(0,0,0,.14),0 4px 22px 3px rgba(0,0,0,.12),0 6px 7px -4px rgba(0,0,0,.4)};--shadow-elevation-16dp:{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.4)};--shadow-elevation-24dp:{box-shadow:0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12),0 11px 15px -7px rgba(0,0,0,.4)};}</style><dom-module id="paper-material-shared-styles" assetpath="../bower_components/paper-material/"><template><style>:host{display:block;position:relative}:host([elevation="1"]){@apply(--shadow-elevation-2dp)}:host([elevation="2"]){@apply(--shadow-elevation-4dp)}:host([elevation="3"]){@apply(--shadow-elevation-6dp)}:host([elevation="4"]){@apply(--shadow-elevation-8dp)}:host([elevation="5"]){@apply(--shadow-elevation-16dp)}</style></template></dom-module><dom-module id="paper-button" assetpath="../bower_components/paper-button/"><template strip-whitespace=""><style include="paper-material-shared-styles">:host{@apply(--layout-inline);@apply(--layout-center-center);position:relative;box-sizing:border-box;min-width:5.14em;margin:0 .29em;background:0 0;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;font:inherit;text-transform:uppercase;outline-width:0;border-radius:3px;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;cursor:pointer;z-index:0;padding:.7em .57em;@apply(--paper-font-common-base);@apply(--paper-button)}:host([hidden]){display:none!important}:host([raised].keyboard-focus){font-weight:700;@apply(--paper-button-raised-keyboard-focus)}:host(:not([raised]).keyboard-focus){font-weight:700;@apply(--paper-button-flat-keyboard-focus)}:host([disabled]){background:#eaeaea;color:#a8a8a8;cursor:auto;pointer-events:none;@apply(--paper-button-disabled)}:host([animated]){@apply(--shadow-transition)}paper-ripple{color:var(--paper-button-ink-color)}</style><content></content></template><script>Polymer({is:"paper-button",behaviors:[Polymer.PaperButtonBehavior],properties:{raised:{type:Boolean,reflectToAttribute:!0,value:!1,observer:"_calculateElevation"}},_calculateElevation:function(){this.raised?Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this):this._setElevation(0)}})</script></dom-module><dom-module id="paper-input-container" assetpath="../bower_components/paper-input/"><template><style>:host{display:block;padding:8px 0;@apply(--paper-input-container)}:host([inline]){display:inline-block}:host([disabled]){pointer-events:none;opacity:.33;@apply(--paper-input-container-disabled)}:host([hidden]){display:none!important}.floated-label-placeholder{@apply(--paper-font-caption)}.underline{position:relative}.focused-line{@apply(--layout-fit);background:var(--paper-input-container-focus-color,--primary-color);height:2px;-webkit-transform-origin:center center;transform-origin:center center;-webkit-transform:scale3d(0,1,1);transform:scale3d(0,1,1);@apply(--paper-input-container-underline-focus)}.underline.is-highlighted .focused-line{-webkit-transform:none;transform:none;-webkit-transition:-webkit-transform .25s;transition:transform .25s;@apply(--paper-transition-easing)}.underline.is-invalid .focused-line{background:var(--paper-input-container-invalid-color,--error-color);-webkit-transform:none;transform:none;-webkit-transition:-webkit-transform .25s;transition:transform .25s;@apply(--paper-transition-easing)}.unfocused-line{@apply(--layout-fit);background:var(--paper-input-container-color,--secondary-text-color);height:1px;@apply(--paper-input-container-underline)}:host([disabled]) .unfocused-line{border-bottom:1px dashed;border-color:var(--paper-input-container-color,--secondary-text-color);background:0 0;@apply(--paper-input-container-underline-disabled)}.label-and-input-container{@apply(--layout-flex-auto);@apply(--layout-relative);width:100%;max-width:100%}.input-content{@apply(--layout-horizontal);@apply(--layout-center);position:relative}.input-content ::content .paper-input-label,.input-content ::content label{position:absolute;top:0;right:0;left:0;width:100%;font:inherit;color:var(--paper-input-container-color,--secondary-text-color);-webkit-transition:-webkit-transform .25s,width .25s;transition:transform .25s,width .25s;-webkit-transform-origin:left top;transform-origin:left top;@apply(--paper-font-common-nowrap);@apply(--paper-font-subhead);@apply(--paper-input-container-label);@apply(--paper-transition-easing)}.input-content.label-is-floating ::content .paper-input-label,.input-content.label-is-floating ::content label{-webkit-transform:translateY(-75%) scale(.75);transform:translateY(-75%) scale(.75);width:133%;@apply(--paper-input-container-label-floating)}:host-context([dir=rtl]) .input-content.label-is-floating ::content .paper-input-label,:host-context([dir=rtl]) .input-content.label-is-floating ::content label{width:100%;-webkit-transform-origin:right top;transform-origin:right top}.input-content.label-is-highlighted ::content .paper-input-label,.input-content.label-is-highlighted ::content label{color:var(--paper-input-container-focus-color,--primary-color);@apply(--paper-input-container-label-focus)}.input-content.is-invalid ::content .paper-input-label,.input-content.is-invalid ::content label{color:var(--paper-input-container-invalid-color,--error-color)}.input-content.label-is-hidden ::content .paper-input-label,.input-content.label-is-hidden ::content label{visibility:hidden}.input-content ::content .paper-input-input,.input-content ::content input,.input-content ::content iron-autogrow-textarea,.input-content ::content textarea{position:relative;outline:0;box-shadow:none;padding:0;width:100%;max-width:100%;background:0 0;border:none;color:var(--paper-input-container-input-color,--primary-text-color);-webkit-appearance:none;text-align:inherit;vertical-align:bottom;@apply(--paper-font-subhead);@apply(--paper-input-container-input)}::content [prefix]{@apply(--paper-font-subhead);@apply(--paper-input-prefix);@apply(--layout-flex-none)}::content [suffix]{@apply(--paper-font-subhead);@apply(--paper-input-suffix);@apply(--layout-flex-none)}.input-content ::content input{min-width:0}.input-content ::content textarea{resize:none}.add-on-content{position:relative}.add-on-content.is-invalid ::content *{color:var(--paper-input-container-invalid-color,--error-color)}.add-on-content.is-highlighted ::content *{color:var(--paper-input-container-focus-color,--primary-color)}</style><template is="dom-if" if="[[!noLabelFloat]]"><div class="floated-label-placeholder" aria-hidden="true"> </div></template><div class$="[[_computeInputContentClass(noLabelFloat,alwaysFloatLabel,focused,invalid,_inputHasContent)]]"><content select="[prefix]" id="prefix"></content><div class="label-and-input-container" id="labelAndInputContainer"><content select=":not([add-on]):not([prefix]):not([suffix])"></content></div><content select="[suffix]"></content></div><div class$="[[_computeUnderlineClass(focused,invalid)]]"><div class="unfocused-line"></div><div class="focused-line"></div></div><div class$="[[_computeAddOnContentClass(focused,invalid)]]"><content id="addOnContent" select="[add-on]"></content></div></template></dom-module><script>Polymer({is:"paper-input-container",properties:{noLabelFloat:{type:Boolean,value:!1},alwaysFloatLabel:{type:Boolean,value:!1},attrForValue:{type:String,value:"bind-value"},autoValidate:{type:Boolean,value:!1},invalid:{observer:"_invalidChanged",type:Boolean,value:!1},focused:{readOnly:!0,type:Boolean,value:!1,notify:!0},_addons:{type:Array},_inputHasContent:{type:Boolean,value:!1},_inputSelector:{type:String,value:"input,textarea,.paper-input-input"},_boundOnFocus:{type:Function,value:function(){return this._onFocus.bind(this)}},_boundOnBlur:{type:Function,value:function(){return this._onBlur.bind(this)}},_boundOnInput:{type:Function,value:function(){return this._onInput.bind(this)}},_boundValueChanged:{type:Function,value:function(){return this._onValueChanged.bind(this)}}},listeners:{"addon-attached":"_onAddonAttached","iron-input-validate":"_onIronInputValidate"},get _valueChangedEvent(){return this.attrForValue+"-changed"},get _propertyForValue(){return Polymer.CaseMap.dashToCamelCase(this.attrForValue)},get _inputElement(){return Polymer.dom(this).querySelector(this._inputSelector)},get _inputElementValue(){return this._inputElement[this._propertyForValue]||this._inputElement.value},ready:function(){this._addons||(this._addons=[]),this.addEventListener("focus",this._boundOnFocus,!0),this.addEventListener("blur",this._boundOnBlur,!0)},attached:function(){this.attrForValue?this._inputElement.addEventListener(this._valueChangedEvent,this._boundValueChanged):this.addEventListener("input",this._onInput),""!=this._inputElementValue?this._handleValueAndAutoValidate(this._inputElement):this._handleValue(this._inputElement)},_onAddonAttached:function(t){this._addons||(this._addons=[]);var n=t.target;this._addons.indexOf(n)===-1&&(this._addons.push(n),this.isAttached&&this._handleValue(this._inputElement))},_onFocus:function(){this._setFocused(!0)},_onBlur:function(){this._setFocused(!1),this._handleValueAndAutoValidate(this._inputElement)},_onInput:function(t){this._handleValueAndAutoValidate(t.target)},_onValueChanged:function(t){this._handleValueAndAutoValidate(t.target)},_handleValue:function(t){var n=this._inputElementValue;n||0===n||"number"===t.type&&!t.checkValidity()?this._inputHasContent=!0:this._inputHasContent=!1,this.updateAddons({inputElement:t,value:n,invalid:this.invalid})},_handleValueAndAutoValidate:function(t){if(this.autoValidate){var n;n=t.validate?t.validate(this._inputElementValue):t.checkValidity(),this.invalid=!n}this._handleValue(t)},_onIronInputValidate:function(t){this.invalid=this._inputElement.invalid},_invalidChanged:function(){this._addons&&this.updateAddons({invalid:this.invalid})},updateAddons:function(t){for(var n,e=0;n=this._addons[e];e++)n.update(t)},_computeInputContentClass:function(t,n,e,i,a){var u="input-content";if(t)a&&(u+=" label-is-hidden");else{var o=this.querySelector("label");n||a?(u+=" label-is-floating",this.$.labelAndInputContainer.style.position="static",i?u+=" is-invalid":e&&(u+=" label-is-highlighted")):o&&(this.$.labelAndInputContainer.style.position="relative")}return u},_computeUnderlineClass:function(t,n){var e="underline";return n?e+=" is-invalid":t&&(e+=" is-highlighted"),e},_computeAddOnContentClass:function(t,n){var e="add-on-content";return n?e+=" is-invalid":t&&(e+=" is-highlighted"),e}})</script><script>Polymer.PaperInputAddonBehavior={hostAttributes:{"add-on":""},attached:function(){this.fire("addon-attached")},update:function(t){}}</script><dom-module id="paper-input-error" assetpath="../bower_components/paper-input/"><template><style>:host{display:inline-block;visibility:hidden;color:var(--paper-input-container-invalid-color,--error-color);@apply(--paper-font-caption);@apply(--paper-input-error);position:absolute;left:0;right:0}:host([invalid]){visibility:visible};</style><content></content></template></dom-module><script>Polymer({is:"paper-input-error",behaviors:[Polymer.PaperInputAddonBehavior],properties:{invalid:{readOnly:!0,reflectToAttribute:!0,type:Boolean}},update:function(e){this._setInvalid(e.invalid)}})</script><dom-module id="iron-a11y-announcer" assetpath="../bower_components/iron-a11y-announcer/"><template><style>:host{display:inline-block;position:fixed;clip:rect(0,0,0,0)}</style><div aria-live$="[[mode]]">[[_text]]</div></template><script>!function(){"use strict";Polymer.IronA11yAnnouncer=Polymer({is:"iron-a11y-announcer",properties:{mode:{type:String,value:"polite"},_text:{type:String,value:""}},created:function(){Polymer.IronA11yAnnouncer.instance||(Polymer.IronA11yAnnouncer.instance=this),document.body.addEventListener("iron-announce",this._onIronAnnounce.bind(this))},announce:function(n){this._text="",this.async(function(){this._text=n},100)},_onIronAnnounce:function(n){n.detail&&n.detail.text&&this.announce(n.detail.text)}}),Polymer.IronA11yAnnouncer.instance=null,Polymer.IronA11yAnnouncer.requestAvailability=function(){Polymer.IronA11yAnnouncer.instance||(Polymer.IronA11yAnnouncer.instance=document.createElement("iron-a11y-announcer")),document.body.appendChild(Polymer.IronA11yAnnouncer.instance)}}()</script></dom-module><script>Polymer({is:"iron-input",extends:"input",behaviors:[Polymer.IronValidatableBehavior],properties:{bindValue:{observer:"_bindValueChanged",type:String},preventInvalidInput:{type:Boolean},allowedPattern:{type:String,observer:"_allowedPatternChanged"},_previousValidInput:{type:String,value:""},_patternAlreadyChecked:{type:Boolean,value:!1}},listeners:{input:"_onInput",keypress:"_onKeypress"},registered:function(){this._canDispatchEventOnDisabled()||(this._origDispatchEvent=this.dispatchEvent,this.dispatchEvent=this._dispatchEventFirefoxIE)},created:function(){Polymer.IronA11yAnnouncer.requestAvailability()},_canDispatchEventOnDisabled:function(){var e=document.createElement("input"),t=!1;e.disabled=!0,e.addEventListener("feature-check-dispatch-event",function(){t=!0});try{e.dispatchEvent(new Event("feature-check-dispatch-event"))}catch(e){}return t},_dispatchEventFirefoxIE:function(){var e=this.disabled;this.disabled=!1,this._origDispatchEvent.apply(this,arguments),this.disabled=e},get _patternRegExp(){var e;if(this.allowedPattern)e=new RegExp(this.allowedPattern);else switch(this.type){case"number":e=/[0-9.,e-]/}return e},ready:function(){this.bindValue=this.value},_bindValueChanged:function(){this.value!==this.bindValue&&(this.value=this.bindValue||0===this.bindValue||this.bindValue===!1?this.bindValue:""),this.fire("bind-value-changed",{value:this.bindValue})},_allowedPatternChanged:function(){this.preventInvalidInput=!!this.allowedPattern},_onInput:function(){if(this.preventInvalidInput&&!this._patternAlreadyChecked){var e=this._checkPatternValidity();e||(this._announceInvalidCharacter("Invalid string of characters not entered."),this.value=this._previousValidInput)}this.bindValue=this.value,this._previousValidInput=this.value,this._patternAlreadyChecked=!1},_isPrintable:function(e){var t=8==e.keyCode||9==e.keyCode||13==e.keyCode||27==e.keyCode,i=19==e.keyCode||20==e.keyCode||45==e.keyCode||46==e.keyCode||144==e.keyCode||145==e.keyCode||e.keyCode>32&&e.keyCode<41||e.keyCode>111&&e.keyCode<124;return!(t||0==e.charCode&&i)},_onKeypress:function(e){if(this.preventInvalidInput||"number"===this.type){var t=this._patternRegExp;if(t&&!(e.metaKey||e.ctrlKey||e.altKey)){this._patternAlreadyChecked=!0;var i=String.fromCharCode(e.charCode);this._isPrintable(e)&&!t.test(i)&&(e.preventDefault(),this._announceInvalidCharacter("Invalid character "+i+" not entered."))}}},_checkPatternValidity:function(){var e=this._patternRegExp;if(!e)return!0;for(var t=0;t<this.value.length;t++)if(!e.test(this.value[t]))return!1;return!0},validate:function(){var e=this.checkValidity();return e&&(this.required&&""===this.value?e=!1:this.hasValidator()&&(e=Polymer.IronValidatableBehavior.validate.call(this,this.value))),this.invalid=!e,this.fire("iron-input-validate"),e},_announceInvalidCharacter:function(e){this.fire("iron-announce",{text:e})}})</script><dom-module id="login-form" assetpath="layouts/"><template><style is="custom-style" include="iron-flex iron-positioning"></style><style>:host{white-space:nowrap}#passwordDecorator{display:block;margin-bottom:16px}paper-checkbox{margin-right:8px}paper-button{margin-left:72px}.interact{height:125px}#validatebox{margin-top:16px;text-align:center}.validatemessage{margin-top:10px}</style><div class="layout vertical center center-center fit"><img src="/static/icons/favicon-192x192.png" height="192"> <a href="#" id="hideKeyboardOnFocus"></a><div class="interact"><div id="loginform" hidden$="[[showLoading]]"><paper-input-container id="passwordDecorator" invalid="[[isInvalid]]"><label>Password</label><input is="iron-input" type="password" id="passwordInput"><paper-input-error invalid="[[isInvalid]]">[[errorMessage]]</paper-input-error></paper-input-container><div class="layout horizontal center"><paper-checkbox for="" id="rememberLogin">Remember</paper-checkbox><paper-button id="loginButton">Log In</paper-button></div></div><div id="validatebox" hidden$="[[!showLoading]]"><paper-spinner active="true"></paper-spinner><br><div class="validatemessage">Loading data</div></div></div></div></template></dom-module><script>Polymer({is:"login-form",behaviors:[window.hassBehavior],properties:{hass:{type:Object},errorMessage:{type:String,bindNuclear:function(e){return e.authGetters.attemptErrorMessage}},isInvalid:{type:Boolean,bindNuclear:function(e){return e.authGetters.isInvalidAttempt}},isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:function(e){return e.authGetters.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(){window.removeInitMsg()},computeShowSpinner:function(e,i){return e||i},validatingChanged:function(e,i){e||i||(this.$.passwordInput.value="")},isValidatingChanged:function(e){e||this.async(function(){this.$.passwordInput.focus()}.bind(this),10)},passwordKeyDown:function(e){13===e.keyCode?(this.validatePassword(),e.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),window.validateAuth(this.$.passwordInput.value,this.$.rememberLogin.checked)}})</script><script>Polymer({is:"iron-media-query",properties:{queryMatches:{type:Boolean,value:!1,readOnly:!0,notify:!0},query:{type:String,observer:"queryChanged"},full:{type:Boolean,value:!1},_boundMQHandler:{value:function(){return this.queryHandler.bind(this)}},_mq:{value:null}},attached:function(){this.style.display="none",this.queryChanged()},detached:function(){this._remove()},_add:function(){this._mq&&this._mq.addListener(this._boundMQHandler)},_remove:function(){this._mq&&this._mq.removeListener(this._boundMQHandler),this._mq=null},queryChanged:function(){this._remove();var e=this.query;e&&(this.full||"("===e[0]||(e="("+e+")"),this._mq=window.matchMedia(e),this._add(),this.queryHandler(this._mq))},queryHandler:function(e){this._setQueryMatches(e.matches)}})</script><script>Polymer.IronSelection=function(e){this.selection=[],this.selectCallback=e},Polymer.IronSelection.prototype={get:function(){return this.multi?this.selection.slice():this.selection[0]},clear:function(e){this.selection.slice().forEach(function(t){(!e||e.indexOf(t)<0)&&this.setItemSelected(t,!1)},this)},isSelected:function(e){return this.selection.indexOf(e)>=0},setItemSelected:function(e,t){if(null!=e&&t!==this.isSelected(e)){if(t)this.selection.push(e);else{var i=this.selection.indexOf(e);i>=0&&this.selection.splice(i,1)}this.selectCallback&&this.selectCallback(e,t)}},select:function(e){this.multi?this.toggle(e):this.get()!==e&&(this.setItemSelected(this.get(),!1),this.setItemSelected(e,!0))},toggle:function(e){this.setItemSelected(e,!this.isSelected(e))}}</script><script>Polymer.IronSelectableBehavior={properties:{attrForSelected:{type:String,value:null},selected:{type:String,notify:!0},selectedItem:{type:Object,readOnly:!0,notify:!0},activateEvent:{type:String,value:"tap",observer:"_activateEventChanged"},selectable:String,selectedClass:{type:String,value:"iron-selected"},selectedAttribute:{type:String,value:null},fallbackSelection:{type:String,value:null},items:{type:Array,readOnly:!0,notify:!0,value:function(){return[]}},_excludedLocalNames:{type:Object,value:function(){return{template:1}}}},observers:["_updateAttrForSelected(attrForSelected)","_updateSelected(selected)","_checkFallback(fallbackSelection)"],created:function(){this._bindFilterItem=this._filterItem.bind(this),this._selection=new Polymer.IronSelection(this._applySelection.bind(this))},attached:function(){this._observer=this._observeItems(this),this._updateItems(),this._shouldUpdateSelection||this._updateSelected(),this._addListener(this.activateEvent)},detached:function(){this._observer&&Polymer.dom(this).unobserveNodes(this._observer),this._removeListener(this.activateEvent)},indexOf:function(e){return this.items.indexOf(e)},select:function(e){this.selected=e},selectPrevious:function(){var e=this.items.length,t=(Number(this._valueToIndex(this.selected))-1+e)%e;this.selected=this._indexToValue(t)},selectNext:function(){var e=(Number(this._valueToIndex(this.selected))+1)%this.items.length;this.selected=this._indexToValue(e)},selectIndex:function(e){this.select(this._indexToValue(e))},forceSynchronousItemUpdate:function(){this._updateItems()},get _shouldUpdateSelection(){return null!=this.selected},_checkFallback:function(){this._shouldUpdateSelection&&this._updateSelected()},_addListener:function(e){this.listen(this,e,"_activateHandler")},_removeListener:function(e){this.unlisten(this,e,"_activateHandler")},_activateEventChanged:function(e,t){this._removeListener(t),this._addListener(e)},_updateItems:function(){var e=Polymer.dom(this).queryDistributedElements(this.selectable||"*");e=Array.prototype.filter.call(e,this._bindFilterItem),this._setItems(e)},_updateAttrForSelected:function(){this._shouldUpdateSelection&&(this.selected=this._indexToValue(this.indexOf(this.selectedItem)))},_updateSelected:function(){this._selectSelected(this.selected)},_selectSelected:function(e){this._selection.select(this._valueToItem(this.selected)),this.fallbackSelection&&this.items.length&&void 0===this._selection.get()&&(this.selected=this.fallbackSelection)},_filterItem:function(e){return!this._excludedLocalNames[e.localName]},_valueToItem:function(e){return null==e?null:this.items[this._valueToIndex(e)]},_valueToIndex:function(e){if(!this.attrForSelected)return Number(e);for(var t,i=0;t=this.items[i];i++)if(this._valueForItem(t)==e)return i},_indexToValue:function(e){if(!this.attrForSelected)return e;var t=this.items[e];return t?this._valueForItem(t):void 0},_valueForItem:function(e){var t=e[Polymer.CaseMap.dashToCamelCase(this.attrForSelected)];return void 0!=t?t:e.getAttribute(this.attrForSelected)},_applySelection:function(e,t){this.selectedClass&&this.toggleClass(this.selectedClass,t,e),this.selectedAttribute&&this.toggleAttribute(this.selectedAttribute,t,e),this._selectionChange(),this.fire("iron-"+(t?"select":"deselect"),{item:e})},_selectionChange:function(){this._setSelectedItem(this._selection.get())},_observeItems:function(e){return Polymer.dom(e).observeNodes(function(e){this._updateItems(),this._shouldUpdateSelection&&this._updateSelected(),this.fire("iron-items-changed",e,{bubbles:!1,cancelable:!1})})},_activateHandler:function(e){for(var t=e.target,i=this.items;t&&t!=this;){var s=i.indexOf(t);if(s>=0){var n=this._indexToValue(s);return void this._itemActivate(n,t)}t=t.parentNode}},_itemActivate:function(e,t){this.fire("iron-activate",{selected:e,item:t},{cancelable:!0}).defaultPrevented||this.select(e)}}</script><script>Polymer.IronMultiSelectableBehaviorImpl={properties:{multi:{type:Boolean,value:!1,observer:"multiChanged"},selectedValues:{type:Array,notify:!0},selectedItems:{type:Array,readOnly:!0,notify:!0}},observers:["_updateSelected(selectedValues.splices)"],select:function(e){this.multi?this.selectedValues?this._toggleSelected(e):this.selectedValues=[e]:this.selected=e},multiChanged:function(e){this._selection.multi=e},get _shouldUpdateSelection(){return null!=this.selected||null!=this.selectedValues&&this.selectedValues.length},_updateAttrForSelected:function(){this.multi?this._shouldUpdateSelection&&(this.selectedValues=this.selectedItems.map(function(e){return this._indexToValue(this.indexOf(e))},this).filter(function(e){return null!=e},this)):Polymer.IronSelectableBehavior._updateAttrForSelected.apply(this)},_updateSelected:function(){this.multi?this._selectMulti(this.selectedValues):this._selectSelected(this.selected)},_selectMulti:function(e){if(e){var t=this._valuesToItems(e);this._selection.clear(t);for(var l=0;l<t.length;l++)this._selection.setItemSelected(t[l],!0);if(this.fallbackSelection&&this.items.length&&!this._selection.get().length){var s=this._valueToItem(this.fallbackSelection);s&&(this.selectedValues=[this.fallbackSelection])}}else this._selection.clear()},_selectionChange:function(){var e=this._selection.get();this.multi?this._setSelectedItems(e):(this._setSelectedItems([e]),this._setSelectedItem(e))},_toggleSelected:function(e){var t=this.selectedValues.indexOf(e),l=t<0;l?this.push("selectedValues",e):this.splice("selectedValues",t,1)},_valuesToItems:function(e){return null==e?null:e.map(function(e){return this._valueToItem(e)},this)}},Polymer.IronMultiSelectableBehavior=[Polymer.IronSelectableBehavior,Polymer.IronMultiSelectableBehaviorImpl]</script><script>Polymer({is:"iron-selector",behaviors:[Polymer.IronMultiSelectableBehavior]})</script><script>Polymer.IronResizableBehavior={properties:{_parentResizable:{type:Object,observer:"_parentResizableChanged"},_notifyingDescendant:{type:Boolean,value:!1}},listeners:{"iron-request-resize-notifications":"_onIronRequestResizeNotifications"},created:function(){this._interestedResizables=[],this._boundNotifyResize=this.notifyResize.bind(this)},attached:function(){this.fire("iron-request-resize-notifications",null,{node:this,bubbles:!0,cancelable:!0}),this._parentResizable||(window.addEventListener("resize",this._boundNotifyResize),this.notifyResize())},detached:function(){this._parentResizable?this._parentResizable.stopResizeNotificationsFor(this):window.removeEventListener("resize",this._boundNotifyResize),this._parentResizable=null},notifyResize:function(){this.isAttached&&(this._interestedResizables.forEach(function(e){this.resizerShouldNotify(e)&&this._notifyDescendant(e)},this),this._fireResize())},assignParentResizable:function(e){this._parentResizable=e},stopResizeNotificationsFor:function(e){var i=this._interestedResizables.indexOf(e);i>-1&&(this._interestedResizables.splice(i,1),this.unlisten(e,"iron-resize","_onDescendantIronResize"))},resizerShouldNotify:function(e){return!0},_onDescendantIronResize:function(e){return this._notifyingDescendant?void e.stopPropagation():void(Polymer.Settings.useShadow||this._fireResize())},_fireResize:function(){this.fire("iron-resize",null,{node:this,bubbles:!1})},_onIronRequestResizeNotifications:function(e){var i=e.path?e.path[0]:e.target;i!==this&&(this._interestedResizables.indexOf(i)===-1&&(this._interestedResizables.push(i),this.listen(i,"iron-resize","_onDescendantIronResize")),i.assignParentResizable(this),this._notifyDescendant(i),e.stopPropagation())},_parentResizableChanged:function(e){e&&window.removeEventListener("resize",this._boundNotifyResize)},_notifyDescendant:function(e){this.isAttached&&(this._notifyingDescendant=!0,e.notifyResize(),this._notifyingDescendant=!1)}}</script><dom-module id="paper-drawer-panel" assetpath="../bower_components/paper-drawer-panel/"><template><style>:host{display:block;position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden}iron-selector>#drawer{position:absolute;top:0;left:0;height:100%;background-color:#fff;-moz-box-sizing:border-box;box-sizing:border-box;@apply(--paper-drawer-panel-drawer-container)}.transition>#drawer{transition:-webkit-transform ease-in-out .3s,width ease-in-out .3s,visibility .3s;transition:transform ease-in-out .3s,width ease-in-out .3s,visibility .3s}.left-drawer>#drawer{@apply(--paper-drawer-panel-left-drawer-container)}.right-drawer>#drawer{left:auto;right:0;@apply(--paper-drawer-panel-right-drawer-container)}iron-selector>#main{position:absolute;top:0;right:0;bottom:0;@apply(--paper-drawer-panel-main-container)}.transition>#main{transition:left ease-in-out .3s,padding ease-in-out .3s}.right-drawer>#main{left:0}.right-drawer.transition>#main{transition:right ease-in-out .3s,padding ease-in-out .3s}#main>::content>[main]{height:100%}#drawer>::content>[drawer]{height:100%}#scrim{position:absolute;top:0;right:0;bottom:0;left:0;visibility:hidden;opacity:0;transition:opacity ease-in-out .38s,visibility ease-in-out .38s;background-color:rgba(0,0,0,.3);@apply(--paper-drawer-panel-scrim)}.narrow-layout>#drawer{will-change:transform}.narrow-layout>#drawer.iron-selected{box-shadow:2px 2px 4px rgba(0,0,0,.15)}.right-drawer.narrow-layout>#drawer.iron-selected{box-shadow:-2px 2px 4px rgba(0,0,0,.15)}.narrow-layout>#drawer>::content>[drawer]{border:0}.left-drawer.narrow-layout>#drawer:not(.iron-selected){visibility:hidden;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.right-drawer.narrow-layout>#drawer:not(.iron-selected){left:auto;visibility:hidden;-webkit-transform:translateX(100%);transform:translateX(100%)}.left-drawer.dragging>#drawer:not(.iron-selected),.left-drawer.peeking>#drawer:not(.iron-selected),.right-drawer.dragging>#drawer:not(.iron-selected),.right-drawer.peeking>#drawer:not(.iron-selected){visibility:visible}.narrow-layout>#main{padding:0}.right-drawer.narrow-layout>#main{left:0;right:0}.dragging>#main>#scrim,.narrow-layout>#main:not(.iron-selected)>#scrim{visibility:visible;opacity:var(--paper-drawer-panel-scrim-opacity,1)}.narrow-layout>#main>*{margin:0;min-height:100%;left:0;right:0;-moz-box-sizing:border-box;box-sizing:border-box}iron-selector:not(.narrow-layout) ::content [paper-drawer-toggle]{display:none}</style><iron-media-query id="mq" on-query-matches-changed="_onQueryMatchesChanged" query="[[_computeMediaQuery(forceNarrow, responsiveWidth)]]"></iron-media-query><iron-selector attr-for-selected="id" class$="[[_computeIronSelectorClass(narrow, _transition, dragging, rightDrawer, peeking)]]" activate-event="" selected="[[selected]]"><div id="main" style$="[[_computeMainStyle(narrow, rightDrawer, drawerWidth)]]"><content select="[main]"></content><div id="scrim" on-tap="closeDrawer"></div></div><div id="drawer" style$="[[_computeDrawerStyle(drawerWidth)]]"><content id="drawerContent" select="[drawer]"></content></div></iron-selector></template><script>!function(){"use strict";function e(e){var t=[];for(var i in e)e.hasOwnProperty(i)&&e[i]&&t.push(i);return t.join(" ")}var t=null;Polymer({is:"paper-drawer-panel",behaviors:[Polymer.IronResizableBehavior],properties:{defaultSelected:{type:String,value:"main"},disableEdgeSwipe:{type:Boolean,value:!1},disableSwipe:{type:Boolean,value:!1},dragging:{type:Boolean,value:!1,readOnly:!0,notify:!0},drawerWidth:{type:String,value:"256px"},edgeSwipeSensitivity:{type:Number,value:30},forceNarrow:{type:Boolean,value:!1},hasTransform:{type:Boolean,value:function(){return"transform"in this.style}},hasWillChange:{type:Boolean,value:function(){return"willChange"in this.style}},narrow:{reflectToAttribute:!0,type:Boolean,value:!1,readOnly:!0,notify:!0},peeking:{type:Boolean,value:!1,readOnly:!0,notify:!0},responsiveWidth:{type:String,value:"768px"},rightDrawer:{type:Boolean,value:!1},selected:{reflectToAttribute:!0,notify:!0,type:String,value:null},drawerToggleAttribute:{type:String,value:"paper-drawer-toggle"},drawerFocusSelector:{type:String,value:'a[href]:not([tabindex="-1"]),area[href]:not([tabindex="-1"]),input:not([disabled]):not([tabindex="-1"]),select:not([disabled]):not([tabindex="-1"]),textarea:not([disabled]):not([tabindex="-1"]),button:not([disabled]):not([tabindex="-1"]),iframe:not([tabindex="-1"]),[tabindex]:not([tabindex="-1"]),[contentEditable=true]:not([tabindex="-1"])'},_transition:{type:Boolean,value:!1}},listeners:{tap:"_onTap",track:"_onTrack",down:"_downHandler",up:"_upHandler",transitionend:"_onTransitionEnd"},observers:["_forceNarrowChanged(forceNarrow, defaultSelected)","_toggleFocusListener(selected)"],ready:function(){this._transition=!0,this._boundFocusListener=this._didFocus.bind(this)},togglePanel:function(){this._isMainSelected()?this.openDrawer():this.closeDrawer()},openDrawer:function(){this.selected="drawer"},closeDrawer:function(){this.selected="main"},_onTransitionEnd:function(e){var t=Polymer.dom(e).localTarget;if(t===this&&("left"!==e.propertyName&&"right"!==e.propertyName||this.notifyResize(),"transform"===e.propertyName&&"drawer"===this.selected)){var i=this._getAutoFocusedNode();i&&i.focus()}},_computeIronSelectorClass:function(t,i,r,n,a){return e({dragging:r,"narrow-layout":t,"right-drawer":n,"left-drawer":!n,transition:i,peeking:a})},_computeDrawerStyle:function(e){return"width:"+e+";"},_computeMainStyle:function(e,t,i){var r="";return r+="left:"+(e||t?"0":i)+";",t&&(r+="right:"+(e?"":i)+";"),r},_computeMediaQuery:function(e,t){return e?"":"(max-width: "+t+")"},_computeSwipeOverlayHidden:function(e,t){return!e||t},_onTrack:function(e){if(!t||this===t)switch(e.detail.state){case"start":this._trackStart(e);break;case"track":this._trackX(e);break;case"end":this._trackEnd(e)}},_responsiveChange:function(e){this._setNarrow(e),this.selected=this.narrow?this.defaultSelected:null,this.setScrollDirection(this._swipeAllowed()?"y":"all"),this.fire("paper-responsive-change",{narrow:this.narrow})},_onQueryMatchesChanged:function(e){this._responsiveChange(e.detail.value)},_forceNarrowChanged:function(){this._responsiveChange(this.forceNarrow||this.$.mq.queryMatches)},_swipeAllowed:function(){return this.narrow&&!this.disableSwipe},_isMainSelected:function(){return"main"===this.selected},_startEdgePeek:function(){this.width=this.$.drawer.offsetWidth,this._moveDrawer(this._translateXForDeltaX(this.rightDrawer?-this.edgeSwipeSensitivity:this.edgeSwipeSensitivity)),this._setPeeking(!0)},_stopEdgePeek:function(){this.peeking&&(this._setPeeking(!1),this._moveDrawer(null))},_downHandler:function(e){!this.dragging&&this._isMainSelected()&&this._isEdgeTouch(e)&&!t&&(this._startEdgePeek(),e.preventDefault(),t=this)},_upHandler:function(){this._stopEdgePeek(),t=null},_onTap:function(e){var t=Polymer.dom(e).localTarget,i=t&&this.drawerToggleAttribute&&t.hasAttribute(this.drawerToggleAttribute);i&&this.togglePanel()},_isEdgeTouch:function(e){var t=e.detail.x;return!this.disableEdgeSwipe&&this._swipeAllowed()&&(this.rightDrawer?t>=this.offsetWidth-this.edgeSwipeSensitivity:t<=this.edgeSwipeSensitivity)},_trackStart:function(e){this._swipeAllowed()&&(t=this,this._setDragging(!0),this._isMainSelected()&&this._setDragging(this.peeking||this._isEdgeTouch(e)),this.dragging&&(this.width=this.$.drawer.offsetWidth,this._transition=!1))},_translateXForDeltaX:function(e){var t=this._isMainSelected();return this.rightDrawer?Math.max(0,t?this.width+e:e):Math.min(0,t?e-this.width:e)},_trackX:function(e){if(this.dragging){var t=e.detail.dx;if(this.peeking){if(Math.abs(t)<=this.edgeSwipeSensitivity)return;this._setPeeking(!1)}this._moveDrawer(this._translateXForDeltaX(t))}},_trackEnd:function(e){if(this.dragging){var i=e.detail.dx>0;this._setDragging(!1),this._transition=!0,t=null,this._moveDrawer(null),this.rightDrawer?this[i?"closeDrawer":"openDrawer"]():this[i?"openDrawer":"closeDrawer"]()}},_transformForTranslateX:function(e){return null===e?"":this.hasWillChange?"translateX("+e+"px)":"translate3d("+e+"px, 0, 0)"},_moveDrawer:function(e){this.transform(this._transformForTranslateX(e),this.$.drawer)},_getDrawerContent:function(){return Polymer.dom(this.$.drawerContent).getDistributedNodes()[0]},_getAutoFocusedNode:function(){var e=this._getDrawerContent();return this.drawerFocusSelector?Polymer.dom(e).querySelector(this.drawerFocusSelector)||e:null},_toggleFocusListener:function(e){"drawer"===e?this.addEventListener("focus",this._boundFocusListener,!0):this.removeEventListener("focus",this._boundFocusListener,!0)},_didFocus:function(e){var t=this._getAutoFocusedNode();if(t){var i=Polymer.dom(e).path,r=(i[0],this._getDrawerContent()),n=i.indexOf(r)!==-1;n||(e.stopPropagation(),t.focus())}},_isDrawerClosed:function(e,t){return!e||"drawer"!==t}})}()</script></dom-module><dom-module id="iron-pages" assetpath="../bower_components/iron-pages/"><template><style>:host{display:block}:host>::content>:not(.iron-selected){display:none!important}</style><content></content></template><script>Polymer({is:"iron-pages",behaviors:[Polymer.IronResizableBehavior,Polymer.IronSelectableBehavior],properties:{activateEvent:{type:String,value:null}},observers:["_selectedPageChanged(selected)"],_selectedPageChanged:function(e,a){this.async(this.notifyResize)}})</script></dom-module><dom-module id="iron-icon" assetpath="../bower_components/iron-icon/"><template><style>:host{@apply(--layout-inline);@apply(--layout-center-center);position:relative;vertical-align:middle;fill:var(--iron-icon-fill-color,currentcolor);stroke:var(--iron-icon-stroke-color,none);width:var(--iron-icon-width,24px);height:var(--iron-icon-height,24px);@apply(--iron-icon)}</style></template><script>Polymer({is:"iron-icon",properties:{icon:{type:String},theme:{type:String},src:{type:String},_meta:{value:Polymer.Base.create("iron-meta",{type:"iconset"})}},observers:["_updateIcon(_meta, isAttached)","_updateIcon(theme, isAttached)","_srcChanged(src, isAttached)","_iconChanged(icon, isAttached)"],_DEFAULT_ICONSET:"icons",_iconChanged:function(t){var i=(t||"").split(":");this._iconName=i.pop(),this._iconsetName=i.pop()||this._DEFAULT_ICONSET,this._updateIcon()},_srcChanged:function(t){this._updateIcon()},_usesIconset:function(){return this.icon||!this.src},_updateIcon:function(){this._usesIconset()?(this._img&&this._img.parentNode&&Polymer.dom(this.root).removeChild(this._img),""===this._iconName?this._iconset&&this._iconset.removeIcon(this):this._iconsetName&&this._meta&&(this._iconset=this._meta.byKey(this._iconsetName),this._iconset?(this._iconset.applyIcon(this,this._iconName,this.theme),this.unlisten(window,"iron-iconset-added","_updateIcon")):this.listen(window,"iron-iconset-added","_updateIcon"))):(this._iconset&&this._iconset.removeIcon(this),this._img||(this._img=document.createElement("img"),this._img.style.width="100%",this._img.style.height="100%",this._img.draggable=!1),this._img.src=this.src,Polymer.dom(this.root).appendChild(this._img))}})</script></dom-module><dom-module id="paper-icon-button" assetpath="../bower_components/paper-icon-button/"><template strip-whitespace=""><style>:host{display:inline-block;position:relative;padding:8px;outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;z-index:0;line-height:1;width:40px;height:40px;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;box-sizing:border-box!important;@apply(--paper-icon-button)}:host #ink{color:var(--paper-icon-button-ink-color,--primary-text-color);opacity:.6}:host([disabled]){color:var(--paper-icon-button-disabled-text,--disabled-text-color);pointer-events:none;cursor:auto;@apply(--paper-icon-button-disabled)}:host(:hover){@apply(--paper-icon-button-hover)}iron-icon{--iron-icon-width:100%;--iron-icon-height:100%}</style><iron-icon id="icon" src="[[src]]" icon="[[icon]]" alt$="[[alt]]"></iron-icon></template><script>Polymer({is:"paper-icon-button",hostAttributes:{role:"button",tabindex:"0"},behaviors:[Polymer.PaperInkyFocusBehavior],properties:{src:{type:String},icon:{type:String},alt:{type:String,observer:"_altChanged"}},_altChanged:function(t,e){var r=this.getAttribute("aria-label");r&&e!=r||this.setAttribute("aria-label",t)}})</script></dom-module><script>Polymer.IronMenuBehaviorImpl={properties:{focusedItem:{observer:"_focusedItemChanged",readOnly:!0,type:Object},attrForItemTitle:{type:String}},_SEARCH_RESET_TIMEOUT_MS:1e3,hostAttributes:{role:"menu",tabindex:"0"},observers:["_updateMultiselectable(multi)"],listeners:{focus:"_onFocus",keydown:"_onKeydown","iron-items-changed":"_onIronItemsChanged"},keyBindings:{up:"_onUpKey",down:"_onDownKey",esc:"_onEscKey","shift+tab:keydown":"_onShiftTabDown"},attached:function(){this._resetTabindices()},select:function(e){this._defaultFocusAsync&&(this.cancelAsync(this._defaultFocusAsync),this._defaultFocusAsync=null);var t=this._valueToItem(e);t&&t.hasAttribute("disabled")||(this._setFocusedItem(t),Polymer.IronMultiSelectableBehaviorImpl.select.apply(this,arguments))},_resetTabindices:function(){var e=this.multi?this.selectedItems&&this.selectedItems[0]:this.selectedItem;this.items.forEach(function(t){t.setAttribute("tabindex",t===e?"0":"-1")},this)},_updateMultiselectable:function(e){e?this.setAttribute("aria-multiselectable","true"):this.removeAttribute("aria-multiselectable")},_focusWithKeyboardEvent:function(e){this.cancelDebouncer("_clearSearchText");var t=this._searchText||"",s=e.key&&1==e.key.length?e.key:String.fromCharCode(e.keyCode);t+=s.toLocaleLowerCase();for(var i,o=t.length,n=0;i=this.items[n];n++)if(!i.hasAttribute("disabled")){var r=this.attrForItemTitle||"textContent",a=(i[r]||i.getAttribute(r)||"").trim();if(!(a.length<o)&&a.slice(0,o).toLocaleLowerCase()==t){this._setFocusedItem(i);break}}this._searchText=t,this.debounce("_clearSearchText",this._clearSearchText,this._SEARCH_RESET_TIMEOUT_MS)},_clearSearchText:function(){this._searchText=""},_focusPrevious:function(){for(var e=this.items.length,t=Number(this.indexOf(this.focusedItem)),s=1;s<e+1;s++){var i=this.items[(t-s+e)%e];if(!i.hasAttribute("disabled")){var o=Polymer.dom(i).getOwnerRoot()||document;if(this._setFocusedItem(i),Polymer.dom(o).activeElement==i)return}}},_focusNext:function(){for(var e=this.items.length,t=Number(this.indexOf(this.focusedItem)),s=1;s<e+1;s++){var i=this.items[(t+s)%e];if(!i.hasAttribute("disabled")){var o=Polymer.dom(i).getOwnerRoot()||document;if(this._setFocusedItem(i),Polymer.dom(o).activeElement==i)return}}},_applySelection:function(e,t){t?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected"),Polymer.IronSelectableBehavior._applySelection.apply(this,arguments)},_focusedItemChanged:function(e,t){t&&t.setAttribute("tabindex","-1"),e&&(e.setAttribute("tabindex","0"),e.focus())},_onIronItemsChanged:function(e){e.detail.addedNodes.length&&this._resetTabindices()},_onShiftTabDown:function(e){var t=this.getAttribute("tabindex");Polymer.IronMenuBehaviorImpl._shiftTabPressed=!0,this._setFocusedItem(null),this.setAttribute("tabindex","-1"),this.async(function(){this.setAttribute("tabindex",t),Polymer.IronMenuBehaviorImpl._shiftTabPressed=!1},1)},_onFocus:function(e){if(!Polymer.IronMenuBehaviorImpl._shiftTabPressed){var t=Polymer.dom(e).rootTarget;(t===this||"undefined"==typeof t.tabIndex||this.isLightDescendant(t))&&(this._defaultFocusAsync=this.async(function(){var e=this.multi?this.selectedItems&&this.selectedItems[0]:this.selectedItem;this._setFocusedItem(null),e?this._setFocusedItem(e):this.items[0]&&this._focusNext()}))}},_onUpKey:function(e){this._focusPrevious(),e.detail.keyboardEvent.preventDefault()},_onDownKey:function(e){this._focusNext(),e.detail.keyboardEvent.preventDefault()},_onEscKey:function(e){this.focusedItem.blur()},_onKeydown:function(e){this.keyboardEventMatchesKeys(e,"up down esc")||this._focusWithKeyboardEvent(e),e.stopPropagation()},_activateHandler:function(e){Polymer.IronSelectableBehavior._activateHandler.call(this,e),e.stopPropagation()}},Polymer.IronMenuBehaviorImpl._shiftTabPressed=!1,Polymer.IronMenuBehavior=[Polymer.IronMultiSelectableBehavior,Polymer.IronA11yKeysBehavior,Polymer.IronMenuBehaviorImpl]</script><script>Polymer.IronMenubarBehaviorImpl={hostAttributes:{role:"menubar"},keyBindings:{left:"_onLeftKey",right:"_onRightKey"},_onUpKey:function(e){this.focusedItem.click(),e.detail.keyboardEvent.preventDefault()},_onDownKey:function(e){this.focusedItem.click(),e.detail.keyboardEvent.preventDefault()},get _isRTL(){return"rtl"===window.getComputedStyle(this).direction},_onLeftKey:function(e){this._isRTL?this._focusNext():this._focusPrevious(),e.detail.keyboardEvent.preventDefault()},_onRightKey:function(e){this._isRTL?this._focusPrevious():this._focusNext(),e.detail.keyboardEvent.preventDefault()},_onKeydown:function(e){this.keyboardEventMatchesKeys(e,"up down left right esc")||this._focusWithKeyboardEvent(e)}},Polymer.IronMenubarBehavior=[Polymer.IronMenuBehavior,Polymer.IronMenubarBehaviorImpl]</script><script>Polymer({is:"iron-iconset-svg",properties:{name:{type:String,observer:"_nameChanged"},size:{type:Number,value:24},rtlMirroring:{type:Boolean,value:!1}},_targetIsRTL:function(e){return e&&e.nodeType!==Node.ELEMENT_NODE&&(e=e.host),e&&"rtl"===window.getComputedStyle(e).direction},attached:function(){this.style.display="none"},getIconNames:function(){return this._icons=this._createIconMap(),Object.keys(this._icons).map(function(e){return this.name+":"+e},this)},applyIcon:function(e,t){e=e.root||e,this.removeIcon(e);var n=this._cloneIcon(t,this.rtlMirroring&&this._targetIsRTL(e));if(n){var o=Polymer.dom(e);return o.insertBefore(n,o.childNodes[0]),e._svgIcon=n}return null},removeIcon:function(e){e=e.root||e,e._svgIcon&&(Polymer.dom(e).removeChild(e._svgIcon),e._svgIcon=null)},_nameChanged:function(){new Polymer.IronMeta({type:"iconset",key:this.name,value:this}),this.async(function(){this.fire("iron-iconset-added",this,{node:window})})},_createIconMap:function(){var e=Object.create(null);return Polymer.dom(this).querySelectorAll("[id]").forEach(function(t){e[t.id]=t}),e},_cloneIcon:function(e,t){return this._icons=this._icons||this._createIconMap(),this._prepareSvgClone(this._icons[e],this.size,t)},_prepareSvgClone:function(e,t,n){if(e){var o=e.cloneNode(!0),r=document.createElementNS("http://www.w3.org/2000/svg","svg"),i=o.getAttribute("viewBox")||"0 0 "+t+" "+t,s="pointer-events: none; display: block; width: 100%; height: 100%;";return n&&o.hasAttribute("mirror-in-rtl")&&(s+="-webkit-transform:scale(-1,1);transform:scale(-1,1);"),r.setAttribute("viewBox",i),r.setAttribute("preserveAspectRatio","xMidYMid meet"),r.style.cssText=s,r.appendChild(o).removeAttribute("id"),r}return null}})</script><iron-iconset-svg name="paper-tabs" size="24"><svg><defs><g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g><g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g></defs></svg></iron-iconset-svg><dom-module id="paper-tab" assetpath="../bower_components/paper-tabs/"><template><style>:host{@apply(--layout-inline);@apply(--layout-center);@apply(--layout-center-justified);@apply(--layout-flex-auto);position:relative;padding:0 12px;overflow:hidden;cursor:pointer;vertical-align:middle;@apply(--paper-font-common-base);@apply(--paper-tab)}:host(:focus){outline:0}:host([link]){padding:0}.tab-content{height:100%;transform:translateZ(0);-webkit-transform:translateZ(0);transition:opacity .1s cubic-bezier(.4,0,1,1);@apply(--layout-horizontal);@apply(--layout-center-center);@apply(--layout-flex-auto);@apply(--paper-tab-content)}:host(:not(.iron-selected))>.tab-content{opacity:.8;@apply(--paper-tab-content-unselected)}:host(:focus) .tab-content{opacity:1;font-weight:700}paper-ripple{color:var(--paper-tab-ink,--paper-yellow-a100)}.tab-content>::content>a{@apply(--layout-flex-auto);height:100%}</style><div class="tab-content"><content></content></div></template><script>Polymer({is:"paper-tab",behaviors:[Polymer.IronControlState,Polymer.IronButtonState,Polymer.PaperRippleBehavior],properties:{link:{type:Boolean,value:!1,reflectToAttribute:!0}},hostAttributes:{role:"tab"},listeners:{down:"_updateNoink",tap:"_onTap"},attached:function(){this._updateNoink()},get _parentNoink(){var t=Polymer.dom(this).parentNode;return!!t&&!!t.noink},_updateNoink:function(){this.noink=!!this.noink||!!this._parentNoink},_onTap:function(t){if(this.link){var e=this.queryEffectiveChildren("a");if(!e)return;if(t.target===e)return;e.click()}}})</script></dom-module><dom-module id="paper-tabs" assetpath="../bower_components/paper-tabs/"><template><style>:host{@apply(--layout);@apply(--layout-center);height:48px;font-size:14px;font-weight:500;overflow:hidden;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;@apply(--paper-tabs)}:host-context([dir=rtl]){@apply(--layout-horizontal-reverse)}#tabsContainer{position:relative;height:100%;white-space:nowrap;overflow:hidden;@apply(--layout-flex-auto)}#tabsContent{height:100%;-moz-flex-basis:auto;-ms-flex-basis:auto;flex-basis:auto}#tabsContent.scrollable{position:absolute;white-space:nowrap}#tabsContent.scrollable.fit-container,#tabsContent:not(.scrollable){@apply(--layout-horizontal)}#tabsContent.scrollable.fit-container{min-width:100%}#tabsContent.scrollable.fit-container>::content>*{-ms-flex:1 0 auto;-webkit-flex:1 0 auto;flex:1 0 auto}.hidden{display:none}.not-visible{opacity:0;cursor:default}paper-icon-button{width:48px;height:48px;padding:12px;margin:0 4px}#selectionBar{position:absolute;height:2px;bottom:0;left:0;right:0;background-color:var(--paper-tabs-selection-bar-color,--paper-yellow-a100);-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:left center;transform-origin:left center;transition:-webkit-transform;transition:transform;@apply(--paper-tabs-selection-bar)}#selectionBar.align-bottom{top:0;bottom:auto}#selectionBar.expand{transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,1,1)}#selectionBar.contract{transition-duration:.18s;transition-timing-function:cubic-bezier(0,0,.2,1)}#tabsContent>::content>:not(#selectionBar){height:100%}</style><paper-icon-button icon="paper-tabs:chevron-left" class$="[[_computeScrollButtonClass(_leftHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButtonUp" on-down="_onLeftScrollButtonDown" tabindex="-1"></paper-icon-button><div id="tabsContainer" on-track="_scroll" on-down="_down"><div id="tabsContent" class$="[[_computeTabsContentClass(scrollable, fitContainer)]]"><div id="selectionBar" class$="[[_computeSelectionBarClass(noBar, alignBottom)]]" on-transitionend="_onBarTransitionEnd"></div><content select="*"></content></div></div><paper-icon-button icon="paper-tabs:chevron-right" class$="[[_computeScrollButtonClass(_rightHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButtonUp" on-down="_onRightScrollButtonDown" tabindex="-1"></paper-icon-button></template><script>Polymer({is:"paper-tabs",behaviors:[Polymer.IronResizableBehavior,Polymer.IronMenubarBehavior],properties:{noink:{type:Boolean,value:!1,observer:"_noinkChanged"},noBar:{type:Boolean,value:!1},noSlide:{type:Boolean,value:!1},scrollable:{type:Boolean,value:!1},fitContainer:{type:Boolean,value:!1},disableDrag:{type:Boolean,value:!1},hideScrollButtons:{type:Boolean,value:!1},alignBottom:{type:Boolean,value:!1},selectable:{type:String,value:"paper-tab"},autoselect:{type:Boolean,value:!1},autoselectDelay:{type:Number,value:0},_step:{type:Number,value:10},_holdDelay:{type:Number,value:1},_leftHidden:{type:Boolean,value:!1},_rightHidden:{type:Boolean,value:!1},_previousTab:{type:Object}},hostAttributes:{role:"tablist"},listeners:{"iron-resize":"_onTabSizingChanged","iron-items-changed":"_onTabSizingChanged","iron-select":"_onIronSelect","iron-deselect":"_onIronDeselect"},keyBindings:{"left:keyup right:keyup":"_onArrowKeyup"},created:function(){this._holdJob=null,this._pendingActivationItem=void 0,this._pendingActivationTimeout=void 0,this._bindDelayedActivationHandler=this._delayedActivationHandler.bind(this),this.addEventListener("blur",this._onBlurCapture.bind(this),!0)},ready:function(){this.setScrollDirection("y",this.$.tabsContainer)},detached:function(){this._cancelPendingActivation()},_noinkChanged:function(t){var e=Polymer.dom(this).querySelectorAll("paper-tab");e.forEach(t?this._setNoinkAttribute:this._removeNoinkAttribute)},_setNoinkAttribute:function(t){t.setAttribute("noink","")},_removeNoinkAttribute:function(t){t.removeAttribute("noink")},_computeScrollButtonClass:function(t,e,i){return!e||i?"hidden":t?"not-visible":""},_computeTabsContentClass:function(t,e){return t?"scrollable"+(e?" fit-container":""):" fit-container"},_computeSelectionBarClass:function(t,e){return t?"hidden":e?"align-bottom":""},_onTabSizingChanged:function(){this.debounce("_onTabSizingChanged",function(){this._scroll(),this._tabChanged(this.selectedItem)},10)},_onIronSelect:function(t){this._tabChanged(t.detail.item,this._previousTab),this._previousTab=t.detail.item,this.cancelDebouncer("tab-changed")},_onIronDeselect:function(t){this.debounce("tab-changed",function(){this._tabChanged(null,this._previousTab),this._previousTab=null},1)},_activateHandler:function(){this._cancelPendingActivation(),Polymer.IronMenuBehaviorImpl._activateHandler.apply(this,arguments)},_scheduleActivation:function(t,e){this._pendingActivationItem=t,this._pendingActivationTimeout=this.async(this._bindDelayedActivationHandler,e)},_delayedActivationHandler:function(){var t=this._pendingActivationItem;this._pendingActivationItem=void 0,this._pendingActivationTimeout=void 0,t.fire(this.activateEvent,null,{bubbles:!0,cancelable:!0})},_cancelPendingActivation:function(){void 0!==this._pendingActivationTimeout&&(this.cancelAsync(this._pendingActivationTimeout),this._pendingActivationItem=void 0,this._pendingActivationTimeout=void 0)},_onArrowKeyup:function(t){this.autoselect&&this._scheduleActivation(this.focusedItem,this.autoselectDelay)},_onBlurCapture:function(t){t.target===this._pendingActivationItem&&this._cancelPendingActivation()},get _tabContainerScrollSize(){return Math.max(0,this.$.tabsContainer.scrollWidth-this.$.tabsContainer.offsetWidth)},_scroll:function(t,e){if(this.scrollable){var i=e&&-e.ddx||0;this._affectScroll(i)}},_down:function(t){this.async(function(){this._defaultFocusAsync&&(this.cancelAsync(this._defaultFocusAsync),this._defaultFocusAsync=null)},1)},_affectScroll:function(t){this.$.tabsContainer.scrollLeft+=t;var e=this.$.tabsContainer.scrollLeft;this._leftHidden=0===e,this._rightHidden=e===this._tabContainerScrollSize},_onLeftScrollButtonDown:function(){this._scrollToLeft(),this._holdJob=setInterval(this._scrollToLeft.bind(this),this._holdDelay)},_onRightScrollButtonDown:function(){this._scrollToRight(),this._holdJob=setInterval(this._scrollToRight.bind(this),this._holdDelay)},_onScrollButtonUp:function(){clearInterval(this._holdJob),this._holdJob=null},_scrollToLeft:function(){this._affectScroll(-this._step)},_scrollToRight:function(){this._affectScroll(this._step)},_tabChanged:function(t,e){if(!t)return this.$.selectionBar.classList.remove("expand"),this.$.selectionBar.classList.remove("contract"),void this._positionBar(0,0);var i=this.$.tabsContent.getBoundingClientRect(),n=i.width,o=t.getBoundingClientRect(),s=o.left-i.left;if(this._pos={width:this._calcPercent(o.width,n),left:this._calcPercent(s,n)},this.noSlide||null==e)return this.$.selectionBar.classList.remove("expand"),this.$.selectionBar.classList.remove("contract"),void this._positionBar(this._pos.width,this._pos.left);var a=e.getBoundingClientRect(),l=this.items.indexOf(e),c=this.items.indexOf(t),r=5;this.$.selectionBar.classList.add("expand");var h=l<c,d=this._isRTL;d&&(h=!h),h?this._positionBar(this._calcPercent(o.left+o.width-a.left,n)-r,this._left):this._positionBar(this._calcPercent(a.left+a.width-o.left,n)-r,this._calcPercent(s,n)+r),this.scrollable&&this._scrollToSelectedIfNeeded(o.width,s)},_scrollToSelectedIfNeeded:function(t,e){var i=e-this.$.tabsContainer.scrollLeft;i<0?this.$.tabsContainer.scrollLeft+=i:(i+=t-this.$.tabsContainer.offsetWidth,i>0&&(this.$.tabsContainer.scrollLeft+=i))},_calcPercent:function(t,e){return 100*t/e},_positionBar:function(t,e){t=t||0,e=e||0,this._width=t,this._left=e,this.transform("translateX("+e+"%) scaleX("+t/100+")",this.$.selectionBar)},_onBarTransitionEnd:function(t){var e=this.$.selectionBar.classList;e.contains("expand")?(e.remove("expand"),e.add("contract"),this._positionBar(this._pos.width,this._pos.left)):e.contains("contract")&&e.remove("contract")}})</script></dom-module><dom-module id="app-header-layout" assetpath="../bower_components/app-layout/app-header-layout/"><template><style>:host{display:block;position:relative;z-index:0}:host>::content>app-header{@apply(--layout-fixed-top);z-index:1}:host([has-scrolling-region]){height:100%}:host([has-scrolling-region])>::content>app-header{position:absolute}:host([has-scrolling-region])>#contentContainer{@apply(--layout-fit);overflow-y:auto;-webkit-overflow-scrolling:touch}:host([fullbleed]){@apply(--layout-vertical);@apply(--layout-fit)}:host([fullbleed])>#contentContainer{@apply(--layout-vertical);@apply(--layout-flex)}#contentContainer{position:relative;z-index:0}</style><content id="header" select="app-header"></content><div id="contentContainer"><content></content></div></template><script>Polymer({is:"app-header-layout",behaviors:[Polymer.IronResizableBehavior],properties:{hasScrollingRegion:{type:Boolean,value:!1,reflectToAttribute:!0}},listeners:{"iron-resize":"_resizeHandler","app-header-reset-layout":"resetLayout"},observers:["resetLayout(isAttached, hasScrollingRegion)"],get header(){return Polymer.dom(this.$.header).getDistributedNodes()[0]},resetLayout:function(){this._updateScroller(),this.debounce("_resetLayout",this._updateContentPosition)},_updateContentPosition:function(){var e=this.header;if(this.isAttached&&e){var t=e.offsetHeight;if(this.hasScrollingRegion)e.style.left="",e.style.right="";else{var i=this.getBoundingClientRect(),o=document.documentElement.clientWidth-i.right;e.style.left=i.left+"px",e.style.right=o+"px"}var n=this.$.contentContainer.style;e.fixed&&!e.willCondense()&&this.hasScrollingRegion?(n.marginTop=t+"px",n.paddingTop=""):(n.paddingTop=t+"px",n.marginTop="")}},_updateScroller:function(){if(this.isAttached){var e=this.header;e&&(e.scrollTarget=this.hasScrollingRegion?this.$.contentContainer:this.ownerDocument.documentElement)}},_resizeHandler:function(){this.resetLayout()}})</script></dom-module><script>Polymer.IronScrollTargetBehavior={properties:{scrollTarget:{type:HTMLElement,value:function(){return this._defaultScrollTarget}}},observers:["_scrollTargetChanged(scrollTarget, isAttached)"],_shouldHaveListener:!0,_scrollTargetChanged:function(t,l){this._oldScrollTarget&&(this._toggleScrollListener(!1,this._oldScrollTarget),this._oldScrollTarget=null),l&&("document"===t?this.scrollTarget=this._doc:"string"==typeof t?this.scrollTarget=this.domHost?this.domHost.$[t]:Polymer.dom(this.ownerDocument).querySelector("#"+t):this._isValidScrollTarget()&&(this._boundScrollHandler=this._boundScrollHandler||this._scrollHandler.bind(this),this._oldScrollTarget=t,this._toggleScrollListener(this._shouldHaveListener,t)))},_scrollHandler:function(){},get _defaultScrollTarget(){return this._doc},get _doc(){return this.ownerDocument.documentElement},get _scrollTop(){return this._isValidScrollTarget()?this.scrollTarget===this._doc?window.pageYOffset:this.scrollTarget.scrollTop:0},get _scrollLeft(){return this._isValidScrollTarget()?this.scrollTarget===this._doc?window.pageXOffset:this.scrollTarget.scrollLeft:0},set _scrollTop(t){this.scrollTarget===this._doc?window.scrollTo(window.pageXOffset,t):this._isValidScrollTarget()&&(this.scrollTarget.scrollTop=t)},set _scrollLeft(t){this.scrollTarget===this._doc?window.scrollTo(t,window.pageYOffset):this._isValidScrollTarget()&&(this.scrollTarget.scrollLeft=t)},scroll:function(t,l){this.scrollTarget===this._doc?window.scrollTo(t,l):this._isValidScrollTarget()&&(this.scrollTarget.scrollLeft=t,this.scrollTarget.scrollTop=l)},get _scrollTargetWidth(){return this._isValidScrollTarget()?this.scrollTarget===this._doc?window.innerWidth:this.scrollTarget.offsetWidth:0},get _scrollTargetHeight(){return this._isValidScrollTarget()?this.scrollTarget===this._doc?window.innerHeight:this.scrollTarget.offsetHeight:0},_isValidScrollTarget:function(){return this.scrollTarget instanceof HTMLElement},_toggleScrollListener:function(t,l){if(this._boundScrollHandler){var e=l===this._doc?window:l;t?e.addEventListener("scroll",this._boundScrollHandler):e.removeEventListener("scroll",this._boundScrollHandler)}},toggleScrollListener:function(t){this._shouldHaveListener=t,this._toggleScrollListener(t,this.scrollTarget)}}</script><script>Polymer.AppLayout=Polymer.AppLayout||{},Polymer.AppLayout._scrollEffects=Polymer.AppLayout._scrollEffects||{},Polymer.AppLayout.scrollTimingFunction=function(o,l,e,r){return o/=r,-e*o*(o-2)+l},Polymer.AppLayout.registerEffect=function(o,l){if(null!=Polymer.AppLayout._scrollEffects[o])throw new Error("effect `"+o+"` is already registered.");Polymer.AppLayout._scrollEffects[o]=l},Polymer.AppLayout.scroll=function(o){o=o||{};var l=document.documentElement,e=o.target||l,r="scrollBehavior"in e.style&&e.scroll,t="app-layout-silent-scroll",s=o.top||0,c=o.left||0,i=e===l?window.scrollTo:function(o,l){e.scrollLeft=o,e.scrollTop=l};if("smooth"===o.behavior)if(r)e.scroll(o);else{var n=Polymer.AppLayout.scrollTimingFunction,a=Date.now(),p=e===l?window.pageYOffset:e.scrollTop,u=e===l?window.pageXOffset:e.scrollLeft,y=s-p,f=c-u,m=300,L=function o(){var l=Date.now(),e=l-a;e<m?(i(n(e,u,f,m),n(e,p,y,m)),requestAnimationFrame(o)):i(c,s)}.bind(this);L()}else"silent"===o.behavior?(l.classList.add(t),clearInterval(Polymer.AppLayout._scrollTimer),Polymer.AppLayout._scrollTimer=setTimeout(function(){l.classList.remove(t),Polymer.AppLayout._scrollTimer=null},100),i(c,s)):i(c,s)}</script><script>Polymer.AppScrollEffectsBehavior=[Polymer.IronScrollTargetBehavior,{properties:{effects:{type:String},effectsConfig:{type:Object,value:function(){return{}}},disabled:{type:Boolean,reflectToAttribute:!0,value:!1},threshold:{type:Number,value:0},thresholdTriggered:{type:Boolean,notify:!0,readOnly:!0,reflectToAttribute:!0}},observers:["_effectsChanged(effects, effectsConfig, isAttached)"],_updateScrollState:function(){},isOnScreen:function(){return!1},isContentBelow:function(){return!1},_effectsRunFn:null,_effects:null,get _clampedScrollTop(){return Math.max(0,this._scrollTop)},detached:function(){this._tearDownEffects()},createEffect:function(t,e){var f=Polymer.AppLayout._scrollEffects[t];if(!f)throw new ReferenceError(this._getUndefinedMsg(t));var n=this._boundEffect(f,e||{});return n.setUp(),n},_effectsChanged:function(t,e,f){this._tearDownEffects(),""!==t&&f&&(t.split(" ").forEach(function(t){var f;""!==t&&((f=Polymer.AppLayout._scrollEffects[t])?this._effects.push(this._boundEffect(f,e[t])):this._warn(this._logf("_effectsChanged",this._getUndefinedMsg(t))))},this),this._setUpEffect())},_layoutIfDirty:function(){return this.offsetWidth},_boundEffect:function(t,e){e=e||{};var f=parseFloat(e.startsAt||0),n=parseFloat(e.endsAt||1),s=n-f,i=function(){},r=0===f&&1===n?t.run:function(e,n){t.run.call(this,Math.max(0,(e-f)/s),n)};return{setUp:t.setUp?t.setUp.bind(this,e):i,run:t.run?r.bind(this):i,tearDown:t.tearDown?t.tearDown.bind(this):i}},_setUpEffect:function(){this.isAttached&&this._effects&&(this._effectsRunFn=[],this._effects.forEach(function(t){t.setUp()!==!1&&this._effectsRunFn.push(t.run)},this))},_tearDownEffects:function(){this._effects&&this._effects.forEach(function(t){t.tearDown()}),this._effectsRunFn=[],this._effects=[]},_runEffects:function(t,e){this._effectsRunFn&&this._effectsRunFn.forEach(function(f){f(t,e)})},_scrollHandler:function(){if(!this.disabled){var t=this._clampedScrollTop;this._updateScrollState(t),this.threshold>0&&this._setThresholdTriggered(t>=this.threshold)}},_getDOMRef:function(t){this._warn(this._logf("_getDOMRef","`"+t+"` is undefined"))},_getUndefinedMsg:function(t){return"Scroll effect `"+t+"` is undefined. Did you forget to import app-layout/app-scroll-effects/effects/"+t+".html ?"}}]</script><script>Polymer.AppLayout.registerEffect("waterfall",{run:function(){this.shadow=this.isOnScreen()&&this.isContentBelow()}})</script><dom-module id="app-header" assetpath="../bower_components/app-layout/app-header/"><template><style>:host{position:relative;display:block;transition-timing-function:linear;transition-property:-webkit-transform;transition-property:transform}:host::after{position:absolute;right:0;bottom:-5px;left:0;width:100%;height:5px;content:"";transition:opacity .4s;pointer-events:none;opacity:0;box-shadow:inset 0 5px 6px -3px rgba(0,0,0,.4);will-change:opacity;@apply(--app-header-shadow)}:host([shadow])::after{opacity:1}::content [condensed-title],::content [main-title]{-webkit-transform-origin:left top;transform-origin:left top;white-space:nowrap}::content [condensed-title]{opacity:0}#background{@apply(--layout-fit);overflow:hidden}#backgroundFrontLayer,#backgroundRearLayer{@apply(--layout-fit);height:100%;pointer-events:none;background-size:cover}#backgroundFrontLayer{@apply(--app-header-background-front-layer)}#backgroundRearLayer{opacity:0;@apply(--app-header-background-rear-layer)}#contentContainer{position:relative;width:100%;height:100%}:host([disabled]),:host([disabled]) #backgroundFrontLayer,:host([disabled]) #backgroundRearLayer,:host([disabled]) ::content>[sticky],:host([disabled]) ::content>app-toolbar:first-of-type,:host([disabled])::after,:host-context(.app-layout-silent-scroll),:host-context(.app-layout-silent-scroll) #backgroundFrontLayer,:host-context(.app-layout-silent-scroll) #backgroundRearLayer,:host-context(.app-layout-silent-scroll) ::content>[sticky],:host-context(.app-layout-silent-scroll) ::content>app-toolbar:first-of-type,:host-context(.app-layout-silent-scroll)::after{transition:none!important}</style><div id="contentContainer"><content id="content"></content></div></template><script>Polymer({is:"app-header",behaviors:[Polymer.AppScrollEffectsBehavior,Polymer.IronResizableBehavior],properties:{condenses:{type:Boolean,value:!1},fixed:{type:Boolean,value:!1},reveals:{type:Boolean,value:!1},shadow:{type:Boolean,reflectToAttribute:!0,value:!1}},observers:["resetLayout(isAttached, condenses, fixed)"],listeners:{"iron-resize":"_resizeHandler"},_height:0,_dHeight:0,_stickyElTop:0,_stickyEl:null,_top:0,_progress:0,_wasScrollingDown:!1,_initScrollTop:0,_initTimestamp:0,_lastTimestamp:0,_lastScrollTop:0,get _maxHeaderTop(){return this.fixed?this._dHeight:this._height+5},_getStickyEl:function(){for(var t,e=Polymer.dom(this.$.content).getDistributedNodes(),i=0;i<e.length;i++)if(e[i].nodeType===Node.ELEMENT_NODE){var s=e[i];if(s.hasAttribute("sticky")){t=s;break}t||(t=s)}return t},resetLayout:function(){this.fire("app-header-reset-layout"),this.debounce("_resetLayout",function(){if(0!==this.offsetWidth||0!==this.offsetHeight){var t=this._clampedScrollTop,e=0===this._height||0===t,i=this.disabled;this._height=this.offsetHeight,this._stickyEl=this._getStickyEl(),this.disabled=!0,e||this._updateScrollState(0,!0),this._mayMove()?this._dHeight=this._stickyEl?this._height-this._stickyEl.offsetHeight:0:this._dHeight=0,this._stickyElTop=this._stickyEl?this._stickyEl.offsetTop:0,this._setUpEffect(),e?this._updateScrollState(t,!0):(this._updateScrollState(this._lastScrollTop,!0),this._layoutIfDirty()),this.disabled=i}})},_updateScrollState:function(t,e){if(0!==this._height){var i=0,s=0,o=this._top,r=(this._lastScrollTop,this._maxHeaderTop),h=t-this._lastScrollTop,n=Math.abs(h),a=t>this._lastScrollTop,l=Date.now();if(this._mayMove()&&(s=this._clamp(this.reveals?o+h:t,0,r)),t>=this._dHeight&&(s=this.condenses&&!this.fixed?Math.max(this._dHeight,s):s,this.style.transitionDuration="0ms"),this.reveals&&!this.disabled&&n<100&&((l-this._initTimestamp>300||this._wasScrollingDown!==a)&&(this._initScrollTop=t,this._initTimestamp=l),t>=r))if(Math.abs(this._initScrollTop-t)>30||n>10){a&&t>=r?s=r:!a&&t>=this._dHeight&&(s=this.condenses&&!this.fixed?this._dHeight:0);var _=h/(l-this._lastTimestamp);this.style.transitionDuration=this._clamp((s-o)/_,0,300)+"ms"}else s=this._top;i=0===this._dHeight?t>0?1:0:s/this._dHeight,e||(this._lastScrollTop=t,this._top=s,this._wasScrollingDown=a,this._lastTimestamp=l),(e||i!==this._progress||o!==s||0===t)&&(this._progress=i,this._runEffects(i,s),this._transformHeader(s))}},_mayMove:function(){return this.condenses||!this.fixed},willCondense:function(){return this._dHeight>0&&this.condenses},isOnScreen:function(){return 0!==this._height&&this._top<this._height},isContentBelow:function(){return 0===this._top?this._clampedScrollTop>0:this._clampedScrollTop-this._maxHeaderTop>=0},_transformHeader:function(t){this.translate3d(0,-t+"px",0),this._stickyEl&&this.condenses&&t>=this._stickyElTop&&this.translate3d(0,Math.min(t,this._dHeight)-this._stickyElTop+"px",0,this._stickyEl)},_resizeHandler:function(){this.resetLayout()},_clamp:function(t,e,i){return Math.min(i,Math.max(e,t))},_ensureBgContainers:function(){this._bgContainer||(this._bgContainer=document.createElement("div"),this._bgContainer.id="background",this._bgRear=document.createElement("div"),this._bgRear.id="backgroundRearLayer",this._bgContainer.appendChild(this._bgRear),this._bgFront=document.createElement("div"),this._bgFront.id="backgroundFrontLayer",this._bgContainer.appendChild(this._bgFront),Polymer.dom(this.root).insertBefore(this._bgContainer,this.$.contentContainer))},_getDOMRef:function(t){switch(t){case"backgroundFrontLayer":return this._ensureBgContainers(),this._bgFront;case"backgroundRearLayer":return this._ensureBgContainers(),this._bgRear;case"background":return this._ensureBgContainers(),this._bgContainer;case"mainTitle":return Polymer.dom(this).querySelector("[main-title]");case"condensedTitle":return Polymer.dom(this).querySelector("[condensed-title]")}return null},getScrollState:function(){return{progress:this._progress,top:this._top}}})</script></dom-module><dom-module id="app-toolbar" assetpath="../bower_components/app-layout/app-toolbar/"><template><style>:host{@apply(--layout-horizontal);@apply(--layout-center);position:relative;height:64px;padding:0 16px;pointer-events:none;font-size:var(--app-toolbar-font-size,20px)}::content>*{pointer-events:auto}::content>paper-icon-button{font-size:0}::content>[condensed-title],::content>[main-title]{pointer-events:none;@apply(--layout-flex)}::content>[bottom-item]{position:absolute;right:0;bottom:0;left:0}::content>[top-item]{position:absolute;top:0;right:0;left:0}::content>[spacer]{margin-left:64px}</style><content></content></template><script>Polymer({is:"app-toolbar"})</script></dom-module><dom-module id="ha-menu-button" assetpath="components/"><template><style>.invisible{visibility:hidden}</style><paper-icon-button icon="mdi:menu" class$="[[computeMenuButtonClass(narrow, showMenu)]]" on-tap="toggleMenu"></paper-icon-button></template></dom-module><script>Polymer({is:"ha-menu-button",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1}},computeMenuButtonClass:function(e,n){return!e&&n?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})</script><dom-module id="ha-label-badge" assetpath="components/"><template><style>.badge-container{display:inline-block;text-align:center;vertical-align:top;margin-bottom:16px}.label-badge{position:relative;display:block;margin:0 auto;width:2.5em;text-align:center;height:2.5em;line-height:2.5em;font-size:1.5em;border-radius:50%;border:.1em solid var(--ha-label-badge-color,--default-primary-color);color:#4c4c4c;white-space:nowrap;background-color:#fff;background-size:cover;transition:border .3s ease-in-out}.label-badge .value{font-size:90%;overflow:hidden;text-overflow:ellipsis}.label-badge .value.big{font-size:70%}.label-badge .label{position:absolute;bottom:-1em;left:0;right:0;line-height:1em;font-size:.5em}.label-badge .label span{max-width:80%;display:inline-block;background-color:var(--ha-label-badge-color,--default-primary-color);color:#fff;border-radius:1em;padding:4px 8px;font-weight:500;text-transform:uppercase;overflow:hidden;text-overflow:ellipsis;transition:background-color .3s ease-in-out}.badge-container .title{margin-top:1em;font-size:.9em;width:5em;font-weight:300;overflow:hidden;text-overflow:ellipsis;line-height:normal}iron-image{border-radius:50%}[hidden]{display:none!important}</style><div class="badge-container"><div class="label-badge" id="badge"><div class$="[[computeClasses(value)]]"><iron-icon icon="[[icon]]" hidden$="[[computeHideIcon(icon, value, image)]]"></iron-icon><span hidden$="[[computeHideValue(value, image)]]">[[value]]</span></div><div class="label" hidden$="[[!label]]"><span>[[label]]</span></div></div><div class="title">[[description]]</div></div></template></dom-module><script>Polymer({is:"ha-label-badge",properties:{value:{type:String,value:null},icon:{type:String,value:null},label:{type:String,value:null},description:{type:String},image:{type:String,value:null,observer:"imageChanged"}},computeClasses:function(e){return e&&e.length>4?"value big":"value"},computeHideIcon:function(e,n,l){return!e||n||l},computeHideValue:function(e,n){return!e||n},imageChanged:function(e){this.$.badge.style.backgroundImage=e?"url("+e+")":""}})</script><dom-module id="ha-demo-badge" assetpath="components/"><template><style>:host{--ha-label-badge-color:#dac90d}</style><ha-label-badge icon="mdi:emoticon" label="Demo" description=""></ha-label-badge></template></dom-module><script>Polymer({is:"ha-demo-badge"})</script><dom-module id="ha-state-label-badge" assetpath="components/entity/"><template><style>:host{cursor:pointer}ha-label-badge{--ha-label-badge-color:rgb(223, 76, 30)}.blue{--ha-label-badge-color:#039be5}.green{--ha-label-badge-color:#0DA035}.grey{--ha-label-badge-color:var(--paper-grey-500)}</style><ha-label-badge class$="[[computeClasses(state)]]" value="[[computeValue(state)]]" icon="[[computeIcon(state)]]" image="[[computeImage(state)]]" label="[[computeLabel(state)]]" description="[[computeDescription(state)]]"></ha-label-badge></template></dom-module><script>Polymer({is:"ha-state-label-badge",properties:{hass:{type:Object},state:{type:Object,observer:"stateChanged"}},listeners:{tap:"badgeTap"},badgeTap:function(e){e.stopPropagation(),this.async(function(){this.hass.moreInfoActions.selectEntity(this.state.entityId)},1)},computeClasses:function(e){switch(e.domain){case"binary_sensor":case"updater":return"blue";default:return""}},computeValue:function(e){switch(e.domain){case"binary_sensor":case"device_tracker":case"updater":case"sun":case"alarm_control_panel":return null;case"sensor":default:return"unknown"===e.state?"-":e.state}},computeIcon:function(e){if("unavailable"===e.state)return null;switch(e.domain){case"alarm_control_panel":return"pending"===e.state?"mdi:clock-fast":"armed_away"===e.state?"mdi:nature":"armed_home"===e.state?"mdi:home-variant":"triggered"===e.state?"mdi:alert-circle":window.hassUtil.domainIcon(e.domain,e.state);case"binary_sensor":case"device_tracker":case"updater":return window.hassUtil.stateIcon(e);case"sun":return"above_horizon"===e.state?window.hassUtil.domainIcon(e.domain):"mdi:brightness-3";default:return null}},computeImage:function(e){return e.attributes.entity_picture||null},computeLabel:function(e){if("unavailable"===e.state)return"unavai";switch(e.domain){case"device_tracker":return"not_home"===e.state?"Away":e.state;case"alarm_control_panel":return"pending"===e.state?"pend":"armed_away"===e.state||"armed_home"===e.state?"armed":"triggered"===e.state?"trig":"disarm";default:return e.attributes.unit_of_measurement||null}},computeDescription:function(e){return e.entityDisplay},stateChanged:function(){this.updateStyles()}})</script><dom-module id="ha-badges-card" assetpath="cards/"><template><template is="dom-repeat" items="[[states]]"><ha-state-label-badge hass="[[hass]]" state="[[item]]"></ha-state-label-badge></template></template></dom-module><script>Polymer({is:"ha-badges-card",properties:{hass:{type:Object},states:{type:Array}}})</script><dom-module id="paper-material" assetpath="../bower_components/paper-material/"><template><style include="paper-material-shared-styles"></style><style>:host([animated]){@apply(--shadow-transition)}</style><content></content></template></dom-module><script>Polymer({is:"paper-material",properties:{elevation:{type:Number,reflectToAttribute:!0,value:1},animated:{type:Boolean,reflectToAttribute:!0,value:!1}}})</script><dom-module id="ha-camera-card" assetpath="cards/"><template><style include="paper-material">:host{display:block;position:relative;font-size:0;border-radius:2px;cursor:pointer;min-height:48px;line-height:0}.camera-feed{width:100%;height:auto;border-radius:2px}.caption{@apply(--paper-font-common-nowrap);position:absolute;left:0;right:0;bottom:0;border-bottom-left-radius:2px;border-bottom-right-radius:2px;background-color:rgba(0,0,0,.3);padding:16px;text-transform:capitalize;font-size:16px;font-weight:500;line-height:16px;color:#fff}</style><img src="[[cameraFeedSrc]]" class="camera-feed" hidden$="[[!imageLoaded]]" on-load="imageLoadSuccess" on-error="imageLoadFail" alt="[[stateObj.entityDisplay]]"><div class="caption">[[stateObj.entityDisplay]]<template is="dom-if" if="[[!imageLoaded]]">(Error loading image)</template></div></template></dom-module><script>Polymer({is:"ha-camera-card",UPDATE_INTERVAL:1e4,properties:{hass:{type:Object},stateObj:{type:Object,observer:"updateCameraFeedSrc"},cameraFeedSrc:{type:String},imageLoaded:{type:Boolean,value:!0},elevation:{type:Number,value:1,reflectToAttribute:!0}},listeners:{tap:"cardTapped"},attached:function(){this.timer=setInterval(function(){this.updateCameraFeedSrc(this.stateObj)}.bind(this),this.UPDATE_INTERVAL)},detached:function(){clearInterval(this.timer)},cardTapped:function(){this.async(function(){this.hass.moreInfoActions.selectEntity(this.stateObj.entityId)}.bind(this),1)},updateCameraFeedSrc:function(e){const t=e.attributes,a=(new Date).getTime();this.cameraFeedSrc=t.entity_picture+"&time="+a},imageLoadSuccess:function(){this.imageLoaded=!0},imageLoadFail:function(){this.imageLoaded=!1}})</script><dom-module id="ha-card" assetpath="components/"><template><style include="paper-material">:host{display:block;border-radius:2px;transition:all .3s ease-out;background-color:#fff}.header{@apply(--paper-font-headline);@apply(--paper-font-common-expensive-kerning);opacity:var(--dark-primary-opacity);padding:24px 16px 16px;text-transform:capitalize}</style><template is="dom-if" if="[[header]]"><div class="header">[[header]]</div></template><slot></slot></template></dom-module><script>Polymer({is:"ha-card",properties:{header:{type:String},elevation:{type:Number,value:1,reflectToAttribute:!0}}})</script><dom-module id="paper-toggle-button" assetpath="../bower_components/paper-toggle-button/"><template strip-whitespace=""><style>:host{display:inline-block;@apply(--layout-horizontal);@apply(--layout-center);@apply(--paper-font-common-base)}:host([disabled]){pointer-events:none}:host(:focus){outline:0}.toggle-bar{position:absolute;height:100%;width:100%;border-radius:8px;pointer-events:none;opacity:.4;transition:background-color linear .08s;background-color:var(--paper-toggle-button-unchecked-bar-color,#000);@apply(--paper-toggle-button-unchecked-bar)}.toggle-button{position:absolute;top:-3px;left:0;height:20px;width:20px;border-radius:50%;box-shadow:0 1px 5px 0 rgba(0,0,0,.6);transition:-webkit-transform linear .08s,background-color linear .08s;transition:transform linear .08s,background-color linear .08s;will-change:transform;background-color:var(--paper-toggle-button-unchecked-button-color,--paper-grey-50);@apply(--paper-toggle-button-unchecked-button)}.toggle-button.dragging{-webkit-transition:none;transition:none}:host([checked]:not([disabled])) .toggle-bar{opacity:.5;background-color:var(--paper-toggle-button-checked-bar-color,--primary-color);@apply(--paper-toggle-button-checked-bar)}:host([disabled]) .toggle-bar{background-color:#000;opacity:.12}:host([checked]) .toggle-button{-webkit-transform:translate(16px,0);transform:translate(16px,0)}:host([checked]:not([disabled])) .toggle-button{background-color:var(--paper-toggle-button-checked-button-color,--primary-color);@apply(--paper-toggle-button-checked-button)}:host([disabled]) .toggle-button{background-color:#bdbdbd;opacity:1}.toggle-ink{position:absolute;top:-14px;left:-14px;right:auto;bottom:auto;width:48px;height:48px;opacity:.5;pointer-events:none;color:var(--paper-toggle-button-unchecked-ink-color,--primary-text-color)}:host([checked]) .toggle-ink{color:var(--paper-toggle-button-checked-ink-color,--primary-color)}.toggle-container{display:inline-block;position:relative;width:36px;height:14px;margin:4px 1px}.toggle-label{position:relative;display:inline-block;vertical-align:middle;padding-left:var(--paper-toggle-button-label-spacing,8px);pointer-events:none;color:var(--paper-toggle-button-label-color,--primary-text-color)}:host([invalid]) .toggle-bar{background-color:var(--paper-toggle-button-invalid-bar-color,--error-color)}:host([invalid]) .toggle-button{background-color:var(--paper-toggle-button-invalid-button-color,--error-color)}:host([invalid]) .toggle-ink{color:var(--paper-toggle-button-invalid-ink-color,--error-color)}</style><div class="toggle-container"><div id="toggleBar" class="toggle-bar"></div><div id="toggleButton" class="toggle-button"></div></div><div class="toggle-label"><content></content></div></template><script>Polymer({is:"paper-toggle-button",behaviors:[Polymer.PaperCheckedElementBehavior],hostAttributes:{role:"button","aria-pressed":"false",tabindex:0},properties:{},listeners:{track:"_ontrack"},attached:function(){Polymer.RenderStatus.afterNextRender(this,function(){this.setScrollDirection("y")})},_ontrack:function(t){var e=t.detail;"start"===e.state?this._trackStart(e):"track"===e.state?this._trackMove(e):"end"===e.state&&this._trackEnd(e)},_trackStart:function(t){this._width=this.$.toggleBar.offsetWidth/2,this._trackChecked=this.checked,this.$.toggleButton.classList.add("dragging")},_trackMove:function(t){var e=t.dx;this._x=Math.min(this._width,Math.max(0,this._trackChecked?this._width+e:e)),this.translate3d(this._x+"px",0,0,this.$.toggleButton),this._userActivate(this._x>this._width/2)},_trackEnd:function(t){this.$.toggleButton.classList.remove("dragging"),this.transform("",this.$.toggleButton)},_createRipple:function(){this._rippleContainer=this.$.toggleButton;var t=Polymer.PaperRippleBehavior._createRipple();return t.id="ink",t.setAttribute("recenters",""),t.classList.add("circle","toggle-ink"),t}})</script></dom-module><dom-module id="ha-entity-toggle" assetpath="components/entity/"><template><style>:host{white-space:nowrap}paper-icon-button{color:var(--primary-text-color);transition:color .5s}paper-icon-button[state-active]{color:var(--default-primary-color)}paper-toggle-button{cursor:pointer;--paper-toggle-button-label-spacing:0;padding:9px 0}</style><template is="dom-if" if="[[stateObj.attributes.assumed_state]]"><paper-icon-button icon="mdi:flash-off" on-tap="turnOff" state-active$="[[!isOn]]"></paper-icon-button><paper-icon-button icon="mdi:flash" on-tap="turnOn" state-active$="[[isOn]]"></paper-icon-button></template><template is="dom-if" if="[[!stateObj.attributes.assumed_state]]"><paper-toggle-button class="self-center" checked="[[toggleChecked]]" on-change="toggleChanged"></paper-toggle-button></template></template></dom-module><script>Polymer({is:"ha-entity-toggle",properties:{hass:{type:Object},stateObj:{type:Object},toggleChecked:{type:Boolean,value:!1},isOn:{type:Boolean,computed:"computeIsOn(stateObj)",observer:"isOnChanged"}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation()},ready:function(){this.forceStateChange()},toggleChanged:function(t){var e=t.target.checked;e&&!this.isOn?this.callService(!0):!e&&this.isOn&&this.callService(!1)},isOnChanged:function(t){this.toggleChecked=t},forceStateChange:function(){this.toggleChecked===this.isOn&&(this.toggleChecked=!this.toggleChecked),this.toggleChecked=this.isOn},turnOn:function(){this.callService(!0)},turnOff:function(){this.callService(!1)},computeIsOn:function(t){return t&&window.hassUtil.OFF_STATES.indexOf(t.state)===-1},callService:function(t){var e,i,n;"lock"===this.stateObj.domain?(e="lock",i=t?"lock":"unlock"):"garage_door"===this.stateObj.domain?(e="garage_door",i=t?"open":"close"):(e="homeassistant",i=t?"turn_on":"turn_off"),n=this.stateObj,this.hass.serviceActions.callService(e,i,{entity_id:this.stateObj.entityId}).then(function(){setTimeout(function(){this.stateObj===n&&this.forceStateChange()}.bind(this),2e3)}.bind(this))}})</script><dom-module id="ha-state-icon" assetpath="components/entity/"><template><iron-icon icon="[[computeIcon(stateObj)]]"></iron-icon></template></dom-module><script>Polymer({is:"ha-state-icon",properties:{stateObj:{type:Object}},computeIcon:function(t){return window.hassUtil.stateIcon(t)}})</script><dom-module id="state-badge" assetpath="components/entity/"><template><style>:host{position:relative;display:inline-block;width:40px;color:#44739E;border-radius:50%;height:40px;text-align:center;background-size:cover;line-height:40px}ha-state-icon{transition:color .3s ease-in-out}ha-state-icon[data-domain=binary_sensor][data-state=on],ha-state-icon[data-domain=light][data-state=on],ha-state-icon[data-domain=sun][data-state=above_horizon],ha-state-icon[data-domain=switch][data-state=on]{color:#FDD835}ha-state-icon[data-state=unavailable]{color:var(--disabled-text-color)}</style><ha-state-icon id="icon" state-obj="[[stateObj]]" data-domain$="[[stateObj.domain]]" data-state$="[[stateObj.state]]"></ha-state-icon></template></dom-module><script>Polymer({is:"state-badge",properties:{stateObj:{type:Object,observer:"updateIconColor"}},updateIconColor:function(t){return t.attributes.entity_picture?(this.style.backgroundImage="url("+t.attributes.entity_picture+")",void(this.$.icon.style.display="none")):(this.style.backgroundImage="",this.$.icon.style.display="inline",void("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))}})</script><script>Polymer({is:"ha-relative-time",properties:{datetime:{type:String,observer:"datetimeChanged"},datetimeObj:{type:Object,observer:"datetimeObjChanged"},parsedDateTime:{type:Object}},created:function(){this.updateRelative=this.updateRelative.bind(this)},attached:function(){this.updateInterval=setInterval(this.updateRelative,6e4)},detached:function(){clearInterval(this.updateInterval)},datetimeChanged:function(e){this.parsedDateTime=e?new Date(e):null,this.updateRelative()},datetimeObjChanged:function(e){this.parsedDateTime=e,this.updateRelative()},updateRelative:function(){var e=Polymer.dom(this);e.innerHTML=this.parsedDateTime?window.hassUtil.relativeTime(this.parsedDateTime):"never"}})</script><dom-module id="state-info" assetpath="components/entity/"><template><style>:host{@apply(--paper-font-body1);min-width:150px;white-space:nowrap}state-badge{float:left}.info{margin-left:56px}.name{@apply(--paper-font-common-nowrap);color:var(--primary-text-color);line-height:40px}.name[in-dialog]{line-height:20px}.time-ago{@apply(--paper-font-common-nowrap);color:var(--secondary-text-color)}</style><div><state-badge state-obj="[[stateObj]]"></state-badge><div class="info"><div class="name" in-dialog$="[[inDialog]]">[[stateObj.entityDisplay]]</div><template is="dom-if" if="[[inDialog]]"><div class="time-ago"><ha-relative-time datetime-obj="[[stateObj.lastChangedAsDate]]"></ha-relative-time></div></template></div></div></template></dom-module><script>Polymer({is:"state-info",properties:{detailed:{type:Boolean,value:!1},stateObj:{type:Object},inDialog:{type:Boolean}}})</script><dom-module id="state-card-climate" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{@apply(--paper-font-body1);line-height:1.5}.state{margin-left:16px;text-align:right}.target{color:var(--primary-text-color)}.current{color:var(--secondary-text-color)}.operation-mode{font-weight:700;text-transform:capitalize}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><div class="target"><span class="operation-mode">[[stateObj.attributes.operation_mode]] </span><span>[[computeTargetTemperature(stateObj)]]</span></div><div class="current"><span>Currently: </span><span>[[stateObj.attributes.current_temperature]]</span> <span></span> <span>[[stateObj.attributes.unit_of_measurement]]</span></div></div></div></template></dom-module><script>Polymer({is:"state-card-climate",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeTargetTemperature:function(t){var e="";return t.attributes.target_temp_low&&t.attributes.target_temp_high?e=t.attributes.target_temp_low+" - "+t.attributes.target_temp_high+" "+t.attributes.unit_of_measurement:t.attributes.temperature&&(e=t.attributes.temperature+" "+t.attributes.unit_of_measurement),e}})</script><dom-module id="state-card-configurator" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><paper-button hidden$="[[inDialog]]">[[stateObj.state]]</paper-button></div><template is="dom-if" if="[[stateObj.attributes.description_image]]"><img hidden="" src="[[stateObj.attributes.description_image]]"></template></template></dom-module><script>Polymer({is:"state-card-configurator",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}}})</script><dom-module id="state-card-cover" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{line-height:1.5}.state{text-align:right;white-space:nowrap;width:127px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><paper-icon-button icon="mdi:arrow-up" on-tap="onOpenTap" disabled="[[computeIsFullyOpen(stateObj)]]"></paper-icon-button><paper-icon-button icon="mdi:stop" on-tap="onStopTap"></paper-icon-button><paper-icon-button icon="mdi:arrow-down" on-tap="onCloseTap" disabled="[[computeIsFullyClosed(stateObj)]]"></paper-icon-button></div></div></template></dom-module><script>Polymer({is:"state-card-cover",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeIsFullyOpen:function(t){return void 0!==t.attributes.current_position?100===t.attributes.current_position:"open"===t.state},computeIsFullyClosed:function(t){return void 0!==t.attributes.current_position?0===t.attributes.current_position:"closed"===t.state},onOpenTap:function(){this.hass.serviceActions.callService("cover","open_cover",{entity_id:this.stateObj.entityId})},onCloseTap:function(){this.hass.serviceActions.callService("cover","close_cover",{entity_id:this.stateObj.entityId})},onStopTap:function(){this.hass.serviceActions.callService("cover","stop_cover",{entity_id:this.stateObj.entityId})}})</script><dom-module id="state-card-display" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>.state{@apply(--paper-font-body1);color:var(--primary-text-color);margin-left:16px;text-align:right;line-height:40px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state">[[stateObj.stateDisplay]]</div></div></template></dom-module><script>Polymer({is:"state-card-display",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}}})</script><dom-module id="state-card-hvac" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>.state{@apply(--paper-font-body1);color:var(--primary-text-color);margin-left:16px;text-transform:capitalize;text-align:right;line-height:40px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state">[[stateObj.state]]</div></div></template></dom-module><script>Polymer({is:"state-card-hvac",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}}})</script><script>Polymer.PaperInputHelper={},Polymer.PaperInputHelper.NextLabelID=1,Polymer.PaperInputHelper.NextAddonID=1,Polymer.PaperInputBehaviorImpl={properties:{label:{type:String},value:{notify:!0,type:String},disabled:{type:Boolean,value:!1},invalid:{type:Boolean,value:!1,notify:!0},preventInvalidInput:{type:Boolean},allowedPattern:{type:String},type:{type:String},list:{type:String},pattern:{type:String},required:{type:Boolean,value:!1},errorMessage:{type:String},charCounter:{type:Boolean,value:!1},noLabelFloat:{type:Boolean,value:!1},alwaysFloatLabel:{type:Boolean,value:!1},autoValidate:{type:Boolean,value:!1},validator:{type:String},autocomplete:{type:String,value:"off"},autofocus:{type:Boolean,observer:"_autofocusChanged"},inputmode:{type:String},minlength:{type:Number},maxlength:{type:Number},min:{type:String},max:{type:String},step:{type:String},name:{type:String},placeholder:{type:String,value:""},readonly:{type:Boolean,value:!1},size:{type:Number},autocapitalize:{type:String,value:"none"},autocorrect:{type:String,value:"off"},autosave:{type:String},results:{type:Number},accept:{type:String},multiple:{type:Boolean},_ariaDescribedBy:{type:String,value:""},_ariaLabelledBy:{type:String,value:""}},listeners:{"addon-attached":"_onAddonAttached"},keyBindings:{"shift+tab:keydown":"_onShiftTabDown"},hostAttributes:{tabindex:0},get inputElement(){return this.$.input},get _focusableElement(){return this.inputElement},registered:function(){this._typesThatHaveText=["date","datetime","datetime-local","month","time","week","file"]},attached:function(){this._updateAriaLabelledBy(),this.inputElement&&this._typesThatHaveText.indexOf(this.inputElement.type)!==-1&&(this.alwaysFloatLabel=!0)},_appendStringWithSpace:function(e,t){return e=e?e+" "+t:t},_onAddonAttached:function(e){var t=e.path?e.path[0]:e.target;if(t.id)this._ariaDescribedBy=this._appendStringWithSpace(this._ariaDescribedBy,t.id);else{var a="paper-input-add-on-"+Polymer.PaperInputHelper.NextAddonID++;t.id=a,this._ariaDescribedBy=this._appendStringWithSpace(this._ariaDescribedBy,a)}},validate:function(){return this.inputElement.validate()},_focusBlurHandler:function(e){Polymer.IronControlState._focusBlurHandler.call(this,e),this.focused&&!this._shiftTabPressed&&this._focusableElement.focus()},_onShiftTabDown:function(e){var t=this.getAttribute("tabindex");this._shiftTabPressed=!0,this.setAttribute("tabindex","-1"),this.async(function(){this.setAttribute("tabindex",t),this._shiftTabPressed=!1},1)},_handleAutoValidate:function(){this.autoValidate&&this.validate()},updateValueAndPreserveCaret:function(e){try{var t=this.inputElement.selectionStart;this.value=e,this.inputElement.selectionStart=t,this.inputElement.selectionEnd=t}catch(t){this.value=e}},_computeAlwaysFloatLabel:function(e,t){return t||e},_updateAriaLabelledBy:function(){var e=Polymer.dom(this.root).querySelector("label");if(!e)return void(this._ariaLabelledBy="");var t;e.id?t=e.id:(t="paper-input-label-"+Polymer.PaperInputHelper.NextLabelID++,e.id=t),this._ariaLabelledBy=t},_onChange:function(e){this.shadowRoot&&this.fire(e.type,{sourceEvent:e},{node:this,bubbles:e.bubbles,cancelable:e.cancelable})},_autofocusChanged:function(){if(this.autofocus&&this._focusableElement){var e=document.activeElement,t=e instanceof HTMLElement,a=t&&e!==document.body&&e!==document.documentElement;a||this._focusableElement.focus()}}},Polymer.PaperInputBehavior=[Polymer.IronControlState,Polymer.IronA11yKeysBehavior,Polymer.PaperInputBehaviorImpl]</script><dom-module id="paper-input-char-counter" assetpath="../bower_components/paper-input/"><template><style>:host{display:inline-block;float:right;@apply(--paper-font-caption);@apply(--paper-input-char-counter)}:host([hidden]){display:none!important}:host-context([dir=rtl]){float:left}</style><span>[[_charCounterStr]]</span></template></dom-module><script>Polymer({is:"paper-input-char-counter",behaviors:[Polymer.PaperInputAddonBehavior],properties:{_charCounterStr:{type:String,value:"0"}},update:function(t){if(t.inputElement){t.value=t.value||"";var e=t.value.toString().length.toString();t.inputElement.hasAttribute("maxlength")&&(e+="/"+t.inputElement.getAttribute("maxlength")),this._charCounterStr=e}}})</script><dom-module id="paper-input" assetpath="../bower_components/paper-input/"><template><style>:host{display:block}:host([focused]){outline:0}:host([hidden]){display:none!important}input::-webkit-input-placeholder{color:var(--paper-input-container-color,--secondary-text-color)}input:-moz-placeholder{color:var(--paper-input-container-color,--secondary-text-color)}input::-moz-placeholder{color:var(--paper-input-container-color,--secondary-text-color)}input:-ms-input-placeholder{color:var(--paper-input-container-color,--secondary-text-color)}label{pointer-events:none}</style><paper-input-container no-label-float="[[noLabelFloat]]" always-float-label="[[_computeAlwaysFloatLabel(alwaysFloatLabel,placeholder)]]" auto-validate$="[[autoValidate]]" disabled$="[[disabled]]" invalid="[[invalid]]"><content select="[prefix]"></content><label hidden$="[[!label]]" aria-hidden="true" for="input">[[label]]</label><input is="iron-input" id="input" aria-labelledby$="[[_ariaLabelledBy]]" aria-describedby$="[[_ariaDescribedBy]]" disabled$="[[disabled]]" title$="[[title]]" bind-value="{{value}}" invalid="{{invalid}}" prevent-invalid-input="[[preventInvalidInput]]" allowed-pattern="[[allowedPattern]]" validator="[[validator]]" type$="[[type]]" pattern$="[[pattern]]" required$="[[required]]" autocomplete$="[[autocomplete]]" autofocus$="[[autofocus]]" inputmode$="[[inputmode]]" minlength$="[[minlength]]" maxlength$="[[maxlength]]" min$="[[min]]" max$="[[max]]" step$="[[step]]" name$="[[name]]" placeholder$="[[placeholder]]" readonly$="[[readonly]]" list$="[[list]]" size$="[[size]]" autocapitalize$="[[autocapitalize]]" autocorrect$="[[autocorrect]]" on-change="_onChange" tabindex$="[[tabindex]]" autosave$="[[autosave]]" results$="[[results]]" accept$="[[accept]]" multiple$="[[multiple]]"><content select="[suffix]"></content><template is="dom-if" if="[[errorMessage]]"><paper-input-error aria-live="assertive">[[errorMessage]]</paper-input-error></template><template is="dom-if" if="[[charCounter]]"><paper-input-char-counter></paper-input-char-counter></template></paper-input-container></template></dom-module><script>Polymer({is:"paper-input",behaviors:[Polymer.IronFormElementBehavior,Polymer.PaperInputBehavior]})</script><script>Polymer.IronFitBehavior={properties:{sizingTarget:{type:Object,value:function(){return this}},fitInto:{type:Object,value:window},noOverlap:{type:Boolean},positionTarget:{type:Element},horizontalAlign:{type:String},verticalAlign:{type:String},dynamicAlign:{type:Boolean},horizontalOffset:{type:Number,value:0,notify:!0},verticalOffset:{type:Number,value:0,notify:!0},autoFitOnAttach:{type:Boolean,value:!1},_fitInfo:{type:Object}},get _fitWidth(){var t;return t=this.fitInto===window?this.fitInto.innerWidth:this.fitInto.getBoundingClientRect().width},get _fitHeight(){var t;return t=this.fitInto===window?this.fitInto.innerHeight:this.fitInto.getBoundingClientRect().height},get _fitLeft(){var t;return t=this.fitInto===window?0:this.fitInto.getBoundingClientRect().left},get _fitTop(){var t;return t=this.fitInto===window?0:this.fitInto.getBoundingClientRect().top},get _defaultPositionTarget(){var t=Polymer.dom(this).parentNode;return t&&t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(t=t.host),t},get _localeHorizontalAlign(){if(this._isRTL){if("right"===this.horizontalAlign)return"left";if("left"===this.horizontalAlign)return"right"}return this.horizontalAlign},attached:function(){this._isRTL="rtl"==window.getComputedStyle(this).direction,this.positionTarget=this.positionTarget||this._defaultPositionTarget,this.autoFitOnAttach&&("none"===window.getComputedStyle(this).display?setTimeout(function(){this.fit()}.bind(this)):this.fit())},fit:function(){this.position(),this.constrain(),this.center()},_discoverInfo:function(){if(!this._fitInfo){var t=window.getComputedStyle(this),i=window.getComputedStyle(this.sizingTarget);this._fitInfo={inlineStyle:{top:this.style.top||"",left:this.style.left||"",position:this.style.position||""},sizerInlineStyle:{maxWidth:this.sizingTarget.style.maxWidth||"",maxHeight:this.sizingTarget.style.maxHeight||"",boxSizing:this.sizingTarget.style.boxSizing||""},positionedBy:{vertically:"auto"!==t.top?"top":"auto"!==t.bottom?"bottom":null,horizontally:"auto"!==t.left?"left":"auto"!==t.right?"right":null},sizedBy:{height:"none"!==i.maxHeight,width:"none"!==i.maxWidth,minWidth:parseInt(i.minWidth,10)||0,minHeight:parseInt(i.minHeight,10)||0},margin:{top:parseInt(t.marginTop,10)||0,right:parseInt(t.marginRight,10)||0,bottom:parseInt(t.marginBottom,10)||0,left:parseInt(t.marginLeft,10)||0}},this.verticalOffset&&(this._fitInfo.margin.top=this._fitInfo.margin.bottom=this.verticalOffset,this._fitInfo.inlineStyle.marginTop=this.style.marginTop||"",this._fitInfo.inlineStyle.marginBottom=this.style.marginBottom||"",this.style.marginTop=this.style.marginBottom=this.verticalOffset+"px"),this.horizontalOffset&&(this._fitInfo.margin.left=this._fitInfo.margin.right=this.horizontalOffset,this._fitInfo.inlineStyle.marginLeft=this.style.marginLeft||"",this._fitInfo.inlineStyle.marginRight=this.style.marginRight||"",this.style.marginLeft=this.style.marginRight=this.horizontalOffset+"px")}},resetFit:function(){var t=this._fitInfo||{};for(var i in t.sizerInlineStyle)this.sizingTarget.style[i]=t.sizerInlineStyle[i];for(var i in t.inlineStyle)this.style[i]=t.inlineStyle[i];this._fitInfo=null},refit:function(){var t=this.sizingTarget.scrollLeft,i=this.sizingTarget.scrollTop;this.resetFit(),this.fit(),this.sizingTarget.scrollLeft=t,this.sizingTarget.scrollTop=i},position:function(){if(this.horizontalAlign||this.verticalAlign){this._discoverInfo(),this.style.position="fixed",this.sizingTarget.style.boxSizing="border-box",this.style.left="0px",this.style.top="0px";var t=this.getBoundingClientRect(),i=this.__getNormalizedRect(this.positionTarget),e=this.__getNormalizedRect(this.fitInto),n=this._fitInfo.margin,o={width:t.width+n.left+n.right,height:t.height+n.top+n.bottom},h=this.__getPosition(this._localeHorizontalAlign,this.verticalAlign,o,i,e),s=h.left+n.left,l=h.top+n.top,r=Math.min(e.right-n.right,s+t.width),a=Math.min(e.bottom-n.bottom,l+t.height),g=this._fitInfo.sizedBy.minWidth,f=this._fitInfo.sizedBy.minHeight;s<n.left&&(s=n.left,r-s<g&&(s=r-g)),l<n.top&&(l=n.top,a-l<f&&(l=a-f)),this.sizingTarget.style.maxWidth=r-s+"px",this.sizingTarget.style.maxHeight=a-l+"px",this.style.left=s-t.left+"px",this.style.top=l-t.top+"px"}},constrain:function(){if(!this.horizontalAlign&&!this.verticalAlign){this._discoverInfo();var t=this._fitInfo;t.positionedBy.vertically||(this.style.position="fixed",this.style.top="0px"),t.positionedBy.horizontally||(this.style.position="fixed",this.style.left="0px"),this.sizingTarget.style.boxSizing="border-box";var i=this.getBoundingClientRect();t.sizedBy.height||this.__sizeDimension(i,t.positionedBy.vertically,"top","bottom","Height"),t.sizedBy.width||this.__sizeDimension(i,t.positionedBy.horizontally,"left","right","Width")}},_sizeDimension:function(t,i,e,n,o){this.__sizeDimension(t,i,e,n,o)},__sizeDimension:function(t,i,e,n,o){var h=this._fitInfo,s=this.__getNormalizedRect(this.fitInto),l="Width"===o?s.width:s.height,r=i===n,a=r?l-t[n]:t[e],g=h.margin[r?e:n],f="offset"+o,p=this[f]-this.sizingTarget[f];this.sizingTarget.style["max"+o]=l-g-a-p+"px"},center:function(){if(!this.horizontalAlign&&!this.verticalAlign){this._discoverInfo();var t=this._fitInfo.positionedBy;if(!t.vertically||!t.horizontally){this.style.position="fixed",t.vertically||(this.style.top="0px"),t.horizontally||(this.style.left="0px");var i=this.getBoundingClientRect(),e=this.__getNormalizedRect(this.fitInto);if(!t.vertically){var n=e.top-i.top+(e.height-i.height)/2;this.style.top=n+"px"}if(!t.horizontally){var o=e.left-i.left+(e.width-i.width)/2;this.style.left=o+"px"}}}},__getNormalizedRect:function(t){return t===document.documentElement||t===window?{top:0,left:0,width:window.innerWidth,height:window.innerHeight,right:window.innerWidth,bottom:window.innerHeight}:t.getBoundingClientRect()},__getCroppedArea:function(t,i,e){var n=Math.min(0,t.top)+Math.min(0,e.bottom-(t.top+i.height)),o=Math.min(0,t.left)+Math.min(0,e.right-(t.left+i.width));return Math.abs(n)*i.width+Math.abs(o)*i.height},__getPosition:function(t,i,e,n,o){var h=[{verticalAlign:"top",horizontalAlign:"left",top:n.top,left:n.left},{verticalAlign:"top",horizontalAlign:"right",top:n.top,left:n.right-e.width},{verticalAlign:"bottom",horizontalAlign:"left",top:n.bottom-e.height,left:n.left},{verticalAlign:"bottom",horizontalAlign:"right",top:n.bottom-e.height,left:n.right-e.width}];if(this.noOverlap){for(var s=0,l=h.length;s<l;s++){var r={};for(var a in h[s])r[a]=h[s][a];h.push(r)}h[0].top=h[1].top+=n.height,h[2].top=h[3].top-=n.height,h[4].left=h[6].left+=n.width,h[5].left=h[7].left-=n.width}i="auto"===i?null:i,t="auto"===t?null:t;for(var g,s=0;s<h.length;s++){var f=h[s];if(!this.dynamicAlign&&!this.noOverlap&&f.verticalAlign===i&&f.horizontalAlign===t){g=f;break}var p=!(i&&f.verticalAlign!==i||t&&f.horizontalAlign!==t);if(this.dynamicAlign||p){g=g||f,f.croppedArea=this.__getCroppedArea(f,e,o);var d=f.croppedArea-g.croppedArea;if((d<0||0===d&&p)&&(g=f),0===g.croppedArea&&p)break}}return g}}</script><dom-module id="iron-overlay-backdrop" assetpath="../bower_components/iron-overlay-behavior/"><template><style>:host{position:fixed;top:0;left:0;width:100%;height:100%;background-color:var(--iron-overlay-backdrop-background-color,#000);opacity:0;transition:opacity .2s;pointer-events:none;@apply(--iron-overlay-backdrop)}:host(.opened){opacity:var(--iron-overlay-backdrop-opacity,.6);pointer-events:auto;@apply(--iron-overlay-backdrop-opened)}</style><content></content></template></dom-module><script>!function(){"use strict";Polymer({is:"iron-overlay-backdrop",properties:{opened:{reflectToAttribute:!0,type:Boolean,value:!1,observer:"_openedChanged"}},listeners:{transitionend:"_onTransitionend"},created:function(){this.__openedRaf=null},attached:function(){this.opened&&this._openedChanged(this.opened)},prepare:function(){this.opened&&!this.parentNode&&Polymer.dom(document.body).appendChild(this)},open:function(){this.opened=!0},close:function(){this.opened=!1},complete:function(){this.opened||this.parentNode!==document.body||Polymer.dom(this.parentNode).removeChild(this)},_onTransitionend:function(e){e&&e.target===this&&this.complete()},_openedChanged:function(e){if(e)this.prepare();else{var t=window.getComputedStyle(this);"0s"!==t.transitionDuration&&0!=t.opacity||this.complete()}this.isAttached&&(this.__openedRaf&&(window.cancelAnimationFrame(this.__openedRaf),this.__openedRaf=null),this.scrollTop=this.scrollTop,this.__openedRaf=window.requestAnimationFrame(function(){this.__openedRaf=null,this.toggleClass("opened",this.opened)}.bind(this)))}})}()</script><script>Polymer.IronOverlayManagerClass=function(){this._overlays=[],this._minimumZ=101,this._backdropElement=null,Polymer.Gestures.add(document,"tap",this._onCaptureClick.bind(this)),document.addEventListener("focus",this._onCaptureFocus.bind(this),!0),document.addEventListener("keydown",this._onCaptureKeyDown.bind(this),!0)},Polymer.IronOverlayManagerClass.prototype={constructor:Polymer.IronOverlayManagerClass,get backdropElement(){return this._backdropElement||(this._backdropElement=document.createElement("iron-overlay-backdrop")),this._backdropElement},get deepActiveElement(){for(var e=document.activeElement||document.body;e.root&&Polymer.dom(e.root).activeElement;)e=Polymer.dom(e.root).activeElement;return e},_bringOverlayAtIndexToFront:function(e){var t=this._overlays[e];if(t){var r=this._overlays.length-1,a=this._overlays[r];if(a&&this._shouldBeBehindOverlay(t,a)&&r--,!(e>=r)){var n=Math.max(this.currentOverlayZ(),this._minimumZ);for(this._getZ(t)<=n&&this._applyOverlayZ(t,n);e<r;)this._overlays[e]=this._overlays[e+1],e++;this._overlays[r]=t}}},addOrRemoveOverlay:function(e){e.opened?this.addOverlay(e):this.removeOverlay(e)},addOverlay:function(e){var t=this._overlays.indexOf(e);if(t>=0)return this._bringOverlayAtIndexToFront(t),void this.trackBackdrop();var r=this._overlays.length,a=this._overlays[r-1],n=Math.max(this._getZ(a),this._minimumZ),o=this._getZ(e);if(a&&this._shouldBeBehindOverlay(e,a)){this._applyOverlayZ(a,n),r--;var i=this._overlays[r-1];n=Math.max(this._getZ(i),this._minimumZ)}o<=n&&this._applyOverlayZ(e,n),this._overlays.splice(r,0,e),this.trackBackdrop()},removeOverlay:function(e){var t=this._overlays.indexOf(e);t!==-1&&(this._overlays.splice(t,1),this.trackBackdrop())},currentOverlay:function(){var e=this._overlays.length-1;return this._overlays[e]},currentOverlayZ:function(){return this._getZ(this.currentOverlay())},ensureMinimumZ:function(e){this._minimumZ=Math.max(this._minimumZ,e)},focusOverlay:function(){var e=this.currentOverlay();e&&e._applyFocus()},trackBackdrop:function(){var e=this._overlayWithBackdrop();(e||this._backdropElement)&&(this.backdropElement.style.zIndex=this._getZ(e)-1,this.backdropElement.opened=!!e)},getBackdrops:function(){for(var e=[],t=0;t<this._overlays.length;t++)this._overlays[t].withBackdrop&&e.push(this._overlays[t]);return e},backdropZ:function(){return this._getZ(this._overlayWithBackdrop())-1},_overlayWithBackdrop:function(){for(var e=0;e<this._overlays.length;e++)if(this._overlays[e].withBackdrop)return this._overlays[e]},_getZ:function(e){var t=this._minimumZ;if(e){var r=Number(e.style.zIndex||window.getComputedStyle(e).zIndex);r===r&&(t=r)}return t},_setZ:function(e,t){e.style.zIndex=t},_applyOverlayZ:function(e,t){this._setZ(e,t+2)},_overlayInPath:function(e){e=e||[];for(var t=0;t<e.length;t++)if(e[t]._manager===this)return e[t]},_onCaptureClick:function(e){var t=this.currentOverlay();t&&this._overlayInPath(Polymer.dom(e).path)!==t&&t._onCaptureClick(e)},_onCaptureFocus:function(e){var t=this.currentOverlay();t&&t._onCaptureFocus(e)},_onCaptureKeyDown:function(e){var t=this.currentOverlay();t&&(Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e,"esc")?t._onCaptureEsc(e):Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e,"tab")&&t._onCaptureTab(e))},_shouldBeBehindOverlay:function(e,t){return!e.alwaysOnTop&&t.alwaysOnTop}},Polymer.IronOverlayManager=new Polymer.IronOverlayManagerClass</script><script>!function(){"use strict";Polymer.IronOverlayBehaviorImpl={properties:{opened:{observer:"_openedChanged",type:Boolean,value:!1,notify:!0},canceled:{observer:"_canceledChanged",readOnly:!0,type:Boolean,value:!1},withBackdrop:{observer:"_withBackdropChanged",type:Boolean},noAutoFocus:{type:Boolean,value:!1},noCancelOnEscKey:{type:Boolean,value:!1},noCancelOnOutsideClick:{type:Boolean,value:!1},closingReason:{type:Object},restoreFocusOnClose:{type:Boolean,value:!1},alwaysOnTop:{type:Boolean},_manager:{type:Object,value:Polymer.IronOverlayManager},_focusedChild:{type:Object}},listeners:{"iron-resize":"_onIronResize"},get backdropElement(){return this._manager.backdropElement},get _focusNode(){return this._focusedChild||Polymer.dom(this).querySelector("[autofocus]")||this},get _focusableNodes(){var e=["a[href]","area[href]","iframe","[tabindex]","[contentEditable=true]"],t=["input","select","textarea","button"],i=e.join(':not([tabindex="-1"]),')+':not([tabindex="-1"]),'+t.join(':not([disabled]):not([tabindex="-1"]),')+':not([disabled]):not([tabindex="-1"])',s=Polymer.dom(this).querySelectorAll(i);return this.tabIndex>=0&&s.splice(0,0,this),s.sort(function(e,t){return e.tabIndex===t.tabIndex?0:0===e.tabIndex||e.tabIndex>t.tabIndex?1:-1})},ready:function(){this.__isAnimating=!1,this.__shouldRemoveTabIndex=!1,this.__firstFocusableNode=this.__lastFocusableNode=null,this.__raf=null,this.__restoreFocusNode=null,this._ensureSetup()},attached:function(){this.opened&&this._openedChanged(this.opened),this._observer=Polymer.dom(this).observeNodes(this._onNodesChange)},detached:function(){Polymer.dom(this).unobserveNodes(this._observer),this._observer=null,this.__raf&&(window.cancelAnimationFrame(this.__raf),this.__raf=null),this._manager.removeOverlay(this)},toggle:function(){this._setCanceled(!1),this.opened=!this.opened},open:function(){this._setCanceled(!1),this.opened=!0},close:function(){this._setCanceled(!1),this.opened=!1},cancel:function(e){var t=this.fire("iron-overlay-canceled",e,{cancelable:!0});t.defaultPrevented||(this._setCanceled(!0),this.opened=!1)},invalidateTabbables:function(){this.__firstFocusableNode=this.__lastFocusableNode=null},_ensureSetup:function(){this._overlaySetup||(this._overlaySetup=!0,this.style.outline="none",this.style.display="none")},_openedChanged:function(e){e?this.removeAttribute("aria-hidden"):this.setAttribute("aria-hidden","true"),this.isAttached&&(this.__isAnimating=!0,this.__onNextAnimationFrame(this.__openedChanged))},_canceledChanged:function(){this.closingReason=this.closingReason||{},this.closingReason.canceled=this.canceled},_withBackdropChanged:function(){this.withBackdrop&&!this.hasAttribute("tabindex")?(this.setAttribute("tabindex","-1"),this.__shouldRemoveTabIndex=!0):this.__shouldRemoveTabIndex&&(this.removeAttribute("tabindex"),this.__shouldRemoveTabIndex=!1),this.opened&&this.isAttached&&this._manager.trackBackdrop()},_prepareRenderOpened:function(){this.__restoreFocusNode=this._manager.deepActiveElement,this._preparePositioning(),this.refit(),this._finishPositioning(),this.noAutoFocus&&document.activeElement===this._focusNode&&(this._focusNode.blur(),this.__restoreFocusNode.focus())},_renderOpened:function(){this._finishRenderOpened()},_renderClosed:function(){this._finishRenderClosed()},_finishRenderOpened:function(){this.notifyResize(),this.__isAnimating=!1,this.fire("iron-overlay-opened")},_finishRenderClosed:function(){this.style.display="none",this.style.zIndex="",this.notifyResize(),this.__isAnimating=!1,this.fire("iron-overlay-closed",this.closingReason)},_preparePositioning:function(){this.style.transition=this.style.webkitTransition="none",this.style.transform=this.style.webkitTransform="none",this.style.display=""},_finishPositioning:function(){this.style.display="none",this.scrollTop=this.scrollTop,this.style.transition=this.style.webkitTransition="",this.style.transform=this.style.webkitTransform="",this.style.display="",this.scrollTop=this.scrollTop},_applyFocus:function(){if(this.opened)this.noAutoFocus||this._focusNode.focus();else{this._focusNode.blur(),this._focusedChild=null,this.restoreFocusOnClose&&this.__restoreFocusNode&&this.__restoreFocusNode.focus(),this.__restoreFocusNode=null;var e=this._manager.currentOverlay();e&&this!==e&&e._applyFocus()}},_onCaptureClick:function(e){this.noCancelOnOutsideClick||this.cancel(e)},_onCaptureFocus:function(e){if(this.withBackdrop){var t=Polymer.dom(e).path;t.indexOf(this)===-1?(e.stopPropagation(),this._applyFocus()):this._focusedChild=t[0]}},_onCaptureEsc:function(e){this.noCancelOnEscKey||this.cancel(e)},_onCaptureTab:function(e){if(this.withBackdrop){this.__ensureFirstLastFocusables();var t=e.shiftKey,i=t?this.__firstFocusableNode:this.__lastFocusableNode,s=t?this.__lastFocusableNode:this.__firstFocusableNode,n=!1;if(i===s)n=!0;else{var o=this._manager.deepActiveElement;n=o===i||o===this}n&&(e.preventDefault(),this._focusedChild=s,this._applyFocus())}},_onIronResize:function(){this.opened&&!this.__isAnimating&&this.__onNextAnimationFrame(this.refit)},_onNodesChange:function(){this.opened&&!this.__isAnimating&&(this.invalidateTabbables(),this.notifyResize())},__ensureFirstLastFocusables:function(){if(!this.__firstFocusableNode||!this.__lastFocusableNode){var e=this._focusableNodes;this.__firstFocusableNode=e[0],this.__lastFocusableNode=e[e.length-1]}},__openedChanged:function(){this.opened?(this._prepareRenderOpened(),this._manager.addOverlay(this),this._applyFocus(),this._renderOpened()):(this._manager.removeOverlay(this),this._applyFocus(),this._renderClosed())},__onNextAnimationFrame:function(e){this.__raf&&window.cancelAnimationFrame(this.__raf);var t=this;this.__raf=window.requestAnimationFrame(function(){t.__raf=null,e.call(t)})}},Polymer.IronOverlayBehavior=[Polymer.IronFitBehavior,Polymer.IronResizableBehavior,Polymer.IronOverlayBehaviorImpl]}()</script><script>Polymer.NeonAnimatableBehavior={properties:{animationConfig:{type:Object},entryAnimation:{observer:"_entryAnimationChanged",type:String},exitAnimation:{observer:"_exitAnimationChanged",type:String}},_entryAnimationChanged:function(){this.animationConfig=this.animationConfig||{},this.animationConfig.entry=[{name:this.entryAnimation,node:this}]},_exitAnimationChanged:function(){this.animationConfig=this.animationConfig||{},this.animationConfig.exit=[{name:this.exitAnimation,node:this}]},_copyProperties:function(i,n){for(var t in n)i[t]=n[t]},_cloneConfig:function(i){var n={isClone:!0};return this._copyProperties(n,i),n},_getAnimationConfigRecursive:function(i,n,t){if(this.animationConfig){if(this.animationConfig.value&&"function"==typeof this.animationConfig.value)return void this._warn(this._logf("playAnimation","Please put 'animationConfig' inside of your components 'properties' object instead of outside of it."));var o;if(o=i?this.animationConfig[i]:this.animationConfig,Array.isArray(o)||(o=[o]),o)for(var e,a=0;e=o[a];a++)if(e.animatable)e.animatable._getAnimationConfigRecursive(e.type||i,n,t);else if(e.id){var r=n[e.id];r?(r.isClone||(n[e.id]=this._cloneConfig(r),r=n[e.id]),this._copyProperties(r,e)):n[e.id]=e}else t.push(e)}},getAnimationConfig:function(i){var n={},t=[];this._getAnimationConfigRecursive(i,n,t);for(var o in n)t.push(n[o]);return t}}</script><script>Polymer.NeonAnimationRunnerBehaviorImpl={_configureAnimations:function(n){var i=[];if(n.length>0)for(var e,t=0;e=n[t];t++){var o=document.createElement(e.name);if(o.isNeonAnimation){var a=null;try{a=o.configure(e),"function"!=typeof a.cancel&&(a=document.timeline.play(a))}catch(n){a=null,console.warn("Couldnt play","(",e.name,").",n)}a&&i.push({neonAnimation:o,config:e,animation:a})}else console.warn(this.is+":",e.name,"not found!")}return i},_shouldComplete:function(n){for(var i=!0,e=0;e<n.length;e++)if("finished"!=n[e].animation.playState){i=!1;break}return i},_complete:function(n){for(var i=0;i<n.length;i++)n[i].neonAnimation.complete(n[i].config);for(var i=0;i<n.length;i++)n[i].animation.cancel()},playAnimation:function(n,i){var e=this.getAnimationConfig(n);if(e){this._active=this._active||{},this._active[n]&&(this._complete(this._active[n]),delete this._active[n]);var t=this._configureAnimations(e);if(0==t.length)return void this.fire("neon-animation-finish",i,{bubbles:!1});this._active[n]=t;for(var o=0;o<t.length;o++)t[o].animation.onfinish=function(){this._shouldComplete(t)&&(this._complete(t),delete this._active[n],this.fire("neon-animation-finish",i,{bubbles:!1}))}.bind(this)}},cancelAnimation:function(){for(var n in this._animations)this._animations[n].cancel();this._animations={}}},Polymer.NeonAnimationRunnerBehavior=[Polymer.NeonAnimatableBehavior,Polymer.NeonAnimationRunnerBehaviorImpl]</script><script>Polymer.NeonAnimationBehavior={properties:{animationTiming:{type:Object,value:function(){return{duration:500,easing:"cubic-bezier(0.4, 0, 0.2, 1)",fill:"both"}}}},isNeonAnimation:!0,timingFromConfig:function(i){if(i.timing)for(var n in i.timing)this.animationTiming[n]=i.timing[n];return this.animationTiming},setPrefixedProperty:function(i,n,r){for(var t,o={transform:["webkitTransform"],transformOrigin:["mozTransformOrigin","webkitTransformOrigin"]},e=o[n],m=0;t=e[m];m++)i.style[t]=r;i.style[n]=r},complete:function(){}}</script><script>!function(a,b){var c={},d={},e={},f=null;!function(t,e){function i(t){if("number"==typeof t)return t;var e={};for(var i in t)e[i]=t[i];return e}function n(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,this._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal",this._easing="linear",this._easingFunction=E}function r(){return t.isDeprecated("Invalid timing inputs","2016-03-02","TypeError exceptions will be thrown instead.",!0)}function o(e,i,r){var o=new n;return i&&(o.fill="both",o.duration="auto"),"number"!=typeof e||isNaN(e)?void 0!==e&&Object.getOwnPropertyNames(e).forEach(function(i){if("auto"!=e[i]){if(("number"==typeof o[i]||"duration"==i)&&("number"!=typeof e[i]||isNaN(e[i])))return;if("fill"==i&&w.indexOf(e[i])==-1)return;if("direction"==i&&T.indexOf(e[i])==-1)return;if("playbackRate"==i&&1!==e[i]&&t.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;o[i]=e[i]}}):o.duration=e,o}function a(t){return"number"==typeof t&&(t=isNaN(t)?{duration:0}:{duration:t}),t}function s(e,i){return e=t.numericTimingToObject(e),o(e,i)}function u(t,e,i,n){return t<0||t>1||i<0||i>1?E:function(r){function o(t,e,i){return 3*t*(1-i)*(1-i)*i+3*e*(1-i)*i*i+i*i*i}if(r<=0){var a=0;return t>0?a=e/t:!e&&i>0&&(a=n/i),a*r}if(r>=1){var s=0;return i<1?s=(n-1)/(i-1):1==i&&t<1&&(s=(e-1)/(t-1)),1+s*(r-1)}for(var u=0,c=1;u<c;){var f=(u+c)/2,l=o(t,i,f);if(Math.abs(r-l)<1e-5)return o(e,n,f);l<r?u=f:c=f}return o(e,n,f)}}function c(t,e){return function(i){if(i>=1)return 1;var n=1/t;return i+=e*n,i-i%n}}function f(t){R||(R=document.createElement("div").style),R.animationTimingFunction="",R.animationTimingFunction=t;var e=R.animationTimingFunction;if(""==e&&r())throw new TypeError(t+" is not a valid value for easing");return e}function l(t){if("linear"==t)return E;var e=O.exec(t);if(e)return u.apply(this,e.slice(1).map(Number));var i=k.exec(t);if(i)return c(Number(i[1]),{start:x,middle:A,end:P}[i[2]]);var n=j[t];return n?n:E}function h(t){return Math.abs(m(t)/t.playbackRate)}function m(t){return 0===t.duration||0===t.iterations?0:t.duration*t.iterations}function d(t,e,i){if(null==e)return S;var n=i.delay+t+i.endDelay;return e<Math.min(i.delay,n)?C:e>=Math.min(i.delay+t,n)?D:F}function p(t,e,i,n,r){switch(n){case C:return"backwards"==e||"both"==e?0:null;case F:return i-r;case D:return"forwards"==e||"both"==e?t:null;case S:return null}}function _(t,e,i,n,r){var o=r;return 0===t?e!==C&&(o+=i):o+=n/t,o}function g(t,e,i,n,r,o){var a=t===1/0?e%1:t%1;return 0!==a||i!==D||0===n||0===r&&0!==o||(a=1),a}function b(t,e,i,n){return t===D&&e===1/0?1/0:1===i?Math.floor(n)-1:Math.floor(n)}function v(t,e,i){var n=t;if("normal"!==t&&"reverse"!==t){var r=e;"alternate-reverse"===t&&(r+=1),n="normal",r!==1/0&&r%2!==0&&(n="reverse")}return"normal"===n?i:1-i}function y(t,e,i){var n=d(t,e,i),r=p(t,i.fill,e,n,i.delay);if(null===r)return null;var o=_(i.duration,n,i.iterations,r,i.iterationStart),a=g(o,i.iterationStart,n,i.iterations,r,i.duration),s=b(n,i.iterations,a,o),u=v(i.direction,s,a);return i._easingFunction(u)}var w="backwards|forwards|both|none".split("|"),T="reverse|alternate|alternate-reverse".split("|"),E=function(t){return t};n.prototype={_setMember:function(e,i){this["_"+e]=i,this._effect&&(this._effect._timingInput[e]=i,this._effect._timing=t.normalizeTimingInput(this._effect._timingInput),this._effect.activeDuration=t.calculateActiveDuration(this._effect._timing),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimation())},get playbackRate(){return this._playbackRate},set delay(t){this._setMember("delay",t)},get delay(){return this._delay},set endDelay(t){this._setMember("endDelay",t)},get endDelay(){return this._endDelay},set fill(t){this._setMember("fill",t)},get fill(){return this._fill},set iterationStart(t){if((isNaN(t)||t<0)&&r())throw new TypeError("iterationStart must be a non-negative number, received: "+timing.iterationStart);this._setMember("iterationStart",t)},get iterationStart(){return this._iterationStart},set duration(t){if("auto"!=t&&(isNaN(t)||t<0)&&r())throw new TypeError("duration must be non-negative or auto, received: "+t);this._setMember("duration",t)},get duration(){return this._duration},set direction(t){this._setMember("direction",t)},get direction(){return this._direction},set easing(t){this._easingFunction=l(f(t)),this._setMember("easing",t)},get easing(){return this._easing},set iterations(t){if((isNaN(t)||t<0)&&r())throw new TypeError("iterations must be non-negative, received: "+t);this._setMember("iterations",t)},get iterations(){return this._iterations}};var x=1,A=.5,P=0,j={ease:u(.25,.1,.25,1),"ease-in":u(.42,0,1,1),"ease-out":u(0,0,.58,1),"ease-in-out":u(.42,0,.58,1),"step-start":c(1,x),"step-middle":c(1,A),"step-end":c(1,P)},R=null,N="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",O=new RegExp("cubic-bezier\\("+N+","+N+","+N+","+N+"\\)"),k=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,S=0,C=1,D=2,F=3;t.cloneTimingInput=i,t.makeTiming=o,t.numericTimingToObject=a,t.normalizeTimingInput=s,t.calculateActiveDuration=h,t.calculateIterationProgress=y,t.calculatePhase=d,t.normalizeEasing=f,t.parseEasingFunction=l}(c,f),function(t,e){function i(t,e){return t in f?f[t][e]||e:e}function n(t){return"display"===t||0===t.lastIndexOf("animation",0)||0===t.lastIndexOf("transition",0)}function r(t,e,r){if(!n(t)){var o=s[t];if(o){u.style[t]=e;for(var a in o){var c=o[a],f=u.style[c];r[c]=i(c,f)}}else r[t]=i(t,e)}}function o(t){var e=[];for(var i in t)if(!(i in["easing","offset","composite"])){var n=t[i];Array.isArray(n)||(n=[n]);for(var r,o=n.length,a=0;a<o;a++)r={},"offset"in t?r.offset=t.offset:1==o?r.offset=1:r.offset=a/(o-1),"easing"in t&&(r.easing=t.easing),"composite"in t&&(r.composite=t.composite),r[i]=n[a],e.push(r)}return e.sort(function(t,e){return t.offset-e.offset}),e}function a(e){function i(){var t=n.length;null==n[t-1].offset&&(n[t-1].offset=1),t>1&&null==n[0].offset&&(n[0].offset=0);for(var e=0,i=n[0].offset,r=1;r<t;r++){var o=n[r].offset;if(null!=o){for(var a=1;a<r-e;a++)n[e+a].offset=i+(o-i)*a/(r-e);e=r,i=o}}}if(null==e)return[];window.Symbol&&Symbol.iterator&&Array.prototype.from&&e[Symbol.iterator]&&(e=Array.from(e)),Array.isArray(e)||(e=o(e));for(var n=e.map(function(e){var i={};for(var n in e){var o=e[n];if("offset"==n){if(null!=o){if(o=Number(o),!isFinite(o))throw new TypeError("Keyframe offsets must be numbers.");if(o<0||o>1)throw new TypeError("Keyframe offsets must be between 0 and 1.")}}else if("composite"==n){if("add"==o||"accumulate"==o)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};if("replace"!=o)throw new TypeError("Invalid composite mode "+o+".")}else o="easing"==n?t.normalizeEasing(o):""+o;r(n,o,i)}return void 0==i.offset&&(i.offset=null),void 0==i.easing&&(i.easing="linear"),i}),a=!0,s=-(1/0),u=0;u<n.length;u++){var c=n[u].offset;if(null!=c){if(c<s)throw new TypeError("Keyframes are not loosely sorted by offset. Sort or specify offsets.");s=c}else a=!1}return n=n.filter(function(t){return t.offset>=0&&t.offset<=1}),a||i(),n}var s={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},u=document.createElementNS("http://www.w3.org/1999/xhtml","div"),c={thin:"1px",medium:"3px",thick:"5px"},f={borderBottomWidth:c,borderLeftWidth:c,borderRightWidth:c,borderTopWidth:c,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:c,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};t.convertToArrayForm=o,t.normalizeKeyframes=a}(c,f),function(t){var e={};t.isDeprecated=function(t,i,n,r){var o=r?"are":"is",a=new Date,s=new Date(i);return s.setMonth(s.getMonth()+3),!(a<s&&(t in e||console.warn("Web Animations: "+t+" "+o+" deprecated and will stop working on "+s.toDateString()+". "+n),e[t]=!0,1))},t.deprecated=function(e,i,n,r){var o=r?"are":"is";if(t.isDeprecated(e,i,n,r))throw new Error(e+" "+o+" no longer supported. "+n)}}(c),function(){if(document.documentElement.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(t){void 0===a[t]&&(b=!0)})),!b)return}!function(t,e,i){function n(t){for(var e={},i=0;i<t.length;i++)for(var n in t[i])if("offset"!=n&&"easing"!=n&&"composite"!=n){var r={offset:t[i].offset,easing:t[i].easing,value:t[i][n]};e[n]=e[n]||[],e[n].push(r)}for(var o in e){var a=e[o];if(0!=a[0].offset||1!=a[a.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"Partial keyframes are not supported"}}return e}function r(i){var n=[];for(var r in i)for(var o=i[r],a=0;a<o.length-1;a++){var s=a,u=a+1,c=o[s].offset,f=o[u].offset,l=c,h=f;0==a&&(l=-(1/0),0==f&&(u=s)),a==o.length-2&&(h=1/0,1==c&&(s=u)),n.push({applyFrom:l,applyTo:h,startOffset:o[s].offset,endOffset:o[u].offset,easingFunction:t.parseEasingFunction(o[s].easing),property:r,interpolation:e.propertyInterpolation(r,o[s].value,o[u].value)})}return n.sort(function(t,e){return t.startOffset-e.startOffset}),n}e.convertEffectInput=function(i){var o=t.normalizeKeyframes(i),a=n(o),s=r(a);return function(t,i){if(null!=i)s.filter(function(t){return i>=t.applyFrom&&i<t.applyTo}).forEach(function(n){var r=i-n.startOffset,o=n.endOffset-n.startOffset,a=0==o?0:n.easingFunction(r/o);e.apply(t,n.property,n.interpolation(a))});else for(var n in a)"offset"!=n&&"easing"!=n&&"composite"!=n&&e.clear(t,n)}}}(c,d,f),function(t,e,i){function n(t){return t.replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function r(t,e,i){s[i]=s[i]||[],s[i].push([t,e])}function o(t,e,i){for(var o=0;o<i.length;o++){var a=i[o];r(t,e,n(a))}}function a(i,r,o){var a=i;/-/.test(i)&&!t.isDeprecated("Hyphenated property names","2016-03-22","Use camelCase instead.",!0)&&(a=n(i)),"initial"!=r&&"initial"!=o||("initial"==r&&(r=u[a]),"initial"==o&&(o=u[a]));for(var c=r==o?[]:s[a],f=0;c&&f<c.length;f++){var l=c[f][0](r),h=c[f][0](o);if(void 0!==l&&void 0!==h){var m=c[f][1](l,h);if(m){var d=e.Interpolation.apply(null,m);return function(t){return 0==t?r:1==t?o:d(t)}}}}return e.Interpolation(!1,!0,function(t){return t?o:r})}var s={};e.addPropertiesHandler=o;var u={backgroundColor:"transparent",backgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadius:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"currentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWidth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto",left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginLeft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",minHeight:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transparent",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto",wordSpacing:"normal",zIndex:"auto"};e.propertyInterpolation=a}(c,d,f),function(t,e,i){function n(e){var i=t.calculateActiveDuration(e),n=function(n){return t.calculateIterationProgress(i,n,e)};return n._totalDuration=e.delay+i+e.endDelay,n}e.KeyframeEffect=function(i,r,o,a){var s,u=n(t.normalizeTimingInput(o)),c=e.convertEffectInput(r),f=function(){c(i,s)};return f._update=function(t){return s=u(t),null!==s},f._clear=function(){c(i,null)},f._hasSameTarget=function(t){return i===t},f._target=i,f._totalDuration=u._totalDuration,f._id=a,f},e.NullEffect=function(t){var e=function(){t&&(t(),t=null)};return e._update=function(){return null},e._totalDuration=0,e._hasSameTarget=function(){return!1},e}}(c,d,f),function(t,e){t.apply=function(e,i,n){e.style[t.propertyName(i)]=n},t.clear=function(e,i){e.style[t.propertyName(i)]=""}}(d,f),function(t){window.Element.prototype.animate=function(e,i){var n="";return i&&i.id&&(n=i.id),t.timeline._play(t.KeyframeEffect(this,e,i,n))}}(d),function(t,e){function i(t,e,n){if("number"==typeof t&&"number"==typeof e)return t*(1-n)+e*n;if("boolean"==typeof t&&"boolean"==typeof e)return n<.5?t:e;if(t.length==e.length){for(var r=[],o=0;o<t.length;o++)r.push(i(t[o],e[o],n));return r}throw"Mismatched interpolation arguments "+t+":"+e}t.Interpolation=function(t,e,n){return function(r){return n(i(t,e,r))}}}(d,f),function(t,e,i){t.sequenceNumber=0;var n=function(t,e,i){this.target=t,this.currentTime=e,this.timelineTime=i,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=t,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};e.Animation=function(e){this.id="",e&&e._id&&(this.id=e._id),this._sequenceNumber=t.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!0,this.onfinish=null,this._finishHandlers=[],this._effect=e,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},e.Animation.prototype={_ensureAlive:function(){this.playbackRate<0&&0===this.currentTime?this._inEffect=this._effect._update(-1):this._inEffect=this._effect._update(this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,e.timeline._animations.push(this))},_tickCurrentTime:function(t,e){t!=this._currentTime&&(this._currentTime=t,this._isFinished&&!e&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(t){t=+t,isNaN(t)||(e.restart(),this._paused||null==this._startTime||(this._startTime=this._timeline.currentTime-t/this._playbackRate),this._currentTimePending=!1,this._currentTime!=t&&(this._idle&&(this._idle=!1,this._paused=!0),this._tickCurrentTime(t,!0),e.applyDirtiedAnimation(this)))},get startTime(){return this._startTime},set startTime(t){t=+t,isNaN(t)||this._paused||this._idle||(this._startTime=t,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),e.applyDirtiedAnimation(this))},get playbackRate(){return this._playbackRate},set playbackRate(t){if(t!=this._playbackRate){var i=this.currentTime;this._playbackRate=t,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&(this._finishedFlag=!1,this._idle=!1,this._ensureAlive(),e.applyDirtiedAnimation(this)),null!=i&&(this.currentTime=i)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished":"running"},_rewind:function(){if(this._playbackRate>=0)this._currentTime=0;else{if(!(this._totalDuration<1/0))throw new DOMException("Unable to rewind negative playback rate animation with infinite duration","InvalidStateError");this._currentTime=this._totalDuration}},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(this._rewind(),this._startTime=null),this._finishedFlag=!1,this._idle=!1,this._ensureAlive(),e.applyDirtiedAnimation(this)},pause:function(){this._isFinished||this._paused||this._idle?this._idle&&(this._rewind(),this._idle=!1):this._currentTimePending=!0,this._startTime=null,this._paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1,e.applyDirtiedAnimation(this))},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this._paused=!1,this._isFinished=!0,this._finishedFlag=!0,this._currentTime=0,this._startTime=null,this._effect._update(null),e.applyDirtiedAnimation(this))},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(t,e){"function"==typeof e&&"finish"==t&&this._finishHandlers.push(e)},removeEventListener:function(t,e){if("finish"==t){var i=this._finishHandlers.indexOf(e);i>=0&&this._finishHandlers.splice(i,1)}},_fireEvents:function(t){if(this._isFinished){if(!this._finishedFlag){var e=new n(this,this._currentTime,t),i=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){i.forEach(function(t){t.call(e.target,e)})},0),this._finishedFlag=!0}}else this._finishedFlag=!1},_tick:function(t,e){this._idle||this._paused||(null==this._startTime?e&&(this.startTime=t-this._currentTime/this.playbackRate):this._isFinished||this._tickCurrentTime((t-this._startTime)*this.playbackRate)),e&&(this._currentTimePending=!1,this._fireEvents(t))},get _needsTick(){return this.playState in{pending:1,running:1}||!this._finishedFlag},_targetAnimations:function(){var t=this._effect._target;return t._activeAnimations||(t._activeAnimations=[]),t._activeAnimations},_markTarget:function(){var t=this._targetAnimations();t.indexOf(this)===-1&&t.push(this)},_unmarkTarget:function(){var t=this._targetAnimations(),e=t.indexOf(this);e!==-1&&t.splice(e,1)}}}(c,d,f),function(t,e,i){function n(t){var e=c;c=[],t<_.currentTime&&(t=_.currentTime),_._animations.sort(r),_._animations=s(t,!0,_._animations)[0],e.forEach(function(e){e[1](t)}),a(),l=void 0}function r(t,e){return t._sequenceNumber-e._sequenceNumber}function o(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function a(){d.forEach(function(t){t()}),d.length=0}function s(t,i,n){p=!0,m=!1;var r=e.timeline;r.currentTime=t,h=!1;var o=[],a=[],s=[],u=[];return n.forEach(function(e){e._tick(t,i),e._inEffect?(a.push(e._effect),e._markTarget()):(o.push(e._effect),e._unmarkTarget()),e._needsTick&&(h=!0);var n=e._inEffect||e._needsTick;e._inTimeline=n,n?s.push(e):u.push(e)}),d.push.apply(d,o),d.push.apply(d,a),h&&requestAnimationFrame(function(){}),p=!1,[s,u]}var u=window.requestAnimationFrame,c=[],f=0;window.requestAnimationFrame=function(t){var e=f++;return 0==c.length&&u(n),c.push([e,t]),e},window.cancelAnimationFrame=function(t){c.forEach(function(e){e[0]==t&&(e[1]=function(){})})},o.prototype={_play:function(i){i._timing=t.normalizeTimingInput(i.timing);var n=new e.Animation(i);return n._idle=!1,n._timeline=this,this._animations.push(n),e.restart(),e.applyDirtiedAnimation(n),n}};var l=void 0,h=!1,m=!1;e.restart=function(){return h||(h=!0,requestAnimationFrame(function(){}),m=!0),m},e.applyDirtiedAnimation=function(t){if(!p){t._markTarget();var i=t._targetAnimations();i.sort(r);var n=s(e.timeline.currentTime,!1,i.slice())[1];n.forEach(function(t){var e=_._animations.indexOf(t);e!==-1&&_._animations.splice(e,1)}),a()}};var d=[],p=!1,_=new o;e.timeline=_}(c,d,f),function(t){function e(t,e){var i=t.exec(e);if(i)return i=t.ignoreCase?i[0].toLowerCase():i[0],[i,e.substr(i.length)]}function i(t,e){e=e.replace(/^\s*/,"");var i=t(e);if(i)return[i[0],i[1].replace(/^\s*/,"")]}function n(t,n,r){t=i.bind(null,t);for(var o=[];;){var a=t(r);if(!a)return[o,r];if(o.push(a[0]),r=a[1],a=e(n,r),!a||""==a[1])return[o,r];r=a[1]}}function r(t,e){for(var i=0,n=0;n<e.length&&(!/\s|,/.test(e[n])||0!=i);n++)if("("==e[n])i++;else if(")"==e[n]&&(i--,0==i&&n++,i<=0))break;var r=t(e.substr(0,n));return void 0==r?void 0:[r,e.substr(n)]}function o(t,e){for(var i=t,n=e;i&&n;)i>n?i%=n:n%=i;return i=t*e/(i+n)}function a(t){return function(e){var i=t(e);return i&&(i[0]=void 0),i}}function s(t,e){return function(i){var n=t(i);return n?n:[e,i]}}function u(e,i){for(var n=[],r=0;r<e.length;r++){var o=t.consumeTrimmed(e[r],i);if(!o||""==o[0])return;void 0!==o[0]&&n.push(o[0]),i=o[1]}if(""==i)return n}function c(t,e,i,n,r){for(var a=[],s=[],u=[],c=o(n.length,r.length),f=0;f<c;f++){var l=e(n[f%n.length],r[f%r.length]);if(!l)return;a.push(l[0]),s.push(l[1]),u.push(l[2])}return[a,s,function(e){var n=e.map(function(t,e){return u[e](t)}).join(i);return t?t(n):n}]}function f(t,e,i){for(var n=[],r=[],o=[],a=0,s=0;s<i.length;s++)if("function"==typeof i[s]){var u=i[s](t[a],e[a++]);n.push(u[0]),r.push(u[1]),o.push(u[2])}else!function(t){n.push(!1),r.push(!1),o.push(function(){return i[t]})}(s);return[n,r,function(t){for(var e="",i=0;i<t.length;i++)e+=o[i](t[i]);return e}]}t.consumeToken=e,t.consumeTrimmed=i,t.consumeRepeated=n,t.consumeParenthesised=r,t.ignore=a,t.optional=s,t.consumeList=u,t.mergeNestedRepeated=c.bind(null,null),t.mergeWrappedNestedRepeated=c,t.mergeList=f}(d),function(t){function e(e){function i(e){var i=t.consumeToken(/^inset/i,e);if(i)return n.inset=!0,i;var i=t.consumeLengthOrPercent(e);if(i)return n.lengths.push(i[0]),i;var i=t.consumeColor(e);return i?(n.color=i[0],i):void 0}var n={inset:!1,lengths:[],color:null},r=t.consumeRepeated(i,/^/,e);if(r&&r[0].length)return[n,r[1]]}function i(i){var n=t.consumeRepeated(e,/^,/,i);if(n&&""==n[1])return n[0]}function n(e,i){for(;e.lengths.length<Math.max(e.lengths.length,i.lengths.length);)e.lengths.push({px:0});for(;i.lengths.length<Math.max(e.lengths.length,i.lengths.length);)i.lengths.push({px:0});if(e.inset==i.inset&&!!e.color==!!i.color){for(var n,r=[],o=[[],0],a=[[],0],s=0;s<e.lengths.length;s++){var u=t.mergeDimensions(e.lengths[s],i.lengths[s],2==s);o[0].push(u[0]),a[0].push(u[1]),r.push(u[2])}if(e.color&&i.color){var c=t.mergeColors(e.color,i.color);o[1]=c[0],a[1]=c[1],n=c[2]}return[o,a,function(t){for(var i=e.inset?"inset ":" ",o=0;o<r.length;o++)i+=r[o](t[0][o])+" ";return n&&(i+=n(t[1])),i}]}}function r(e,i,n,r){function o(t){return{inset:t,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(var a=[],s=[],u=0;u<n.length||u<r.length;u++){var c=n[u]||o(r[u].inset),f=r[u]||o(n[u].inset);a.push(c),s.push(f)}return t.mergeNestedRepeated(e,i,a,s)}var o=r.bind(null,n,", ");t.addPropertiesHandler(i,o,["box-shadow","text-shadow"])}(d),function(t,e){function i(t){return t.toFixed(3).replace(".000","")}function n(t,e,i){return Math.min(e,Math.max(t,i))}function r(t){if(/^\s*[-+]?(\d*\.)?\d+\s*$/.test(t))return Number(t)}function o(t,e){return[t,e,i]}function a(t,e){if(0!=t)return u(0,1/0)(t,e)}function s(t,e){return[t,e,function(t){return Math.round(n(1,1/0,t))}]}function u(t,e){return function(r,o){return[r,o,function(r){return i(n(t,e,r))}]}}function c(t,e){return[t,e,Math.round]}t.clamp=n,t.addPropertiesHandler(r,u(0,1/0),["border-image-width","line-height"]),t.addPropertiesHandler(r,u(0,1),["opacity","shape-image-threshold"]),t.addPropertiesHandler(r,a,["flex-grow","flex-shrink"]),t.addPropertiesHandler(r,s,["orphans","widows"]),t.addPropertiesHandler(r,c,["z-index"]),t.parseNumber=r,t.mergeNumbers=o,t.numberToString=i}(d,f),function(t,e){function i(t,e){if("visible"==t||"visible"==e)return[0,1,function(i){return i<=0?t:i>=1?e:"visible"}]}t.addPropertiesHandler(String,i,["visibility"])}(d),function(t,e){function i(t){t=t.trim(),o.fillStyle="#000",o.fillStyle=t;var e=o.fillStyle;if(o.fillStyle="#fff",o.fillStyle=t,e==o.fillStyle){o.fillRect(0,0,1,1);var i=o.getImageData(0,0,1,1).data;o.clearRect(0,0,1,1);var n=i[3]/255;return[i[0]*n,i[1]*n,i[2]*n,n]}}function n(e,i){return[e,i,function(e){function i(t){return Math.max(0,Math.min(255,t))}if(e[3])for(var n=0;n<3;n++)e[n]=Math.round(i(e[n]/e[3]));return e[3]=t.numberToString(t.clamp(0,1,e[3])),"rgba("+e.join(",")+")"}]}var r=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");r.width=r.height=1;var o=r.getContext("2d");t.addPropertiesHandler(i,n,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","outline-color","text-decoration-color"]),t.consumeColor=t.consumeParenthesised.bind(null,i),t.mergeColors=n}(d,f),function(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.replace(a,function(t){return c[t]=null,"U"+t});for(var d="U("+a.source+")",e=b.replace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*\/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g"),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(t,i){return e(t,i,!0)}function e(t,e,i){var n,r=[];for(n in t)r.push(n);for(n in e)r.indexOf(n)<0&&r.push(n);return t=r.map(function(e){return t[e]||0}),e=r.map(function(t){return e[t]||0}),[t,e,function(t){var e=t.map(function(e,n){return 1==t.length&&i&&(e=Math.max(e,0)),a.numberToString(e)+r[n]}).join(" + ");return t.length>1?"calc("+e+")":e}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,new RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|grad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.consumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consumeRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(t){var e=l(t);if(e&&""==e[1])return e[0]},n=a.mergeNestedRepeated.bind(void 0,d," "),o=a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPropertiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","text-indent","top","vertical-align","word-spacing"])}(d,f),function(t,e){function i(e){return t.consumeLengthOrPercent(e)||t.consumeToken(/^auto/,e)}function n(e){var n=t.consumeList([t.ignore(t.consumeToken.bind(null,/^rect/)),t.ignore(t.consumeToken.bind(null,/^\(/)),t.consumeRepeated.bind(null,i,/^,/),t.ignore(t.consumeToken.bind(null,/^\)/))],e);if(n&&4==n[0].length)return n[0]}function r(e,i){return"auto"==e||"auto"==i?[!0,!1,function(n){var r=n?e:i;if("auto"==r)return"auto";var o=t.mergeDimensions(r,r);return o[2](o[0])}]:t.mergeDimensions(e,i)}function o(t){return"rect("+t+")"}var a=t.mergeWrappedNestedRepeated.bind(null,o,r,", ");t.parseBox=n,t.mergeBoxes=a,t.addPropertiesHandler(n,a,["clip"])}(d,f),function(t,e){function i(t){return function(e){var i=0;return t.map(function(t){return t===f?e[i++]:t})}}function n(t){return t}function r(e){if(e=e.toLowerCase().trim(),"none"==e)return[];for(var i,n=/\s*(\w+)\(([^)]*)\)/g,r=[],o=0;i=n.exec(e);){if(i.index!=o)return;o=i.index+i[0].length;var a=i[1],s=m[a];if(!s)return;var u=i[2].split(","),c=s[0];if(c.length<u.length)return;for(var f=[],d=0;d<c.length;d++){var p,_=u[d],g=c[d];if(p=_?{A:function(e){return"0"==e.trim()?h:t.parseAngle(e)},N:t.parseNumber,T:t.parseLengthOrPercent,L:t.parseLength}[g.toUpperCase()](_):{a:h,n:f[0],t:l}[g],void 0===p)return;f.push(p)}if(r.push({t:a,d:f}),n.lastIndex==e.length)return r}}function o(t){return t.toFixed(6).replace(".000000","")}function a(e,i){if(e.decompositionPair!==i){e.decompositionPair=i;var n=t.makeMatrixDecomposition(e)}if(i.decompositionPair!==e){i.decompositionPair=e;var r=t.makeMatrixDecomposition(i)}return null==n[0]||null==r[0]?[[!1],[!0],function(t){return t?i[0].d:e[0].d}]:(n[0].push(0),r[0].push(1),[n,r,function(e){var i=t.quat(n[0][3],r[0][3],e[5]),a=t.composeMatrix(e[0],e[1],e[2],i,e[4]),s=a.map(o).join(",");return s}])}function s(t){return t.replace(/[xy]/,"")}function u(t){return t.replace(/(x|y|z|3d)?$/,"3d")}function c(e,i){var n=t.makeMatrixDecomposition&&!0,r=!1;if(!e.length||!i.length){e.length||(r=!0,e=i,i=[]);for(var o=0;o<e.length;o++){var c=e[o].t,f=e[o].d,l="scale"==c.substr(0,5)?1:0;i.push({t:c,d:f.map(function(t){if("number"==typeof t)return l;var e={};for(var i in t)e[i]=l;return e})})}}var h=function(t,e){return"perspective"==t&&"perspective"==e||("matrix"==t||"matrix3d"==t)&&("matrix"==e||"matrix3d"==e)},d=[],p=[],_=[];if(e.length!=i.length){if(!n)return;var g=a(e,i);d=[g[0]],p=[g[1]],_=[["matrix",[g[2]]]]}else for(var o=0;o<e.length;o++){var c,b=e[o].t,v=i[o].t,y=e[o].d,w=i[o].d,T=m[b],E=m[v];if(h(b,v)){if(!n)return;var g=a([e[o]],[i[o]]);d.push(g[0]),p.push(g[1]),_.push(["matrix",[g[2]]])}else{if(b==v)c=b;else if(T[2]&&E[2]&&s(b)==s(v))c=s(b),y=T[2](y),w=E[2](w);else{if(!T[1]||!E[1]||u(b)!=u(v)){if(!n)return;var g=a(e,i);d=[g[0]],p=[g[1]],_=[["matrix",[g[2]]]];break}c=u(b),y=T[1](y),w=E[1](w)}for(var x=[],A=[],P=[],j=0;j<y.length;j++){var R="number"==typeof y[j]?t.mergeNumbers:t.mergeDimensions,g=R(y[j],w[j]);x[j]=g[0],A[j]=g[1],P.push(g[2])}d.push(x),p.push(A),_.push([c,P])}}if(r){var N=d;d=p,p=N}return[d,p,function(t){return t.map(function(t,e){var i=t.map(function(t,i){return _[e][1][i](t)}).join(",");return"matrix"==_[e][0]&&16==i.split(",").length&&(_[e][0]="matrix3d"),_[e][0]+"("+i+")"}).join(" ")}]}var f=null,l={px:0},h={deg:0},m={matrix:["NNNNNN",[f,f,0,0,f,f,0,0,0,0,1,0,f,f,0,1],n],matrix3d:["NNNNNNNNNNNNNNNN",n],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",i([f,f,1]),n],scalex:["N",i([f,1,1]),i([f,1])],scaley:["N",i([1,f,1]),i([1,f])],scalez:["N",i([1,1,f])],scale3d:["NNN",n],skew:["Aa",null,n],skewx:["A",null,i([f,h])],skewy:["A",null,i([h,f])],translate:["Tt",i([f,f,l]),n],translatex:["T",i([f,l,l]),i([f,l])],translatey:["T",i([l,f,l]),i([l,f])],translatez:["L",i([l,l,f])],translate3d:["TTL",n]};t.addPropertiesHandler(r,c,["transform"])}(d,f),function(t,e){function i(t,e){e.concat([t]).forEach(function(e){e in document.documentElement.style&&(n[t]=e)})}var n={};i("transform",["webkitTransform","msTransform"]),i("transformOrigin",["webkitTransformOrigin"]),i("perspective",["webkitPerspective"]),i("perspectiveOrigin",["webkitPerspectiveOrigin"]),t.propertyName=function(t){return n[t]||t}}(d,f)}(),!function(){if(void 0===document.createElement("div").animate([]).oncancel){var t;if(window.performance&&performance.now)var t=function(){return performance.now()};else var t=function(){return Date.now()};var e=function(t,e,i){this.target=t,this.currentTime=e,this.timelineTime=i,this.type="cancel",this.bubbles=!1,this.cancelable=!1, -this.currentTarget=t,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},i=window.Element.prototype.animate;window.Element.prototype.animate=function(n,r){var o=i.call(this,n,r);o._cancelHandlers=[],o.oncancel=null;var a=o.cancel;o.cancel=function(){a.call(this);var i=new e(this,null,t()),n=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){n.forEach(function(t){t.call(i.target,i)})},0)};var s=o.addEventListener;o.addEventListener=function(t,e){"function"==typeof e&&"cancel"==t?this._cancelHandlers.push(e):s.call(this,t,e)};var u=o.removeEventListener;return o.removeEventListener=function(t,e){if("cancel"==t){var i=this._cancelHandlers.indexOf(e);i>=0&&this._cancelHandlers.splice(i,1)}else u.call(this,t,e)},o}}}(),function(t){var e=document.documentElement,i=null,n=!1;try{var r=getComputedStyle(e).getPropertyValue("opacity"),o="0"==r?"1":"0";i=e.animate({opacity:[o,o]},{duration:1}),i.currentTime=0,n=getComputedStyle(e).getPropertyValue("opacity")==o}catch(t){}finally{i&&i.cancel()}if(!n){var a=window.Element.prototype.animate;window.Element.prototype.animate=function(e,i){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&e[Symbol.iterator]&&(e=Array.from(e)),Array.isArray(e)||null===e||(e=t.convertToArrayForm(e)),a.call(this,e,i)}}}(c),!function(t,e,i){function n(t){var i=e.timeline;i.currentTime=t,i._discardAnimations(),0==i._animations.length?o=!1:requestAnimationFrame(n)}var r=window.requestAnimationFrame;window.requestAnimationFrame=function(t){return r(function(i){e.timeline._updateAnimationsPromises(),t(i),e.timeline._updateAnimationsPromises()})},e.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},e.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){e.animationsWithPromises=e.animationsWithPromises.filter(function(t){return t._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(t){return"finished"!=t.playState&&"idle"!=t.playState})},_play:function(t){var i=new e.Animation(t,this);return this._animations.push(i),e.restartWebAnimationsNextTick(),i._updatePromises(),i._animation.play(),i._updatePromises(),i},play:function(t){return t&&t.remove(),this._play(t)}};var o=!1;e.restartWebAnimationsNextTick=function(){o||(o=!0,requestAnimationFrame(n))};var a=new e.AnimationTimeline;e.timeline=a;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return a}})}catch(t){}try{window.document.timeline=a}catch(t){}}(c,e,f),function(t,e,i){e.animationsWithPromises=[],e.Animation=function(e,i){if(this.id="",e&&e._id&&(this.id=e._id),this.effect=e,e&&(e._animation=this),!i)throw new Error("Animation with null timeline is not supported");this._timeline=i,this._sequenceNumber=t.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},e.Animation.prototype={_updatePromises:function(){var t=this._oldPlayState,e=this.playState;return this._readyPromise&&e!==t&&("idle"==e?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==t?this._resolveReadyPromise():"pending"==e&&(this._readyPromise=void 0)),this._finishedPromise&&e!==t&&("idle"==e?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==e?this._resolveFinishedPromise():"finished"==t&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var t,i,n,r,o=!!this._animation;o&&(t=this.playbackRate,i=this._paused,n=this.startTime,r=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=e.newUnderlyingAnimationForKeyframeEffect(this.effect),e.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=e.newUnderlyingAnimationForGroup(this.effect),e.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&e.bindAnimationForCustomEffect(this),o&&(1!=t&&(this.playbackRate=t),null!==n?this.startTime=n:null!==r?this.currentTime=r:null!==this._holdTime&&(this.currentTime=this._holdTime),i&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var t=this.effect._timing.delay;this._childAnimations.forEach(function(i){this._arrangeChildren(i,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i.effect))}.bind(this))}},_setExternalAnimation:function(t){if(this.effect&&this._isGroup)for(var e=0;e<this.effect.children.length;e++)this.effect.children[e]._animation=t,this._childAnimations[e]._setExternalAnimation(t)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var t=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(i){var n=e.timeline._play(i);this._childAnimations.push(n),n.playbackRate=this.playbackRate,this._paused&&n.pause(),i._animation=this.effect._animation,this._arrangeChildren(n,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i))}.bind(this))}},_arrangeChildren:function(t,e){null===this.startTime?t.currentTime=this.currentTime-e/this.playbackRate:t.startTime!==this.startTime+e/this.playbackRate&&(t.startTime=this.startTime+e/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(e.animationsWithPromises.indexOf(this)==-1&&e.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(t,e){this._resolveFinishedPromise=function(){t(this)},this._rejectFinishedPromise=function(){e({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(e.animationsWithPromises.indexOf(this)==-1&&e.animationsWithPromises.push(this),this._readyPromise=new Promise(function(t,e){this._resolveReadyPromise=function(){t(this)},this._rejectReadyPromise=function(){e({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._animation.onfinish},set onfinish(t){"function"==typeof t?this._animation.onfinish=function(e){e.target=this,t.call(this,e)}.bind(this):this._animation.onfinish=t},get oncancel(){return this._animation.oncancel},set oncancel(t){"function"==typeof t?this._animation.oncancel=function(e){e.target=this,t.call(this,e)}.bind(this):this._animation.oncancel=t},get currentTime(){this._updatePromises();var t=this._animation.currentTime;return this._updatePromises(),t},set currentTime(t){this._updatePromises(),this._animation.currentTime=isFinite(t)?t:Math.sign(t)*Number.MAX_VALUE,this._register(),this._forEachChild(function(e,i){e.currentTime=t-i}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(t){this._updatePromises(),this._animation.startTime=isFinite(t)?t:Math.sign(t)*Number.MAX_VALUE,this._register(),this._forEachChild(function(e,i){e.startTime=t+i}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(t){this._updatePromises();var e=this.currentTime;this._animation.playbackRate=t,this._forEachChild(function(e){e.playbackRate=t}),null!==e&&(this.currentTime=e),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),this._timeline._animations.indexOf(this)==-1&&this._timeline._animations.push(this),this._register(),e.awaitStartTime(this),this._forEachChild(function(t){var e=t.currentTime;t.play(),t.currentTime=e}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(t){t.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var t=this.currentTime;this._animation.reverse(),this._forEachChild(function(t){t.reverse()}),null!==t&&(this.currentTime=t),this._updatePromises()},addEventListener:function(t,e){var i=e;"function"==typeof e&&(i=function(t){t.target=this,e.call(this,t)}.bind(this),e._wrapper=i),this._animation.addEventListener(t,i)},removeEventListener:function(t,e){this._animation.removeEventListener(t,e&&e._wrapper||e)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(e){var i=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(t){e.call(this,t,i),this.effect instanceof window.SequenceEffect&&(i+=t.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var n=this.effect._timing,r=this.currentTime;null!==r&&(r=t.calculateIterationProgress(t.calculateActiveDuration(n),r,n)),(null==r||isNaN(r))&&this._removeChildAnimations()}}},window.Animation=e.Animation}(c,e,f),function(t,e,i){function n(e){this._frames=t.normalizeKeyframes(e)}function r(){for(var t=!1;u.length;){var e=u.shift();e._updateChildren(),t=!0}return t}var o=function(t){if(t._animation=void 0,t instanceof window.SequenceEffect||t instanceof window.GroupEffect)for(var e=0;e<t.children.length;e++)o(t.children[e])};e.removeMulti=function(t){for(var e=[],i=0;i<t.length;i++){var n=t[i];n._parent?(e.indexOf(n._parent)==-1&&e.push(n._parent),n._parent.children.splice(n._parent.children.indexOf(n),1),n._parent=null,o(n)):n._animation&&n._animation.effect==n&&(n._animation.cancel(),n._animation.effect=new KeyframeEffect(null,[]),n._animation._callback&&(n._animation._callback._animation=null),n._animation._rebuildUnderlyingAnimation(),o(n))}for(i=0;i<e.length;i++)e[i]._rebuild()},e.KeyframeEffect=function(e,i,r,o){return this.target=e,this._parent=null,r=t.numericTimingToObject(r),this._timingInput=t.cloneTimingInput(r),this._timing=t.normalizeTimingInput(r),this.timing=t.makeTiming(r,!1,this),this.timing._effect=this,"function"==typeof i?(t.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=i):this._normalizedKeyframes=new n(i),this._keyframes=i,this.activeDuration=t.calculateActiveDuration(this._timing),this._id=o,this},e.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(t){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=t,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var e=new KeyframeEffect(this.target,[],t.cloneTimingInput(this._timingInput),this._id);return e._normalizedKeyframes=this._normalizedKeyframes,e._keyframes=this._keyframes,e},remove:function(){e.removeMulti([this])}};var a=Element.prototype.animate;Element.prototype.animate=function(t,i){var n="";return i&&i.id&&(n=i.id),e.timeline._play(new e.KeyframeEffect(this,t,i,n))};var s=document.createElementNS("http://www.w3.org/1999/xhtml","div");e.newUnderlyingAnimationForKeyframeEffect=function(t){if(t){var e=t.target||s,i=t._keyframes;"function"==typeof i&&(i=[]);var n=t._timingInput;n.id=t._id}else var e=s,i=[],n=0;return a.apply(e,[i,n])},e.bindAnimationForKeyframeEffect=function(t){t.effect&&"function"==typeof t.effect._normalizedKeyframes&&e.bindAnimationForCustomEffect(t)};var u=[];e.awaitStartTime=function(t){null===t.startTime&&t._isGroup&&(0==u.length&&requestAnimationFrame(r),u.push(t))};var c=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){e.timeline._updateAnimationsPromises();var t=c.apply(this,arguments);return r()&&(t=c.apply(this,arguments)),e.timeline._updateAnimationsPromises(),t}}),window.KeyframeEffect=e.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(t){return null!==t.effect&&t.effect.target==this}.bind(this))}}(c,e,f),function(t,e,i){function n(t){t._registered||(t._registered=!0,a.push(t),s||(s=!0,requestAnimationFrame(r)))}function r(t){var e=a;a=[],e.sort(function(t,e){return t._sequenceNumber-e._sequenceNumber}),e=e.filter(function(t){t();var e=t._animation?t._animation.playState:"idle";return"running"!=e&&"pending"!=e&&(t._registered=!1),t._registered}),a.push.apply(a,e),a.length?(s=!0,requestAnimationFrame(r)):s=!1}var o=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);e.bindAnimationForCustomEffect=function(e){var i,r=e.effect.target,a="function"==typeof e.effect.getFrames();i=a?e.effect.getFrames():e.effect._onsample;var s=e.effect.timing,u=null;s=t.normalizeTimingInput(s);var c=function(){var n=c._animation?c._animation.currentTime:null;null!==n&&(n=t.calculateIterationProgress(t.calculateActiveDuration(s),n,s),isNaN(n)&&(n=null)),n!==u&&(a?i(n,r,e.effect):i(n,e.effect,e.effect._animation)),u=n};c._animation=e,c._registered=!1,c._sequenceNumber=o++,e._callback=c,n(c)};var a=[],s=!1;e.Animation.prototype._register=function(){this._callback&&n(this._callback)}}(c,e,f),function(t,e,i){function n(t){return t._timing.delay+t.activeDuration+t._timing.endDelay}function r(e,i,n){this._id=n,this._parent=null,this.children=e||[],this._reparent(this.children),i=t.numericTimingToObject(i),this._timingInput=t.cloneTimingInput(i),this._timing=t.normalizeTimingInput(i,!0),this.timing=t.makeTiming(i,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){r.apply(this,arguments)},window.GroupEffect=function(){r.apply(this,arguments)},r.prototype={_isAncestor:function(t){for(var e=this;null!==e;){if(e==t)return!0;e=e._parent}return!1},_rebuild:function(){for(var t=this;t;)"auto"===t.timing.duration&&(t._timing.duration=t.activeDuration),t=t._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(t){e.removeMulti(t);for(var i=0;i<t.length;i++)t[i]._parent=this},_putChild:function(t,e){for(var i=e?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",n=0;n<t.length;n++)if(this._isAncestor(t[n]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:i};for(var n=0;n<t.length;n++)e?this.children.push(t[n]):this.children.unshift(t[n]);this._reparent(t),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var e=t.cloneTimingInput(this._timingInput),i=[],n=0;n<this.children.length;n++)i.push(this.children[n].clone());return this instanceof GroupEffect?new GroupEffect(i,e):new SequenceEffect(i,e)},remove:function(){e.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(r.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var t=0;return this.children.forEach(function(e){t+=n(e)}),Math.max(t,0)}}),window.GroupEffect.prototype=Object.create(r.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var t=0;return this.children.forEach(function(e){t=Math.max(t,n(e))}),t}}),e.newUnderlyingAnimationForGroup=function(i){var n,r=null,o=function(e){var i=n._wrapper;if(i&&"pending"!=i.playState&&i.effect)return null==e?void i._removeChildAnimations():0==e&&i.playbackRate<0&&(r||(r=t.normalizeTimingInput(i.effect.timing)),e=t.calculateIterationProgress(t.calculateActiveDuration(r),-1,r),isNaN(e)||null==e)?(i._forEachChild(function(t){t.currentTime=-1}),void i._removeChildAnimations()):void 0},a=new KeyframeEffect(null,[],i._timing,i._id);return a.onsample=o,n=e.timeline._play(a)},e.bindAnimationForGroup=function(t){t._animation._wrapper=t,t._isGroup=!0,e.awaitStartTime(t),t._constructChildAnimations(),t._setExternalAnimation(t)},e.groupChildDuration=n}(c,e,f),b.true=a}({},function(){return this}())</script><script>Polymer({is:"opaque-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node;return this._effect=new KeyframeEffect(i,[{opacity:"1"},{opacity:"1"}],this.timingFromConfig(e)),i.style.opacity="0",this._effect},complete:function(e){e.node.style.opacity=""}})</script><script>!function(){"use strict";var e={pageX:0,pageY:0},t=null,l=[];Polymer.IronDropdownScrollManager={get currentLockingElement(){return this._lockingElements[this._lockingElements.length-1]},elementIsScrollLocked:function(e){var t=this.currentLockingElement;if(void 0===t)return!1;var l;return!!this._hasCachedLockedElement(e)||!this._hasCachedUnlockedElement(e)&&(l=!!t&&t!==e&&!this._composedTreeContains(t,e),l?this._lockedElementCache.push(e):this._unlockedElementCache.push(e),l)},pushScrollLock:function(e){this._lockingElements.indexOf(e)>=0||(0===this._lockingElements.length&&this._lockScrollInteractions(),this._lockingElements.push(e),this._lockedElementCache=[],this._unlockedElementCache=[])},removeScrollLock:function(e){var t=this._lockingElements.indexOf(e);t!==-1&&(this._lockingElements.splice(t,1),this._lockedElementCache=[],this._unlockedElementCache=[],0===this._lockingElements.length&&this._unlockScrollInteractions())},_lockingElements:[],_lockedElementCache:null,_unlockedElementCache:null,_hasCachedLockedElement:function(e){return this._lockedElementCache.indexOf(e)>-1},_hasCachedUnlockedElement:function(e){return this._unlockedElementCache.indexOf(e)>-1},_composedTreeContains:function(e,t){var l,n,o,r;if(e.contains(t))return!0;for(l=Polymer.dom(e).querySelectorAll("content"),o=0;o<l.length;++o)for(n=Polymer.dom(l[o]).getDistributedNodes(),r=0;r<n.length;++r)if(this._composedTreeContains(n[r],t))return!0;return!1},_scrollInteractionHandler:function(t){if(t.cancelable&&this._shouldPreventScrolling(t)&&t.preventDefault(),t.targetTouches){var l=t.targetTouches[0];e.pageX=l.pageX,e.pageY=l.pageY}},_lockScrollInteractions:function(){this._boundScrollHandler=this._boundScrollHandler||this._scrollInteractionHandler.bind(this),document.addEventListener("wheel",this._boundScrollHandler,!0),document.addEventListener("mousewheel",this._boundScrollHandler,!0),document.addEventListener("DOMMouseScroll",this._boundScrollHandler,!0),document.addEventListener("touchstart",this._boundScrollHandler,!0),document.addEventListener("touchmove",this._boundScrollHandler,!0)},_unlockScrollInteractions:function(){document.removeEventListener("wheel",this._boundScrollHandler,!0),document.removeEventListener("mousewheel",this._boundScrollHandler,!0),document.removeEventListener("DOMMouseScroll",this._boundScrollHandler,!0),document.removeEventListener("touchstart",this._boundScrollHandler,!0),document.removeEventListener("touchmove",this._boundScrollHandler,!0)},_shouldPreventScrolling:function(e){var n=Polymer.dom(e).rootTarget;if("touchmove"!==e.type&&t!==n&&(t=n,l=this._getScrollableNodes(Polymer.dom(e).path)),!l.length)return!0;if("touchstart"===e.type)return!1;var o=this._getScrollInfo(e);return!this._getScrollingNode(l,o.deltaX,o.deltaY)},_getScrollableNodes:function(e){for(var t=[],l=e.indexOf(this.currentLockingElement),n=0;n<=l;n++){var o=e[n];if(11!==o.nodeType){var r=o.style;"scroll"!==r.overflow&&"auto"!==r.overflow&&(r=window.getComputedStyle(o)),"scroll"!==r.overflow&&"auto"!==r.overflow||t.push(o)}}return t},_getScrollingNode:function(e,t,l){if(t||l)for(var n=Math.abs(l)>=Math.abs(t),o=0;o<e.length;o++){var r=e[o],c=!1;if(c=n?l<0?r.scrollTop>0:r.scrollTop<r.scrollHeight-r.clientHeight:t<0?r.scrollLeft>0:r.scrollLeft<r.scrollWidth-r.clientWidth)return r}},_getScrollInfo:function(t){var l={deltaX:t.deltaX,deltaY:t.deltaY};if("deltaX"in t);else if("wheelDeltaX"in t)l.deltaX=-t.wheelDeltaX,l.deltaY=-t.wheelDeltaY;else if("axis"in t)l.deltaX=1===t.axis?t.detail:0,l.deltaY=2===t.axis?t.detail:0;else if(t.targetTouches){var n=t.targetTouches[0];l.deltaX=e.pageX-n.pageX,l.deltaY=e.pageY-n.pageY}return l}}}()</script><dom-module id="iron-dropdown" assetpath="../bower_components/iron-dropdown/"><template><style>:host{position:fixed}#contentWrapper ::content>*{overflow:auto}#contentWrapper.animating ::content>*{overflow:hidden}</style><div id="contentWrapper"><content id="content" select=".dropdown-content"></content></div></template><script>!function(){"use strict";Polymer({is:"iron-dropdown",behaviors:[Polymer.IronControlState,Polymer.IronA11yKeysBehavior,Polymer.IronOverlayBehavior,Polymer.NeonAnimationRunnerBehavior],properties:{horizontalAlign:{type:String,value:"left",reflectToAttribute:!0},verticalAlign:{type:String,value:"top",reflectToAttribute:!0},openAnimationConfig:{type:Object},closeAnimationConfig:{type:Object},focusTarget:{type:Object},noAnimations:{type:Boolean,value:!1},allowOutsideScroll:{type:Boolean,value:!1},_boundOnCaptureScroll:{type:Function,value:function(){return this._onCaptureScroll.bind(this)}}},listeners:{"neon-animation-finish":"_onNeonAnimationFinish"},observers:["_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)"],get containedElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},get _focusTarget(){return this.focusTarget||this.containedElement},ready:function(){this._scrollTop=0,this._scrollLeft=0,this._refitOnScrollRAF=null},attached:function(){this.sizingTarget&&this.sizingTarget!==this||(this.sizingTarget=this.containedElement)},detached:function(){this.cancelAnimation(),document.removeEventListener("scroll",this._boundOnCaptureScroll),Polymer.IronDropdownScrollManager.removeScrollLock(this)},_openedChanged:function(){this.opened&&this.disabled?this.cancel():(this.cancelAnimation(),this._updateAnimationConfig(),this._saveScrollPosition(),this.opened?(document.addEventListener("scroll",this._boundOnCaptureScroll),!this.allowOutsideScroll&&Polymer.IronDropdownScrollManager.pushScrollLock(this)):(document.removeEventListener("scroll",this._boundOnCaptureScroll),Polymer.IronDropdownScrollManager.removeScrollLock(this)),Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this,arguments))},_renderOpened:function(){!this.noAnimations&&this.animationConfig.open?(this.$.contentWrapper.classList.add("animating"),this.playAnimation("open")):Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this,arguments)},_renderClosed:function(){!this.noAnimations&&this.animationConfig.close?(this.$.contentWrapper.classList.add("animating"),this.playAnimation("close")):Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this,arguments)},_onNeonAnimationFinish:function(){this.$.contentWrapper.classList.remove("animating"),this.opened?this._finishRenderOpened():this._finishRenderClosed()},_onCaptureScroll:function(){this.allowOutsideScroll?(this._refitOnScrollRAF&&window.cancelAnimationFrame(this._refitOnScrollRAF),this._refitOnScrollRAF=window.requestAnimationFrame(this.refit.bind(this))):this._restoreScrollPosition()},_saveScrollPosition:function(){document.scrollingElement?(this._scrollTop=document.scrollingElement.scrollTop,this._scrollLeft=document.scrollingElement.scrollLeft):(this._scrollTop=Math.max(document.documentElement.scrollTop,document.body.scrollTop),this._scrollLeft=Math.max(document.documentElement.scrollLeft,document.body.scrollLeft))},_restoreScrollPosition:function(){document.scrollingElement?(document.scrollingElement.scrollTop=this._scrollTop,document.scrollingElement.scrollLeft=this._scrollLeft):(document.documentElement.scrollTop=this._scrollTop,document.documentElement.scrollLeft=this._scrollLeft,document.body.scrollTop=this._scrollTop,document.body.scrollLeft=this._scrollLeft)},_updateAnimationConfig:function(){for(var o=(this.openAnimationConfig||[]).concat(this.closeAnimationConfig||[]),t=0;t<o.length;t++)o[t].node=this.containedElement;this.animationConfig={open:this.openAnimationConfig,close:this.closeAnimationConfig}},_updateOverlayPosition:function(){this.isAttached&&this.notifyResize()},_applyFocus:function(){var o=this.focusTarget||this.containedElement;o&&this.opened&&!this.noAutoFocus?o.focus():Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this,arguments)}})}()</script></dom-module><script>Polymer({is:"fade-in-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(i){var e=i.node;return this._effect=new KeyframeEffect(e,[{opacity:"0"},{opacity:"1"}],this.timingFromConfig(i)),this._effect}})</script><script>Polymer({is:"fade-out-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node;return this._effect=new KeyframeEffect(i,[{opacity:"1"},{opacity:"0"}],this.timingFromConfig(e)),this._effect}})</script><script>Polymer({is:"paper-menu-grow-height-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.height;return this._effect=new KeyframeEffect(i,[{height:n/2+"px"},{height:n+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-grow-width-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.width;return this._effect=new KeyframeEffect(i,[{width:n/2+"px"},{width:n+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-shrink-width-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.width;return this._effect=new KeyframeEffect(i,[{width:n+"px"},{width:n-n/20+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-shrink-height-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.height;t.top;return this.setPrefixedProperty(i,"transformOrigin","0 0"),this._effect=new KeyframeEffect(i,[{height:n+"px",transform:"translateY(0)"},{height:n/2+"px",transform:"translateY(-20px)"}],this.timingFromConfig(e)),this._effect}})</script><dom-module id="paper-menu-button" assetpath="../bower_components/paper-menu-button/"><template><style>:host{display:inline-block;position:relative;padding:8px;outline:0;@apply(--paper-menu-button)}:host([disabled]){cursor:auto;color:var(--disabled-text-color);@apply(--paper-menu-button-disabled)}iron-dropdown{@apply(--paper-menu-button-dropdown)}.dropdown-content{@apply(--shadow-elevation-2dp);position:relative;border-radius:2px;background-color:var(--paper-menu-button-dropdown-background,--primary-background-color);@apply(--paper-menu-button-content)}:host([vertical-align=top]) .dropdown-content{margin-bottom:20px;margin-top:-10px;top:10px}:host([vertical-align=bottom]) .dropdown-content{bottom:10px;margin-bottom:-10px;margin-top:20px}#trigger{cursor:pointer}</style><div id="trigger" on-tap="toggle"><content select=".dropdown-trigger"></content></div><iron-dropdown id="dropdown" opened="{{opened}}" horizontal-align="[[horizontalAlign]]" vertical-align="[[verticalAlign]]" dynamic-align="[[dynamicAlign]]" horizontal-offset="[[horizontalOffset]]" vertical-offset="[[verticalOffset]]" no-overlap="[[noOverlap]]" open-animation-config="[[openAnimationConfig]]" close-animation-config="[[closeAnimationConfig]]" no-animations="[[noAnimations]]" focus-target="[[_dropdownContent]]" allow-outside-scroll="[[allowOutsideScroll]]" restore-focus-on-close="[[restoreFocusOnClose]]" on-iron-overlay-canceled="__onIronOverlayCanceled"><div class="dropdown-content"><content id="content" select=".dropdown-content"></content></div></iron-dropdown></template><script>!function(){"use strict";var e={ANIMATION_CUBIC_BEZIER:"cubic-bezier(.3,.95,.5,1)",MAX_ANIMATION_TIME_MS:400},n=Polymer({is:"paper-menu-button",behaviors:[Polymer.IronA11yKeysBehavior,Polymer.IronControlState],properties:{opened:{type:Boolean,value:!1,notify:!0,observer:"_openedChanged"},horizontalAlign:{type:String,value:"left",reflectToAttribute:!0},verticalAlign:{type:String,value:"top",reflectToAttribute:!0},dynamicAlign:{type:Boolean},horizontalOffset:{type:Number,value:0,notify:!0},verticalOffset:{type:Number,value:0,notify:!0},noOverlap:{type:Boolean},noAnimations:{type:Boolean,value:!1},ignoreSelect:{type:Boolean,value:!1},closeOnActivate:{type:Boolean,value:!1},openAnimationConfig:{type:Object,value:function(){return[{name:"fade-in-animation",timing:{delay:100,duration:200}},{name:"paper-menu-grow-width-animation",timing:{delay:100,duration:150,easing:e.ANIMATION_CUBIC_BEZIER}},{name:"paper-menu-grow-height-animation",timing:{delay:100,duration:275,easing:e.ANIMATION_CUBIC_BEZIER}}]}},closeAnimationConfig:{type:Object,value:function(){return[{name:"fade-out-animation",timing:{duration:150}},{name:"paper-menu-shrink-width-animation",timing:{delay:100,duration:50,easing:e.ANIMATION_CUBIC_BEZIER}},{name:"paper-menu-shrink-height-animation",timing:{duration:200,easing:"ease-in"}}]}},allowOutsideScroll:{type:Boolean,value:!1},restoreFocusOnClose:{type:Boolean,value:!0},_dropdownContent:{type:Object}},hostAttributes:{role:"group","aria-haspopup":"true"},listeners:{"iron-activate":"_onIronActivate","iron-select":"_onIronSelect"},get contentElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},toggle:function(){this.opened?this.close():this.open()},open:function(){this.disabled||this.$.dropdown.open()},close:function(){this.$.dropdown.close()},_onIronSelect:function(e){this.ignoreSelect||this.close()},_onIronActivate:function(e){this.closeOnActivate&&this.close()},_openedChanged:function(e,n){e?(this._dropdownContent=this.contentElement,this.fire("paper-dropdown-open")):null!=n&&this.fire("paper-dropdown-close")},_disabledChanged:function(e){Polymer.IronControlState._disabledChanged.apply(this,arguments),e&&this.opened&&this.close()},__onIronOverlayCanceled:function(e){var n=e.detail,t=(Polymer.dom(n).rootTarget,this.$.trigger),o=Polymer.dom(n).path;o.indexOf(t)>-1&&e.preventDefault()}});Object.keys(e).forEach(function(t){n[t]=e[t]}),Polymer.PaperMenuButton=n}()</script></dom-module><iron-iconset-svg name="paper-dropdown-menu" size="24"><svg><defs><g id="arrow-drop-down"><path d="M7 10l5 5 5-5z"></path></g></defs></svg></iron-iconset-svg><dom-module id="paper-dropdown-menu-shared-styles" assetpath="../bower_components/paper-dropdown-menu/"><template><style>:host{display:inline-block;position:relative;text-align:left;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;--paper-input-container-input:{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:100%;box-sizing:border-box;cursor:pointer};@apply(--paper-dropdown-menu)}:host([disabled]){@apply(--paper-dropdown-menu-disabled)}:host([noink]) paper-ripple{display:none}:host([no-label-float]) paper-ripple{top:8px}paper-ripple{top:12px;left:0;bottom:8px;right:0;@apply(--paper-dropdown-menu-ripple)}paper-menu-button{display:block;padding:0;@apply(--paper-dropdown-menu-button)}paper-input{@apply(--paper-dropdown-menu-input)}iron-icon{color:var(--disabled-text-color);@apply(--paper-dropdown-menu-icon)}</style></template></dom-module><dom-module id="paper-dropdown-menu" assetpath="../bower_components/paper-dropdown-menu/"><template><style include="paper-dropdown-menu-shared-styles"></style><span role="button"></span><paper-menu-button id="menuButton" vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]" dynamic-align="[[dynamicAlign]]" vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]" disabled="[[disabled]]" no-animations="[[noAnimations]]" on-iron-select="_onIronSelect" on-iron-deselect="_onIronDeselect" opened="{{opened}}" close-on-activate="" allow-outside-scroll="[[allowOutsideScroll]]"><div class="dropdown-trigger"><paper-ripple></paper-ripple><paper-input type="text" invalid="[[invalid]]" readonly="" disabled="[[disabled]]" value="[[selectedItemLabel]]" placeholder="[[placeholder]]" error-message="[[errorMessage]]" always-float-label="[[alwaysFloatLabel]]" no-label-float="[[noLabelFloat]]" label="[[label]]"><iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix=""></iron-icon></paper-input></div><content id="content" select=".dropdown-content"></content></paper-menu-button></template><script>!function(){"use strict";Polymer({is:"paper-dropdown-menu",behaviors:[Polymer.IronButtonState,Polymer.IronControlState,Polymer.IronFormElementBehavior,Polymer.IronValidatableBehavior],properties:{selectedItemLabel:{type:String,notify:!0,readOnly:!0},selectedItem:{type:Object,notify:!0,readOnly:!0},value:{type:String,notify:!0,readOnly:!0},label:{type:String},placeholder:{type:String},errorMessage:{type:String},opened:{type:Boolean,notify:!0,value:!1,observer:"_openedChanged"},allowOutsideScroll:{type:Boolean,value:!1},noLabelFloat:{type:Boolean,value:!1,reflectToAttribute:!0},alwaysFloatLabel:{type:Boolean,value:!1},noAnimations:{type:Boolean,value:!1},horizontalAlign:{type:String,value:"right"},verticalAlign:{type:String,value:"top"},dynamicAlign:{type:Boolean}},listeners:{tap:"_onTap"},keyBindings:{"up down":"open",esc:"close"},hostAttributes:{role:"combobox","aria-autocomplete":"none","aria-haspopup":"true"},observers:["_selectedItemChanged(selectedItem)"],attached:function(){var e=this.contentElement;e&&e.selectedItem&&this._setSelectedItem(e.selectedItem)},get contentElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},open:function(){this.$.menuButton.open()},close:function(){this.$.menuButton.close()},_onIronSelect:function(e){this._setSelectedItem(e.detail.item)},_onIronDeselect:function(e){this._setSelectedItem(null)},_onTap:function(e){Polymer.Gestures.findOriginalTarget(e)===this&&this.open()},_selectedItemChanged:function(e){var t="";t=e?e.label||e.getAttribute("label")||e.textContent.trim():"",this._setValue(t),this._setSelectedItemLabel(t)},_computeMenuVerticalOffset:function(e){return e?-4:8},_getValidity:function(e){return this.disabled||!this.required||this.required&&!!this.value},_openedChanged:function(){var e=this.opened?"true":"false",t=this.contentElement;t&&t.setAttribute("aria-expanded",e)}})}()</script></dom-module><dom-module id="paper-menu-shared-styles" assetpath="../bower_components/paper-menu/"><template><style>.selectable-content>::content>.iron-selected{font-weight:700;@apply(--paper-menu-selected-item)}.selectable-content>::content>[disabled]{color:var(--paper-menu-disabled-color,--disabled-text-color)}.selectable-content>::content>:focus{position:relative;outline:0;@apply(--paper-menu-focused-item)}.selectable-content>::content>:focus:after{@apply(--layout-fit);background:currentColor;opacity:var(--dark-divider-opacity);content:'';pointer-events:none;@apply(--paper-menu-focused-item-after)}.selectable-content>::content>[colored]:focus:after{opacity:.26}</style></template></dom-module><dom-module id="paper-menu" assetpath="../bower_components/paper-menu/"><template><style include="paper-menu-shared-styles"></style><style>:host{display:block;padding:8px 0;background:var(--paper-menu-background-color,--primary-background-color);color:var(--paper-menu-color,--primary-text-color);@apply(--paper-menu)}</style><div class="selectable-content"><content></content></div></template><script>!function(){Polymer({is:"paper-menu",behaviors:[Polymer.IronMenuBehavior]})}()</script></dom-module><script>Polymer.PaperItemBehaviorImpl={hostAttributes:{role:"option",tabindex:"0"}},Polymer.PaperItemBehavior=[Polymer.IronButtonState,Polymer.IronControlState,Polymer.PaperItemBehaviorImpl]</script><dom-module id="paper-item-shared-styles" assetpath="../bower_components/paper-item/"><template><style>.paper-item,:host{display:block;position:relative;min-height:var(--paper-item-min-height,48px);padding:0 16px}.paper-item{@apply(--paper-font-subhead);border:none;outline:0;background:#fff;width:100%;text-align:left}.paper-item[hidden],:host([hidden]){display:none!important}.paper-item.iron-selected,:host(.iron-selected){font-weight:var(--paper-item-selected-weight,bold);@apply(--paper-item-selected)}.paper-item[disabled],:host([disabled]){color:var(--paper-item-disabled-color,--disabled-text-color);@apply(--paper-item-disabled)}.paper-item:focus,:host(:focus){position:relative;outline:0;@apply(--paper-item-focused)}.paper-item:focus:before,:host(:focus):before{@apply(--layout-fit);background:currentColor;content:'';opacity:var(--dark-divider-opacity);pointer-events:none;@apply(--paper-item-focused-before)}</style></template></dom-module><dom-module id="paper-item" assetpath="../bower_components/paper-item/"><template><style include="paper-item-shared-styles"></style><style>:host{@apply(--layout-horizontal);@apply(--layout-center);@apply(--paper-font-subhead);@apply(--paper-item)}</style><content></content></template><script>Polymer({is:"paper-item",behaviors:[Polymer.PaperItemBehavior]})</script></dom-module><dom-module id="state-card-input_select" assetpath="state-summary/"><template><style>:host{display:block}state-badge{float:left;margin-top:10px}paper-dropdown-menu{display:block;margin-left:53px}</style><state-badge state-obj="[[stateObj]]"></state-badge><paper-dropdown-menu on-tap="stopPropagation" selected-item-label="{{selectedOption}}" label="[[stateObj.entityDisplay]]"><paper-menu class="dropdown-content" selected="[[computeSelected(stateObj)]]"><template is="dom-repeat" items="[[stateObj.attributes.options]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></template></dom-module><script>Polymer({is:"state-card-input_select",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object},selectedOption:{type:String,observer:"selectedOptionChanged"}},computeSelected:function(t){return t.attributes.options.indexOf(t.state)},selectedOptionChanged:function(t){""!==t&&t!==this.stateObj.state&&this.hass.serviceActions.callService("input_select","select_option",{option:t,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})</script><script>Polymer.IronRangeBehavior={properties:{value:{type:Number,value:0,notify:!0,reflectToAttribute:!0},min:{type:Number,value:0,notify:!0},max:{type:Number,value:100,notify:!0},step:{type:Number,value:1,notify:!0},ratio:{type:Number,value:0,readOnly:!0,notify:!0}},observers:["_update(value, min, max, step)"],_calcRatio:function(t){return(this._clampValue(t)-this.min)/(this.max-this.min)},_clampValue:function(t){return Math.min(this.max,Math.max(this.min,this._calcStep(t)))},_calcStep:function(t){if(t=parseFloat(t),!this.step)return t;var e=Math.round((t-this.min)/this.step);return this.step<1?e/(1/this.step)+this.min:e*this.step+this.min},_validateValue:function(){var t=this._clampValue(this.value);return this.value=this.oldValue=isNaN(t)?this.oldValue:t,this.value!==t},_update:function(){this._validateValue(),this._setRatio(100*this._calcRatio(this.value))}}</script><dom-module id="paper-progress" assetpath="../bower_components/paper-progress/"><template><style>:host{display:block;width:200px;position:relative;overflow:hidden}:host([hidden]){display:none!important}#progressContainer{@apply(--paper-progress-container);position:relative}#progressContainer,.indeterminate::after{height:var(--paper-progress-height,4px)}#primaryProgress,#secondaryProgress,.indeterminate::after{@apply(--layout-fit)}#progressContainer,.indeterminate::after{background:var(--paper-progress-container-color,--google-grey-300)}:host(.transiting) #primaryProgress,:host(.transiting) #secondaryProgress{-webkit-transition-property:-webkit-transform;transition-property:transform;-webkit-transition-duration:var(--paper-progress-transition-duration,.08s);transition-duration:var(--paper-progress-transition-duration,.08s);-webkit-transition-timing-function:var(--paper-progress-transition-timing-function,ease);transition-timing-function:var(--paper-progress-transition-timing-function,ease);-webkit-transition-delay:var(--paper-progress-transition-delay,0s);transition-delay:var(--paper-progress-transition-delay,0s)}#primaryProgress,#secondaryProgress{@apply(--layout-fit);-webkit-transform-origin:left center;transform-origin:left center;-webkit-transform:scaleX(0);transform:scaleX(0);will-change:transform}#primaryProgress{background:var(--paper-progress-active-color,--google-green-500)}#secondaryProgress{background:var(--paper-progress-secondary-color,--google-green-100)}:host([disabled]) #primaryProgress{background:var(--paper-progress-disabled-active-color,--google-grey-500)}:host([disabled]) #secondaryProgress{background:var(--paper-progress-disabled-secondary-color,--google-grey-300)}:host(:not([disabled])) #primaryProgress.indeterminate{-webkit-transform-origin:right center;transform-origin:right center;-webkit-animation:indeterminate-bar var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite;animation:indeterminate-bar var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite}:host(:not([disabled])) #primaryProgress.indeterminate::after{content:"";-webkit-transform-origin:center center;transform-origin:center center;-webkit-animation:indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite;animation:indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite}@-webkit-keyframes indeterminate-bar{0%{-webkit-transform:scaleX(1) translateX(-100%)}50%{-webkit-transform:scaleX(1) translateX(0)}75%{-webkit-transform:scaleX(1) translateX(0);-webkit-animation-timing-function:cubic-bezier(.28,.62,.37,.91)}100%{-webkit-transform:scaleX(0) translateX(0)}}@-webkit-keyframes indeterminate-splitter{0%{-webkit-transform:scaleX(.75) translateX(-125%)}30%{-webkit-transform:scaleX(.75) translateX(-125%);-webkit-animation-timing-function:cubic-bezier(.42,0,.6,.8)}90%{-webkit-transform:scaleX(.75) translateX(125%)}100%{-webkit-transform:scaleX(.75) translateX(125%)}}@keyframes indeterminate-bar{0%{transform:scaleX(1) translateX(-100%)}50%{transform:scaleX(1) translateX(0)}75%{transform:scaleX(1) translateX(0);animation-timing-function:cubic-bezier(.28,.62,.37,.91)}100%{transform:scaleX(0) translateX(0)}}@keyframes indeterminate-splitter{0%{transform:scaleX(.75) translateX(-125%)}30%{transform:scaleX(.75) translateX(-125%);animation-timing-function:cubic-bezier(.42,0,.6,.8)}90%{transform:scaleX(.75) translateX(125%)}100%{transform:scaleX(.75) translateX(125%)}}</style><div id="progressContainer"><div id="secondaryProgress" hidden$="[[_hideSecondaryProgress(secondaryRatio)]]"></div><div id="primaryProgress"></div></div></template></dom-module><script>Polymer({is:"paper-progress",behaviors:[Polymer.IronRangeBehavior],properties:{secondaryProgress:{type:Number,value:0},secondaryRatio:{type:Number,value:0,readOnly:!0},indeterminate:{type:Boolean,value:!1,observer:"_toggleIndeterminate"},disabled:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"_disabledChanged"}},observers:["_progressChanged(secondaryProgress, value, min, max)"],hostAttributes:{role:"progressbar"},_toggleIndeterminate:function(e){this.toggleClass("indeterminate",e,this.$.primaryProgress)},_transformProgress:function(e,r){var s="scaleX("+r/100+")";e.style.transform=e.style.webkitTransform=s},_mainRatioChanged:function(e){this._transformProgress(this.$.primaryProgress,e)},_progressChanged:function(e,r,s,t){e=this._clampValue(e),r=this._clampValue(r);var a=100*this._calcRatio(e),i=100*this._calcRatio(r);this._setSecondaryRatio(a),this._transformProgress(this.$.secondaryProgress,a),this._transformProgress(this.$.primaryProgress,i),this.secondaryProgress=e,this.setAttribute("aria-valuenow",r),this.setAttribute("aria-valuemin",s),this.setAttribute("aria-valuemax",t)},_disabledChanged:function(e){this.setAttribute("aria-disabled",e?"true":"false")},_hideSecondaryProgress:function(e){return 0===e}})</script><dom-module id="paper-slider" assetpath="../bower_components/paper-slider/"><template strip-whitespace=""><style>:host{@apply(--layout);@apply(--layout-justified);@apply(--layout-center);width:200px;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;--paper-progress-active-color:var(--paper-slider-active-color, --google-blue-700);--paper-progress-secondary-color:var(--paper-slider-secondary-color, --google-blue-300);--paper-progress-disabled-active-color:var(--paper-slider-disabled-active-color, --paper-grey-400);--paper-progress-disabled-secondary-color:var(--paper-slider-disabled-secondary-color, --paper-grey-400);--calculated-paper-slider-height:var(--paper-slider-height, 2px)}:host(:focus){outline:0}#sliderContainer{position:relative;width:100%;height:calc(30px + var(--calculated-paper-slider-height));margin-left:calc(15px + var(--calculated-paper-slider-height)/ 2);margin-right:calc(15px + var(--calculated-paper-slider-height)/ 2)}#sliderContainer:focus{outline:0}#sliderContainer.editable{margin-top:12px;margin-bottom:12px}.bar-container{position:absolute;top:0;bottom:0;left:0;right:0;overflow:hidden}.ring>.bar-container{left:calc(5px + var(--calculated-paper-slider-height)/ 2);transition:left .18s ease}.ring.expand.dragging>.bar-container{transition:none}.ring.expand:not(.pin)>.bar-container{left:calc(8px + var(--calculated-paper-slider-height)/ 2)}#sliderBar{padding:15px 0;width:100%;background-color:var(--paper-slider-bar-color,transparent);--paper-progress-container-color:var(--paper-slider-container-color, --paper-grey-400);--paper-progress-height:var(--calculated-paper-slider-height)}.slider-markers{position:absolute;top:calc(14px + var(--paper-slider-height,2px)/ 2);height:var(--calculated-paper-slider-height);left:0;right:-1px;box-sizing:border-box;pointer-events:none;@apply(--layout-horizontal)}.slider-marker{@apply(--layout-flex)}.slider-marker::after,.slider-markers::after{content:"";display:block;margin-left:-1px;width:2px;height:var(--calculated-paper-slider-height);border-radius:50%;background-color:var(--paper-slider-markers-color,#000)}.slider-knob{position:absolute;left:0;top:0;margin-left:calc(-15px - var(--calculated-paper-slider-height)/ 2);width:calc(30px + var(--calculated-paper-slider-height));height:calc(30px + var(--calculated-paper-slider-height))}.transiting>.slider-knob{transition:left 80ms ease}.slider-knob:focus{outline:0}.slider-knob.dragging{transition:none}.snaps>.slider-knob.dragging{transition:-webkit-transform 80ms ease;transition:transform 80ms ease}.slider-knob-inner{margin:10px;width:calc(100% - 20px);height:calc(100% - 20px);background-color:var(--paper-slider-knob-color,--google-blue-700);border:2px solid var(--paper-slider-knob-color,--google-blue-700);border-radius:50%;-moz-box-sizing:border-box;box-sizing:border-box;transition-property:-webkit-transform,background-color,border;transition-property:transform,background-color,border;transition-duration:.18s;transition-timing-function:ease}.expand:not(.pin)>.slider-knob>.slider-knob-inner{-webkit-transform:scale(1.5);transform:scale(1.5)}.ring>.slider-knob>.slider-knob-inner{background-color:var(--paper-slider-knob-start-color,transparent);border:2px solid var(--paper-slider-knob-start-border-color,--paper-grey-400)}.slider-knob-inner::before{background-color:var(--paper-slider-pin-color,--google-blue-700)}.pin>.slider-knob>.slider-knob-inner::before{content:"";position:absolute;top:0;left:50%;margin-left:-13px;width:26px;height:26px;border-radius:50% 50% 50% 0;-webkit-transform:rotate(-45deg) scale(0) translate(0);transform:rotate(-45deg) scale(0) translate(0)}.slider-knob-inner::after,.slider-knob-inner::before{transition:-webkit-transform .18s ease,background-color .18s ease;transition:transform .18s ease,background-color .18s ease}.pin.ring>.slider-knob>.slider-knob-inner::before{background-color:var(--paper-slider-pin-start-color,--paper-grey-400)}.pin.expand>.slider-knob>.slider-knob-inner::before{-webkit-transform:rotate(-45deg) scale(1) translate(17px,-17px);transform:rotate(-45deg) scale(1) translate(17px,-17px)}.pin>.slider-knob>.slider-knob-inner::after{content:attr(value);position:absolute;top:0;left:50%;margin-left:-16px;width:32px;height:26px;text-align:center;color:var(--paper-slider-font-color,#fff);font-size:10px;-webkit-transform:scale(0) translate(0);transform:scale(0) translate(0)}.pin.expand>.slider-knob>.slider-knob-inner::after{-webkit-transform:scale(1) translate(0,-17px);transform:scale(1) translate(0,-17px)}.slider-input{width:50px;overflow:hidden;--paper-input-container-input:{text-align:center};@apply(--paper-slider-input)}#sliderContainer.disabled{pointer-events:none}.disabled>.slider-knob>.slider-knob-inner{background-color:var(--paper-slider-disabled-knob-color,--paper-grey-400);border:2px solid var(--paper-slider-disabled-knob-color,--paper-grey-400);-webkit-transform:scale3d(.75,.75,1);transform:scale3d(.75,.75,1)}.disabled.ring>.slider-knob>.slider-knob-inner{background-color:var(--paper-slider-knob-start-color,transparent);border:2px solid var(--paper-slider-knob-start-border-color,--paper-grey-400)}paper-ripple{color:var(--paper-slider-knob-color,--google-blue-700)}</style><div id="sliderContainer" class$="[[_getClassNames(disabled, pin, snaps, immediateValue, min, expand, dragging, transiting, editable)]]"><div class="bar-container"><paper-progress disabled$="[[disabled]]" id="sliderBar" aria-hidden="true" min="[[min]]" max="[[max]]" step="[[step]]" value="[[immediateValue]]" secondary-progress="[[secondaryProgress]]" on-down="_bardown" on-up="_resetKnob" on-track="_onTrack"></paper-progress></div><template is="dom-if" if="[[snaps]]"><div class="slider-markers"><template is="dom-repeat" items="[[markers]]"><div class="slider-marker"></div></template></div></template><div id="sliderKnob" class="slider-knob" on-down="_knobdown" on-up="_resetKnob" on-track="_onTrack" on-transitionend="_knobTransitionEnd"><div class="slider-knob-inner" value$="[[immediateValue]]"></div></div></div><template is="dom-if" if="[[editable]]"><paper-input id="input" type="number" step="[[step]]" min="[[min]]" max="[[max]]" class="slider-input" disabled$="[[disabled]]" value="[[immediateValue]]" on-change="_changeValue" on-keydown="_inputKeyDown" no-label-float=""></paper-input></template></template><script>Polymer({is:"paper-slider",behaviors:[Polymer.IronA11yKeysBehavior,Polymer.IronFormElementBehavior,Polymer.PaperInkyFocusBehavior,Polymer.IronRangeBehavior],properties:{snaps:{type:Boolean,value:!1,notify:!0},pin:{type:Boolean,value:!1,notify:!0},secondaryProgress:{type:Number,value:0,notify:!0,observer:"_secondaryProgressChanged"},editable:{type:Boolean,value:!1},immediateValue:{type:Number,value:0,readOnly:!0,notify:!0},maxMarkers:{type:Number,value:0,notify:!0},expand:{type:Boolean,value:!1,readOnly:!0},dragging:{type:Boolean,value:!1,readOnly:!0},transiting:{type:Boolean,value:!1,readOnly:!0},markers:{type:Array,readOnly:!0,value:function(){return[]}}},observers:["_updateKnob(value, min, max, snaps, step)","_valueChanged(value)","_immediateValueChanged(immediateValue)","_updateMarkers(maxMarkers, min, max, snaps)"],hostAttributes:{role:"slider",tabindex:0},keyBindings:{"left down pagedown home":"_decrementKey","right up pageup end":"_incrementKey"},increment:function(){this.value=this._clampValue(this.value+this.step)},decrement:function(){this.value=this._clampValue(this.value-this.step)},_updateKnob:function(t,e,i,s,a){this.setAttribute("aria-valuemin",e),this.setAttribute("aria-valuemax",i),this.setAttribute("aria-valuenow",t),this._positionKnob(this._calcRatio(t))},_valueChanged:function(){this.fire("value-change")},_immediateValueChanged:function(){this.dragging?this.fire("immediate-value-change"):this.value=this.immediateValue},_secondaryProgressChanged:function(){this.secondaryProgress=this._clampValue(this.secondaryProgress)},_expandKnob:function(){this._setExpand(!0)},_resetKnob:function(){this.cancelDebouncer("expandKnob"),this._setExpand(!1)},_positionKnob:function(t){this._setImmediateValue(this._calcStep(this._calcKnobPosition(t))),this._setRatio(this._calcRatio(this.immediateValue)),this.$.sliderKnob.style.left=100*this.ratio+"%",this.dragging&&(this._knobstartx=this.ratio*this._w,this.translate3d(0,0,0,this.$.sliderKnob))},_calcKnobPosition:function(t){return(this.max-this.min)*t+this.min},_onTrack:function(t){switch(t.stopPropagation(),t.detail.state){case"start":this._trackStart(t);break;case"track":this._trackX(t);break;case"end":this._trackEnd()}},_trackStart:function(t){this._w=this.$.sliderBar.offsetWidth,this._x=this.ratio*this._w,this._startx=this._x,this._knobstartx=this._startx,this._minx=-this._startx,this._maxx=this._w-this._startx,this.$.sliderKnob.classList.add("dragging"),this._setDragging(!0)},_trackX:function(t){this.dragging||this._trackStart(t);var e=Math.min(this._maxx,Math.max(this._minx,t.detail.dx));this._x=this._startx+e;var i=this._calcStep(this._calcKnobPosition(this._x/this._w));this._setImmediateValue(i);var s=this._calcRatio(this.immediateValue)*this._w-this._knobstartx;this.translate3d(s+"px",0,0,this.$.sliderKnob)},_trackEnd:function(){var t=this.$.sliderKnob.style;this.$.sliderKnob.classList.remove("dragging"),this._setDragging(!1),this._resetKnob(),this.value=this.immediateValue,t.transform=t.webkitTransform="",this.fire("change")},_knobdown:function(t){this._expandKnob(),t.preventDefault(),this.focus()},_bardown:function(t){this._w=this.$.sliderBar.offsetWidth;var e=this.$.sliderBar.getBoundingClientRect(),i=(t.detail.x-e.left)/this._w,s=this.ratio;this._setTransiting(!0),this._positionKnob(i),this.debounce("expandKnob",this._expandKnob,60),s===this.ratio&&this._setTransiting(!1),this.async(function(){this.fire("change")}),t.preventDefault(),this.focus()},_knobTransitionEnd:function(t){t.target===this.$.sliderKnob&&this._setTransiting(!1)},_updateMarkers:function(t,e,i,s){s||this._setMarkers([]);var a=Math.round((i-e)/this.step);a>t&&(a=t),this._setMarkers(new Array(a))},_mergeClasses:function(t){return Object.keys(t).filter(function(e){return t[e]}).join(" ")},_getClassNames:function(){return this._mergeClasses({disabled:this.disabled,pin:this.pin,snaps:this.snaps,ring:this.immediateValue<=this.min,expand:this.expand,dragging:this.dragging,transiting:this.transiting,editable:this.editable})},_incrementKey:function(t){this.disabled||("end"===t.detail.key?this.value=this.max:this.increment(),this.fire("change"))},_decrementKey:function(t){this.disabled||("home"===t.detail.key?this.value=this.min:this.decrement(),this.fire("change"))},_changeValue:function(t){this.value=t.target.value,this.fire("change")},_inputKeyDown:function(t){t.stopPropagation()},_createRipple:function(){return this._rippleContainer=this.$.sliderKnob,Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this)},_focusedChanged:function(t){t&&this.ensureRipple(),this.hasRipple()&&(t?this._ripple.style.display="":this._ripple.style.display="none",this._ripple.holdDown=t)}})</script></dom-module><dom-module id="state-card-input_slider" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-slider{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><paper-slider min="[[min]]" max="[[max]]" value="{{value}}" step="[[step]]" pin="" on-change="selectedValueChanged" on-tap="stopPropagation"></paper-slider></div></template></dom-module><script>Polymer({is:"state-card-input_slider",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object,observer:"stateObjectChanged"},min:{type:Number},max:{type:Number},step:{type:Number},value:{type:Number}},stateObjectChanged:function(t){this.value=Number(t.state),this.min=Number(t.attributes.min),this.max=Number(t.attributes.max),this.step=Number(t.attributes.step)},selectedValueChanged:function(){this.value!==Number(this.stateObj.state)&&this.hass.serviceActions.callService("input_slider","select_value",{value:this.value,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})</script><dom-module id="state-card-media_player" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{line-height:1.5}.state{@apply(--paper-font-common-nowrap);@apply(--paper-font-body1);margin-left:16px;text-align:right}.main-text{@apply(--paper-font-common-nowrap);color:var(--primary-text-color);text-transform:capitalize}.main-text[take-height]{line-height:40px}.secondary-text{@apply(--paper-font-common-nowrap);color:var(--secondary-text-color)}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><div class="main-text" take-height$="[[!secondaryText]]">[[computePrimaryText(stateObj, isPlaying)]]</div><div class="secondary-text">[[secondaryText]]</div></div></div></template></dom-module><script>Polymer({PLAYING_STATES:["playing","paused"],is:"state-card-media_player",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"},secondaryText:{type:String,computed:"computeSecondaryText(stateObj)"}},computeIsPlaying:function(t){return this.PLAYING_STATES.indexOf(t.state)!==-1},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e;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:""}})</script><dom-module id="state-card-rollershutter" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{line-height:1.5}.state{text-align:right;white-space:nowrap;width:127px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><paper-icon-button icon="mdi:arrow-up" on-tap="onMoveUpTap" disabled="[[computeIsFullyClosed(stateObj)]]"></paper-icon-button><paper-icon-button icon="mdi:stop" on-tap="onStopTap"></paper-icon-button><paper-icon-button icon="mdi:arrow-down" on-tap="onMoveDownTap" disabled="[[computeIsFullyOpen(stateObj)]]"></paper-icon-button></div></div></template></dom-module><script>Polymer({is:"state-card-rollershutter",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeIsFullyOpen:function(t){return 100===t.attributes.current_position},computeIsFullyClosed:function(t){return 0===t.attributes.current_position},onMoveUpTap:function(){this.hass.serviceActions.callService("rollershutter","move_up",{entity_id:this.stateObj.entityId})},onMoveDownTap:function(){this.hass.serviceActions.callService("rollershutter","move_down",{entity_id:this.stateObj.entityId})},onStopTap:function(){this.hass.serviceActions.callService("rollershutter","stop",{entity_id:this.stateObj.entityId})}})</script><dom-module id="state-card-scene" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><paper-button on-tap="activateScene">ACTIVATE</paper-button></div></template></dom-module><script>Polymer({is:"state-card-scene",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},activateScene:function(t){t.stopPropagation(),this.hass.serviceActions.callTurnOn(this.stateObj.entityId)}})</script><dom-module id="state-card-script" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}ha-entity-toggle{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><template is="dom-if" if="[[stateObj.attributes.can_cancel]]"><ha-entity-toggle state-obj="[[stateObj]]" hass="[[hass]]"></ha-entity-toggle></template><template is="dom-if" if="[[!stateObj.attributes.can_cancel]]"><paper-button on-tap="fireScript">ACTIVATE</paper-button></template></div></template></dom-module><script>Polymer({is:"state-card-script",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},fireScript:function(t){t.stopPropagation(),this.hass.serviceActions.callTurnOn(this.stateObj.entityId)}})</script><dom-module id="state-card-thermostat" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{@apply(--paper-font-body1);line-height:1.5}.state{margin-left:16px;text-align:right}.target{color:var(--primary-text-color)}.current{color:var(--secondary-text-color)}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><div class="target">[[computeTargetTemperature(stateObj)]]</div><div class="current"><span>Currently: </span><span>[[stateObj.attributes.current_temperature]]</span> <span></span> <span>[[stateObj.attributes.unit_of_measurement]]</span></div></div></div></template></dom-module><script>Polymer({is:"state-card-thermostat",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeTargetTemperature:function(t){return t.attributes.temperature+" "+t.attributes.unit_of_measurement}})</script><dom-module id="state-card-toggle" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>ha-entity-toggle{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><ha-entity-toggle state-obj="[[stateObj]]" hass="[[hass]]"></ha-entity-toggle></div></template></dom-module><script>Polymer({is:"state-card-toggle",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}}})</script><dom-module id="state-card-weblink" assetpath="state-summary/"><template><style>:host{display:block}.name{@apply(--paper-font-common-nowrap);@apply(--paper-font-body1);color:var(--primary-color);text-transform:capitalize;line-height:40px;margin-left:16px}</style><state-badge state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-badge><a href$="[[stateObj.state]]" target="_blank" class="name" id="link">[[stateObj.entityDisplay]]</a></template></dom-module><script>Polymer({is:"state-card-weblink",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation(),t.target!==this.$.link&&window.open(this.stateObj.state,"_blank")}})</script><script>Polymer({is:"state-card-content",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},observers:["inputChanged(hass, inDialog, stateObj)"],inputChanged:function(t,e,s){s&&window.hassUtil.dynamicContentUpdater(this,"STATE-CARD-"+window.hassUtil.stateCardType(this.hass,s).toUpperCase(),{hass:t,stateObj:s,inDialog:e})}})</script><dom-module id="ha-entities-card" assetpath="cards/"><template><style is="custom-style" include="iron-flex"></style><style>.states{padding-bottom:16px}.state{padding:4px 16px;cursor:pointer}.header{@apply(--paper-font-headline);line-height:40px;color:var(--primary-text-color);padding:20px 16px 12px;text-transform:capitalize}.header .name{@apply(--paper-font-common-nowrap)}ha-entity-toggle{margin-left:16px}.header-more-info{cursor:pointer}</style><ha-card><div class$="[[computeTitleClass(groupEntity)]]" on-tap="entityTapped"><div class="flex name">[[computeTitle(states, groupEntity)]]</div><template is="dom-if" if="[[showGroupToggle(groupEntity, states)]]"><ha-entity-toggle hass="[[hass]]" state-obj="[[groupEntity]]"></ha-entity-toggle></template></div><div class="states"><template is="dom-repeat" items="[[states]]"><div class="state" on-tap="entityTapped"><state-card-content hass="[[hass]]" class="state-card" state-obj="[[item]]"></state-card-content></div></template></div></ha-card></template></dom-module><script>Polymer({is:"ha-entities-card",properties:{hass:{type:Object},states:{type:Array},groupEntity:{type:Object}},computeTitle:function(t,e){return e?e.entityDisplay:t[0].domain.replace(/_/g," ")},computeTitleClass:function(t){var e="header horizontal layout center ";return t&&(e+="header-more-info"),e},entityTapped:function(t){var e;t.target.classList.contains("paper-toggle-button")||t.target.classList.contains("paper-icon-button")||!t.model&&!this.groupEntity||(t.stopPropagation(),e=t.model?t.model.item.entityId:this.groupEntity.entityId,this.async(function(){this.hass.moreInfoActions.selectEntity(e)}.bind(this),1))},showGroupToggle:function(t,e){var n;return!(!t||!e||"on"!==t.state&&"off"!==t.state)&&(n=e.reduce(function(t,e){return t+window.hassUtil.canToggle(this.hass,e.entityId)},0),n>1)}})</script><dom-module id="ha-introduction-card" assetpath="cards/"><template><style>:host{@apply(--paper-font-body1)}a{color:var(--dark-primary-color)}ul{margin:8px;padding-left:16px}li{margin-bottom:8px}.content{padding:0 16px 16px}.install{display:block;line-height:1.5em;margin-top:8px;margin-bottom:16px}</style><ha-card header="Welcome Home!"><div class="content"><template is="dom-if" if="[[hass.demo]]">To install Home Assistant, run:<br><code class="install">pip3 install homeassistant<br>hass --open-ui</code></template>Here are some resources to get started.<ul><template is="dom-if" if="[[hass.demo]]"><li><a href="https://home-assistant.io/getting-started/">Home Assistant website</a></li><li><a href="https://home-assistant.io/getting-started/">Installation instructions</a></li><li><a href="https://home-assistant.io/getting-started/troubleshooting/">Troubleshooting your installation</a></li></template><li><a href="https://home-assistant.io/getting-started/configuration/" target="_blank">Configuring Home Assistant</a></li><li><a href="https://home-assistant.io/components/" target="_blank">Available components</a></li><li><a href="https://home-assistant.io/getting-started/troubleshooting-configuration/" target="_blank">Troubleshooting your configuration</a></li><li><a href="https://home-assistant.io/help/" target="_blank">Ask community for help</a></li></ul><template is="dom-if" if="[[showHideInstruction]]">To remove this card, edit your config in <code>configuration.yaml</code> and disable the <code>introduction</code> component.</template></div></ha-card></template></dom-module><script>Polymer({is:"ha-introduction-card",properties:{hass:{type:Object},showHideInstruction:{type:Boolean,value:!0}}})</script><dom-module id="ha-media_player-card" assetpath="cards/"><template><style include="paper-material iron-flex iron-flex-alignment iron-positioning">:host{display:block;position:relative;font-size:0;border-radius:2px;overflow:hidden}.banner{position:relative;background-position:center center;background-image:url(/static/images/card_media_player_bg.png);background-repeat:no-repeat;background-color:var(--primary-color);border-top-left-radius:2px;border-top-right-radius:2px}.banner:before{display:block;content:"";width:100%;padding-top:56%;transition:padding-top .8s}.banner.no-cover:before{padding-top:91px}.banner>.cover{position:absolute;top:0;left:0;right:0;bottom:0;border-top-left-radius:2px;border-top-right-radius:2px;background-position:center center;background-size:cover;transition:opacity .8s;opacity:1}.banner.is-off>.cover{opacity:0}.banner>.caption{@apply(--paper-font-caption);position:absolute;left:0;right:0;bottom:0;background-color:rgba(0,0,0,var(--dark-secondary-opacity));padding:8px 16px;text-transform:capitalize;font-size:14px;font-weight:500;color:#fff;transition:background-color .5s}.banner.is-off>.caption{background-color:initial}.banner>.caption .title{@apply(--paper-font-common-nowrap);font-size:1.2em;margin:8px 0 4px}.controls{@apply(--paper-font-body1);padding:8px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;background-color:#fff}.controls paper-icon-button{width:44px;height:44px}paper-icon-button{opacity:var(--dark-primary-opacity)}paper-icon-button[disabled]{opacity:var(--dark-disabled-opacity)}paper-icon-button.primary{width:56px!important;height:56px!important;background-color:var(--primary-color);color:#fff;border-radius:50%;padding:8px;transition:background-color .5s}paper-icon-button.primary[disabled]{background-color:rgba(0,0,0,var(--dark-disabled-opacity))}[invisible]{visibility:hidden!important}</style><div class$="[[computeBannerClasses(playerObj)]]"><div class="cover" id="cover"></div><div class="caption">[[stateObj.entityDisplay]]<div class="title">[[playerObj.primaryText]]</div>[[playerObj.secondaryText]]<br></div></div><div class="controls layout horizontal justified"><paper-icon-button icon="mdi:power" on-tap="handleTogglePower" invisible$="[[!playerObj.supportsTurnOff]]" class="self-center secondary"></paper-icon-button><div><paper-icon-button icon="mdi:skip-previous" invisible$="[[!playerObj.supportsPreviousTrack]]" disabled="[[playerObj.isOff]]" on-tap="handlePrevious"></paper-icon-button><paper-icon-button class="primary" icon="[[computePlaybackControlIcon(playerObj)]]" invisible="[[!computePlaybackControlIcon(playerObj)]]" disabled="[[playerObj.isOff]]" on-tap="handlePlaybackControl"></paper-icon-button><paper-icon-button icon="mdi:skip-next" invisible$="[[!playerObj.supportsNextTrack]]" disabled="[[playerObj.isOff]]" on-tap="handleNext"></paper-icon-button></div><paper-icon-button icon="mdi:dots-vertical" on-tap="handleOpenMoreInfo" class="self-center secondary"></paper-icon-button></div></template></dom-module><script>Polymer({is:"ha-media_player-card",properties:{hass:{type:Object},stateObj:{type:Object},playerObj:{type:Object,computed:"computePlayerObj(stateObj)",observer:"playerObjChanged"},playbackControlIcon:{type:String,computed:"computePlaybackControlIcon(playerObj)"},elevation:{type:Number,value:1,reflectToAttribute:!0}},playerObjChanged:function(t){t.isOff||t.isIdle||(this.$.cover.style.backgroundImage=t.stateObj.attributes.entity_picture?"url("+t.stateObj.attributes.entity_picture+")":"")},computeBannerClasses:function(t){var e="banner";return(t.isOff||t.isIdle)&&(e+=" is-off"),t.stateObj.attributes.entity_picture||(e+=" no-cover"),e},computeHidePowerOnButton:function(t){return!t.isOff||!t.supportsTurnOn},computePlayerObj:function(t){return t.domainModel(this.hass)},computePlaybackControlIcon:function(t){return t.isPlaying?t.supportsPause?"mdi:pause":"mdi:stop":t.isPaused||t.isOff?"mdi:play":""},computeShowControls:function(t){return!t.isOff},handleNext:function(t){t.stopPropagation(),this.playerObj.nextTrack()},handleOpenMoreInfo:function(t){t.stopPropagation(),this.async(function(){this.hass.moreInfoActions.selectEntity(this.stateObj.entityId)},1)},handlePlaybackControl:function(t){t.stopPropagation(),this.playerObj.mediaPlayPause()},handlePrevious:function(t){t.stopPropagation(),this.playerObj.previousTrack()},handleTogglePower:function(t){t.stopPropagation(),this.playerObj.togglePower()}})</script><dom-module id="ha-persistent_notification-card" assetpath="cards/"><template><style>:host{@apply(--paper-font-body1)}.content{padding:0 16px 16px}paper-button{margin:8px;font-weight:500}</style><ha-card header="[[computeTitle(stateObj)]]"><div id="pnContent" class="content"></div><paper-button on-tap="dismissTap">DISMISS</paper-button></ha-card></template></dom-module><script>Polymer({is:"ha-persistent_notification-card",properties:{hass:{type:Object},stateObj:{type:Object},scriptLoaded:{type:Boolean,value:!1}},observers:["computeContent(stateObj, scriptLoaded)"],computeTitle:function(t){return t.attributes.title||t.entityDisplay},loadScript:function(){var t=function(){this.scriptLoaded=!0}.bind(this),e=function(){console.error("Micromarkdown was not loaded.")};this.importHref("/static/micromarkdown-js.html",t,e)},computeContent:function(t,e){var i="",n="";e&&(i=this.$.pnContent,n=window.micromarkdown.parse(t.state),i.innerHTML=n)},ready:function(){this.loadScript()},dismissTap:function(t){t.preventDefault(),this.hass.entityActions.delete(this.stateObj)}})</script><script>Polymer({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(a){a&&window.hassUtil.dynamicContentUpdater(this,"HA-"+a.cardType.toUpperCase()+"-CARD",a)}})</script><dom-module id="ha-cards" assetpath="components/"><template><style is="custom-style" include="iron-flex iron-flex-factors"></style><style>:host{display:block;padding-top:8px;padding-right:8px}.badges{font-size:85%;text-align:center}.column{max-width:500px;overflow-x:hidden}.zone-card{margin-left:8px;margin-bottom:8px}@media (max-width:500px){:host{padding-right:0}.zone-card{margin-left:0}}@media (max-width:599px){.column{max-width:600px}}</style><div class="main"><template is="dom-if" if="[[cards.badges]]"><div class="badges"><template is="dom-if" if="[[cards.demo]]"><ha-demo-badge></ha-demo-badge></template><ha-badges-card states="[[cards.badges]]" hass="[[hass]]"></ha-badges-card></div></template><div class="horizontal layout center-justified"><template is="dom-repeat" items="[[cards.columns]]" as="column"><div class="column flex-1"><template is="dom-repeat" items="[[column]]" as="card"><div class="zone-card"><ha-card-chooser card-data="[[card]]" hass="[[hass]]"></ha-card-chooser></div></template></div></template></div></div></template></dom-module><script>!function(){"use strict";function t(t){return t in o?o[t]:30}function e(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}var n={camera:4,media_player:3,persistent_notification:0},o={configurator:-20,persistent_notification:-15,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,sensor:5,binary_sensor:6};Polymer({is:"ha-cards",properties:{hass:{type:Object},showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},panelVisible:{type:Boolean},viewVisible:{type:Boolean},cards:{type:Object}},observers:["updateCards(columns, states, showIntroduction, panelVisible, viewVisible)"],updateCards:function(t,e,n,o,r){o&&r&&this.debounce("updateCards",function(){this.panelVisible&&this.viewVisible&&(this.cards=this.computeCards(t,e,n))}.bind(this))},computeCards:function(o,r,s){function i(t){return t.filter(function(t){return!(t.entityId in h)})}function a(t){var e=0;for(p=e;p<y.length;p++){if(y[p]<5){e=p;break}y[p]<y[e]&&(e=p)}return y[e]+=t,e}function u(t,e,o){var r,s,i,u;0!==e.length&&(r=[],s=[],i=0,e.forEach(function(t){t.domain in n?(r.push(t),i+=n[t.domain]):(s.push(t),i++)}),i+=s.length>1,u=a(i),s.length>0&&f.columns[u].push({hass:d,cardType:"entities",states:s,groupEntity:o||!1}),r.forEach(function(t){f.columns[u].push({hass:d,cardType:t.domain,stateObj:t})}))}var c,p,d=this.hass,l=r.groupBy(function(t){return t.domain}),h={},f={demo:!1,badges:[],columns:[]},y=[];for(p=0;p<o;p++)f.columns.push([]),y.push(0);return s&&f.columns[a(5)].push({hass:d,cardType:"introduction",showHideInstruction:r.size>0&&!d.demo}),c=this.hass.util.expandGroup,l.keySeq().sortBy(function(e){return t(e)}).forEach(function(n){var o;return"a"===n?void(f.demo=!0):(o=t(n),void(o>=0&&o<10?f.badges.push.apply(f.badges,i(l.get(n)).sortBy(e).toArray()):"group"===n?l.get(n).sortBy(e).forEach(function(t){var e=c(t,r);e.forEach(function(t){h[t.entityId]=!0}),u(t.entityId,e.toArray(),t)}):u(n,i(l.get(n)).sortBy(e).toArray())))}),f.columns=f.columns.filter(function(t){return t.length>0}),f}})}()</script><dom-module id="partial-cards" assetpath="layouts/"><template><style include="iron-flex iron-positioning ha-style">:host{-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none}app-header-layout{background-color:#E5E5E5}paper-tabs{margin-left:12px;--paper-tabs-selection-bar-color:#FFF;text-transform:uppercase}</style><app-header-layout has-scrolling-region="" id="layout"><app-header effects="waterfall" condenses="" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">[[computeTitle(views, locationName)]]</div><paper-icon-button icon="mdi:refresh" class$="[[computeRefreshButtonClass(isFetching)]]" on-tap="handleRefresh" hidden$="[[isStreaming]]"></paper-icon-button><paper-icon-button icon="mdi:microphone" hidden$="[[!canListen]]" on-tap="handleListenClick"></paper-icon-button></app-toolbar><div sticky="" hidden$="[[!views.length]]"><paper-tabs scrollable="" selected="[[currentView]]" attr-for-selected="data-entity" on-iron-select="handleViewSelected"><paper-tab data-entity="" on-tap="scrollToTop">[[locationName]]</paper-tab><template is="dom-repeat" items="[[views]]"><paper-tab data-entity$="[[item.entityId]]" on-tap="scrollToTop"><template is="dom-if" if="[[item.attributes.icon]]"><iron-icon icon="[[item.attributes.icon]]"></iron-icon></template><template is="dom-if" if="[[!item.attributes.icon]]">[[item.entityDisplay]]</template></paper-tab></template></paper-tabs></div></app-header><iron-pages attr-for-selected="data-view" selected="[[currentView]]" selected-attribute="view-visible"><ha-cards data-view="" show-introduction="[[computeShowIntroduction(currentView, introductionLoaded, states)]]" states="[[states]]" columns="[[_columns]]" hass="[[hass]]" panel-visible="[[panelVisible]]"></ha-cards><template is="dom-repeat" items="[[views]]"><ha-cards data-view$="[[item.entityId]]" show-introduction="[[computeShowIntroduction(currentView, introductionLoaded, states)]]" states="[[states]]" columns="[[_columns]]" hass="[[hass]]" panel-visible="[[panelVisible]]"></ha-cards></template></iron-pages></app-header-layout></template></dom-module><script>Polymer({is:"partial-cards",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1,observer:"handleWindowChange"},panelVisible:{type:Boolean,value:!1},_columns:{type:Number,value:1},isFetching:{type:Boolean,bindNuclear:function(e){return e.syncGetters.isFetching}},isStreaming:{type:Boolean,bindNuclear:function(e){return e.streamGetters.isStreamingEvents}},canListen:{type:Boolean,bindNuclear:function(e){return[e.voiceGetters.isVoiceSupported,e.configGetters.isComponentLoaded("conversation"),function(e,n){return e&&n}]}},introductionLoaded:{type:Boolean,bindNuclear:function(e){return e.configGetters.isComponentLoaded("introduction")}},locationName:{type:String,bindNuclear:function(e){return e.configGetters.locationName}},currentView:{type:String,bindNuclear:function(e){return[e.viewGetters.currentView,function(e){return e||""}]}},views:{type:Array,bindNuclear:function(e){return[e.viewGetters.views,function(e){return e.valueSeq().sortBy(function(e){return e.attributes.order}).toArray()}]}},states:{type:Object,bindNuclear:function(e){return e.viewGetters.currentViewEntities}}},created:function(){this.handleWindowChange=this.handleWindowChange.bind(this),this.mqls=[300,600,900,1200].map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(this.handleWindowChange),n}.bind(this))},detached:function(){this.mqls.forEach(function(e){e.removeListener(this.handleWindowChange)})},handleWindowChange:function(){var e=this.mqls.reduce(function(e,n){return e+n.matches},0);this._columns=Math.max(1,e-(!this.narrow&&this.showMenu))},scrollToTop:function(){var e=0,n=this.$.layout.header.scrollTarget,t=function(e,n,t,i){return e/=i,-t*e*(e-2)+n},i=Math.random(),r=200,o=Date.now(),s=n.scrollTop,a=e-s;this._currentAnimationId=i,function c(){var u=Date.now(),l=u-o;l>r?n.scrollTop=e:this._currentAnimationId===i&&(n.scrollTop=t(l,s,a,r),requestAnimationFrame(c.bind(this)))}.call(this)},handleRefresh:function(){this.hass.syncActions.fetchAll()},handleListenClick:function(){this.hass.voiceActions.listen()},handleViewSelected:function(e){var n=e.detail.item.getAttribute("data-entity")||null,t=this.currentView||null;n!==t&&this.async(function(){this.hass.viewActions.selectView(n)}.bind(this),0)},computeRefreshButtonClass:function(e){return e?"ha-spin":""},computeTitle:function(e,n){return e.length>0?"Home Assistant":n},computeShowIntroduction:function(e,n,t){return""===e&&(n||0===t.size)}})</script><style is="custom-style">html{font-size:14px;--dark-primary-color:#0288D1;--default-primary-color:#03A9F4;--primary-color:#03A9F4;--light-primary-color:#B3E5FC;--text-primary-color:#ffffff;--accent-color:#FF9800;--primary-background-color:#ffffff;--primary-text-color:#212121;--secondary-text-color:#727272;--disabled-text-color:#bdbdbd;--divider-color:#B6B6B6;--paper-toggle-button-checked-ink-color:#039be5;--paper-toggle-button-checked-button-color:#039be5;--paper-toggle-button-checked-bar-color:#039be5;--paper-slider-knob-color:var(--primary-color);--paper-slider-knob-start-color:var(--primary-color);--paper-slider-pin-color:var(--primary-color);--paper-slider-active-color:var(--primary-color);--paper-slider-secondary-color:var(--light-primary-color);--paper-slider-container-color:var(--divider-color);--google-red-500:#db4437;--google-blue-500:#4285f4;--google-green-500:#0f9d58;--google-yellow-500:#f4b400;--paper-grey-50:#fafafa;--paper-green-400:#66bb6a;--paper-blue-400:#42a5f5;--paper-orange-400:#ffa726;--dark-divider-opacity:0.12;--dark-disabled-opacity:0.38;--dark-secondary-opacity:0.54;--dark-primary-opacity:0.87;--light-divider-opacity:0.12;--light-disabled-opacity:0.3;--light-secondary-opacity:0.7;--light-primary-opacity:1.0}@-webkit-keyframes ha-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes ha-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.ha-spin{-webkit-animation:ha-spin 2s infinite linear;animation:ha-spin 2s infinite linear}</style><dom-module id="ha-style" assetpath="resources/"><template><style>:host{@apply(--paper-font-body1)}app-header,app-toolbar{background-color:var(--primary-color);font-weight:400;color:#fff}app-toolbar ha-menu-button+[main-title]{margin-left:24px}h1{@apply(--paper-font-title)}</style></template></dom-module><dom-module id="partial-panel-resolver" assetpath="layouts/"><template><style include="iron-flex ha-style">[hidden]{display:none!important}.placeholder{height:100%}.layout{height:calc(100% - 64px)}</style><div hidden$="[[resolved]]" class="placeholder"><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button></app-toolbar><div class="layout horizontal center-center"><template is="dom-if" if="[[!errorLoading]]"><paper-spinner active=""></paper-spinner></template><template is="dom-if" if="[[errorLoading]]">Error loading panel :(</template></div></div><span id="panel" hidden$="[[!resolved]]"></span></template></dom-module><script>Polymer({is:"partial-panel-resolver",behaviors:[window.hassBehavior],properties:{hass:{type:Object,observer:"updateAttributes"},narrow:{type:Boolean,value:!1,observer:"updateAttributes"},showMenu:{type:Boolean,value:!1,observer:"updateAttributes"},resolved:{type:Boolean,value:!1},errorLoading:{type:Boolean,value:!1},panel:{type:Object,bindNuclear:function(e){return e.navigationGetters.activePanel},observer:"panelChanged"}},panelChanged:function(e){return e?(this.resolved=!1,this.errorLoading=!1,void this.importHref(e.get("url"),function(){window.hassUtil.dynamicContentUpdater(this.$.panel,"ha-panel-"+e.get("component_name"),{hass:this.hass,narrow:this.narrow,showMenu:this.showMenu,panel:e.toJS()}),this.resolved=!0}.bind(this),function(){this.errorLoading=!0}.bind(this),!0)):void(this.$.panel.lastChild&&this.$.panel.removeChild(this.$.panel.lastChild))},updateAttributes:function(){var e=Polymer.dom(this.$.panel).lastChild;e&&(e.hass=this.hass,e.narrow=this.narrow,e.showMenu=this.showMenu)}})</script><dom-module id="paper-toast" assetpath="../bower_components/paper-toast/"><template><style>:host{display:block;position:fixed;background-color:var(--paper-toast-background-color,#323232);color:var(--paper-toast-color,#f1f1f1);min-height:48px;min-width:288px;padding:16px 24px;box-sizing:border-box;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:2px;margin:12px;font-size:14px;cursor:default;-webkit-transition:-webkit-transform .3s,opacity .3s;transition:transform .3s,opacity .3s;opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px);@apply(--paper-font-common-base)}:host(.capsule){border-radius:24px}:host(.fit-bottom){width:100%;min-width:0;border-radius:0;margin:0}:host(.paper-toast-open){opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}</style><span id="label">{{text}}</span><content></content></template><script>!function(){var e=null;Polymer({is:"paper-toast",behaviors:[Polymer.IronOverlayBehavior],properties:{fitInto:{type:Object,value:window,observer:"_onFitIntoChanged"},horizontalAlign:{type:String,value:"left"},verticalAlign:{type:String,value:"bottom"},duration:{type:Number,value:3e3},text:{type:String,value:""},noCancelOnOutsideClick:{type:Boolean,value:!0},noAutoFocus:{type:Boolean,value:!0}},listeners:{transitionend:"__onTransitionEnd"},get visible(){return Polymer.Base._warn("`visible` is deprecated, use `opened` instead"),this.opened},get _canAutoClose(){return this.duration>0&&this.duration!==1/0},created:function(){this._autoClose=null,Polymer.IronA11yAnnouncer.requestAvailability()},show:function(e){"string"==typeof e&&(e={text:e});for(var t in e)0===t.indexOf("_")?Polymer.Base._warn('The property "'+t+'" is private and was not set.'):t in this?this[t]=e[t]:Polymer.Base._warn('The property "'+t+'" is not valid.');this.open()},hide:function(){this.close()},__onTransitionEnd:function(e){e&&e.target===this&&"opacity"===e.propertyName&&(this.opened?this._finishRenderOpened():this._finishRenderClosed())},_openedChanged:function(){null!==this._autoClose&&(this.cancelAsync(this._autoClose),this._autoClose=null),this.opened?(e&&e!==this&&e.close(),e=this,this.fire("iron-announce",{text:this.text}),this._canAutoClose&&(this._autoClose=this.async(this.close,this.duration))):e===this&&(e=null),Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this,arguments)},_renderOpened:function(){this.classList.add("paper-toast-open")},_renderClosed:function(){this.classList.remove("paper-toast-open")},_onFitIntoChanged:function(e){this.positionTarget=e}})}()</script></dom-module><dom-module id="notification-manager" assetpath="managers/"><template><style>paper-toast{z-index:1}</style><paper-toast id="toast" text="{{text}}" no-cancel-on-outside-click="[[neg]]"></paper-toast></template></dom-module><script>Polymer({is:"notification-manager",behaviors:[window.hassBehavior],properties:{hass:{type:Object},neg:{type:Boolean,value:!1},text:{type:String,bindNuclear:function(t){return t.notificationGetters.lastNotificationMessage},observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}})</script><script>Polymer.PaperDialogBehaviorImpl={hostAttributes:{role:"dialog",tabindex:"-1"},properties:{modal:{type:Boolean,value:!1}},observers:["_modalChanged(modal, _readied)"],listeners:{tap:"_onDialogClick"},ready:function(){this.__prevNoCancelOnOutsideClick=this.noCancelOnOutsideClick,this.__prevNoCancelOnEscKey=this.noCancelOnEscKey,this.__prevWithBackdrop=this.withBackdrop},_modalChanged:function(i,e){e&&(i?(this.__prevNoCancelOnOutsideClick=this.noCancelOnOutsideClick,this.__prevNoCancelOnEscKey=this.noCancelOnEscKey,this.__prevWithBackdrop=this.withBackdrop,this.noCancelOnOutsideClick=!0,this.noCancelOnEscKey=!0,this.withBackdrop=!0):(this.noCancelOnOutsideClick=this.noCancelOnOutsideClick&&this.__prevNoCancelOnOutsideClick,this.noCancelOnEscKey=this.noCancelOnEscKey&&this.__prevNoCancelOnEscKey,this.withBackdrop=this.withBackdrop&&this.__prevWithBackdrop))},_updateClosingReasonConfirmed:function(i){this.closingReason=this.closingReason||{},this.closingReason.confirmed=i},_onDialogClick:function(i){for(var e=Polymer.dom(i).path,o=0;o<e.indexOf(this);o++){var t=e[o];if(t.hasAttribute&&(t.hasAttribute("dialog-dismiss")||t.hasAttribute("dialog-confirm"))){this._updateClosingReasonConfirmed(t.hasAttribute("dialog-confirm")),this.close(),i.stopPropagation();break}}}},Polymer.PaperDialogBehavior=[Polymer.IronOverlayBehavior,Polymer.PaperDialogBehaviorImpl]</script><dom-module id="paper-dialog-shared-styles" assetpath="../bower_components/paper-dialog-behavior/"><template><style>:host{display:block;margin:24px 40px;background:var(--paper-dialog-background-color,--primary-background-color);color:var(--paper-dialog-color,--primary-text-color);@apply(--paper-font-body1);@apply(--shadow-elevation-16dp);@apply(--paper-dialog)}:host>::content>*{margin-top:20px;padding:0 24px}:host>::content>.no-padding{padding:0}:host>::content>:first-child{margin-top:24px}:host>::content>:last-child{margin-bottom:24px}:host>::content h2{position:relative;margin:0;@apply(--paper-font-title);@apply(--paper-dialog-title)}:host>::content .buttons{position:relative;padding:8px 8px 8px 24px;margin:0;color:var(--paper-dialog-button-color,--primary-color);@apply(--layout-horizontal);@apply(--layout-end-justified)}</style></template></dom-module><dom-module id="paper-dialog" assetpath="../bower_components/paper-dialog/"><template><style include="paper-dialog-shared-styles"></style><content></content></template></dom-module><script>!function(){Polymer({is:"paper-dialog",behaviors:[Polymer.PaperDialogBehavior,Polymer.NeonAnimationRunnerBehavior],listeners:{"neon-animation-finish":"_onNeonAnimationFinish"},_renderOpened:function(){this.cancelAnimation(),this.playAnimation("entry")},_renderClosed:function(){this.cancelAnimation(),this.playAnimation("exit")},_onNeonAnimationFinish:function(){this.opened?this._finishRenderOpened():this._finishRenderClosed()}})}()</script><dom-module id="paper-dialog-scrollable" assetpath="../bower_components/paper-dialog-scrollable/"><template><style>:host{display:block;@apply(--layout-relative)}:host(.is-scrolled:not(:first-child))::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:var(--divider-color)}:host(.can-scroll:not(.scrolled-to-bottom):not(:last-child))::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:var(--divider-color)}.scrollable{padding:0 24px;@apply(--layout-scroll);@apply(--paper-dialog-scrollable)}.fit{@apply(--layout-fit)}</style><div id="scrollable" class="scrollable"><content></content></div></template></dom-module><script>Polymer({is:"paper-dialog-scrollable",properties:{dialogElement:{type:Object}},listeners:{"scrollable.scroll":"_scroll"},get scrollTarget(){return this.$.scrollable},ready:function(){this._ensureTarget()},attached:function(){this.classList.add("no-padding"),this._ensureTarget(),requestAnimationFrame(this._scroll.bind(this))},_scroll:function(){this.toggleClass("is-scrolled",this.scrollTarget.scrollTop>0),this.toggleClass("can-scroll",this.scrollTarget.offsetHeight<this.scrollTarget.scrollHeight),this.toggleClass("scrolled-to-bottom",this.scrollTarget.scrollTop+this.scrollTarget.offsetHeight>=this.scrollTarget.scrollHeight)},_ensureTarget:function(){this.dialogElement=this.dialogElement||Polymer.dom(this).parentNode,this.dialogElement&&this.dialogElement.behaviors&&this.dialogElement.behaviors.indexOf(Polymer.PaperDialogBehaviorImpl)>=0?(this.dialogElement.sizingTarget=this.scrollTarget,this.scrollTarget.classList.remove("fit")):this.dialogElement&&this.scrollTarget.classList.add("fit")}})</script><script>!function(){"use strict";Polymer.IronJsonpLibraryBehavior={properties:{libraryLoaded:{type:Boolean,value:!1,notify:!0,readOnly:!0},libraryErrorMessage:{type:String,value:null,notify:!0,readOnly:!0}},observers:["_libraryUrlChanged(libraryUrl)"],_libraryUrlChanged:function(r){this._isReady&&this.libraryUrl&&this._loadLibrary()},_libraryLoadCallback:function(r,i){r?(console.warn("Library load failed:",r.message),this._setLibraryErrorMessage(r.message)):(this._setLibraryErrorMessage(null),this._setLibraryLoaded(!0),this.notifyEvent&&this.fire(this.notifyEvent,i))},_loadLibrary:function(){r.require(this.libraryUrl,this._libraryLoadCallback.bind(this),this.callbackName)},ready:function(){this._isReady=!0,this.libraryUrl&&this._loadLibrary()}};var r={apiMap:{},require:function(r,t,e){var a=this.nameFromUrl(r);this.apiMap[a]||(this.apiMap[a]=new i(a,r,e)),this.apiMap[a].requestNotify(t)},nameFromUrl:function(r){return r.replace(/[\:\/\%\?\&\.\=\-\,]/g,"_")+"_api"}},i=function(r,i,t){if(this.notifiers=[],!t){if(!(i.indexOf(this.callbackMacro)>=0))return void(this.error=new Error("IronJsonpLibraryBehavior a %%callback%% parameter is required in libraryUrl"));t=r+"_loaded",i=i.replace(this.callbackMacro,t)}this.callbackName=t,window[this.callbackName]=this.success.bind(this),this.addScript(i)};i.prototype={callbackMacro:"%%callback%%",loaded:!1,addScript:function(r){var i=document.createElement("script");i.src=r,i.onerror=this.handleError.bind(this);var t=document.querySelector("script")||document.body;t.parentNode.insertBefore(i,t),this.script=i},removeScript:function(){this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.script=null},handleError:function(r){this.error=new Error("Library failed to load"),this.notifyAll(),this.cleanup()},success:function(){this.loaded=!0,this.result=Array.prototype.slice.call(arguments),this.notifyAll(),this.cleanup()},cleanup:function(){delete window[this.callbackName]},notifyAll:function(){this.notifiers.forEach(function(r){r(this.error,this.result)}.bind(this)),this.notifiers=[]},requestNotify:function(r){this.loaded||this.error?r(this.error,this.result):this.notifiers.push(r)}}}()</script><script>Polymer({is:"iron-jsonp-library",behaviors:[Polymer.IronJsonpLibraryBehavior],properties:{libraryUrl:String,callbackName:String,notifyEvent:String}})</script><script>Polymer({is:"google-legacy-loader",behaviors:[Polymer.IronJsonpLibraryBehavior],properties:{libraryUrl:{type:String,value:"https://www.google.com/jsapi?callback=%%callback%%"},notifyEvent:{type:String,value:"api-load"}},get api(){return google}})</script><style>div.charts-tooltip{z-index:200!important}</style><script>Polymer({is:"state-history-chart-timeline",properties:{data:{type:Object,observer:"dataChanged"},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){function t(t,e,n,i){var d=e.replace(/_/g," ");a.addRow([t,d,n,i])}var e,a,n,i,d,l=Polymer.dom(this),o=this.data;if(this.isAttached){for(;l.node.lastChild;)l.node.removeChild(l.node.lastChild);o&&0!==o.length&&(e=new window.google.visualization.Timeline(this),a=new window.google.visualization.DataTable,a.addColumn({type:"string",id:"Entity"}),a.addColumn({type:"string",id:"State"}),a.addColumn({type:"date",id:"Start"}),a.addColumn({type:"date",id:"End"}),n=new Date(o.reduce(function(t,e){return Math.min(t,e[0].lastChangedAsDate)},new Date)),i=new Date(n),i.setDate(i.getDate()+1),i>new Date&&(i=new Date),d=0,o.forEach(function(e){var a,n,l=null,o=null;0!==e.length&&(a=e[0].entityDisplay,e.forEach(function(e){null!==l&&e.state!==l?(n=e.lastChangedAsDate,t(a,l,o,n),l=e.state,o=n):null===l&&(l=e.state,o=e.lastChangedAsDate)}),t(a,l,o,i),d++)}),e.draw(a,{height:55+42*d,timeline:{showRowLabels:o.length>1},hAxis:{format:"H:mm"}}))}}})</script><script>!function(){"use strict";function t(t,e){var a,r=[];for(a=t;a<e;a++)r.push(a);return r}function e(t){var e=parseFloat(t);return!isNaN(e)&&isFinite(e)?e:null}Polymer({is:"state-history-chart-line",properties:{data:{type:Object,observer:"dataChanged"},unit:{type:String},isSingleDevice:{type:Boolean,value:!1},isAttached:{type:Boolean,value:!1,observer:"dataChanged"},chartEngine:{type:Object}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){var a,r,n,i,u,o=this.unit,s=this.data;this.isAttached&&(this.chartEngine||(this.chartEngine=new window.google.visualization.LineChart(this)),0!==s.length&&(a={legend:{position:"top"},interpolateNulls:!0,titlePosition:"none",vAxes:{0:{title:o}},hAxis:{format:"H:mm"},chartArea:{left:"60",width:"95%"},explorer:{actions:["dragToZoom","rightClickToReset","dragToPan"],keepInBounds:!0,axis:"horizontal",maxZoomIn:.1}},this.isSingleDevice&&(a.legend.position="none",a.vAxes[0].title=null,a.chartArea.left=40,a.chartArea.height="80%",a.chartArea.top=5,a.enableInteractivity=!1),r=new Date(Math.min.apply(null,s.map(function(t){return t[0].lastChangedAsDate}))),n=new Date(r),n.setDate(n.getDate()+1),n>new Date&&(n=new Date),i=s.map(function(t){function a(t,e){r&&e&&c.push([t[0]].concat(r.slice(1).map(function(t,a){return e[a]?t:null}))),c.push(t),r=t}var r,i,u,o,s=t[t.length-1],l=s.domain,d=s.entityDisplay,c=[],h=new window.google.visualization.DataTable;return h.addColumn({type:"datetime",id:"Time"}),"thermostat"===l?(i=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1),h.addColumn("number",d+" current temperature"),i?(h.addColumn("number",d+" target temperature high"),h.addColumn("number",d+" target temperature low"),o=[!1,!0,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.target_temp_high),i=e(t.attributes.target_temp_low);a([t.lastUpdatedAsDate,r,n,i],o)}):(h.addColumn("number",d+" target temperature"),o=[!1,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.temperature);a([t.lastUpdatedAsDate,r,n],o)}),t.forEach(u)):"climate"===l?(i=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1),h.addColumn("number",d+" current temperature"),i?(h.addColumn("number",d+" target temperature high"),h.addColumn("number",d+" target temperature low"),o=[!1,!0,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.target_temp_high),i=e(t.attributes.target_temp_low);a([t.lastUpdatedAsDate,r,n,i],o)}):(h.addColumn("number",d+" target temperature"),o=[!1,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.temperature);a([t.lastUpdatedAsDate,r,n],o)}),t.forEach(u)):(h.addColumn("number",d),o="sensor"!==l&&[!0],t.forEach(function(t){var r=e(t.state);a([t.lastChangedAsDate,r],o)})),a([n].concat(r.slice(1)),!1),h.addRows(c),h}),u=1===i.length?i[0]:i.slice(1).reduce(function(e,a){return window.google.visualization.data.join(e,a,"full",[[0,0]],t(1,e.getNumberOfColumns()),t(1,a.getNumberOfColumns()))},i[0]),this.chartEngine.draw(u,a)))}})}()</script><dom-module id="state-history-charts" assetpath="components/"><template><style>:host{display:block}.loading-container{text-align:center;padding:8px}.loading{height:0;overflow:hidden}</style><google-legacy-loader on-api-load="googleApiLoaded"></google-legacy-loader><div hidden$="[[!isLoading]]" class="loading-container"><paper-spinner active="" alt="Updating history data"></paper-spinner></div><div class$="[[computeContentClasses(isLoading)]]"><template is="dom-if" if="[[computeIsEmpty(stateHistory)]]">No state history found.</template><state-history-chart-timeline data="[[groupedStateHistory.timeline]]" is-single-device="[[isSingleDevice]]"></state-history-chart-timeline><template is="dom-repeat" items="[[groupedStateHistory.line]]"><state-history-chart-line unit="[[item.unit]]" data="[[item.data]]" is-single-device="[[isSingleDevice]]"></state-history-chart-line></template></div></template></dom-module><script>Polymer({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){var i,o={},n=[];return t||!e?{line:[],timeline:[]}:(e.forEach(function(t){var e,i;t&&0!==t.size&&(e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=!!e&&e.attributes.unit_of_measurement,i?i in o?o[i].push(t.toArray()):o[i]=[t.toArray()]:n.push(t.toArray()))}),n=n.length>0&&n,i=Object.keys(o).map(function(t){return{unit:t,data:o[t]}}),{line:i,timeline:n})},googleApiLoaded:function(){window.google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){this.apiLoaded=!0}.bind(this)})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size}})</script><dom-module id="more-info-automation" assetpath="more-infos/"><template><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}</style><p>Last triggered:<ha-relative-time datetime="[[stateObj.attributes.last_triggered]]"></ha-relative-time></p><paper-button on-tap="handleTriggerTapped">TRIGGER</paper-button></template></dom-module><script>Polymer({is:"more-info-automation",properties:{hass:{type:Object},stateObj:{type:Object}},handleTriggerTapped:function(){this.hass.serviceActions.callService("automation","trigger",{entity_id:this.stateObj.entityId})}})</script><dom-module id="paper-range-slider" assetpath="../bower_components/paper-range-slider/"><template><style>:host{--paper-range-slider-width:200px;@apply(--layout);@apply(--layout-justified);@apply(--layout-center);--paper-range-slider-lower-color:var(--paper-grey-400);--paper-range-slider-active-color:var(--primary-color);--paper-range-slider-higher-color:var(--paper-grey-400);--paper-range-slider-knob-color:var(--primary-color);--paper-range-slider-pin-color:var(--primary-color);--paper-range-slider-pin-start-color:var(--paper-grey-400);--paper-range-slider-knob-start-color:transparent;--paper-range-slider-knob-start-border-color:var(--paper-grey-400)}#sliderOuterDiv_0{display:inline-block;width:var(--paper-range-slider-width)}#sliderOuterDiv_1{position:relative;height:calc(30px + var(--paper-slider-height,2px));margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}#sliderMax{--paper-slider-bar-color:transparent;--paper-slider-knob-color:var(--paper-range-slider-knob-color);--paper-slider-pin-color:var(--paper-range-slider-pin-color);--paper-slider-active-color:var(--paper-range-slider-active-color);--paper-slider-secondary-color:var(--paper-range-slider-higher-color);--paper-slider-pin-start-color:var(--paper-range-slider-pin-start-color);--paper-slider-knob-start-color:var(--paper-range-slider-knob-start-color);--paper-slider-knob-start-border-color:var(--paper-range-slider-knob-start-border-color)}#sliderMin{--paper-slider-active-color:var(--paper-range-slider-lower-color);--paper-slider-secondary-color:transparent;--paper-slider-knob-color:var(--paper-range-slider-knob-color);--paper-slider-pin-color:var(--paper-range-slider-pin-color);--paper-slider-pin-start-color:var(--paper-range-slider-pin-start-color);--paper-slider-knob-start-color:var(--paper-range-slider-knob-start-color);--paper-slider-knob-start-border-color:var(--paper-range-slider-knob-start-border-color)}</style><div id="sliderOuterDiv_0" style=""><div id="sliderOuterDiv_1"><div id="diffDiv" on-down="_diffDivDown" on-up="_diffDivUp" on-track="_diffDivOnTrack" on-transitionend="_diffDivTransEnd" style="position:absolute;width:100%;display:block;top:0"><div id="diffDivInner_0" style="line-height:200%"><br></div></div><div style="position:absolute;width:100%;display:block;top:0;pointer-events:none"><paper-slider id="sliderMax" disabled$="[[disabled]]" on-down="_sliderMaxDown" on-up="_sliderMaxUp" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMax]]" secondary-progress="[[max]]" style="width:100%"></paper-slider></div><div style="position:absolute;width:100%;display:block;top:0;pointer-events:none"><paper-slider id="sliderMin" disabled$="[[disabled]]" on-down="_sliderMinDown" on-up="_sliderMinUp" noink="" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMin]]" style="width:100%"></paper-slider></div><div id="diffDivInner_1" style="line-height:100%"><br></div></div></div></template><script>Polymer({is:"paper-range-slider",behaviors:[Polymer.IronRangeBehavior],properties:{sliderWidth:{type:String,value:"",notify:!0,reflectToAttribute:!0},style:{type:String,value:"",notify:!0,reflectToAttribute:!0},min:{type:Number,value:0,notify:!0,reflectToAttribute:!0},max:{type:Number,value:100,notify:!0,reflectToAttribute:!0},valueMin:{type:Number,value:0,notify:!0,reflectToAttribute:!0},valueMax:{type:Number,value:100,notify:!0,reflectToAttribute:!0},step:{type:Number,value:1,notify:!0,reflectToAttribute:!0},valueDiffMin:{type:Number,value:0,notify:!0,reflectToAttribute:!0},valueDiffMax:{type:Number,value:0,notify:!0,reflectToAttribute:!0},alwaysShowPin:{type:Boolean,value:!1,notify:!0},pin:{type:Boolean,value:!1,notify:!0},snaps:{type:Boolean,value:!1,notify:!0},disabled:{type:Boolean,value:!1,notify:!0},singleSlider:{type:Boolean,value:!1,notify:!0}},ready:function(){this.init(),this.setPadding();var i=this;this.$.sliderMin.addEventListener("immediate-value-change",function(t){i._setValueMinMax(i._getValuesMinMax(this.immediateValue,null)),i.$.sliderMin._expandKnob(),i.$.sliderMax._expandKnob()}),this.$.sliderMax.addEventListener("immediate-value-change",function(t){i._setValueMinMax(i._getValuesMinMax(null,this.immediateValue))}),this.$.sliderMin.addEventListener("change",function(t){i._setValueMinMax(i._getValuesMinMax(this.immediateValue,null)),i.alwaysShowPin&&i.$.sliderMin._expandKnob()}),this.$.sliderMax.addEventListener("change",function(t){i._setValueMinMax(i._getValuesMinMax(null,this.immediateValue)),i.alwaysShowPin&&i.$.sliderMax._expandKnob()})},setPadding:function(){var i=document.createElement("div");i.setAttribute("style","position:absolute; top:0px; opacity:0;"),i.innerHTML="invisibleText",document.body.insertBefore(i,document.body.children[0]);var t=i.offsetHeight/2;this.style.paddingTop=t+"px",this.style.paddingBottom=t+"px",i.remove()},_setValueDiff:function(){this._valueDiffMax=Math.max(this.valueDiffMax,0),this._valueDiffMin=Math.max(this.valueDiffMin,0)},_getValuesMinMax:function(i,t){var e=null!=i&&i>=this.min&&i<=this.max,s=null!=t&&t>=this.min&&t<=this.max;if(!e&&!s)return[this.valueMin,this.valueMax];var n=e?i:this.valueMin,a=s?t:this.valueMax;n=Math.min(Math.max(n,this.min),this.max),a=Math.min(Math.max(a,this.min),this.max);var l=a-n;return e?l<this._valueDiffMin?(a=Math.min(this.max,n+this._valueDiffMin),l=a-n,l<this._valueDiffMin&&(n=a-this._valueDiffMin)):l>this._valueDiffMax&&this._valueDiffMax>0&&(a=n+this._valueDiffMax):l<this._valueDiffMin?(n=Math.max(this.min,a-this._valueDiffMin),l=a-n,l<this._valueDiffMin&&(a=n+this._valueDiffMin)):l>this._valueDiffMax&&this._valueDiffMax>0&&(n=a-this._valueDiffMax),[n,a]},_setValueMin:function(i){i=Math.max(i,this.min),this.$.sliderMin.value=i,this.valueMin=i},_setValueMax:function(i){i=Math.min(i,this.max),this.$.sliderMax.value=i,this.valueMax=i},_setValueMinMax:function(i){this._setValueMin(i[0]),this._setValueMax(i[1]),this._updateDiffDiv(),this.updateValues()},_diffDivOnTrack:function(i){switch(i.stopPropagation(),i.detail.state){case"start":this._trackStart(i);break;case"track":this._trackX(i);break;case"end":this._trackEnd()}},_trackStart:function(i){},_trackX:function(i){this._x1_Min=this._x0_Min+i.detail.dx;var t=this._calcStep(this._getRatioPos(this.$.sliderMin,this._x1_Min/this._xWidth));this._x1_Max=this._x0_Max+i.detail.dx;var e=this._calcStep(this._getRatioPos(this.$.sliderMax,this._x1_Max/this._xWidth));t>=this.min&&e<=this.max&&(this.valueMin=t,this.valueMax=e,this._updateDiffDiv(),this.updateValues())},_trackEnd:function(){},_sliderMinDown:function(){this.$.sliderMax._expandKnob()},_sliderMaxDown:function(i){if(this.singleSlider){var t=this.$.sliderMax.querySelector("#sliderContainer").offsetWidth,e=this.$.sliderMax.querySelector("#sliderContainer").getBoundingClientRect(),s=(i.detail.x-e.left)/t,n=this.min+s*(this.max-this.min);this.setValues(null,n)}else this.$.sliderMin._expandKnob()},_sliderMinUp:function(){this.alwaysShowPin?this.$.sliderMin._expandKnob():this.$.sliderMax._resetKnob()},_sliderMaxUp:function(){this.alwaysShowPin?this.$.sliderMax._expandKnob():(this.$.sliderMin._resetKnob(),this.singleSlider&&this.$.sliderMax._resetKnob())},_diffDivDown:function(i){this._sliderMinDown(),this._sliderMaxDown(),this._xWidth=this.$.sliderMin.querySelector("#sliderBar").offsetWidth,this._x0_Min=this.$.sliderMin.ratio*this._xWidth,this._x0_Max=this.$.sliderMax.ratio*this._xWidth},_diffDivUp:function(){this._sliderMinUp(),this._sliderMaxUp()},_diffDivTransEnd:function(i){},_updateDiffDiv:function(){},_getPos:function(i){return i.ratio*Number(i.style.width.replace("px",""))},_getRatioPos:function(i,t){return(i.max-i.min)*t+i.min},init:function(){this.setSingleSlider(this.singleSlider),this.setDisabled(this.disabled),this.alwaysShowPin&&(this.pin=!0),this.$.sliderMin.pin=this.pin,this.$.sliderMax.pin=this.pin,this.$.sliderMin.snaps=this.snaps,this.$.sliderMax.snaps=this.snaps,""!=this.sliderWidth&&(this.customStyle["--paper-range-slider-width"]=this.sliderWidth,this.updateStyles()),this.$.sliderMin.querySelector("#sliderBar").querySelector("#progressContainer").style.background="transparent",this._prevUpdateValues=[this.min,this.max],this._setValueDiff(),this._setValueMinMax(this._getValuesMinMax(this.valueMin,this.valueMax)),this._updateDiffDiv(),this.alwaysShowPin&&(this.$.sliderMin._expandKnob(),this.$.sliderMax._expandKnob())},setValues:function(i,t){null!=i&&(i<this.min||i>this.max)&&(i=null),null!=t&&(t<this.min||t>this.max)&&(t=null),null!=i&&null!=t&&(i=Math.min(i,t)),this._setValueMinMax(this._getValuesMinMax(i,t))},updateValues:function(){this._prevUpdateValues[0]==this.valueMin&&this._prevUpdateValues[1]==this.valueMax||(this._prevUpdateValues=[this.valueMin,this.valueMax],this.async(function(){this.fire("updateValues")}))},setMin:function(i){this.max<i&&(this.max=i),this.min=i,this._prevUpdateValues=[this.min,this.max]},setMax:function(i){this.min>i&&(this.min=i),this.max=i,this._prevUpdateValues=[this.min,this.max]},setStep:function(i){this.step=i},setValueDiffMin:function(i){this._valueDiffMin=i},setValueDiffMax:function(i){this._valueDiffMax=i},setDisabled:function(i){this.disabled=i;var t=this.disabled?"none":"auto";this.$.sliderMax.querySelector("#sliderKnobInner").style.pointerEvents=t,this.$.sliderMin.querySelector("#sliderKnobInner").style.pointerEvents=t,this.$.sliderOuterDiv_1.style.pointerEvents=t},setSingleSlider:function(i){this.singleSlider=i,this.singleSlider?(this.$.diffDiv.style.display="none",this.$.sliderMax.style.display="",this.$.sliderMax.style.pointerEvents="auto",this.$.sliderMin.style.display="none"):(this.$.diffDiv.style.display="block",this.$.sliderMax.style.display="",this.$.sliderMax.style.pointerEvents="none",this.$.sliderMin.style.display=""),this.$.sliderMax.querySelector("#sliderContainer").style.pointerEvents=this.singleSlider?"auto":"none",this.$.sliderMin.querySelector("#sliderContainer").style.pointerEvents="none"}})</script></dom-module><dom-module id="more-info-climate" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>:host{color:var(--primary-text-color);--paper-input-container-input:{text-transform:capitalize};}.container-aux_heat,.container-away_mode,.container-fan_list,.container-humidity,.container-operation_list,.container-swing_list,.container-temperature{display:none}.has-aux_heat .container-aux_heat,.has-away_mode .container-away_mode,.has-fan_list .container-fan_list,.has-humidity .container-humidity,.has-operation_list .container-operation_list,.has-swing_list .container-swing_list,.has-temperature .container-temperature{display:block}.container-fan_list iron-icon,.container-operation_list iron-icon,.container-swing_list iron-icon{margin:22px 16px 0 0}paper-dropdown-menu{width:100%}.heat{--paper-slider-active-color:var(--paper-orange-400);--paper-slider-secondary-color:var(--paper-green-400)}.cool{--paper-slider-active-color:var(--paper-green-400);--paper-slider-secondary-color:var(--paper-blue-400)}#temp-range-slider{--paper-range-slider-lower-color:var(--paper-orange-400);--paper-range-slider-active-color:var(--paper-green-400);--paper-range-slider-higher-color:var(--paper-blue-400);--paper-range-slider-knob-color:var(--primary-color);--paper-range-slider-pin-color:var(--primary-color)}.single-row{padding:8px 0}.capitalize{text-transform:capitalize}</style><div class$="[[computeClassNames(stateObj)]]"><div class="container-temperature"><div class$="single-row, [[stateObj.attributes.operation_mode]]"><div hidden$="[[computeTargetTempHidden(stateObj)]]">Target Temperature</div><paper-slider id="temp-slider" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" secondary-progress="[[stateObj.attributes.max_temp]]" pin="" step="0.5" value="[[stateObj.attributes.temperature]]" hidden$="[[computeHideTempSlider(stateObj)]]" on-change="targetTemperatureSliderChanged"></paper-slider><paper-range-slider id="temp-range-slider" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" pin="" step="0.5" value-min="[[stateObj.attributes.target_temp_low]]" value-max="[[stateObj.attributes.target_temp_high]]" value-diff-min="2" hidden$="[[computeHideTempRangeSlider(stateObj)]]" on-change="targetTemperatureRangeSliderChanged"></paper-range-slider></div></div><div class="container-humidity"><div class="single-row"><div>Target Humidity</div><paper-slider min="[[stateObj.attributes.min_humidity]]" max="[[stateObj.attributes.max_humidity]]" step="1" pin="" value="[[stateObj.attributes.humidity]]" on-change="targetHumiditySliderChanged"></paper-slider></div></div><div class="container-operation_list"><div class="controls"><paper-dropdown-menu label-float="" label="Operation"><paper-menu class="dropdown-content" selected="{{operationIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.operation_list]]"><paper-item class="capitalize">[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div><div class="container-fan_list"><paper-dropdown-menu label-float="" label="Fan Mode"><paper-menu class="dropdown-content" selected="{{fanIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.fan_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-swing_list"><paper-dropdown-menu label-float="" label="Swing Mode"><paper-menu class="dropdown-content" selected="{{swingIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.swing_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-away_mode"><div class="center horizontal layout single-row"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="awayToggleChanged"></paper-toggle-button></div></div><div class="container-aux_heat"><div class="center horizontal layout single-row"><div class="flex">Aux Heat</div><paper-toggle-button checked="[[auxToggleChecked]]" on-change="auxToggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-climate",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},operationIndex:{type:Number,value:-1,observer:"handleOperationmodeChanged"},fanIndex:{type:Number,value:-1,observer:"handleFanmodeChanged"},swingIndex:{type:Number,value:-1,observer:"handleSwingmodeChanged"},awayToggleChecked:{type:Boolean},auxToggleChecked:{type:Boolean}},stateObjChanged:function(e){this.targetTemperatureSliderValue=e.attributes.temperature,this.awayToggleChecked="on"===e.attributes.away_mode,this.auxheatToggleChecked="on"===e.attributes.aux_heat,e.attributes.fan_list?this.fanIndex=e.attributes.fan_list.indexOf(e.attributes.fan_mode):this.fanIndex=-1,e.attributes.operation_list?this.operationIndex=e.attributes.operation_list.indexOf(e.attributes.operation_mode):this.operationIndex=-1,e.attributes.swing_list?this.swingIndex=e.attributes.swing_list.indexOf(e.attributes.swing_mode):this.swingIndex=-1,this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeTargetTempHidden:function(e){return!e.attributes.temperature&&!e.attributes.target_temp_low&&!e.attributes.target_temp_high},computeHideTempRangeSlider:function(e){return!e.attributes.target_temp_low&&!e.attributes.target_temp_high},computeHideTempSlider:function(e){return!e.attributes.temperature},computeClassNames:function(e){return"more-info-climate "+window.hassUtil.attributeClassNames(e,["away_mode","aux_heat","temperature","humidity","operation_list","fan_list","swing_list"])},targetTemperatureSliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.temperature&&this.callServiceHelper("set_temperature",{temperature:t})},targetTemperatureRangeSliderChanged:function(e){var t=e.currentTarget.valueMin,a=e.currentTarget.valueMax;t===this.stateObj.attributes.target_temp_low&&a===this.stateObj.attributes.target_temp_high||this.callServiceHelper("set_temperature",{target_temp_low:t,target_temp_high:a})},targetHumiditySliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.humidity&&this.callServiceHelper("set_humidity",{humidity:t})},awayToggleChanged:function(e){var t="on"===this.stateObj.attributes.away_mode,a=e.target.checked;t!==a&&this.callServiceHelper("set_away_mode",{away_mode:a})},auxToggleChanged:function(e){var t="on"===this.stateObj.attributes.aux_heat,a=e.target.checked;t!==a&&this.callServiceHelper("set_aux_heat",{aux_heat:a})},handleFanmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.fan_list[e],t!==this.stateObj.attributes.fan_mode&&this.callServiceHelper("set_fan_mode",{fan_mode:t}))},handleOperationmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.operation_list[e],t!==this.stateObj.attributes.operation_mode&&this.callServiceHelper("set_operation_mode",{operation_mode:t}))},handleSwingmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.swing_list[e],t!==this.stateObj.attributes.swing_mode&&this.callServiceHelper("set_swing_mode",{swing_mode:t}))},callServiceHelper:function(e,t){t.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("climate",e,t).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><dom-module id="more-info-cover" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.current_position,.current_tilt_position{max-height:0;overflow:hidden}.has-current_position .current_position,.has-current_tilt_position .current_tilt_position{max-height:90px}</style><div class$="[[computeClassNames(stateObj)]]"><div class="current_position"><div>Position</div><paper-slider min="0" max="100" value="{{coverPositionSliderValue}}" step="1" pin="" on-change="coverPositionSliderChanged"></paper-slider></div><div class="current_tilt_position"><div>Tilt position</div><paper-icon-button icon="mdi:arrow-top-right" on-tap="onOpenTiltTap" title="Open tilt" disabled="[[computeIsFullyOpenTilt(stateObj)]]"></paper-icon-button><paper-icon-button icon="mdi:stop" on-tap="onStopTiltTap" title="Stop tilt"></paper-icon-button><paper-icon-button icon="mdi:arrow-bottom-left" on-tap="onCloseTiltTap" title="Close tilt" disabled="[[computeIsFullyClosedTilt(stateObj)]]"></paper-icon-button><paper-slider min="0" max="100" value="{{coverTiltPositionSliderValue}}" step="1" pin="" on-change="coverTiltPositionSliderChanged"></paper-slider></div></div></template></dom-module><script>Polymer({is:"more-info-cover",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},coverPositionSliderValue:{type:Number},coverTiltPositionSliderValue:{type:Number}},stateObjChanged:function(t){this.coverPositionSliderValue=t.attributes.current_position,this.coverTiltPositionSliderValue=t.attributes.current_tilt_position},computeClassNames:function(t){return window.hassUtil.attributeClassNames(t,["current_position","current_tilt_position"])},coverPositionSliderChanged:function(t){this.hass.serviceActions.callService("cover","set_cover_position",{entity_id:this.stateObj.entityId,position:t.target.value})},coverTiltPositionSliderChanged:function(t){this.hass.serviceActions.callService("cover","set_cover_tilt_position",{entity_id:this.stateObj.entityId,tilt_position:t.target.value})},computeIsFullyOpenTilt:function(t){return 100===t.attributes.current_tilt_position},computeIsFullyClosedTilt:function(t){return 0===t.attributes.current_tilt_position},onOpenTiltTap:function(){this.hass.serviceActions.callService("cover","open_cover_tilt",{entity_id:this.stateObj.entityId})},onCloseTiltTap:function(){this.hass.serviceActions.callService("cover","close_cover_tilt",{entity_id:this.stateObj.entityId})},onStopTiltTap:function(){this.hass.serviceActions.callService("cover","stop_cover",{entity_id:this.stateObj.entityId})}})</script><dom-module id="more-info-default" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>.data-entry .value{max-width:200px}</style><div class="layout vertical"><template is="dom-repeat" items="[[computeDisplayAttributes(stateObj)]]" as="attribute"><div class="data-entry layout justified horizontal"><div class="key">[[formatAttribute(attribute)]]</div><div class="value">[[getAttributeValue(stateObj, attribute)]]</div></div></template></div></template></dom-module><script>!function(){"use strict";var e=["entity_picture","friendly_name","icon","unit_of_measurement","emulated_hue","emulated_hue_name","haaska_hidden","haaska_name","homebridge_hidden","homebridge_name"];Polymer({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return e.indexOf(t)===-1}):[]},formatAttribute:function(e){return e.replace(/_/g," ")},getAttributeValue:function(e,t){var r=e.attributes[t];return Array.isArray(r)?r.join(", "):r}})}()</script><dom-module id="more-info-group" assetpath="more-infos/"><template><style>.child-card{margin-bottom:8px}.child-card:last-child{margin-bottom:0}</style><div id="groupedControlDetails"></div><template is="dom-repeat" items="[[states]]" as="state"><div class="child-card"><state-card-content state-obj="[[state]]" hass="[[hass]]"></state-card-content></div></template></template></dom-module><script>Polymer({is:"more-info-group",behaviors:[window.hassBehavior],properties:{hass:{type:Object},stateObj:{type:Object},states:{type:Array,bindNuclear:function(t){return[t.moreInfoGetters.currentEntity,t.entityGetters.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}}},observers:["statesChanged(stateObj, states)"],statesChanged:function(t,e){var s,i,a,n,r=!1;if(e&&e.length>0)for(s=e[0],r=s.set("entityId",t.entityId).set("attributes",Object.assign({},s.attributes)),i=0;i<e.length;i++)a=e[i],a&&a.domain&&r.domain!==a.domain&&(r=!1);r?window.hassUtil.dynamicContentUpdater(this.$.groupedControlDetails,"MORE-INFO-"+window.hassUtil.stateMoreInfoType(r).toUpperCase(),{stateObj:r,hass:this.hass}):(n=Polymer.dom(this.$.groupedControlDetails),n.lastChild&&n.removeChild(n.lastChild))}})</script><dom-module id="more-info-sun" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><template is="dom-repeat" items="[[computeOrder(risingDate, settingDate)]]"><div class="data-entry layout justified horizontal"><div class="key"><span>[[itemCaption(item)]]</span><ha-relative-time datetime-obj="[[itemDate(item)]]"></ha-relative-time></div><div class="value">[[itemValue(item)]]</div></div></template><div class="data-entry layout justified horizontal"><div class="key">Elevation</div><div class="value">[[stateObj.attributes.elevation]]</div></div></template></dom-module><script>Polymer({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return new Date(t.attributes.next_rising)},computeSetting:function(t){return new Date(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 window.hassUtil.formatTime(this.itemDate(t))}})</script><dom-module id="more-info-configurator" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>p{margin:8px 0}p>img{max-width:100%}p.center{text-align:center}p.error{color:#C62828}p.submit{text-align:center;height:41px}paper-spinner{width:14px;height:14px;margin-right:20px}</style><div class="layout vertical"><template is="dom-if" if="[[isConfigurable]]"><p hidden$="[[!stateObj.attributes.description]]">[[stateObj.attributes.description]] <a hidden$="[[!stateObj.attributes.link_url]]" href="[[stateObj.attributes.link_url]]" target="_blank">[[stateObj.attributes.link_name]]</a></p><p class="error" hidden$="[[!stateObj.attributes.errors]]">[[stateObj.attributes.errors]]</p><p class="center" hidden$="[[!stateObj.attributes.description_image]]"><img src="[[stateObj.attributes.description_image]]"></p><template is="dom-repeat" items="[[stateObj.attributes.fields]]"><paper-input-container id="paper-input-fields-{{item.id}}"><label>[[item.name]]</label><input is="iron-input" type="[[item.type]]" id="[[item.id]]" on-change="fieldChanged"></paper-input-container></template><p class="submit"><paper-button raised="" disabled="[[isConfiguring]]" on-tap="submitClicked"><paper-spinner active="[[isConfiguring]]" hidden="[[!isConfiguring]]" alt="Configuring"></paper-spinner>[[submitCaption]]</paper-button></p></template></div></template></dom-module><script>Polymer({is:"more-info-configurator",behaviors:[window.hassBehavior],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:function(i){return i.streamGetters.isStreamingEvents}},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"},fieldInput:{type:Object,value:{}}},computeIsConfigurable:function(i){return"configure"===i.state},computeSubmitCaption:function(i){return i.attributes.submit_caption||"Set configuration"},fieldChanged:function(i){var t=i.target;this.fieldInput[t.id]=t.value},submitClicked:function(){var i={configure_id:this.stateObj.attributes.configure_id,fields:this.fieldInput};this.isConfiguring=!0,this.hass.serviceActions.callService("configurator","configure",i).then(function(){this.isConfiguring=!1,this.isStreaming||this.hass.syncActions.fetchAll()}.bind(this),function(){this.isConfiguring=!1}.bind(this))}})</script><dom-module id="more-info-thermostat" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>paper-slider{width:100%}.away-mode-toggle{display:none;margin-top:16px}.has-away_mode .away-mode-toggle{display:block}</style><div class$="[[computeClassNames(stateObj)]]"><div><div>Target Temperature</div><paper-slider min="[[tempMin]]" max="[[tempMax]]" step="0.5" value="[[targetTemperatureSliderValue]]" pin="" on-change="targetTemperatureSliderChanged"></paper-slider></div><div class="away-mode-toggle"><div class="center horizontal layout"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="toggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-thermostat",properties:{hass:{type:Object},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 window.hassUtil.attributeClassNames(t,["away_mode"])},targetTemperatureSliderChanged:function(t){this.hass.serviceActions.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:t.target.value})},toggleChanged:function(t){const 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){this.hass.serviceActions.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><dom-module id="more-info-script" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><div class="layout vertical"><div class="data-entry layout justified horizontal"><div class="key">Last Action</div><div class="value">[[stateObj.attributes.last_action]]</div></div></div></template></dom-module><script>Polymer({is:"more-info-script",properties:{stateObj:{type:Object}}})</script><dom-module id="ha-labeled-slider" assetpath="components/"><template><style>:host{display:block;padding-bottom:16px}.title{margin-bottom:16px;opacity:var(--dark-primary-opacity)}iron-icon{float:left;margin-top:4px;opacity:var(--dark-secondary-opacity)}.slider-container{margin-left:24px}</style><div class="title">[[caption]]</div><iron-icon icon="[[icon]]"></iron-icon><div class="slider-container"><paper-slider min="[[min]]" max="[[max]]" value="{{value}}"></paper-slider></div></template></dom-module><script>Polymer({is:"ha-labeled-slider",properties:{caption:{type:String},icon:{type:String},min:{type:Number},max:{type:Number},value:{type:Number,notify:!0}}})</script><dom-module id="ha-color-picker" assetpath="components/"><template><style>canvas{cursor:crosshair}</style><canvas width="[[width]]" height="[[height]]"></canvas></template></dom-module><script>Polymer({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){this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){this.mouseMoveIsThrottled=!0}.bind(this),100))},onMouseMove:function(t){this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){this.mouseMoveIsThrottled=!0}.bind(this),100))},processColorSelect:function(t){var o=this.canvas.getBoundingClientRect();t.clientX<o.left||t.clientX>=o.left+o.width||t.clientY<o.top||t.clientY>=o.top+o.height||this.onColorSelect(t.clientX-o.left,t.clientY-o.top)},onColorSelect:function(t,o){var e=this.context.getImageData(t,o,1,1).data;this.setColor({r:e[0],g:e[1],b:e[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{rgb:this.color})},ready:function(){this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.drawGradient()},drawGradient:function(){var t,o,e,i,s;this.width&&this.height||(t=getComputedStyle(this)),o=this.width||parseInt(t.width,10),e=this.height||parseInt(t.height,10),i=this.context.createLinearGradient(0,0,o,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)"),this.context.fillStyle=i,this.context.fillRect(0,0,o,e),s=this.context.createLinearGradient(0,0,0,e),s.addColorStop(0,"rgba(255,255,255,1)"),s.addColorStop(.5,"rgba(255,255,255,0)"),s.addColorStop(.5,"rgba(0,0,0,0)"),s.addColorStop(1,"rgba(0,0,0,1)"),this.context.fillStyle=s,this.context.fillRect(0,0,o,e)}})</script><dom-module id="more-info-light" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.brightness,.color_temp,.white_value{max-height:0;overflow:hidden;transition:max-height .5s ease-in}ha-color-picker{display:block;width:250px;max-height:0;overflow:hidden;transition:max-height .2s ease-in}.has-brightness .brightness,.has-color_temp .color_temp,.has-white_value .white_value{max-height:84px}.has-rgb_color ha-color-picker{max-height:200px}</style><div class$="[[computeClassNames(stateObj)]]"><div class="brightness"><ha-labeled-slider caption="Brightness" icon="mdi:brightness-5" max="255" value="{{brightnessSliderValue}}" on-change="brightnessSliderChanged"></ha-labeled-slider></div><div class="color_temp"><ha-labeled-slider caption="Color Temperature" icon="mdi:thermometer" min="154" max="500" value="{{ctSliderValue}}" on-change="ctSliderChanged"></ha-labeled-slider></div><div class="white_value"><ha-labeled-slider caption="White Value" icon="mdi:file-word-box" max="255" value="{{wvSliderValue}}" on-change="wvSliderChanged"></ha-labeled-slider></div><ha-color-picker on-colorselected="colorPicked" height="200" width="250"></ha-color-picker></div></template></dom-module><script>Polymer({is:"more-info-light",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0},ctSliderValue:{type:Number,value:0},wvSliderValue:{type:Number,value:0}},stateObjChanged:function(t){t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness,this.ctSliderValue=t.attributes.color_temp,this.wvSliderValue=t.attributes.white_value),this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(t){return window.hassUtil.attributeClassNames(t,["brightness","rgb_color","color_temp","white_value"])},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?this.hass.serviceActions.callTurnOff(this.stateObj.entityId):this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},ctSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,color_temp:e})},wvSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,white_value:e})},serviceChangeColor:function(t,e,i){t.serviceActions.callService("light","turn_on",{entity_id:e,rgb_color:[i.r,i.g,i.b]})},colorPicked:function(t){return this.skipColorPicked?void(this.colorChanged=!0):(this.color=t.detail.rgb,this.serviceChangeColor(this.hass,this.stateObj.entityId,this.color),this.colorChanged=!1,this.skipColorPicked=!0,void(this.colorDebounce=setTimeout(function(){this.colorChanged&&this.serviceChangeColor(this.hass,this.stateObj.entityId,this.color),this.skipColorPicked=!1}.bind(this),500)))}})</script><dom-module id="more-info-media_player" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.media-state{text-transform:capitalize}paper-icon-button[highlight]{color:var(--accent-color)}.volume{margin-bottom:8px;max-height:0;overflow:hidden;transition:max-height .5s ease-in}.has-volume_level .volume{max-height:40px}iron-icon.source-input{padding:7px;margin-top:15px}paper-dropdown-menu.source-input{margin-left:10px}[hidden]{display:none!important}</style><div class$="[[computeClassNames(stateObj)]]"><div class="layout horizontal"><div class="flex"><paper-icon-button icon="mdi:power" highlight$="[[isOff]]" on-tap="handleTogglePower" hidden$="[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]"></paper-icon-button></div><div><template is="dom-if" if="[[computeShowPlaybackControls(isOff, hasMediaControl)]]"><paper-icon-button icon="mdi:skip-previous" on-tap="handlePrevious" hidden$="[[!supportsPreviousTrack]]"></paper-icon-button><paper-icon-button icon="[[computePlaybackControlIcon(stateObj)]]" on-tap="handlePlaybackControl" highlight=""></paper-icon-button><paper-icon-button icon="mdi:skip-next" on-tap="handleNext" hidden$="[[!supportsNextTrack]]"></paper-icon-button></template></div></div><div class="volume_buttons center horizontal layout" hidden$="[[computeHideVolumeButtons(isOff, supportsVolumeButtons)]]"><paper-icon-button on-tap="handleVolumeTap" icon="mdi:volume-off"></paper-icon-button><paper-icon-button id="volumeDown" disabled$="[[isMuted]]" on-mousedown="handleVolumeDown" on-touchstart="handleVolumeDown" icon="mdi:volume-medium"></paper-icon-button><paper-icon-button id="volumeUp" disabled$="[[isMuted]]" on-mousedown="handleVolumeUp" on-touchstart="handleVolumeUp" icon="mdi:volume-high"></paper-icon-button></div><div class="volume center horizontal layout" hidden$="[[!supportsVolumeSet]]"><paper-icon-button on-tap="handleVolumeTap" hidden$="[[supportsVolumeButtons]]" icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button><paper-slider disabled$="[[isMuted]]" min="0" max="100" value="[[volumeSliderValue]]" on-change="volumeSliderChanged" class="flex"></paper-slider></div><div class="controls layout horizontal justified" hidden$="[[!computeHideSelectSource(isOff, supportsSelectSource)]]"><iron-icon class="source-input" icon="mdi:login-variant"></iron-icon><paper-dropdown-menu class="source-input" label-float="" label="Source"><paper-menu class="dropdown-content" selected="{{sourceIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.source_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div></template></dom-module><script>Polymer({is:"more-info-media_player",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},source:{type:String,value:""},sourceIndex:{type:Number,value:0,observer:"handleSourceChanged"},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},supportsSelectSource:{type:Boolean,value:!1},hasMediaControl:{type:Boolean,value:!1}},HAS_MEDIA_STATES:["playing","paused","unknown"],stateObjChanged:function(e){e&&(this.isOff="off"===e.state,this.isPlaying="playing"===e.state,this.hasMediaControl=this.HAS_MEDIA_STATES.indexOf(e.state)!==-1,this.volumeSliderValue=100*e.attributes.volume_level,this.isMuted=e.attributes.is_volume_muted,this.source=e.attributes.source,this.supportsPause=0!==(1&e.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&e.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&e.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&e.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&e.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&e.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&e.attributes.supported_media_commands),this.supportsVolumeButtons=0!==(1024&e.attributes.supported_media_commands),this.supportsSelectSource=0!==(2048&e.attributes.supported_media_commands),void 0!==e.attributes.source_list&&(this.sourceIndex=e.attributes.source_list.indexOf(this.source))),this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(e){return window.hassUtil.attributeClassNames(e,["volume_level"])},computeIsOff:function(e){return"off"===e.state},computeMuteVolumeIcon:function(e){return e?"mdi:volume-off":"mdi:volume-high"},computeHideVolumeButtons:function(e,t){return!t||e},computeShowPlaybackControls:function(e,t){return!e&&t},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"mdi:pause":"mdi:stop":"mdi:play"},computeHidePowerButton:function(e,t,s){return e?!t:!s},computeHideSelectSource:function(e,t){return!e&&t},computeSelectedSource:function(e){return e.attributes.source_list.indexOf(e.attributes.source)},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")},handleSourceChanged:function(e){var t;!this.stateObj||void 0===this.stateObj.attributes.source_list||e<0||e>=this.stateObj.attributes.source_list.length||(t=this.stateObj.attributes.source_list[e],t!==this.stateObj.attributes.source&&this.callService("select_source",{source:t}))},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},handleVolumeUp:function(){var e=this.$.volumeUp;this.handleVolumeWorker("volume_up",e,!0)},handleVolumeDown:function(){var e=this.$.volumeDown;this.handleVolumeWorker("volume_down",e,!0)},handleVolumeWorker:function(e,t,s){(s||void 0!==t&&t.pointerDown)&&(this.callService(e),this.async(function(){this.handleVolumeWorker(e,t,!1)}.bind(this),500))},volumeSliderChanged:function(e){var t=parseFloat(e.target.value),s=t>0?t/100:0;this.callService("volume_set",{volume_level:s})},callService:function(e,t){var s=t||{};s.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("media_player",e,s)}})</script><dom-module id="more-info-camera" assetpath="more-infos/"><template><style>:host{max-width:640px}.camera-image{width:100%}</style><img class="camera-image" src="[[computeCameraImageUrl(hass, stateObj)]]" on-load="imageLoaded" alt="[[stateObj.entityDisplay]]"></template></dom-module><script>Polymer({is:"more-info-camera",properties:{hass:{type:Object},stateObj:{type:Object}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(e,t){return e.demo?"/demo/webcam.jpg":t?"/api/camera_proxy_stream/"+t.entityId+"?token="+t.attributes.access_token:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}})</script><dom-module id="more-info-updater" assetpath="more-infos/"><template><style>.link{color:#03A9F4}</style><div><a class="link" href="https://home-assistant.io/getting-started/updating/" target="_blank">Update Instructions</a></div></template></dom-module><script>Polymer({is:"more-info-updater",properties:{stateObj:{type:Object}},computeReleaseNotes:function(t){return t.attributes.release_notes||"https://home-assistant.io/getting-started/updating/"}})</script><dom-module id="more-info-alarm_control_panel" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><div class="layout horizontal"><paper-input label="code" value="{{enteredCode}}" pattern="[[codeFormat]]" type="password" hidden$="[[!codeInputVisible]]" disabled="[[!codeInputEnabled]]"></paper-input></div><div class="layout horizontal"><paper-button on-tap="handleDisarmTap" hidden$="[[!disarmButtonVisible]]" disabled="[[!codeValid]]">Disarm</paper-button><paper-button on-tap="handleHomeTap" hidden$="[[!armHomeButtonVisible]]" disabled="[[!codeValid]]">Arm Home</paper-button><paper-button on-tap="handleAwayTap" hidden$="[[!armAwayButtonVisible]]" disabled="[[!codeValid]]">Arm Away</paper-button></div></template></dom-module><script>Polymer({is:"more-info-alarm_control_panel",properties:{hass:{type:Object},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(e,t){var a=new RegExp(t);return null===t||a.test(e)},stateObjChanged:function(e){e&&(this.codeFormat=e.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="armed_home"===e.state||"armed_away"===e.state||"disarmed"===e.state||"pending"===e.state||"triggered"===e.state,this.disarmButtonVisible="armed_home"===e.state||"armed_away"===e.state||"pending"===e.state||"triggered"===e.state,this.armHomeButtonVisible="disarmed"===e.state,this.armAwayButtonVisible="disarmed"===e.state),this.async(function(){this.fire("iron-resize")}.bind(this),500)},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})},callService:function(e,t){var a=t||{};a.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("alarm_control_panel",e,a).then(function(){this.enteredCode=""}.bind(this))}})</script><dom-module id="more-info-lock" assetpath="more-infos/"><template><style>paper-input{display:inline-block}</style><div hidden$="[[!stateObj.attributes.code_format]]"><paper-input label="code" value="{{enteredCode}}" pattern="[[stateObj.attributes.code_format]]" type="password"></paper-input><paper-button on-tap="handleUnlockTap" hidden$="[[!isLocked]]">Unlock</paper-button><paper-button on-tap="handleLockTap" hidden$="[[isLocked]]">Lock</paper-button></div></template></dom-module><script>Polymer({is:"more-info-lock",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""}},handleUnlockTap:function(){this.callService("unlock",{code:this.enteredCode})},handleLockTap:function(){this.callService("lock",{code:this.enteredCode})},stateObjChanged:function(e){e&&(this.isLocked="locked"===e.state),this.async(function(){this.fire("iron-resize")}.bind(this),500)},callService:function(e,t){var i=t||{};i.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("lock",e,i)}})</script><dom-module id="more-info-hvac" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>:host{color:var(--primary-text-color)}.container-aux_heat,.container-away_mode,.container-fan_list,.container-humidity,.container-operation_list,.container-swing_list,.container-temperature{display:none}.has-aux_heat .container-aux_heat,.has-away_mode .container-away_mode,.has-fan_list .container-fan_list,.has-humidity .container-humidity,.has-operation_list .container-operation_list,.has-swing_list .container-swing_list,.has-temperature .container-temperature{display:block}.container-fan_list iron-icon,.container-operation_list iron-icon,.container-swing_list iron-icon{margin:22px 16px 0 0}paper-dropdown-menu{width:100%}.single-row{padding:8px 0}</style><div class$="[[computeClassNames(stateObj)]]"><div class="container-temperature"><div class="single-row"><div>Target Temperature</div><paper-slider min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" step="0.5" pin="" value="[[stateObj.attributes.temperature]]" on-change="targetTemperatureSliderChanged"></paper-slider></div></div><div class="container-humidity"><div class="single-row"><div>Target Humidity</div><paper-slider min="[[stateObj.attributes.min_humidity]]" max="[[stateObj.attributes.max_humidity]]" step="1" pin="" value="[[stateObj.attributes.humidity]]" on-change="targetHumiditySliderChanged"></paper-slider></div></div><div class="container-operation_list"><div class="controls"><paper-dropdown-menu label-float="" label="Operation"><paper-menu class="dropdown-content" selected="{{operationIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.operation_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div><div class="container-fan_list"><paper-dropdown-menu label-float="" label="Fan Mode"><paper-menu class="dropdown-content" selected="{{fanIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.fan_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-swing_list"><paper-dropdown-menu label-float="" label="Swing Mode"><paper-menu class="dropdown-content" selected="{{swingIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.swing_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-away_mode"><div class="center horizontal layout single-row"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="awayToggleChanged"></paper-toggle-button></div></div><div class="container-aux_heat"><div class="center horizontal layout single-row"><div class="flex">Aux Heat</div><paper-toggle-button checked="[[auxToggleChecked]]" on-change="auxToggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-hvac",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},operationIndex:{type:Number,value:-1,observer:"handleOperationmodeChanged"},fanIndex:{type:Number,value:-1,observer:"handleFanmodeChanged"},swingIndex:{type:Number,value:-1,observer:"handleSwingmodeChanged"},awayToggleChecked:{type:Boolean},auxToggleChecked:{type:Boolean}},stateObjChanged:function(e){this.awayToggleChecked="on"===e.attributes.away_mode,this.auxheatToggleChecked="on"===e.attributes.aux_heat,e.attributes.fan_list?this.fanIndex=e.attributes.fan_list.indexOf(e.attributes.fan_mode):this.fanIndex=-1,e.attributes.operation_list?this.operationIndex=e.attributes.operation_list.indexOf(e.attributes.operation_mode):this.operationIndex=-1,e.attributes.swing_list?this.swingIndex=e.attributes.swing_list.indexOf(e.attributes.swing_mode):this.swingIndex=-1,this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(e){return"more-info-hvac "+window.hassUtil.attributeClassNames(e,["away_mode","aux_heat","temperature","humidity","operation_list","fan_list","swing_list"])},targetTemperatureSliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.temperature&&this.callServiceHelper("set_temperature",{temperature:t})},targetHumiditySliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.humidity&&this.callServiceHelper("set_humidity",{humidity:t})},awayToggleChanged:function(e){var t="on"===this.stateObj.attributes.away_mode,a=e.target.checked;t!==a&&this.callServiceHelper("set_away_mode",{away_mode:a})},auxToggleChanged:function(e){var t="on"===this.stateObj.attributes.aux_heat,a=e.target.checked;t!==a&&this.callServiceHelper("set_aux_heat",{aux_heat:a})},handleFanmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.fan_list[e],t!==this.stateObj.attributes.fan_mode&&this.callServiceHelper("set_fan_mode",{fan_mode:t}))},handleOperationmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.operation_list[e],t!==this.stateObj.attributes.operation_mode&&this.callServiceHelper("set_operation_mode",{operation_mode:t}))},handleSwingmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.swing_list[e],t!==this.stateObj.attributes.swing_mode&&this.callServiceHelper("set_swing_mode",{swing_mode:t}))},callServiceHelper:function(e,t){t.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("hvac",e,t).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><script>Polymer({is:"more-info-content",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"}},created:function(){this.style.display="block"},stateObjChanged:function(t){t&&window.hassUtil.dynamicContentUpdater(this,"MORE-INFO-"+window.hassUtil.stateMoreInfoType(t).toUpperCase(),{hass:this.hass,stateObj:t})}})</script><dom-module id="more-info-dialog" assetpath="dialogs/"><template><style>paper-dialog{font-size:14px;width:365px}paper-dialog[data-domain=camera]{width:auto}state-history-charts{position:relative;z-index:1;max-width:365px}state-card-content{margin-bottom:24px;font-size:14px}@media all and (max-width:450px),all and (max-height:500px){paper-dialog{margin:0;width:100%;max-height:calc(100% - 64px);position:fixed!important;bottom:0;left:0;right:0;overflow:scroll}}</style><paper-dialog id="dialog" with-backdrop="" opened="{{dialogOpen}}" data-domain$="[[stateObj.domain]]"><h2><state-card-content state-obj="[[stateObj]]" hass="[[hass]]" in-dialog=""></state-card-content></h2><template is="dom-if" if="[[showHistoryComponent]]"><state-history-charts state-history="[[stateHistory]]" is-loading-data="[[isLoadingHistoryData]]"></state-history-charts></template><paper-dialog-scrollable id="scrollable"><more-info-content state-obj="[[stateObj]]" hass="[[hass]]"></more-info-content></paper-dialog-scrollable></paper-dialog></template></dom-module><script>Polymer({is:"more-info-dialog",behaviors:[window.hassBehavior],properties:{hass:{type:Object},stateObj:{type:Object,bindNuclear:function(t){return t.moreInfoGetters.currentEntity},observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:function(t){return[t.moreInfoGetters.currentEntityHistory,function(t){return!!t&&[t]}]}},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData)"},isLoadingEntityHistoryData:{type:Boolean,bindNuclear:function(t){return t.entityHistoryGetters.isLoadingEntityHistory}},hasHistoryComponent:{type:Boolean,bindNuclear:function(t){return t.configGetters.isComponentLoaded("history")},observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:function(t){return t.moreInfoGetters.isCurrentEntityHistoryStale},observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1,computed:"computeShowHistoryComponent(hasHistoryComponent, stateObj)"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},delayedDialogOpen:{type:Boolean,value:!1}},ready:function(){this.$.scrollable.dialogElement=this.$.dialog},computeIsLoadingHistoryData:function(t,e){return!t||e},computeShowHistoryComponent:function(t,e){return this.hasHistoryComponent&&e&&window.hassUtil.DOMAINS_WITH_NO_HISTORY.indexOf(e.domain)===-1},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&this.hass.entityHistoryActions.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){return t?void this.async(function(){this.fetchHistoryData(),this.dialogOpen=!0}.bind(this),10):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){t?this.async(function(){this.delayedDialogOpen=!0}.bind(this),10):!t&&this.stateObj&&(this.async(function(){this.hass.moreInfoActions.deselectEntity()}.bind(this),10),this.delayedDialogOpen=!1)}})</script><dom-module id="ha-voice-command-dialog" assetpath="dialogs/"><template><style>iron-icon{margin-right:8px}.content{width:300px;min-height:80px;font-size:18px}.icon{float:left}.text{margin-left:48px;margin-right:24px}.interimTranscript{color:#a9a9a9}@media all and (max-width:450px){paper-dialog{margin:0;width:100%;max-height:calc(100% - 64px);position:fixed!important;bottom:0;left:0;right:0;overflow:scroll}}</style><paper-dialog id="dialog" with-backdrop="" opened="{{dialogOpen}}"><div class="content"><div class="icon"><iron-icon icon="mdi:text-to-speech" hidden$="[[isTransmitting]]"></iron-icon><paper-spinner active$="[[isTransmitting]]" hidden$="[[!isTransmitting]]"></paper-spinner></div><div class="text"><span>{{finalTranscript}}</span> <span class="interimTranscript">[[interimTranscript]]</span> …</div></div></paper-dialog></template></dom-module><script>Polymer({is:"ha-voice-command-dialog",behaviors:[window.hassBehavior],properties:{hass:{type:Object},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},finalTranscript:{type:String,bindNuclear:function(e){return e.voiceGetters.finalTranscript}},interimTranscript:{type:String,bindNuclear:function(e){return e.voiceGetters.extraInterimTranscript}},isTransmitting:{type:Boolean,bindNuclear:function(e){return e.voiceGetters.isTransmitting}},isListening:{type:Boolean,bindNuclear:function(e){return e.voiceGetters.isListening}},showListenInterface:{type:Boolean,computed:"computeShowListenInterface(isListening, isTransmitting)",observer:"showListenInterfaceChanged"}},computeShowListenInterface:function(e,n){return e||n},dialogOpenChanged:function(e){!e&&this.isListening&&this.hass.voiceActions.stop()},showListenInterfaceChanged:function(e){!e&&this.dialogOpen?this.dialogOpen=!1:e&&(this.dialogOpen=!0)}})</script><dom-module id="paper-icon-item" assetpath="../bower_components/paper-item/"><template><style include="paper-item-shared-styles"></style><style>:host{@apply(--layout-horizontal);@apply(--layout-center);@apply(--paper-font-subhead);@apply(--paper-item);@apply(--paper-icon-item)}.content-icon{@apply(--layout-horizontal);@apply(--layout-center);width:var(--paper-item-icon-width,56px);@apply(--paper-item-icon)}</style><div id="contentIcon" class="content-icon"><content select="[item-icon]"></content></div><content></content></template><script>Polymer({is:"paper-icon-item",behaviors:[Polymer.PaperItemBehavior]})</script></dom-module><dom-module id="stream-status" assetpath="components/"><template><style>:host{display:inline-block;height:24px}paper-toggle-button{vertical-align:middle}iron-icon{opacity:var(--dark-primary-opacity)}[hidden]{display:none!important}</style><iron-icon icon="mdi:alert" hidden$="[[!hasError]]"></iron-icon><paper-toggle-button id="toggle" on-change="toggleChanged" checked$="[[isStreaming]]" hidden$="[[hasError]]"></paper-toggle-button></template></dom-module><script>Polymer({is:"stream-status",behaviors:[window.hassBehavior],properties:{hass:{type:Object},isStreaming:{type:Boolean,bindNuclear:function(t){return t.streamGetters.isStreamingEvents}},hasError:{type:Boolean,bindNuclear:function(t){return t.streamGetters.hasStreamingEventsError}}},toggleChanged:function(){this.isStreaming?this.hass.streamActions.stop():this.hass.streamActions.start()}})</script><dom-module id="ha-sidebar" assetpath="components/"><template><style include="iron-flex iron-flex-alignment iron-positioning">:host{--sidebar-text:{opacity:var(--dark-primary-opacity);font-weight:500;font-size:14px};display:block;overflow:auto;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none}app-toolbar{font-weight:400;opacity:var(--dark-primary-opacity);border-bottom:1px solid #e0e0e0}paper-menu{padding-bottom:0}paper-icon-item{--paper-icon-item:{cursor:pointer};--paper-item-icon:{color:#000;opacity:var(--dark-secondary-opacity)};--paper-item-selected:{color:var(--default-primary-color);background-color:#e8e8e8;opacity:1};}paper-icon-item.iron-selected{--paper-item-icon:{color:var(--default-primary-color);opacity:1};}paper-icon-item .item-text{@apply(--sidebar-text)}paper-icon-item.iron-selected .item-text{opacity:1}paper-icon-item.logout{margin-top:16px}.divider{height:1px;background-color:#000;margin:4px 0;opacity:var(--dark-divider-opacity)}.setting{@apply(--sidebar-text)}.subheader{@apply(--sidebar-text);padding:16px}.dev-tools{padding:0 8px;opacity:var(--dark-secondary-opacity)}</style><app-toolbar><div main-title="">Home Assistant</div><paper-icon-button icon="mdi:chevron-left" hidden$="[[narrow]]" on-tap="toggleMenu"></paper-icon-button></app-toolbar><paper-menu attr-for-selected="data-panel" selected="[[selected]]" on-iron-select="menuSelect"><paper-icon-item on-tap="menuClicked" data-panel="states"><iron-icon item-icon="" icon="mdi:apps"></iron-icon><span class="item-text">States</span></paper-icon-item><template is="dom-repeat" items="[[computePanels(panels)]]"><paper-icon-item on-tap="menuClicked" data-panel$="[[item.url_path]]"><iron-icon item-icon="" icon="[[item.icon]]"></iron-icon><span class="item-text">[[item.title]]</span></paper-icon-item></template><paper-icon-item on-tap="menuClicked" data-panel="logout" class="logout"><iron-icon item-icon="" icon="mdi:exit-to-app"></iron-icon><span class="item-text">Log Out</span></paper-icon-item></paper-menu><div><div class="divider"></div><template is="dom-if" if="[[supportPush]]"><paper-item class="horizontal layout justified"><div class="setting">Push Notifications</div><paper-toggle-button on-change="handlePushChange" checked="{{pushToggleChecked}}"></paper-toggle-button></paper-item></template><paper-item class="horizontal layout justified"><div class="setting">Streaming updates</div><stream-status hass="[[hass]]"></stream-status></paper-item><div class="divider"></div><div class="subheader">Developer Tools</div><div class="dev-tools layout horizontal justified"><paper-icon-button icon="mdi:remote" data-panel="dev-service" alt="Services" title="Services" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:code-tags" data-panel="dev-state" alt="States" title="States" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:radio-tower" data-panel="dev-event" alt="Events" title="Events" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:file-xml" data-panel="dev-template" alt="Templates" title="Templates" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:information-outline" data-panel="dev-info" alt="Info" title="Info" on-tap="menuClicked"></paper-icon-button></div></div></template></dom-module><script>Polymer({is:"ha-sidebar",behaviors:[window.hassBehavior],properties:{hass:{type:Object},menuShown:{type:Boolean},menuSelected:{type:String},narrow:{type:Boolean},selected:{type:String,bindNuclear:function(t){return t.navigationGetters.activePanelName}},panels:{type:Array,bindNuclear:function(t){return[t.navigationGetters.panels,function(t){return t.toJS()}]}},supportPush:{type:Boolean,value:!1,bindNuclear:function(t){return t.pushNotificationGetters.isSupported}},pushToggleChecked:{type:Boolean,bindNuclear:function(t){return t.pushNotificationGetters.isActive}}},created:function(){this._boundUpdateStyles=this.updateStyles.bind(this)},computePanels:function(t){var e={map:1,logbook:2,history:3},n=[];return Object.keys(t).forEach(function(e){t[e].title&&n.push(t[e])}),n.sort(function(t,n){var i=t.component_name in e,o=n.component_name in e;return i&&o?e[t.component_name]-e[n.component_name]:i?-1:o?1:t.title>n.title?1:t.title<n.title?-1:0}),n},menuSelect:function(){this.debounce("updateStyles",this._boundUpdateStyles,1)},menuClicked:function(t){for(var e=t.target,n=5,i=e.getAttribute("data-panel");n&&!i;)e=e.parentElement,i=e.getAttribute("data-panel"),n--;n&&this.selectPanel(i)},toggleMenu:function(){this.fire("close-menu")},selectPanel:function(t){if(t!==this.selected){if("logout"===t)return void this.handleLogOut();this.hass.navigationActions.navigate.apply(null,t.split("/")),this.debounce("updateStyles",this._boundUpdateStyles,1)}},handlePushChange:function(t){t.target.checked?this.hass.pushNotificationActions.subscribePushNotifications().then(function(t){this.pushToggleChecked=t}.bind(this)):this.hass.pushNotificationActions.unsubscribePushNotifications().then(function(t){this.pushToggleChecked=!t}.bind(this))},handleLogOut:function(){this.hass.authActions.logOut()}})</script><dom-module id="home-assistant-main" assetpath="layouts/"><template><notification-manager hass="[[hass]]"></notification-manager><more-info-dialog hass="[[hass]]"></more-info-dialog><ha-voice-command-dialog hass="[[hass]]"></ha-voice-command-dialog><iron-media-query query="(max-width: 870px)" query-matches="{{narrow}}"></iron-media-query><paper-drawer-panel id="drawer" force-narrow="[[computeForceNarrow(narrow, showSidebar)]]" responsive-width="0" disable-swipe="[[isSelectedMap]]" disable-edge-swipe="[[isSelectedMap]]"><ha-sidebar drawer="" narrow="[[narrow]]" hass="[[hass]]"></ha-sidebar><iron-pages main="" attr-for-selected="id" fallback-selection="panel-resolver" selected="[[activePanel]]" selected-attribute="panel-visible"><partial-cards id="states" narrow="[[narrow]]" hass="[[hass]]" show-menu="[[showSidebar]]"></partial-cards><partial-panel-resolver id="panel-resolver" narrow="[[narrow]]" hass="[[hass]]" show-menu="[[showSidebar]]"></partial-panel-resolver></iron-pages></paper-drawer-panel></template></dom-module><script>Polymer({is:"home-assistant-main",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!0},activePanel:{type:String,bindNuclear:function(e){return e.navigationGetters.activePanelName},observer:"activePanelChanged"},showSidebar:{type:Boolean,value:!1,bindNuclear:function(e){return e.navigationGetters.showSidebar}}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():this.hass.navigationActions.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&this.hass.navigationActions.showSidebar(!1)},activePanelChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){window.removeInitMsg(),this.hass.startUrlSync()},computeForceNarrow:function(e,n){return e||!n},detached:function(){this.hass.stopUrlSync()}})</script></div><dom-module id="home-assistant"><template><template is="dom-if" if="[[loaded]]"><home-assistant-main hass="[[hass]]"></home-assistant-main></template><template is="dom-if" if="[[!loaded]]"><login-form hass="[[hass]]" force-show-loading="[[computeForceShowLoading(dataLoaded, iconsLoaded)]]"></login-form></template></template></dom-module><script>Polymer({is:"home-assistant",hostAttributes:{icons:null},behaviors:[window.hassBehavior],properties:{hass:{type:Object,value:window.hass},icons:{type:String},dataLoaded:{type:Boolean,bindNuclear:function(o){return o.syncGetters.isDataLoaded}},iconsLoaded:{type:Boolean,value:!1},loaded:{type:Boolean,computed:"computeLoaded(dataLoaded, iconsLoaded)"}},computeLoaded:function(o,t){return o&&t},computeForceShowLoading:function(o,t){return o&&!t},loadIcons:function(){var o=function(){this.iconsLoaded=!0}.bind(this);this.importHref("/static/mdi-"+this.icons+".html",o,function(){this.importHref("/static/mdi.html",o,o)})},ready:function(){this.loadIcons()}})</script></body></html> \ No newline at end of file +this.currentTarget=t,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},i=window.Element.prototype.animate;window.Element.prototype.animate=function(n,r){var o=i.call(this,n,r);o._cancelHandlers=[],o.oncancel=null;var a=o.cancel;o.cancel=function(){a.call(this);var i=new e(this,null,t()),n=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){n.forEach(function(t){t.call(i.target,i)})},0)};var s=o.addEventListener;o.addEventListener=function(t,e){"function"==typeof e&&"cancel"==t?this._cancelHandlers.push(e):s.call(this,t,e)};var u=o.removeEventListener;return o.removeEventListener=function(t,e){if("cancel"==t){var i=this._cancelHandlers.indexOf(e);i>=0&&this._cancelHandlers.splice(i,1)}else u.call(this,t,e)},o}}}(),function(t){var e=document.documentElement,i=null,n=!1;try{var r=getComputedStyle(e).getPropertyValue("opacity"),o="0"==r?"1":"0";i=e.animate({opacity:[o,o]},{duration:1}),i.currentTime=0,n=getComputedStyle(e).getPropertyValue("opacity")==o}catch(t){}finally{i&&i.cancel()}if(!n){var a=window.Element.prototype.animate;window.Element.prototype.animate=function(e,i){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&e[Symbol.iterator]&&(e=Array.from(e)),Array.isArray(e)||null===e||(e=t.convertToArrayForm(e)),a.call(this,e,i)}}}(c),!function(t,e,i){function n(t){var i=e.timeline;i.currentTime=t,i._discardAnimations(),0==i._animations.length?o=!1:requestAnimationFrame(n)}var r=window.requestAnimationFrame;window.requestAnimationFrame=function(t){return r(function(i){e.timeline._updateAnimationsPromises(),t(i),e.timeline._updateAnimationsPromises()})},e.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},e.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){e.animationsWithPromises=e.animationsWithPromises.filter(function(t){return t._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(t){return"finished"!=t.playState&&"idle"!=t.playState})},_play:function(t){var i=new e.Animation(t,this);return this._animations.push(i),e.restartWebAnimationsNextTick(),i._updatePromises(),i._animation.play(),i._updatePromises(),i},play:function(t){return t&&t.remove(),this._play(t)}};var o=!1;e.restartWebAnimationsNextTick=function(){o||(o=!0,requestAnimationFrame(n))};var a=new e.AnimationTimeline;e.timeline=a;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return a}})}catch(t){}try{window.document.timeline=a}catch(t){}}(c,e,f),function(t,e,i){e.animationsWithPromises=[],e.Animation=function(e,i){if(this.id="",e&&e._id&&(this.id=e._id),this.effect=e,e&&(e._animation=this),!i)throw new Error("Animation with null timeline is not supported");this._timeline=i,this._sequenceNumber=t.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},e.Animation.prototype={_updatePromises:function(){var t=this._oldPlayState,e=this.playState;return this._readyPromise&&e!==t&&("idle"==e?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==t?this._resolveReadyPromise():"pending"==e&&(this._readyPromise=void 0)),this._finishedPromise&&e!==t&&("idle"==e?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==e?this._resolveFinishedPromise():"finished"==t&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var t,i,n,r,o=!!this._animation;o&&(t=this.playbackRate,i=this._paused,n=this.startTime,r=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=e.newUnderlyingAnimationForKeyframeEffect(this.effect),e.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=e.newUnderlyingAnimationForGroup(this.effect),e.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&e.bindAnimationForCustomEffect(this),o&&(1!=t&&(this.playbackRate=t),null!==n?this.startTime=n:null!==r?this.currentTime=r:null!==this._holdTime&&(this.currentTime=this._holdTime),i&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var t=this.effect._timing.delay;this._childAnimations.forEach(function(i){this._arrangeChildren(i,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i.effect))}.bind(this))}},_setExternalAnimation:function(t){if(this.effect&&this._isGroup)for(var e=0;e<this.effect.children.length;e++)this.effect.children[e]._animation=t,this._childAnimations[e]._setExternalAnimation(t)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var t=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(i){var n=e.timeline._play(i);this._childAnimations.push(n),n.playbackRate=this.playbackRate,this._paused&&n.pause(),i._animation=this.effect._animation,this._arrangeChildren(n,t),this.effect instanceof window.SequenceEffect&&(t+=e.groupChildDuration(i))}.bind(this))}},_arrangeChildren:function(t,e){null===this.startTime?t.currentTime=this.currentTime-e/this.playbackRate:t.startTime!==this.startTime+e/this.playbackRate&&(t.startTime=this.startTime+e/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(e.animationsWithPromises.indexOf(this)==-1&&e.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(t,e){this._resolveFinishedPromise=function(){t(this)},this._rejectFinishedPromise=function(){e({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(e.animationsWithPromises.indexOf(this)==-1&&e.animationsWithPromises.push(this),this._readyPromise=new Promise(function(t,e){this._resolveReadyPromise=function(){t(this)},this._rejectReadyPromise=function(){e({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._animation.onfinish},set onfinish(t){"function"==typeof t?this._animation.onfinish=function(e){e.target=this,t.call(this,e)}.bind(this):this._animation.onfinish=t},get oncancel(){return this._animation.oncancel},set oncancel(t){"function"==typeof t?this._animation.oncancel=function(e){e.target=this,t.call(this,e)}.bind(this):this._animation.oncancel=t},get currentTime(){this._updatePromises();var t=this._animation.currentTime;return this._updatePromises(),t},set currentTime(t){this._updatePromises(),this._animation.currentTime=isFinite(t)?t:Math.sign(t)*Number.MAX_VALUE,this._register(),this._forEachChild(function(e,i){e.currentTime=t-i}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(t){this._updatePromises(),this._animation.startTime=isFinite(t)?t:Math.sign(t)*Number.MAX_VALUE,this._register(),this._forEachChild(function(e,i){e.startTime=t+i}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(t){this._updatePromises();var e=this.currentTime;this._animation.playbackRate=t,this._forEachChild(function(e){e.playbackRate=t}),null!==e&&(this.currentTime=e),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),this._timeline._animations.indexOf(this)==-1&&this._timeline._animations.push(this),this._register(),e.awaitStartTime(this),this._forEachChild(function(t){var e=t.currentTime;t.play(),t.currentTime=e}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(t){t.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var t=this.currentTime;this._animation.reverse(),this._forEachChild(function(t){t.reverse()}),null!==t&&(this.currentTime=t),this._updatePromises()},addEventListener:function(t,e){var i=e;"function"==typeof e&&(i=function(t){t.target=this,e.call(this,t)}.bind(this),e._wrapper=i),this._animation.addEventListener(t,i)},removeEventListener:function(t,e){this._animation.removeEventListener(t,e&&e._wrapper||e)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(e){var i=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(t){e.call(this,t,i),this.effect instanceof window.SequenceEffect&&(i+=t.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var n=this.effect._timing,r=this.currentTime;null!==r&&(r=t.calculateIterationProgress(t.calculateActiveDuration(n),r,n)),(null==r||isNaN(r))&&this._removeChildAnimations()}}},window.Animation=e.Animation}(c,e,f),function(t,e,i){function n(e){this._frames=t.normalizeKeyframes(e)}function r(){for(var t=!1;u.length;){var e=u.shift();e._updateChildren(),t=!0}return t}var o=function(t){if(t._animation=void 0,t instanceof window.SequenceEffect||t instanceof window.GroupEffect)for(var e=0;e<t.children.length;e++)o(t.children[e])};e.removeMulti=function(t){for(var e=[],i=0;i<t.length;i++){var n=t[i];n._parent?(e.indexOf(n._parent)==-1&&e.push(n._parent),n._parent.children.splice(n._parent.children.indexOf(n),1),n._parent=null,o(n)):n._animation&&n._animation.effect==n&&(n._animation.cancel(),n._animation.effect=new KeyframeEffect(null,[]),n._animation._callback&&(n._animation._callback._animation=null),n._animation._rebuildUnderlyingAnimation(),o(n))}for(i=0;i<e.length;i++)e[i]._rebuild()},e.KeyframeEffect=function(e,i,r,o){return this.target=e,this._parent=null,r=t.numericTimingToObject(r),this._timingInput=t.cloneTimingInput(r),this._timing=t.normalizeTimingInput(r),this.timing=t.makeTiming(r,!1,this),this.timing._effect=this,"function"==typeof i?(t.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=i):this._normalizedKeyframes=new n(i),this._keyframes=i,this.activeDuration=t.calculateActiveDuration(this._timing),this._id=o,this},e.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(t){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=t,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var e=new KeyframeEffect(this.target,[],t.cloneTimingInput(this._timingInput),this._id);return e._normalizedKeyframes=this._normalizedKeyframes,e._keyframes=this._keyframes,e},remove:function(){e.removeMulti([this])}};var a=Element.prototype.animate;Element.prototype.animate=function(t,i){var n="";return i&&i.id&&(n=i.id),e.timeline._play(new e.KeyframeEffect(this,t,i,n))};var s=document.createElementNS("http://www.w3.org/1999/xhtml","div");e.newUnderlyingAnimationForKeyframeEffect=function(t){if(t){var e=t.target||s,i=t._keyframes;"function"==typeof i&&(i=[]);var n=t._timingInput;n.id=t._id}else var e=s,i=[],n=0;return a.apply(e,[i,n])},e.bindAnimationForKeyframeEffect=function(t){t.effect&&"function"==typeof t.effect._normalizedKeyframes&&e.bindAnimationForCustomEffect(t)};var u=[];e.awaitStartTime=function(t){null===t.startTime&&t._isGroup&&(0==u.length&&requestAnimationFrame(r),u.push(t))};var c=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){e.timeline._updateAnimationsPromises();var t=c.apply(this,arguments);return r()&&(t=c.apply(this,arguments)),e.timeline._updateAnimationsPromises(),t}}),window.KeyframeEffect=e.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(t){return null!==t.effect&&t.effect.target==this}.bind(this))}}(c,e,f),function(t,e,i){function n(t){t._registered||(t._registered=!0,a.push(t),s||(s=!0,requestAnimationFrame(r)))}function r(t){var e=a;a=[],e.sort(function(t,e){return t._sequenceNumber-e._sequenceNumber}),e=e.filter(function(t){t();var e=t._animation?t._animation.playState:"idle";return"running"!=e&&"pending"!=e&&(t._registered=!1),t._registered}),a.push.apply(a,e),a.length?(s=!0,requestAnimationFrame(r)):s=!1}var o=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);e.bindAnimationForCustomEffect=function(e){var i,r=e.effect.target,a="function"==typeof e.effect.getFrames();i=a?e.effect.getFrames():e.effect._onsample;var s=e.effect.timing,u=null;s=t.normalizeTimingInput(s);var c=function(){var n=c._animation?c._animation.currentTime:null;null!==n&&(n=t.calculateIterationProgress(t.calculateActiveDuration(s),n,s),isNaN(n)&&(n=null)),n!==u&&(a?i(n,r,e.effect):i(n,e.effect,e.effect._animation)),u=n};c._animation=e,c._registered=!1,c._sequenceNumber=o++,e._callback=c,n(c)};var a=[],s=!1;e.Animation.prototype._register=function(){this._callback&&n(this._callback)}}(c,e,f),function(t,e,i){function n(t){return t._timing.delay+t.activeDuration+t._timing.endDelay}function r(e,i,n){this._id=n,this._parent=null,this.children=e||[],this._reparent(this.children),i=t.numericTimingToObject(i),this._timingInput=t.cloneTimingInput(i),this._timing=t.normalizeTimingInput(i,!0),this.timing=t.makeTiming(i,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){r.apply(this,arguments)},window.GroupEffect=function(){r.apply(this,arguments)},r.prototype={_isAncestor:function(t){for(var e=this;null!==e;){if(e==t)return!0;e=e._parent}return!1},_rebuild:function(){for(var t=this;t;)"auto"===t.timing.duration&&(t._timing.duration=t.activeDuration),t=t._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(t){e.removeMulti(t);for(var i=0;i<t.length;i++)t[i]._parent=this},_putChild:function(t,e){for(var i=e?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",n=0;n<t.length;n++)if(this._isAncestor(t[n]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:i};for(var n=0;n<t.length;n++)e?this.children.push(t[n]):this.children.unshift(t[n]);this._reparent(t),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var e=t.cloneTimingInput(this._timingInput),i=[],n=0;n<this.children.length;n++)i.push(this.children[n].clone());return this instanceof GroupEffect?new GroupEffect(i,e):new SequenceEffect(i,e)},remove:function(){e.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(r.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var t=0;return this.children.forEach(function(e){t+=n(e)}),Math.max(t,0)}}),window.GroupEffect.prototype=Object.create(r.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var t=0;return this.children.forEach(function(e){t=Math.max(t,n(e))}),t}}),e.newUnderlyingAnimationForGroup=function(i){var n,r=null,o=function(e){var i=n._wrapper;if(i&&"pending"!=i.playState&&i.effect)return null==e?void i._removeChildAnimations():0==e&&i.playbackRate<0&&(r||(r=t.normalizeTimingInput(i.effect.timing)),e=t.calculateIterationProgress(t.calculateActiveDuration(r),-1,r),isNaN(e)||null==e)?(i._forEachChild(function(t){t.currentTime=-1}),void i._removeChildAnimations()):void 0},a=new KeyframeEffect(null,[],i._timing,i._id);return a.onsample=o,n=e.timeline._play(a)},e.bindAnimationForGroup=function(t){t._animation._wrapper=t,t._isGroup=!0,e.awaitStartTime(t),t._constructChildAnimations(),t._setExternalAnimation(t)},e.groupChildDuration=n}(c,e,f),b.true=a}({},function(){return this}())</script><script>Polymer({is:"opaque-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node;return this._effect=new KeyframeEffect(i,[{opacity:"1"},{opacity:"1"}],this.timingFromConfig(e)),i.style.opacity="0",this._effect},complete:function(e){e.node.style.opacity=""}})</script><script>!function(){"use strict";var e={pageX:0,pageY:0},t=null,l=[];Polymer.IronDropdownScrollManager={get currentLockingElement(){return this._lockingElements[this._lockingElements.length-1]},elementIsScrollLocked:function(e){var t=this.currentLockingElement;if(void 0===t)return!1;var l;return!!this._hasCachedLockedElement(e)||!this._hasCachedUnlockedElement(e)&&(l=!!t&&t!==e&&!this._composedTreeContains(t,e),l?this._lockedElementCache.push(e):this._unlockedElementCache.push(e),l)},pushScrollLock:function(e){this._lockingElements.indexOf(e)>=0||(0===this._lockingElements.length&&this._lockScrollInteractions(),this._lockingElements.push(e),this._lockedElementCache=[],this._unlockedElementCache=[])},removeScrollLock:function(e){var t=this._lockingElements.indexOf(e);t!==-1&&(this._lockingElements.splice(t,1),this._lockedElementCache=[],this._unlockedElementCache=[],0===this._lockingElements.length&&this._unlockScrollInteractions())},_lockingElements:[],_lockedElementCache:null,_unlockedElementCache:null,_hasCachedLockedElement:function(e){return this._lockedElementCache.indexOf(e)>-1},_hasCachedUnlockedElement:function(e){return this._unlockedElementCache.indexOf(e)>-1},_composedTreeContains:function(e,t){var l,n,o,r;if(e.contains(t))return!0;for(l=Polymer.dom(e).querySelectorAll("content"),o=0;o<l.length;++o)for(n=Polymer.dom(l[o]).getDistributedNodes(),r=0;r<n.length;++r)if(this._composedTreeContains(n[r],t))return!0;return!1},_scrollInteractionHandler:function(t){if(t.cancelable&&this._shouldPreventScrolling(t)&&t.preventDefault(),t.targetTouches){var l=t.targetTouches[0];e.pageX=l.pageX,e.pageY=l.pageY}},_lockScrollInteractions:function(){this._boundScrollHandler=this._boundScrollHandler||this._scrollInteractionHandler.bind(this),document.addEventListener("wheel",this._boundScrollHandler,!0),document.addEventListener("mousewheel",this._boundScrollHandler,!0),document.addEventListener("DOMMouseScroll",this._boundScrollHandler,!0),document.addEventListener("touchstart",this._boundScrollHandler,!0),document.addEventListener("touchmove",this._boundScrollHandler,!0)},_unlockScrollInteractions:function(){document.removeEventListener("wheel",this._boundScrollHandler,!0),document.removeEventListener("mousewheel",this._boundScrollHandler,!0),document.removeEventListener("DOMMouseScroll",this._boundScrollHandler,!0),document.removeEventListener("touchstart",this._boundScrollHandler,!0),document.removeEventListener("touchmove",this._boundScrollHandler,!0)},_shouldPreventScrolling:function(e){var n=Polymer.dom(e).rootTarget;if("touchmove"!==e.type&&t!==n&&(t=n,l=this._getScrollableNodes(Polymer.dom(e).path)),!l.length)return!0;if("touchstart"===e.type)return!1;var o=this._getScrollInfo(e);return!this._getScrollingNode(l,o.deltaX,o.deltaY)},_getScrollableNodes:function(e){for(var t=[],l=e.indexOf(this.currentLockingElement),n=0;n<=l;n++){var o=e[n];if(11!==o.nodeType){var r=o.style;"scroll"!==r.overflow&&"auto"!==r.overflow&&(r=window.getComputedStyle(o)),"scroll"!==r.overflow&&"auto"!==r.overflow||t.push(o)}}return t},_getScrollingNode:function(e,t,l){if(t||l)for(var n=Math.abs(l)>=Math.abs(t),o=0;o<e.length;o++){var r=e[o],c=!1;if(c=n?l<0?r.scrollTop>0:r.scrollTop<r.scrollHeight-r.clientHeight:t<0?r.scrollLeft>0:r.scrollLeft<r.scrollWidth-r.clientWidth)return r}},_getScrollInfo:function(t){var l={deltaX:t.deltaX,deltaY:t.deltaY};if("deltaX"in t);else if("wheelDeltaX"in t)l.deltaX=-t.wheelDeltaX,l.deltaY=-t.wheelDeltaY;else if("axis"in t)l.deltaX=1===t.axis?t.detail:0,l.deltaY=2===t.axis?t.detail:0;else if(t.targetTouches){var n=t.targetTouches[0];l.deltaX=e.pageX-n.pageX,l.deltaY=e.pageY-n.pageY}return l}}}()</script><dom-module id="iron-dropdown" assetpath="../bower_components/iron-dropdown/"><template><style>:host{position:fixed}#contentWrapper ::content>*{overflow:auto}#contentWrapper.animating ::content>*{overflow:hidden}</style><div id="contentWrapper"><content id="content" select=".dropdown-content"></content></div></template><script>!function(){"use strict";Polymer({is:"iron-dropdown",behaviors:[Polymer.IronControlState,Polymer.IronA11yKeysBehavior,Polymer.IronOverlayBehavior,Polymer.NeonAnimationRunnerBehavior],properties:{horizontalAlign:{type:String,value:"left",reflectToAttribute:!0},verticalAlign:{type:String,value:"top",reflectToAttribute:!0},openAnimationConfig:{type:Object},closeAnimationConfig:{type:Object},focusTarget:{type:Object},noAnimations:{type:Boolean,value:!1},allowOutsideScroll:{type:Boolean,value:!1},_boundOnCaptureScroll:{type:Function,value:function(){return this._onCaptureScroll.bind(this)}}},listeners:{"neon-animation-finish":"_onNeonAnimationFinish"},observers:["_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)"],get containedElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},get _focusTarget(){return this.focusTarget||this.containedElement},ready:function(){this._scrollTop=0,this._scrollLeft=0,this._refitOnScrollRAF=null},attached:function(){this.sizingTarget&&this.sizingTarget!==this||(this.sizingTarget=this.containedElement)},detached:function(){this.cancelAnimation(),document.removeEventListener("scroll",this._boundOnCaptureScroll),Polymer.IronDropdownScrollManager.removeScrollLock(this)},_openedChanged:function(){this.opened&&this.disabled?this.cancel():(this.cancelAnimation(),this._updateAnimationConfig(),this._saveScrollPosition(),this.opened?(document.addEventListener("scroll",this._boundOnCaptureScroll),!this.allowOutsideScroll&&Polymer.IronDropdownScrollManager.pushScrollLock(this)):(document.removeEventListener("scroll",this._boundOnCaptureScroll),Polymer.IronDropdownScrollManager.removeScrollLock(this)),Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this,arguments))},_renderOpened:function(){!this.noAnimations&&this.animationConfig.open?(this.$.contentWrapper.classList.add("animating"),this.playAnimation("open")):Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this,arguments)},_renderClosed:function(){!this.noAnimations&&this.animationConfig.close?(this.$.contentWrapper.classList.add("animating"),this.playAnimation("close")):Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this,arguments)},_onNeonAnimationFinish:function(){this.$.contentWrapper.classList.remove("animating"),this.opened?this._finishRenderOpened():this._finishRenderClosed()},_onCaptureScroll:function(){this.allowOutsideScroll?(this._refitOnScrollRAF&&window.cancelAnimationFrame(this._refitOnScrollRAF),this._refitOnScrollRAF=window.requestAnimationFrame(this.refit.bind(this))):this._restoreScrollPosition()},_saveScrollPosition:function(){document.scrollingElement?(this._scrollTop=document.scrollingElement.scrollTop,this._scrollLeft=document.scrollingElement.scrollLeft):(this._scrollTop=Math.max(document.documentElement.scrollTop,document.body.scrollTop),this._scrollLeft=Math.max(document.documentElement.scrollLeft,document.body.scrollLeft))},_restoreScrollPosition:function(){document.scrollingElement?(document.scrollingElement.scrollTop=this._scrollTop,document.scrollingElement.scrollLeft=this._scrollLeft):(document.documentElement.scrollTop=this._scrollTop,document.documentElement.scrollLeft=this._scrollLeft,document.body.scrollTop=this._scrollTop,document.body.scrollLeft=this._scrollLeft)},_updateAnimationConfig:function(){for(var o=(this.openAnimationConfig||[]).concat(this.closeAnimationConfig||[]),t=0;t<o.length;t++)o[t].node=this.containedElement;this.animationConfig={open:this.openAnimationConfig,close:this.closeAnimationConfig}},_updateOverlayPosition:function(){this.isAttached&&this.notifyResize()},_applyFocus:function(){var o=this.focusTarget||this.containedElement;o&&this.opened&&!this.noAutoFocus?o.focus():Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this,arguments)}})}()</script></dom-module><script>Polymer({is:"fade-in-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(i){var e=i.node;return this._effect=new KeyframeEffect(e,[{opacity:"0"},{opacity:"1"}],this.timingFromConfig(i)),this._effect}})</script><script>Polymer({is:"fade-out-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node;return this._effect=new KeyframeEffect(i,[{opacity:"1"},{opacity:"0"}],this.timingFromConfig(e)),this._effect}})</script><script>Polymer({is:"paper-menu-grow-height-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.height;return this._effect=new KeyframeEffect(i,[{height:n/2+"px"},{height:n+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-grow-width-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.width;return this._effect=new KeyframeEffect(i,[{width:n/2+"px"},{width:n+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-shrink-width-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.width;return this._effect=new KeyframeEffect(i,[{width:n+"px"},{width:n-n/20+"px"}],this.timingFromConfig(e)),this._effect}}),Polymer({is:"paper-menu-shrink-height-animation",behaviors:[Polymer.NeonAnimationBehavior],configure:function(e){var i=e.node,t=i.getBoundingClientRect(),n=t.height;t.top;return this.setPrefixedProperty(i,"transformOrigin","0 0"),this._effect=new KeyframeEffect(i,[{height:n+"px",transform:"translateY(0)"},{height:n/2+"px",transform:"translateY(-20px)"}],this.timingFromConfig(e)),this._effect}})</script><dom-module id="paper-menu-button" assetpath="../bower_components/paper-menu-button/"><template><style>:host{display:inline-block;position:relative;padding:8px;outline:0;@apply(--paper-menu-button)}:host([disabled]){cursor:auto;color:var(--disabled-text-color);@apply(--paper-menu-button-disabled)}iron-dropdown{@apply(--paper-menu-button-dropdown)}.dropdown-content{@apply(--shadow-elevation-2dp);position:relative;border-radius:2px;background-color:var(--paper-menu-button-dropdown-background,--primary-background-color);@apply(--paper-menu-button-content)}:host([vertical-align=top]) .dropdown-content{margin-bottom:20px;margin-top:-10px;top:10px}:host([vertical-align=bottom]) .dropdown-content{bottom:10px;margin-bottom:-10px;margin-top:20px}#trigger{cursor:pointer}</style><div id="trigger" on-tap="toggle"><content select=".dropdown-trigger"></content></div><iron-dropdown id="dropdown" opened="{{opened}}" horizontal-align="[[horizontalAlign]]" vertical-align="[[verticalAlign]]" dynamic-align="[[dynamicAlign]]" horizontal-offset="[[horizontalOffset]]" vertical-offset="[[verticalOffset]]" no-overlap="[[noOverlap]]" open-animation-config="[[openAnimationConfig]]" close-animation-config="[[closeAnimationConfig]]" no-animations="[[noAnimations]]" focus-target="[[_dropdownContent]]" allow-outside-scroll="[[allowOutsideScroll]]" restore-focus-on-close="[[restoreFocusOnClose]]" on-iron-overlay-canceled="__onIronOverlayCanceled"><div class="dropdown-content"><content id="content" select=".dropdown-content"></content></div></iron-dropdown></template><script>!function(){"use strict";var e={ANIMATION_CUBIC_BEZIER:"cubic-bezier(.3,.95,.5,1)",MAX_ANIMATION_TIME_MS:400},n=Polymer({is:"paper-menu-button",behaviors:[Polymer.IronA11yKeysBehavior,Polymer.IronControlState],properties:{opened:{type:Boolean,value:!1,notify:!0,observer:"_openedChanged"},horizontalAlign:{type:String,value:"left",reflectToAttribute:!0},verticalAlign:{type:String,value:"top",reflectToAttribute:!0},dynamicAlign:{type:Boolean},horizontalOffset:{type:Number,value:0,notify:!0},verticalOffset:{type:Number,value:0,notify:!0},noOverlap:{type:Boolean},noAnimations:{type:Boolean,value:!1},ignoreSelect:{type:Boolean,value:!1},closeOnActivate:{type:Boolean,value:!1},openAnimationConfig:{type:Object,value:function(){return[{name:"fade-in-animation",timing:{delay:100,duration:200}},{name:"paper-menu-grow-width-animation",timing:{delay:100,duration:150,easing:e.ANIMATION_CUBIC_BEZIER}},{name:"paper-menu-grow-height-animation",timing:{delay:100,duration:275,easing:e.ANIMATION_CUBIC_BEZIER}}]}},closeAnimationConfig:{type:Object,value:function(){return[{name:"fade-out-animation",timing:{duration:150}},{name:"paper-menu-shrink-width-animation",timing:{delay:100,duration:50,easing:e.ANIMATION_CUBIC_BEZIER}},{name:"paper-menu-shrink-height-animation",timing:{duration:200,easing:"ease-in"}}]}},allowOutsideScroll:{type:Boolean,value:!1},restoreFocusOnClose:{type:Boolean,value:!0},_dropdownContent:{type:Object}},hostAttributes:{role:"group","aria-haspopup":"true"},listeners:{"iron-activate":"_onIronActivate","iron-select":"_onIronSelect"},get contentElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},toggle:function(){this.opened?this.close():this.open()},open:function(){this.disabled||this.$.dropdown.open()},close:function(){this.$.dropdown.close()},_onIronSelect:function(e){this.ignoreSelect||this.close()},_onIronActivate:function(e){this.closeOnActivate&&this.close()},_openedChanged:function(e,n){e?(this._dropdownContent=this.contentElement,this.fire("paper-dropdown-open")):null!=n&&this.fire("paper-dropdown-close")},_disabledChanged:function(e){Polymer.IronControlState._disabledChanged.apply(this,arguments),e&&this.opened&&this.close()},__onIronOverlayCanceled:function(e){var n=e.detail,t=(Polymer.dom(n).rootTarget,this.$.trigger),o=Polymer.dom(n).path;o.indexOf(t)>-1&&e.preventDefault()}});Object.keys(e).forEach(function(t){n[t]=e[t]}),Polymer.PaperMenuButton=n}()</script></dom-module><iron-iconset-svg name="paper-dropdown-menu" size="24"><svg><defs><g id="arrow-drop-down"><path d="M7 10l5 5 5-5z"></path></g></defs></svg></iron-iconset-svg><dom-module id="paper-dropdown-menu-shared-styles" assetpath="../bower_components/paper-dropdown-menu/"><template><style>:host{display:inline-block;position:relative;text-align:left;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;--paper-input-container-input:{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:100%;box-sizing:border-box;cursor:pointer};@apply(--paper-dropdown-menu)}:host([disabled]){@apply(--paper-dropdown-menu-disabled)}:host([noink]) paper-ripple{display:none}:host([no-label-float]) paper-ripple{top:8px}paper-ripple{top:12px;left:0;bottom:8px;right:0;@apply(--paper-dropdown-menu-ripple)}paper-menu-button{display:block;padding:0;@apply(--paper-dropdown-menu-button)}paper-input{@apply(--paper-dropdown-menu-input)}iron-icon{color:var(--disabled-text-color);@apply(--paper-dropdown-menu-icon)}</style></template></dom-module><dom-module id="paper-dropdown-menu" assetpath="../bower_components/paper-dropdown-menu/"><template><style include="paper-dropdown-menu-shared-styles"></style><span role="button"></span><paper-menu-button id="menuButton" vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]" dynamic-align="[[dynamicAlign]]" vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]" disabled="[[disabled]]" no-animations="[[noAnimations]]" on-iron-select="_onIronSelect" on-iron-deselect="_onIronDeselect" opened="{{opened}}" close-on-activate="" allow-outside-scroll="[[allowOutsideScroll]]"><div class="dropdown-trigger"><paper-ripple></paper-ripple><paper-input type="text" invalid="[[invalid]]" readonly="" disabled="[[disabled]]" value="[[selectedItemLabel]]" placeholder="[[placeholder]]" error-message="[[errorMessage]]" always-float-label="[[alwaysFloatLabel]]" no-label-float="[[noLabelFloat]]" label="[[label]]"><iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix=""></iron-icon></paper-input></div><content id="content" select=".dropdown-content"></content></paper-menu-button></template><script>!function(){"use strict";Polymer({is:"paper-dropdown-menu",behaviors:[Polymer.IronButtonState,Polymer.IronControlState,Polymer.IronFormElementBehavior,Polymer.IronValidatableBehavior],properties:{selectedItemLabel:{type:String,notify:!0,readOnly:!0},selectedItem:{type:Object,notify:!0,readOnly:!0},value:{type:String,notify:!0,readOnly:!0},label:{type:String},placeholder:{type:String},errorMessage:{type:String},opened:{type:Boolean,notify:!0,value:!1,observer:"_openedChanged"},allowOutsideScroll:{type:Boolean,value:!1},noLabelFloat:{type:Boolean,value:!1,reflectToAttribute:!0},alwaysFloatLabel:{type:Boolean,value:!1},noAnimations:{type:Boolean,value:!1},horizontalAlign:{type:String,value:"right"},verticalAlign:{type:String,value:"top"},dynamicAlign:{type:Boolean}},listeners:{tap:"_onTap"},keyBindings:{"up down":"open",esc:"close"},hostAttributes:{role:"combobox","aria-autocomplete":"none","aria-haspopup":"true"},observers:["_selectedItemChanged(selectedItem)"],attached:function(){var e=this.contentElement;e&&e.selectedItem&&this._setSelectedItem(e.selectedItem)},get contentElement(){return Polymer.dom(this.$.content).getDistributedNodes()[0]},open:function(){this.$.menuButton.open()},close:function(){this.$.menuButton.close()},_onIronSelect:function(e){this._setSelectedItem(e.detail.item)},_onIronDeselect:function(e){this._setSelectedItem(null)},_onTap:function(e){Polymer.Gestures.findOriginalTarget(e)===this&&this.open()},_selectedItemChanged:function(e){var t="";t=e?e.label||e.getAttribute("label")||e.textContent.trim():"",this._setValue(t),this._setSelectedItemLabel(t)},_computeMenuVerticalOffset:function(e){return e?-4:8},_getValidity:function(e){return this.disabled||!this.required||this.required&&!!this.value},_openedChanged:function(){var e=this.opened?"true":"false",t=this.contentElement;t&&t.setAttribute("aria-expanded",e)}})}()</script></dom-module><dom-module id="paper-menu-shared-styles" assetpath="../bower_components/paper-menu/"><template><style>.selectable-content>::content>.iron-selected{font-weight:700;@apply(--paper-menu-selected-item)}.selectable-content>::content>[disabled]{color:var(--paper-menu-disabled-color,--disabled-text-color)}.selectable-content>::content>:focus{position:relative;outline:0;@apply(--paper-menu-focused-item)}.selectable-content>::content>:focus:after{@apply(--layout-fit);background:currentColor;opacity:var(--dark-divider-opacity);content:'';pointer-events:none;@apply(--paper-menu-focused-item-after)}.selectable-content>::content>[colored]:focus:after{opacity:.26}</style></template></dom-module><dom-module id="paper-menu" assetpath="../bower_components/paper-menu/"><template><style include="paper-menu-shared-styles"></style><style>:host{display:block;padding:8px 0;background:var(--paper-menu-background-color,--primary-background-color);color:var(--paper-menu-color,--primary-text-color);@apply(--paper-menu)}</style><div class="selectable-content"><content></content></div></template><script>!function(){Polymer({is:"paper-menu",behaviors:[Polymer.IronMenuBehavior]})}()</script></dom-module><script>Polymer.PaperItemBehaviorImpl={hostAttributes:{role:"option",tabindex:"0"}},Polymer.PaperItemBehavior=[Polymer.IronButtonState,Polymer.IronControlState,Polymer.PaperItemBehaviorImpl]</script><dom-module id="paper-item-shared-styles" assetpath="../bower_components/paper-item/"><template><style>.paper-item,:host{display:block;position:relative;min-height:var(--paper-item-min-height,48px);padding:0 16px}.paper-item{@apply(--paper-font-subhead);border:none;outline:0;background:#fff;width:100%;text-align:left}.paper-item[hidden],:host([hidden]){display:none!important}.paper-item.iron-selected,:host(.iron-selected){font-weight:var(--paper-item-selected-weight,bold);@apply(--paper-item-selected)}.paper-item[disabled],:host([disabled]){color:var(--paper-item-disabled-color,--disabled-text-color);@apply(--paper-item-disabled)}.paper-item:focus,:host(:focus){position:relative;outline:0;@apply(--paper-item-focused)}.paper-item:focus:before,:host(:focus):before{@apply(--layout-fit);background:currentColor;content:'';opacity:var(--dark-divider-opacity);pointer-events:none;@apply(--paper-item-focused-before)}</style></template></dom-module><dom-module id="paper-item" assetpath="../bower_components/paper-item/"><template><style include="paper-item-shared-styles"></style><style>:host{@apply(--layout-horizontal);@apply(--layout-center);@apply(--paper-font-subhead);@apply(--paper-item)}</style><content></content></template><script>Polymer({is:"paper-item",behaviors:[Polymer.PaperItemBehavior]})</script></dom-module><dom-module id="state-card-input_select" assetpath="state-summary/"><template><style>:host{display:block}state-badge{float:left;margin-top:10px}paper-dropdown-menu{display:block;margin-left:53px}</style><state-badge state-obj="[[stateObj]]"></state-badge><paper-dropdown-menu on-tap="stopPropagation" selected-item-label="{{selectedOption}}" label="[[stateObj.entityDisplay]]"><paper-menu class="dropdown-content" selected="[[computeSelected(stateObj)]]"><template is="dom-repeat" items="[[stateObj.attributes.options]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></template></dom-module><script>Polymer({is:"state-card-input_select",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object},selectedOption:{type:String,observer:"selectedOptionChanged"}},computeSelected:function(t){return t.attributes.options.indexOf(t.state)},selectedOptionChanged:function(t){""!==t&&t!==this.stateObj.state&&this.hass.serviceActions.callService("input_select","select_option",{option:t,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})</script><script>Polymer.IronRangeBehavior={properties:{value:{type:Number,value:0,notify:!0,reflectToAttribute:!0},min:{type:Number,value:0,notify:!0},max:{type:Number,value:100,notify:!0},step:{type:Number,value:1,notify:!0},ratio:{type:Number,value:0,readOnly:!0,notify:!0}},observers:["_update(value, min, max, step)"],_calcRatio:function(t){return(this._clampValue(t)-this.min)/(this.max-this.min)},_clampValue:function(t){return Math.min(this.max,Math.max(this.min,this._calcStep(t)))},_calcStep:function(t){if(t=parseFloat(t),!this.step)return t;var e=Math.round((t-this.min)/this.step);return this.step<1?e/(1/this.step)+this.min:e*this.step+this.min},_validateValue:function(){var t=this._clampValue(this.value);return this.value=this.oldValue=isNaN(t)?this.oldValue:t,this.value!==t},_update:function(){this._validateValue(),this._setRatio(100*this._calcRatio(this.value))}}</script><dom-module id="paper-progress" assetpath="../bower_components/paper-progress/"><template><style>:host{display:block;width:200px;position:relative;overflow:hidden}:host([hidden]){display:none!important}#progressContainer{@apply(--paper-progress-container);position:relative}#progressContainer,.indeterminate::after{height:var(--paper-progress-height,4px)}#primaryProgress,#secondaryProgress,.indeterminate::after{@apply(--layout-fit)}#progressContainer,.indeterminate::after{background:var(--paper-progress-container-color,--google-grey-300)}:host(.transiting) #primaryProgress,:host(.transiting) #secondaryProgress{-webkit-transition-property:-webkit-transform;transition-property:transform;-webkit-transition-duration:var(--paper-progress-transition-duration,.08s);transition-duration:var(--paper-progress-transition-duration,.08s);-webkit-transition-timing-function:var(--paper-progress-transition-timing-function,ease);transition-timing-function:var(--paper-progress-transition-timing-function,ease);-webkit-transition-delay:var(--paper-progress-transition-delay,0s);transition-delay:var(--paper-progress-transition-delay,0s)}#primaryProgress,#secondaryProgress{@apply(--layout-fit);-webkit-transform-origin:left center;transform-origin:left center;-webkit-transform:scaleX(0);transform:scaleX(0);will-change:transform}#primaryProgress{background:var(--paper-progress-active-color,--google-green-500)}#secondaryProgress{background:var(--paper-progress-secondary-color,--google-green-100)}:host([disabled]) #primaryProgress{background:var(--paper-progress-disabled-active-color,--google-grey-500)}:host([disabled]) #secondaryProgress{background:var(--paper-progress-disabled-secondary-color,--google-grey-300)}:host(:not([disabled])) #primaryProgress.indeterminate{-webkit-transform-origin:right center;transform-origin:right center;-webkit-animation:indeterminate-bar var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite;animation:indeterminate-bar var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite}:host(:not([disabled])) #primaryProgress.indeterminate::after{content:"";-webkit-transform-origin:center center;transform-origin:center center;-webkit-animation:indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite;animation:indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration,2s) linear infinite}@-webkit-keyframes indeterminate-bar{0%{-webkit-transform:scaleX(1) translateX(-100%)}50%{-webkit-transform:scaleX(1) translateX(0)}75%{-webkit-transform:scaleX(1) translateX(0);-webkit-animation-timing-function:cubic-bezier(.28,.62,.37,.91)}100%{-webkit-transform:scaleX(0) translateX(0)}}@-webkit-keyframes indeterminate-splitter{0%{-webkit-transform:scaleX(.75) translateX(-125%)}30%{-webkit-transform:scaleX(.75) translateX(-125%);-webkit-animation-timing-function:cubic-bezier(.42,0,.6,.8)}90%{-webkit-transform:scaleX(.75) translateX(125%)}100%{-webkit-transform:scaleX(.75) translateX(125%)}}@keyframes indeterminate-bar{0%{transform:scaleX(1) translateX(-100%)}50%{transform:scaleX(1) translateX(0)}75%{transform:scaleX(1) translateX(0);animation-timing-function:cubic-bezier(.28,.62,.37,.91)}100%{transform:scaleX(0) translateX(0)}}@keyframes indeterminate-splitter{0%{transform:scaleX(.75) translateX(-125%)}30%{transform:scaleX(.75) translateX(-125%);animation-timing-function:cubic-bezier(.42,0,.6,.8)}90%{transform:scaleX(.75) translateX(125%)}100%{transform:scaleX(.75) translateX(125%)}}</style><div id="progressContainer"><div id="secondaryProgress" hidden$="[[_hideSecondaryProgress(secondaryRatio)]]"></div><div id="primaryProgress"></div></div></template></dom-module><script>Polymer({is:"paper-progress",behaviors:[Polymer.IronRangeBehavior],properties:{secondaryProgress:{type:Number,value:0},secondaryRatio:{type:Number,value:0,readOnly:!0},indeterminate:{type:Boolean,value:!1,observer:"_toggleIndeterminate"},disabled:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"_disabledChanged"}},observers:["_progressChanged(secondaryProgress, value, min, max)"],hostAttributes:{role:"progressbar"},_toggleIndeterminate:function(e){this.toggleClass("indeterminate",e,this.$.primaryProgress)},_transformProgress:function(e,r){var s="scaleX("+r/100+")";e.style.transform=e.style.webkitTransform=s},_mainRatioChanged:function(e){this._transformProgress(this.$.primaryProgress,e)},_progressChanged:function(e,r,s,t){e=this._clampValue(e),r=this._clampValue(r);var a=100*this._calcRatio(e),i=100*this._calcRatio(r);this._setSecondaryRatio(a),this._transformProgress(this.$.secondaryProgress,a),this._transformProgress(this.$.primaryProgress,i),this.secondaryProgress=e,this.setAttribute("aria-valuenow",r),this.setAttribute("aria-valuemin",s),this.setAttribute("aria-valuemax",t)},_disabledChanged:function(e){this.setAttribute("aria-disabled",e?"true":"false")},_hideSecondaryProgress:function(e){return 0===e}})</script><dom-module id="paper-slider" assetpath="../bower_components/paper-slider/"><template strip-whitespace=""><style>:host{@apply(--layout);@apply(--layout-justified);@apply(--layout-center);width:200px;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;--paper-progress-active-color:var(--paper-slider-active-color, --google-blue-700);--paper-progress-secondary-color:var(--paper-slider-secondary-color, --google-blue-300);--paper-progress-disabled-active-color:var(--paper-slider-disabled-active-color, --paper-grey-400);--paper-progress-disabled-secondary-color:var(--paper-slider-disabled-secondary-color, --paper-grey-400)}:host(:focus){outline:0}#sliderContainer{position:relative;width:100%;height:calc(30px + var(--paper-slider-height,2px));margin-left:calc(15px + var(--paper-slider-height,2px)/ 2);margin-right:calc(15px + var(--paper-slider-height,2px)/ 2)}#sliderContainer:focus{outline:0}#sliderContainer.editable{margin-top:12px;margin-bottom:12px}.bar-container{position:absolute;top:0;bottom:0;left:0;right:0;overflow:hidden}.ring>.bar-container{left:calc(5px + var(--paper-slider-height,2px)/ 2);transition:left .18s ease}.ring.expand.dragging>.bar-container{transition:none}.ring.expand:not(.pin)>.bar-container{left:calc(8px + var(--paper-slider-height,2px)/ 2)}#sliderBar{padding:15px 0;width:100%;background-color:var(--paper-slider-bar-color,transparent);--paper-progress-container-color:var(--paper-slider-container-color, --paper-grey-400);--paper-progress-height:var(--paper-slider-height, 2px)}.slider-markers{position:absolute;top:calc(14px + var(--paper-slider-height,2px)/ 2);height:var(--paper-slider-height,2px);left:0;right:-1px;box-sizing:border-box;pointer-events:none;@apply(--layout-horizontal)}.slider-marker{@apply(--layout-flex)}.slider-marker::after,.slider-markers::after{content:"";display:block;margin-left:-1px;width:2px;height:2px;border-radius:50%;background-color:#000}#sliderKnob{position:absolute;left:0;top:0;margin-left:calc(-15px - var(--paper-slider-height,2px)/ 2);width:calc(30px + var(--paper-slider-height,2px));height:calc(30px + var(--paper-slider-height,2px))}.transiting>#sliderKnob{transition:left 80ms ease}#sliderKnob:focus{outline:0}#sliderKnob.dragging{transition:none}.snaps>#sliderKnob.dragging{transition:-webkit-transform 80ms ease;transition:transform 80ms ease}#sliderKnobInner{margin:10px;width:calc(100% - 20px);height:calc(100% - 20px);background-color:var(--paper-slider-knob-color,--google-blue-700);border:2px solid var(--paper-slider-knob-color,--google-blue-700);border-radius:50%;-moz-box-sizing:border-box;box-sizing:border-box;transition-property:-webkit-transform,background-color,border;transition-property:transform,background-color,border;transition-duration:.18s;transition-timing-function:ease}.expand:not(.pin)>#sliderKnob>#sliderKnobInner{-webkit-transform:scale(1.5);transform:scale(1.5)}.ring>#sliderKnob>#sliderKnobInner{background-color:var(--paper-slider-knob-start-color,transparent);border:2px solid var(--paper-slider-knob-start-border-color,--paper-grey-400)}#sliderKnobInner::before{background-color:var(--paper-slider-pin-color,--google-blue-700)}.pin>#sliderKnob>#sliderKnobInner::before{content:"";position:absolute;top:0;left:50%;margin-left:-13px;width:26px;height:26px;border-radius:50% 50% 50% 0;-webkit-transform:rotate(-45deg) scale(0) translate(0);transform:rotate(-45deg) scale(0) translate(0)}#sliderKnobInner::after,#sliderKnobInner::before{transition:-webkit-transform .18s ease,background-color .18s ease;transition:transform .18s ease,background-color .18s ease}.pin.ring>#sliderKnob>#sliderKnobInner::before{background-color:var(--paper-slider-pin-start-color,--paper-grey-400)}.pin.expand>#sliderKnob>#sliderKnobInner::before{-webkit-transform:rotate(-45deg) scale(1) translate(17px,-17px);transform:rotate(-45deg) scale(1) translate(17px,-17px)}.pin>#sliderKnob>#sliderKnobInner::after{content:attr(value);position:absolute;top:0;left:50%;margin-left:-16px;width:32px;height:26px;text-align:center;color:var(--paper-slider-font-color,#fff);font-size:10px;-webkit-transform:scale(0) translate(0);transform:scale(0) translate(0)}.pin.expand>#sliderKnob>#sliderKnobInner::after{-webkit-transform:scale(1) translate(0,-17px);transform:scale(1) translate(0,-17px)}.slider-input{width:50px;overflow:hidden;--paper-input-container-input:{text-align:center};@apply(--paper-slider-input)}#sliderContainer.disabled{pointer-events:none}.disabled>#sliderKnob>#sliderKnobInner{background-color:var(--paper-slider-disabled-knob-color,--paper-grey-400);border:2px solid var(--paper-slider-disabled-knob-color,--paper-grey-400);-webkit-transform:scale3d(.75,.75,1);transform:scale3d(.75,.75,1)}.disabled.ring>#sliderKnob>#sliderKnobInner{background-color:var(--paper-slider-knob-start-color,transparent);border:2px solid var(--paper-slider-knob-start-border-color,--paper-grey-400)}paper-ripple{color:var(--paper-slider-knob-color,--google-blue-700)}</style><div id="sliderContainer" class$="[[_getClassNames(disabled, pin, snaps, immediateValue, min, expand, dragging, transiting, editable)]]"><div class="bar-container"><paper-progress disabled$="[[disabled]]" id="sliderBar" aria-hidden="true" min="[[min]]" max="[[max]]" step="[[step]]" value="[[immediateValue]]" secondary-progress="[[secondaryProgress]]" on-down="_bardown" on-up="_resetKnob" on-track="_onTrack"></paper-progress></div><template is="dom-if" if="[[snaps]]"><div class="slider-markers"><template is="dom-repeat" items="[[markers]]"><div class="slider-marker"></div></template></div></template><div id="sliderKnob" on-down="_knobdown" on-up="_resetKnob" on-track="_onTrack" on-transitionend="_knobTransitionEnd"><div id="sliderKnobInner" value$="[[immediateValue]]"></div></div></div><template is="dom-if" if="[[editable]]"><paper-input id="input" type="number" step="[[step]]" min="[[min]]" max="[[max]]" class="slider-input" disabled$="[[disabled]]" value="[[immediateValue]]" on-change="_changeValue" on-keydown="_inputKeyDown" no-label-float=""></paper-input></template></template><script>Polymer({is:"paper-slider",behaviors:[Polymer.IronA11yKeysBehavior,Polymer.IronFormElementBehavior,Polymer.PaperInkyFocusBehavior,Polymer.IronRangeBehavior],properties:{snaps:{type:Boolean,value:!1,notify:!0},pin:{type:Boolean,value:!1,notify:!0},secondaryProgress:{type:Number,value:0,notify:!0,observer:"_secondaryProgressChanged"},editable:{type:Boolean,value:!1},immediateValue:{type:Number,value:0,readOnly:!0,notify:!0},maxMarkers:{type:Number,value:0,notify:!0},expand:{type:Boolean,value:!1,readOnly:!0},dragging:{type:Boolean,value:!1,readOnly:!0},transiting:{type:Boolean,value:!1,readOnly:!0},markers:{type:Array,readOnly:!0,value:[]}},observers:["_updateKnob(value, min, max, snaps, step)","_valueChanged(value)","_immediateValueChanged(immediateValue)","_updateMarkers(maxMarkers, min, max, snaps)"],hostAttributes:{role:"slider",tabindex:0},keyBindings:{"left down pagedown home":"_decrementKey","right up pageup end":"_incrementKey"},increment:function(){this.value=this._clampValue(this.value+this.step)},decrement:function(){this.value=this._clampValue(this.value-this.step)},_updateKnob:function(t,e,i,s,a){this.setAttribute("aria-valuemin",e),this.setAttribute("aria-valuemax",i),this.setAttribute("aria-valuenow",t),this._positionKnob(this._calcRatio(t))},_valueChanged:function(){this.fire("value-change")},_immediateValueChanged:function(){this.dragging?this.fire("immediate-value-change"):this.value=this.immediateValue},_secondaryProgressChanged:function(){this.secondaryProgress=this._clampValue(this.secondaryProgress)},_expandKnob:function(){this._setExpand(!0)},_resetKnob:function(){this.cancelDebouncer("expandKnob"),this._setExpand(!1)},_positionKnob:function(t){this._setImmediateValue(this._calcStep(this._calcKnobPosition(t))),this._setRatio(this._calcRatio(this.immediateValue)),this.$.sliderKnob.style.left=100*this.ratio+"%",this.dragging&&(this._knobstartx=this.ratio*this._w,this.translate3d(0,0,0,this.$.sliderKnob))},_calcKnobPosition:function(t){return(this.max-this.min)*t+this.min},_onTrack:function(t){switch(t.stopPropagation(),t.detail.state){case"start":this._trackStart(t);break;case"track":this._trackX(t);break;case"end":this._trackEnd()}},_trackStart:function(t){this._w=this.$.sliderBar.offsetWidth,this._x=this.ratio*this._w,this._startx=this._x,this._knobstartx=this._startx,this._minx=-this._startx,this._maxx=this._w-this._startx,this.$.sliderKnob.classList.add("dragging"),this._setDragging(!0)},_trackX:function(t){this.dragging||this._trackStart(t);var e=Math.min(this._maxx,Math.max(this._minx,t.detail.dx));this._x=this._startx+e;var i=this._calcStep(this._calcKnobPosition(this._x/this._w));this._setImmediateValue(i);var s=this._calcRatio(this.immediateValue)*this._w-this._knobstartx;this.translate3d(s+"px",0,0,this.$.sliderKnob)},_trackEnd:function(){var t=this.$.sliderKnob.style;this.$.sliderKnob.classList.remove("dragging"),this._setDragging(!1),this._resetKnob(),this.value=this.immediateValue,t.transform=t.webkitTransform="",this.fire("change")},_knobdown:function(t){this._expandKnob(),t.preventDefault(),this.focus()},_bardown:function(t){this._w=this.$.sliderBar.offsetWidth;var e=this.$.sliderBar.getBoundingClientRect(),i=(t.detail.x-e.left)/this._w,s=this.ratio;this._setTransiting(!0),this._positionKnob(i),this.debounce("expandKnob",this._expandKnob,60),s===this.ratio&&this._setTransiting(!1),this.async(function(){this.fire("change")}),t.preventDefault(),this.focus()},_knobTransitionEnd:function(t){t.target===this.$.sliderKnob&&this._setTransiting(!1)},_updateMarkers:function(t,e,i,s){s||this._setMarkers([]);var a=Math.round((i-e)/this.step);a>t&&(a=t),this._setMarkers(new Array(a))},_mergeClasses:function(t){return Object.keys(t).filter(function(e){return t[e]}).join(" ")},_getClassNames:function(){return this._mergeClasses({disabled:this.disabled,pin:this.pin,snaps:this.snaps,ring:this.immediateValue<=this.min,expand:this.expand,dragging:this.dragging,transiting:this.transiting,editable:this.editable})},_incrementKey:function(t){this.disabled||("end"===t.detail.key?this.value=this.max:this.increment(),this.fire("change"))},_decrementKey:function(t){this.disabled||("home"===t.detail.key?this.value=this.min:this.decrement(),this.fire("change"))},_changeValue:function(t){this.value=t.target.value,this.fire("change")},_inputKeyDown:function(t){t.stopPropagation()},_createRipple:function(){return this._rippleContainer=this.$.sliderKnob,Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this)},_focusedChanged:function(t){t&&this.ensureRipple(),this.hasRipple()&&(t?this._ripple.style.display="":this._ripple.style.display="none",this._ripple.holdDown=t)}})</script></dom-module><dom-module id="state-card-input_slider" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-slider{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><paper-slider min="[[min]]" max="[[max]]" value="{{value}}" step="[[step]]" pin="" on-change="selectedValueChanged" on-tap="stopPropagation"></paper-slider></div></template></dom-module><script>Polymer({is:"state-card-input_slider",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object,observer:"stateObjectChanged"},min:{type:Number},max:{type:Number},step:{type:Number},value:{type:Number}},stateObjectChanged:function(t){this.value=Number(t.state),this.min=Number(t.attributes.min),this.max=Number(t.attributes.max),this.step=Number(t.attributes.step)},selectedValueChanged:function(){this.value!==Number(this.stateObj.state)&&this.hass.serviceActions.callService("input_slider","select_value",{value:this.value,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})</script><dom-module id="state-card-media_player" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{line-height:1.5}.state{@apply(--paper-font-common-nowrap);@apply(--paper-font-body1);margin-left:16px;text-align:right}.main-text{@apply(--paper-font-common-nowrap);color:var(--primary-text-color);text-transform:capitalize}.main-text[take-height]{line-height:40px}.secondary-text{@apply(--paper-font-common-nowrap);color:var(--secondary-text-color)}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><div class="main-text" take-height$="[[!secondaryText]]">[[computePrimaryText(stateObj, isPlaying)]]</div><div class="secondary-text">[[secondaryText]]</div></div></div></template></dom-module><script>Polymer({PLAYING_STATES:["playing","paused"],is:"state-card-media_player",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"},secondaryText:{type:String,computed:"computeSecondaryText(stateObj)"}},computeIsPlaying:function(t){return this.PLAYING_STATES.indexOf(t.state)!==-1},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e;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:""}})</script><dom-module id="state-card-rollershutter" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{line-height:1.5}.state{text-align:right;white-space:nowrap;width:127px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><paper-icon-button icon="mdi:arrow-up" on-tap="onMoveUpTap" disabled="[[computeIsFullyClosed(stateObj)]]"></paper-icon-button><paper-icon-button icon="mdi:stop" on-tap="onStopTap"></paper-icon-button><paper-icon-button icon="mdi:arrow-down" on-tap="onMoveDownTap" disabled="[[computeIsFullyOpen(stateObj)]]"></paper-icon-button></div></div></template></dom-module><script>Polymer({is:"state-card-rollershutter",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeIsFullyOpen:function(t){return 100===t.attributes.current_position},computeIsFullyClosed:function(t){return 0===t.attributes.current_position},onMoveUpTap:function(){this.hass.serviceActions.callService("rollershutter","move_up",{entity_id:this.stateObj.entityId})},onMoveDownTap:function(){this.hass.serviceActions.callService("rollershutter","move_down",{entity_id:this.stateObj.entityId})},onStopTap:function(){this.hass.serviceActions.callService("rollershutter","stop",{entity_id:this.stateObj.entityId})}})</script><dom-module id="state-card-scene" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><paper-button on-tap="activateScene">ACTIVATE</paper-button></div></template></dom-module><script>Polymer({is:"state-card-scene",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},activateScene:function(t){t.stopPropagation(),this.hass.serviceActions.callTurnOn(this.stateObj.entityId)}})</script><dom-module id="state-card-script" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}ha-entity-toggle{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><template is="dom-if" if="[[stateObj.attributes.can_cancel]]"><ha-entity-toggle state-obj="[[stateObj]]" hass="[[hass]]"></ha-entity-toggle></template><template is="dom-if" if="[[!stateObj.attributes.can_cancel]]"><paper-button on-tap="fireScript">ACTIVATE</paper-button></template></div></template></dom-module><script>Polymer({is:"state-card-script",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},fireScript:function(t){t.stopPropagation(),this.hass.serviceActions.callTurnOn(this.stateObj.entityId)}})</script><dom-module id="state-card-thermostat" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>:host{@apply(--paper-font-body1);line-height:1.5}.state{margin-left:16px;text-align:right}.target{color:var(--primary-text-color)}.current{color:var(--secondary-text-color)}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><div class="state"><div class="target">[[computeTargetTemperature(stateObj)]]</div><div class="current"><span>Currently: </span><span>[[stateObj.attributes.current_temperature]]</span> <span></span> <span>[[stateObj.attributes.unit_of_measurement]]</span></div></div></div></template></dom-module><script>Polymer({is:"state-card-thermostat",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},computeTargetTemperature:function(t){return t.attributes.temperature+" "+t.attributes.unit_of_measurement}})</script><dom-module id="state-card-toggle" assetpath="state-summary/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>ha-entity-toggle{margin-left:16px}</style><div class="horizontal justified layout"><state-info state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-info><ha-entity-toggle state-obj="[[stateObj]]" hass="[[hass]]"></ha-entity-toggle></div></template></dom-module><script>Polymer({is:"state-card-toggle",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}}})</script><dom-module id="state-card-weblink" assetpath="state-summary/"><template><style>:host{display:block}.name{@apply(--paper-font-common-nowrap);@apply(--paper-font-body1);color:var(--primary-color);text-transform:capitalize;line-height:40px;margin-left:16px}</style><state-badge state-obj="[[stateObj]]" in-dialog="[[inDialog]]"></state-badge><a href$="[[stateObj.state]]" target="_blank" class="name" id="link">[[stateObj.entityDisplay]]</a></template></dom-module><script>Polymer({is:"state-card-weblink",properties:{inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation(),t.target!==this.$.link&&window.open(this.stateObj.state,"_blank")}})</script><script>Polymer({is:"state-card-content",properties:{hass:{type:Object},inDialog:{type:Boolean,value:!1},stateObj:{type:Object}},observers:["inputChanged(hass, inDialog, stateObj)"],inputChanged:function(t,e,s){s&&window.hassUtil.dynamicContentUpdater(this,"STATE-CARD-"+window.hassUtil.stateCardType(this.hass,s).toUpperCase(),{hass:t,stateObj:s,inDialog:e})}})</script><dom-module id="ha-entities-card" assetpath="cards/"><template><style is="custom-style" include="iron-flex"></style><style>.states{padding-bottom:16px}.state{padding:4px 16px;cursor:pointer}.header{@apply(--paper-font-headline);line-height:40px;color:var(--primary-text-color);padding:20px 16px 12px;text-transform:capitalize}.header .name{@apply(--paper-font-common-nowrap)}ha-entity-toggle{margin-left:16px}.header-more-info{cursor:pointer}</style><ha-card><div class$="[[computeTitleClass(groupEntity)]]" on-tap="entityTapped"><div class="flex name">[[computeTitle(states, groupEntity)]]</div><template is="dom-if" if="[[showGroupToggle(groupEntity, states)]]"><ha-entity-toggle hass="[[hass]]" state-obj="[[groupEntity]]"></ha-entity-toggle></template></div><div class="states"><template is="dom-repeat" items="[[states]]"><div class="state" on-tap="entityTapped"><state-card-content hass="[[hass]]" class="state-card" state-obj="[[item]]"></state-card-content></div></template></div></ha-card></template></dom-module><script>Polymer({is:"ha-entities-card",properties:{hass:{type:Object},states:{type:Array},groupEntity:{type:Object}},computeTitle:function(t,e){return e?e.entityDisplay:t[0].domain.replace(/_/g," ")},computeTitleClass:function(t){var e="header horizontal layout center ";return t&&(e+="header-more-info"),e},entityTapped:function(t){var e;t.target.classList.contains("paper-toggle-button")||t.target.classList.contains("paper-icon-button")||!t.model&&!this.groupEntity||(t.stopPropagation(),e=t.model?t.model.item.entityId:this.groupEntity.entityId,this.async(function(){this.hass.moreInfoActions.selectEntity(e)}.bind(this),1))},showGroupToggle:function(t,e){var n;return!(!t||!e||"on"!==t.state&&"off"!==t.state)&&(n=e.reduce(function(t,e){return t+window.hassUtil.canToggle(this.hass,e.entityId)},0),n>1)}})</script><dom-module id="ha-introduction-card" assetpath="cards/"><template><style>:host{@apply(--paper-font-body1)}a{color:var(--dark-primary-color)}ul{margin:8px;padding-left:16px}li{margin-bottom:8px}.content{padding:0 16px 16px}.install{display:block;line-height:1.5em;margin-top:8px;margin-bottom:16px}</style><ha-card header="Welcome Home!"><div class="content"><template is="dom-if" if="[[hass.demo]]">To install Home Assistant, run:<br><code class="install">pip3 install homeassistant<br>hass --open-ui</code></template>Here are some resources to get started.<ul><template is="dom-if" if="[[hass.demo]]"><li><a href="https://home-assistant.io/getting-started/">Home Assistant website</a></li><li><a href="https://home-assistant.io/getting-started/">Installation instructions</a></li><li><a href="https://home-assistant.io/getting-started/troubleshooting/">Troubleshooting your installation</a></li></template><li><a href="https://home-assistant.io/getting-started/configuration/" target="_blank">Configuring Home Assistant</a></li><li><a href="https://home-assistant.io/components/" target="_blank">Available components</a></li><li><a href="https://home-assistant.io/getting-started/troubleshooting-configuration/" target="_blank">Troubleshooting your configuration</a></li><li><a href="https://home-assistant.io/help/" target="_blank">Ask community for help</a></li></ul><template is="dom-if" if="[[showHideInstruction]]">To remove this card, edit your config in <code>configuration.yaml</code> and disable the <code>introduction</code> component.</template></div></ha-card></template></dom-module><script>Polymer({is:"ha-introduction-card",properties:{hass:{type:Object},showHideInstruction:{type:Boolean,value:!0}}})</script><dom-module id="ha-media_player-card" assetpath="cards/"><template><style include="paper-material iron-flex iron-flex-alignment iron-positioning">:host{display:block;position:relative;font-size:0;border-radius:2px;overflow:hidden}.banner{position:relative;background-position:center center;background-image:url(/static/images/card_media_player_bg.png);background-repeat:no-repeat;background-color:var(--primary-color);border-top-left-radius:2px;border-top-right-radius:2px}.banner:before{display:block;content:"";width:100%;padding-top:56%;transition:padding-top .8s}.banner.no-cover:before{padding-top:91px}.banner>.cover{position:absolute;top:0;left:0;right:0;bottom:0;border-top-left-radius:2px;border-top-right-radius:2px;background-position:center center;background-size:cover;transition:opacity .8s;opacity:1}.banner.is-off>.cover{opacity:0}.banner>.caption{@apply(--paper-font-caption);position:absolute;left:0;right:0;bottom:0;background-color:rgba(0,0,0,var(--dark-secondary-opacity));padding:8px 16px;text-transform:capitalize;font-size:14px;font-weight:500;color:#fff;transition:background-color .5s}.banner.is-off>.caption{background-color:initial}.banner>.caption .title{@apply(--paper-font-common-nowrap);font-size:1.2em;margin:8px 0 4px}.controls{@apply(--paper-font-body1);padding:8px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;background-color:#fff}.controls paper-icon-button{width:44px;height:44px}paper-icon-button{opacity:var(--dark-primary-opacity)}paper-icon-button[disabled]{opacity:var(--dark-disabled-opacity)}paper-icon-button.primary{width:56px!important;height:56px!important;background-color:var(--primary-color);color:#fff;border-radius:50%;padding:8px;transition:background-color .5s}paper-icon-button.primary[disabled]{background-color:rgba(0,0,0,var(--dark-disabled-opacity))}[invisible]{visibility:hidden!important}</style><div class$="[[computeBannerClasses(playerObj)]]"><div class="cover" id="cover"></div><div class="caption">[[stateObj.entityDisplay]]<div class="title">[[playerObj.primaryText]]</div>[[playerObj.secondaryText]]<br></div></div><div class="controls layout horizontal justified"><paper-icon-button icon="mdi:power" on-tap="handleTogglePower" invisible$="[[!playerObj.supportsTurnOff]]" class="self-center secondary"></paper-icon-button><div><paper-icon-button icon="mdi:skip-previous" invisible$="[[!playerObj.supportsPreviousTrack]]" disabled="[[playerObj.isOff]]" on-tap="handlePrevious"></paper-icon-button><paper-icon-button class="primary" icon="[[computePlaybackControlIcon(playerObj)]]" invisible="[[!computePlaybackControlIcon(playerObj)]]" disabled="[[playerObj.isOff]]" on-tap="handlePlaybackControl"></paper-icon-button><paper-icon-button icon="mdi:skip-next" invisible$="[[!playerObj.supportsNextTrack]]" disabled="[[playerObj.isOff]]" on-tap="handleNext"></paper-icon-button></div><paper-icon-button icon="mdi:dots-vertical" on-tap="handleOpenMoreInfo" class="self-center secondary"></paper-icon-button></div></template></dom-module><script>Polymer({is:"ha-media_player-card",properties:{hass:{type:Object},stateObj:{type:Object},playerObj:{type:Object,computed:"computePlayerObj(stateObj)",observer:"playerObjChanged"},playbackControlIcon:{type:String,computed:"computePlaybackControlIcon(playerObj)"},elevation:{type:Number,value:1,reflectToAttribute:!0}},playerObjChanged:function(t){t.isOff||t.isIdle||(this.$.cover.style.backgroundImage=t.stateObj.attributes.entity_picture?"url("+t.stateObj.attributes.entity_picture+")":"")},computeBannerClasses:function(t){var e="banner";return(t.isOff||t.isIdle)&&(e+=" is-off"),t.stateObj.attributes.entity_picture||(e+=" no-cover"),e},computeHidePowerOnButton:function(t){return!t.isOff||!t.supportsTurnOn},computePlayerObj:function(t){return t.domainModel(this.hass)},computePlaybackControlIcon:function(t){return t.isPlaying?t.supportsPause?"mdi:pause":"mdi:stop":t.isPaused||t.isOff?"mdi:play":""},computeShowControls:function(t){return!t.isOff},handleNext:function(t){t.stopPropagation(),this.playerObj.nextTrack()},handleOpenMoreInfo:function(t){t.stopPropagation(),this.async(function(){this.hass.moreInfoActions.selectEntity(this.stateObj.entityId)},1)},handlePlaybackControl:function(t){t.stopPropagation(),this.playerObj.mediaPlayPause()},handlePrevious:function(t){t.stopPropagation(),this.playerObj.previousTrack()},handleTogglePower:function(t){t.stopPropagation(),this.playerObj.togglePower()}})</script><dom-module id="ha-persistent_notification-card" assetpath="cards/"><template><style>:host{@apply(--paper-font-body1)}.content{padding:0 16px 16px}paper-button{margin:8px;font-weight:500}</style><ha-card header="[[computeTitle(stateObj)]]"><div id="pnContent" class="content"></div><paper-button on-tap="dismissTap">DISMISS</paper-button></ha-card></template></dom-module><script>Polymer({is:"ha-persistent_notification-card",properties:{hass:{type:Object},stateObj:{type:Object},scriptLoaded:{type:Boolean,value:!1}},observers:["computeContent(stateObj, scriptLoaded)"],computeTitle:function(t){return t.attributes.title||t.entityDisplay},loadScript:function(){var t=function(){this.scriptLoaded=!0}.bind(this),e=function(){console.error("Micromarkdown was not loaded.")};this.importHref("/static/micromarkdown-js.html",t,e)},computeContent:function(t,e){var i="",n="";e&&(i=this.$.pnContent,n=window.micromarkdown.parse(t.state),i.innerHTML=n)},ready:function(){this.loadScript()},dismissTap:function(t){t.preventDefault(),this.hass.entityActions.delete(this.stateObj)}})</script><script>Polymer({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(a){a&&window.hassUtil.dynamicContentUpdater(this,"HA-"+a.cardType.toUpperCase()+"-CARD",a)}})</script><dom-module id="ha-cards" assetpath="components/"><template><style is="custom-style" include="iron-flex iron-flex-factors"></style><style>:host{display:block;padding-top:8px;padding-right:8px}.badges{font-size:85%;text-align:center}.column{max-width:500px;overflow-x:hidden}.zone-card{margin-left:8px;margin-bottom:8px}@media (max-width:500px){:host{padding-right:0}.zone-card{margin-left:0}}@media (max-width:599px){.column{max-width:600px}}</style><div class="main"><template is="dom-if" if="[[cards.badges]]"><div class="badges"><template is="dom-if" if="[[cards.demo]]"><ha-demo-badge></ha-demo-badge></template><ha-badges-card states="[[cards.badges]]" hass="[[hass]]"></ha-badges-card></div></template><div class="horizontal layout center-justified"><template is="dom-repeat" items="[[cards.columns]]" as="column"><div class="column flex-1"><template is="dom-repeat" items="[[column]]" as="card"><div class="zone-card"><ha-card-chooser card-data="[[card]]" hass="[[hass]]"></ha-card-chooser></div></template></div></template></div></div></template></dom-module><script>!function(){"use strict";function t(t){return t in o?o[t]:30}function e(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}var n={camera:4,media_player:3,persistent_notification:0},o={configurator:-20,persistent_notification:-15,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,sensor:5,binary_sensor:6};Polymer({is:"ha-cards",properties:{hass:{type:Object},showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},panelVisible:{type:Boolean},viewVisible:{type:Boolean},cards:{type:Object}},observers:["updateCards(columns, states, showIntroduction, panelVisible, viewVisible)"],updateCards:function(t,e,n,o,r){o&&r&&this.debounce("updateCards",function(){this.panelVisible&&this.viewVisible&&(this.cards=this.computeCards(t,e,n))}.bind(this))},computeCards:function(o,r,s){function i(t){return t.filter(function(t){return!(t.entityId in h)})}function a(t){var e=0;for(p=e;p<y.length;p++){if(y[p]<5){e=p;break}y[p]<y[e]&&(e=p)}return y[e]+=t,e}function u(t,e,o){var r,s,i,u;0!==e.length&&(r=[],s=[],i=0,e.forEach(function(t){t.domain in n?(r.push(t),i+=n[t.domain]):(s.push(t),i++)}),i+=s.length>1,u=a(i),s.length>0&&f.columns[u].push({hass:d,cardType:"entities",states:s,groupEntity:o||!1}),r.forEach(function(t){f.columns[u].push({hass:d,cardType:t.domain,stateObj:t})}))}var c,p,d=this.hass,l=r.groupBy(function(t){return t.domain}),h={},f={demo:!1,badges:[],columns:[]},y=[];for(p=0;p<o;p++)f.columns.push([]),y.push(0);return s&&f.columns[a(5)].push({hass:d,cardType:"introduction",showHideInstruction:r.size>0&&!d.demo}),c=this.hass.util.expandGroup,l.keySeq().sortBy(function(e){return t(e)}).forEach(function(n){var o;return"a"===n?void(f.demo=!0):(o=t(n),void(o>=0&&o<10?f.badges.push.apply(f.badges,i(l.get(n)).sortBy(e).toArray()):"group"===n?l.get(n).sortBy(e).forEach(function(t){var e=c(t,r);e.forEach(function(t){h[t.entityId]=!0}),u(t.entityId,e.toArray(),t)}):u(n,i(l.get(n)).sortBy(e).toArray())))}),f.columns=f.columns.filter(function(t){return t.length>0}),f}})}()</script><dom-module id="partial-cards" assetpath="layouts/"><template><style include="iron-flex iron-positioning ha-style">:host{-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none}app-header-layout{background-color:#E5E5E5}paper-tabs{margin-left:12px;--paper-tabs-selection-bar-color:#FFF;text-transform:uppercase}</style><app-header-layout has-scrolling-region="" id="layout"><app-header effects="waterfall" condenses="" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">[[computeTitle(views, locationName)]]</div><paper-icon-button icon="mdi:refresh" class$="[[computeRefreshButtonClass(isFetching)]]" on-tap="handleRefresh" hidden$="[[isStreaming]]"></paper-icon-button><paper-icon-button icon="mdi:microphone" hidden$="[[!canListen]]" on-tap="handleListenClick"></paper-icon-button></app-toolbar><div sticky="" hidden$="[[!views.length]]"><paper-tabs scrollable="" selected="[[currentView]]" attr-for-selected="data-entity" on-iron-select="handleViewSelected"><paper-tab data-entity="" on-tap="scrollToTop">[[locationName]]</paper-tab><template is="dom-repeat" items="[[views]]"><paper-tab data-entity$="[[item.entityId]]" on-tap="scrollToTop"><template is="dom-if" if="[[item.attributes.icon]]"><iron-icon icon="[[item.attributes.icon]]"></iron-icon></template><template is="dom-if" if="[[!item.attributes.icon]]">[[item.entityDisplay]]</template></paper-tab></template></paper-tabs></div></app-header><iron-pages attr-for-selected="data-view" selected="[[currentView]]" selected-attribute="view-visible"><ha-cards data-view="" show-introduction="[[computeShowIntroduction(currentView, introductionLoaded, states)]]" states="[[states]]" columns="[[_columns]]" hass="[[hass]]" panel-visible="[[panelVisible]]"></ha-cards><template is="dom-repeat" items="[[views]]"><ha-cards data-view$="[[item.entityId]]" show-introduction="[[computeShowIntroduction(currentView, introductionLoaded, states)]]" states="[[states]]" columns="[[_columns]]" hass="[[hass]]" panel-visible="[[panelVisible]]"></ha-cards></template></iron-pages></app-header-layout></template></dom-module><script>Polymer({is:"partial-cards",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1,observer:"handleWindowChange"},panelVisible:{type:Boolean,value:!1},_columns:{type:Number,value:1},isFetching:{type:Boolean,bindNuclear:function(e){return e.syncGetters.isFetching}},isStreaming:{type:Boolean,bindNuclear:function(e){return e.streamGetters.isStreamingEvents}},canListen:{type:Boolean,bindNuclear:function(e){return[e.voiceGetters.isVoiceSupported,e.configGetters.isComponentLoaded("conversation"),function(e,n){return e&&n}]}},introductionLoaded:{type:Boolean,bindNuclear:function(e){return e.configGetters.isComponentLoaded("introduction")}},locationName:{type:String,bindNuclear:function(e){return e.configGetters.locationName}},currentView:{type:String,bindNuclear:function(e){return[e.viewGetters.currentView,function(e){return e||""}]}},views:{type:Array,bindNuclear:function(e){return[e.viewGetters.views,function(e){return e.valueSeq().sortBy(function(e){return e.attributes.order}).toArray()}]}},states:{type:Object,bindNuclear:function(e){return e.viewGetters.currentViewEntities}}},created:function(){this.handleWindowChange=this.handleWindowChange.bind(this),this.mqls=[300,600,900,1200].map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(this.handleWindowChange),n}.bind(this))},detached:function(){this.mqls.forEach(function(e){e.removeListener(this.handleWindowChange)})},handleWindowChange:function(){var e=this.mqls.reduce(function(e,n){return e+n.matches},0);this._columns=Math.max(1,e-(!this.narrow&&this.showMenu))},scrollToTop:function(){var e=0,n=this.$.layout.header.scrollTarget,t=function(e,n,t,i){return e/=i,-t*e*(e-2)+n},i=Math.random(),r=200,o=Date.now(),s=n.scrollTop,a=e-s;this._currentAnimationId=i,function c(){var u=Date.now(),l=u-o;l>r?n.scrollTop=e:this._currentAnimationId===i&&(n.scrollTop=t(l,s,a,r),requestAnimationFrame(c.bind(this)))}.call(this)},handleRefresh:function(){this.hass.syncActions.fetchAll()},handleListenClick:function(){this.hass.voiceActions.listen()},handleViewSelected:function(e){var n=e.detail.item.getAttribute("data-entity")||null,t=this.currentView||null;n!==t&&this.async(function(){this.hass.viewActions.selectView(n)}.bind(this),0)},computeRefreshButtonClass:function(e){return e?"ha-spin":""},computeTitle:function(e,n){return e.length>0?"Home Assistant":n},computeShowIntroduction:function(e,n,t){return""===e&&(n||0===t.size)}})</script><style is="custom-style">html{font-size:14px;--dark-primary-color:#0288D1;--default-primary-color:#03A9F4;--primary-color:#03A9F4;--light-primary-color:#B3E5FC;--text-primary-color:#ffffff;--accent-color:#FF9800;--primary-background-color:#ffffff;--primary-text-color:#212121;--secondary-text-color:#727272;--disabled-text-color:#bdbdbd;--divider-color:#B6B6B6;--paper-toggle-button-checked-ink-color:#039be5;--paper-toggle-button-checked-button-color:#039be5;--paper-toggle-button-checked-bar-color:#039be5;--paper-slider-knob-color:var(--primary-color);--paper-slider-knob-start-color:var(--primary-color);--paper-slider-pin-color:var(--primary-color);--paper-slider-active-color:var(--primary-color);--paper-slider-secondary-color:var(--light-primary-color);--paper-slider-container-color:var(--divider-color);--google-red-500:#db4437;--google-blue-500:#4285f4;--google-green-500:#0f9d58;--google-yellow-500:#f4b400;--paper-grey-50:#fafafa;--paper-green-400:#66bb6a;--paper-blue-400:#42a5f5;--paper-orange-400:#ffa726;--dark-divider-opacity:0.12;--dark-disabled-opacity:0.38;--dark-secondary-opacity:0.54;--dark-primary-opacity:0.87;--light-divider-opacity:0.12;--light-disabled-opacity:0.3;--light-secondary-opacity:0.7;--light-primary-opacity:1.0}@-webkit-keyframes ha-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes ha-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.ha-spin{-webkit-animation:ha-spin 2s infinite linear;animation:ha-spin 2s infinite linear}</style><dom-module id="ha-style" assetpath="resources/"><template><style>:host{@apply(--paper-font-body1)}app-header,app-toolbar{background-color:var(--primary-color);font-weight:400;color:#fff}app-toolbar ha-menu-button+[main-title]{margin-left:24px}h1{@apply(--paper-font-title)}</style></template></dom-module><dom-module id="partial-panel-resolver" assetpath="layouts/"><template><style include="iron-flex ha-style">[hidden]{display:none!important}.placeholder{height:100%}.layout{height:calc(100% - 64px)}</style><div hidden$="[[resolved]]" class="placeholder"><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button></app-toolbar><div class="layout horizontal center-center"><template is="dom-if" if="[[!errorLoading]]"><paper-spinner active=""></paper-spinner></template><template is="dom-if" if="[[errorLoading]]">Error loading panel :(</template></div></div><span id="panel" hidden$="[[!resolved]]"></span></template></dom-module><script>Polymer({is:"partial-panel-resolver",behaviors:[window.hassBehavior],properties:{hass:{type:Object,observer:"updateAttributes"},narrow:{type:Boolean,value:!1,observer:"updateAttributes"},showMenu:{type:Boolean,value:!1,observer:"updateAttributes"},resolved:{type:Boolean,value:!1},errorLoading:{type:Boolean,value:!1},panel:{type:Object,bindNuclear:function(e){return e.navigationGetters.activePanel},observer:"panelChanged"}},panelChanged:function(e){return e?(this.resolved=!1,this.errorLoading=!1,void this.importHref(e.get("url"),function(){window.hassUtil.dynamicContentUpdater(this.$.panel,"ha-panel-"+e.get("component_name"),{hass:this.hass,narrow:this.narrow,showMenu:this.showMenu,panel:e.toJS()}),this.resolved=!0}.bind(this),function(){this.errorLoading=!0}.bind(this),!0)):void(this.$.panel.lastChild&&this.$.panel.removeChild(this.$.panel.lastChild))},updateAttributes:function(){var e=Polymer.dom(this.$.panel).lastChild;e&&(e.hass=this.hass,e.narrow=this.narrow,e.showMenu=this.showMenu)}})</script><dom-module id="paper-toast" assetpath="../bower_components/paper-toast/"><template><style>:host{display:block;position:fixed;background-color:var(--paper-toast-background-color,#323232);color:var(--paper-toast-color,#f1f1f1);min-height:48px;min-width:288px;padding:16px 24px;box-sizing:border-box;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:2px;margin:12px;font-size:14px;cursor:default;-webkit-transition:-webkit-transform .3s,opacity .3s;transition:transform .3s,opacity .3s;opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px);@apply(--paper-font-common-base)}:host(.capsule){border-radius:24px}:host(.fit-bottom){width:100%;min-width:0;border-radius:0;margin:0}:host(.paper-toast-open){opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}</style><span id="label">{{text}}</span><content></content></template><script>!function(){var e=null;Polymer({is:"paper-toast",behaviors:[Polymer.IronOverlayBehavior],properties:{fitInto:{type:Object,value:window,observer:"_onFitIntoChanged"},horizontalAlign:{type:String,value:"left"},verticalAlign:{type:String,value:"bottom"},duration:{type:Number,value:3e3},text:{type:String,value:""},noCancelOnOutsideClick:{type:Boolean,value:!0},noAutoFocus:{type:Boolean,value:!0}},listeners:{transitionend:"__onTransitionEnd"},get visible(){return Polymer.Base._warn("`visible` is deprecated, use `opened` instead"),this.opened},get _canAutoClose(){return this.duration>0&&this.duration!==1/0},created:function(){this._autoClose=null,Polymer.IronA11yAnnouncer.requestAvailability()},show:function(e){"string"==typeof e&&(e={text:e});for(var t in e)0===t.indexOf("_")?Polymer.Base._warn('The property "'+t+'" is private and was not set.'):t in this?this[t]=e[t]:Polymer.Base._warn('The property "'+t+'" is not valid.');this.open()},hide:function(){this.close()},__onTransitionEnd:function(e){e&&e.target===this&&"opacity"===e.propertyName&&(this.opened?this._finishRenderOpened():this._finishRenderClosed())},_openedChanged:function(){null!==this._autoClose&&(this.cancelAsync(this._autoClose),this._autoClose=null),this.opened?(e&&e!==this&&e.close(),e=this,this.fire("iron-announce",{text:this.text}),this._canAutoClose&&(this._autoClose=this.async(this.close,this.duration))):e===this&&(e=null),Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this,arguments)},_renderOpened:function(){this.classList.add("paper-toast-open")},_renderClosed:function(){this.classList.remove("paper-toast-open")},_onFitIntoChanged:function(e){this.positionTarget=e}})}()</script></dom-module><dom-module id="notification-manager" assetpath="managers/"><template><style>paper-toast{z-index:1}</style><paper-toast id="toast" text="{{text}}" no-cancel-on-outside-click="[[neg]]"></paper-toast></template></dom-module><script>Polymer({is:"notification-manager",behaviors:[window.hassBehavior],properties:{hass:{type:Object},neg:{type:Boolean,value:!1},text:{type:String,bindNuclear:function(t){return t.notificationGetters.lastNotificationMessage},observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}})</script><script>Polymer.PaperDialogBehaviorImpl={hostAttributes:{role:"dialog",tabindex:"-1"},properties:{modal:{type:Boolean,value:!1}},observers:["_modalChanged(modal, _readied)"],listeners:{tap:"_onDialogClick"},ready:function(){this.__prevNoCancelOnOutsideClick=this.noCancelOnOutsideClick,this.__prevNoCancelOnEscKey=this.noCancelOnEscKey,this.__prevWithBackdrop=this.withBackdrop},_modalChanged:function(i,e){e&&(i?(this.__prevNoCancelOnOutsideClick=this.noCancelOnOutsideClick,this.__prevNoCancelOnEscKey=this.noCancelOnEscKey,this.__prevWithBackdrop=this.withBackdrop,this.noCancelOnOutsideClick=!0,this.noCancelOnEscKey=!0,this.withBackdrop=!0):(this.noCancelOnOutsideClick=this.noCancelOnOutsideClick&&this.__prevNoCancelOnOutsideClick,this.noCancelOnEscKey=this.noCancelOnEscKey&&this.__prevNoCancelOnEscKey,this.withBackdrop=this.withBackdrop&&this.__prevWithBackdrop))},_updateClosingReasonConfirmed:function(i){this.closingReason=this.closingReason||{},this.closingReason.confirmed=i},_onDialogClick:function(i){for(var e=Polymer.dom(i).path,o=0;o<e.indexOf(this);o++){var t=e[o];if(t.hasAttribute&&(t.hasAttribute("dialog-dismiss")||t.hasAttribute("dialog-confirm"))){this._updateClosingReasonConfirmed(t.hasAttribute("dialog-confirm")),this.close(),i.stopPropagation();break}}}},Polymer.PaperDialogBehavior=[Polymer.IronOverlayBehavior,Polymer.PaperDialogBehaviorImpl]</script><dom-module id="paper-dialog-shared-styles" assetpath="../bower_components/paper-dialog-behavior/"><template><style>:host{display:block;margin:24px 40px;background:var(--paper-dialog-background-color,--primary-background-color);color:var(--paper-dialog-color,--primary-text-color);@apply(--paper-font-body1);@apply(--shadow-elevation-16dp);@apply(--paper-dialog)}:host>::content>*{margin-top:20px;padding:0 24px}:host>::content>.no-padding{padding:0}:host>::content>:first-child{margin-top:24px}:host>::content>:last-child{margin-bottom:24px}:host>::content h2{position:relative;margin:0;@apply(--paper-font-title);@apply(--paper-dialog-title)}:host>::content .buttons{position:relative;padding:8px 8px 8px 24px;margin:0;color:var(--paper-dialog-button-color,--primary-color);@apply(--layout-horizontal);@apply(--layout-end-justified)}</style></template></dom-module><dom-module id="paper-dialog" assetpath="../bower_components/paper-dialog/"><template><style include="paper-dialog-shared-styles"></style><content></content></template></dom-module><script>!function(){Polymer({is:"paper-dialog",behaviors:[Polymer.PaperDialogBehavior,Polymer.NeonAnimationRunnerBehavior],listeners:{"neon-animation-finish":"_onNeonAnimationFinish"},_renderOpened:function(){this.cancelAnimation(),this.playAnimation("entry")},_renderClosed:function(){this.cancelAnimation(),this.playAnimation("exit")},_onNeonAnimationFinish:function(){this.opened?this._finishRenderOpened():this._finishRenderClosed()}})}()</script><dom-module id="paper-dialog-scrollable" assetpath="../bower_components/paper-dialog-scrollable/"><template><style>:host{display:block;@apply(--layout-relative)}:host(.is-scrolled:not(:first-child))::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:var(--divider-color)}:host(.can-scroll:not(.scrolled-to-bottom):not(:last-child))::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:var(--divider-color)}.scrollable{padding:0 24px;@apply(--layout-scroll);@apply(--paper-dialog-scrollable)}.fit{@apply(--layout-fit)}</style><div id="scrollable" class="scrollable"><content></content></div></template></dom-module><script>Polymer({is:"paper-dialog-scrollable",properties:{dialogElement:{type:Object}},listeners:{"scrollable.scroll":"_scroll"},get scrollTarget(){return this.$.scrollable},ready:function(){this._ensureTarget()},attached:function(){this.classList.add("no-padding"),this._ensureTarget(),requestAnimationFrame(this._scroll.bind(this))},_scroll:function(){this.toggleClass("is-scrolled",this.scrollTarget.scrollTop>0),this.toggleClass("can-scroll",this.scrollTarget.offsetHeight<this.scrollTarget.scrollHeight),this.toggleClass("scrolled-to-bottom",this.scrollTarget.scrollTop+this.scrollTarget.offsetHeight>=this.scrollTarget.scrollHeight)},_ensureTarget:function(){this.dialogElement=this.dialogElement||Polymer.dom(this).parentNode,this.dialogElement&&this.dialogElement.behaviors&&this.dialogElement.behaviors.indexOf(Polymer.PaperDialogBehaviorImpl)>=0?(this.dialogElement.sizingTarget=this.scrollTarget,this.scrollTarget.classList.remove("fit")):this.dialogElement&&this.scrollTarget.classList.add("fit")}})</script><script>!function(){"use strict";Polymer.IronJsonpLibraryBehavior={properties:{libraryLoaded:{type:Boolean,value:!1,notify:!0,readOnly:!0},libraryErrorMessage:{type:String,value:null,notify:!0,readOnly:!0}},observers:["_libraryUrlChanged(libraryUrl)"],_libraryUrlChanged:function(r){this._isReady&&this.libraryUrl&&this._loadLibrary()},_libraryLoadCallback:function(r,i){r?(console.warn("Library load failed:",r.message),this._setLibraryErrorMessage(r.message)):(this._setLibraryErrorMessage(null),this._setLibraryLoaded(!0),this.notifyEvent&&this.fire(this.notifyEvent,i))},_loadLibrary:function(){r.require(this.libraryUrl,this._libraryLoadCallback.bind(this),this.callbackName)},ready:function(){this._isReady=!0,this.libraryUrl&&this._loadLibrary()}};var r={apiMap:{},require:function(r,t,e){var a=this.nameFromUrl(r);this.apiMap[a]||(this.apiMap[a]=new i(a,r,e)),this.apiMap[a].requestNotify(t)},nameFromUrl:function(r){return r.replace(/[\:\/\%\?\&\.\=\-\,]/g,"_")+"_api"}},i=function(r,i,t){if(this.notifiers=[],!t){if(!(i.indexOf(this.callbackMacro)>=0))return void(this.error=new Error("IronJsonpLibraryBehavior a %%callback%% parameter is required in libraryUrl"));t=r+"_loaded",i=i.replace(this.callbackMacro,t)}this.callbackName=t,window[this.callbackName]=this.success.bind(this),this.addScript(i)};i.prototype={callbackMacro:"%%callback%%",loaded:!1,addScript:function(r){var i=document.createElement("script");i.src=r,i.onerror=this.handleError.bind(this);var t=document.querySelector("script")||document.body;t.parentNode.insertBefore(i,t),this.script=i},removeScript:function(){this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.script=null},handleError:function(r){this.error=new Error("Library failed to load"),this.notifyAll(),this.cleanup()},success:function(){this.loaded=!0,this.result=Array.prototype.slice.call(arguments),this.notifyAll(),this.cleanup()},cleanup:function(){delete window[this.callbackName]},notifyAll:function(){this.notifiers.forEach(function(r){r(this.error,this.result)}.bind(this)),this.notifiers=[]},requestNotify:function(r){this.loaded||this.error?r(this.error,this.result):this.notifiers.push(r)}}}()</script><script>Polymer({is:"iron-jsonp-library",behaviors:[Polymer.IronJsonpLibraryBehavior],properties:{libraryUrl:String,callbackName:String,notifyEvent:String}})</script><script>Polymer({is:"google-legacy-loader",behaviors:[Polymer.IronJsonpLibraryBehavior],properties:{libraryUrl:{type:String,value:"https://www.google.com/jsapi?callback=%%callback%%"},notifyEvent:{type:String,value:"api-load"}},get api(){return google}})</script><style>div.charts-tooltip{z-index:200!important}</style><script>Polymer({is:"state-history-chart-timeline",properties:{data:{type:Object,observer:"dataChanged"},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){function t(t,e,n,i){var d=e.replace(/_/g," ");a.addRow([t,d,n,i])}var e,a,n,i,d,l=Polymer.dom(this),o=this.data;if(this.isAttached){for(;l.node.lastChild;)l.node.removeChild(l.node.lastChild);o&&0!==o.length&&(e=new window.google.visualization.Timeline(this),a=new window.google.visualization.DataTable,a.addColumn({type:"string",id:"Entity"}),a.addColumn({type:"string",id:"State"}),a.addColumn({type:"date",id:"Start"}),a.addColumn({type:"date",id:"End"}),n=new Date(o.reduce(function(t,e){return Math.min(t,e[0].lastChangedAsDate)},new Date)),i=new Date(n),i.setDate(i.getDate()+1),i>new Date&&(i=new Date),d=0,o.forEach(function(e){var a,n,l=null,o=null;0!==e.length&&(a=e[0].entityDisplay,e.forEach(function(e){null!==l&&e.state!==l?(n=e.lastChangedAsDate,t(a,l,o,n),l=e.state,o=n):null===l&&(l=e.state,o=e.lastChangedAsDate)}),t(a,l,o,i),d++)}),e.draw(a,{height:55+42*d,timeline:{showRowLabels:o.length>1},hAxis:{format:"H:mm"}}))}}})</script><script>!function(){"use strict";function t(t,e){var a,r=[];for(a=t;a<e;a++)r.push(a);return r}function e(t){var e=parseFloat(t);return!isNaN(e)&&isFinite(e)?e:null}Polymer({is:"state-history-chart-line",properties:{data:{type:Object,observer:"dataChanged"},unit:{type:String},isSingleDevice:{type:Boolean,value:!1},isAttached:{type:Boolean,value:!1,observer:"dataChanged"},chartEngine:{type:Object}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){var a,r,n,i,u,o=this.unit,s=this.data;this.isAttached&&(this.chartEngine||(this.chartEngine=new window.google.visualization.LineChart(this)),0!==s.length&&(a={legend:{position:"top"},interpolateNulls:!0,titlePosition:"none",vAxes:{0:{title:o}},hAxis:{format:"H:mm"},chartArea:{left:"60",width:"95%"},explorer:{actions:["dragToZoom","rightClickToReset","dragToPan"],keepInBounds:!0,axis:"horizontal",maxZoomIn:.1}},this.isSingleDevice&&(a.legend.position="none",a.vAxes[0].title=null,a.chartArea.left=40,a.chartArea.height="80%",a.chartArea.top=5,a.enableInteractivity=!1),r=new Date(Math.min.apply(null,s.map(function(t){return t[0].lastChangedAsDate}))),n=new Date(r),n.setDate(n.getDate()+1),n>new Date&&(n=new Date),i=s.map(function(t){function a(t,e){r&&e&&c.push([t[0]].concat(r.slice(1).map(function(t,a){return e[a]?t:null}))),c.push(t),r=t}var r,i,u,o,s=t[t.length-1],l=s.domain,d=s.entityDisplay,c=[],h=new window.google.visualization.DataTable;return h.addColumn({type:"datetime",id:"Time"}),"thermostat"===l?(i=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1),h.addColumn("number",d+" current temperature"),i?(h.addColumn("number",d+" target temperature high"),h.addColumn("number",d+" target temperature low"),o=[!1,!0,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.target_temp_high),i=e(t.attributes.target_temp_low);a([t.lastUpdatedAsDate,r,n,i],o)}):(h.addColumn("number",d+" target temperature"),o=[!1,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.temperature);a([t.lastUpdatedAsDate,r,n],o)}),t.forEach(u)):"climate"===l?(i=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1),h.addColumn("number",d+" current temperature"),i?(h.addColumn("number",d+" target temperature high"),h.addColumn("number",d+" target temperature low"),o=[!1,!0,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.target_temp_high),i=e(t.attributes.target_temp_low);a([t.lastUpdatedAsDate,r,n,i],o)}):(h.addColumn("number",d+" target temperature"),o=[!1,!0],u=function(t){var r=e(t.attributes.current_temperature),n=e(t.attributes.temperature);a([t.lastUpdatedAsDate,r,n],o)}),t.forEach(u)):(h.addColumn("number",d),o="sensor"!==l&&[!0],t.forEach(function(t){var r=e(t.state);a([t.lastChangedAsDate,r],o)})),a([n].concat(r.slice(1)),!1),h.addRows(c),h}),u=1===i.length?i[0]:i.slice(1).reduce(function(e,a){return window.google.visualization.data.join(e,a,"full",[[0,0]],t(1,e.getNumberOfColumns()),t(1,a.getNumberOfColumns()))},i[0]),this.chartEngine.draw(u,a)))}})}()</script><dom-module id="state-history-charts" assetpath="components/"><template><style>:host{display:block}.loading-container{text-align:center;padding:8px}.loading{height:0;overflow:hidden}</style><google-legacy-loader on-api-load="googleApiLoaded"></google-legacy-loader><div hidden$="[[!isLoading]]" class="loading-container"><paper-spinner active="" alt="Updating history data"></paper-spinner></div><div class$="[[computeContentClasses(isLoading)]]"><template is="dom-if" if="[[computeIsEmpty(stateHistory)]]">No state history found.</template><state-history-chart-timeline data="[[groupedStateHistory.timeline]]" is-single-device="[[isSingleDevice]]"></state-history-chart-timeline><template is="dom-repeat" items="[[groupedStateHistory.line]]"><state-history-chart-line unit="[[item.unit]]" data="[[item.data]]" is-single-device="[[isSingleDevice]]"></state-history-chart-line></template></div></template></dom-module><script>Polymer({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){var i,o={},n=[];return t||!e?{line:[],timeline:[]}:(e.forEach(function(t){var e,i;t&&0!==t.size&&(e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=!!e&&e.attributes.unit_of_measurement,i?i in o?o[i].push(t.toArray()):o[i]=[t.toArray()]:n.push(t.toArray()))}),n=n.length>0&&n,i=Object.keys(o).map(function(t){return{unit:t,data:o[t]}}),{line:i,timeline:n})},googleApiLoaded:function(){window.google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){this.apiLoaded=!0}.bind(this)})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size}})</script><dom-module id="more-info-automation" assetpath="more-infos/"><template><style>paper-button{color:var(--default-primary-color);font-weight:500;top:3px;height:37px}</style><p>Last triggered:<ha-relative-time datetime="[[stateObj.attributes.last_triggered]]"></ha-relative-time></p><paper-button on-tap="handleTriggerTapped">TRIGGER</paper-button></template></dom-module><script>Polymer({is:"more-info-automation",properties:{hass:{type:Object},stateObj:{type:Object}},handleTriggerTapped:function(){this.hass.serviceActions.callService("automation","trigger",{entity_id:this.stateObj.entityId})}})</script><dom-module id="paper-range-slider" assetpath="../bower_components/paper-range-slider/"><template><style>:host{--paper-range-slider-width:200px;@apply(--layout);@apply(--layout-justified);@apply(--layout-center);--paper-range-slider-lower-color:var(--paper-grey-400);--paper-range-slider-active-color:var(--primary-color);--paper-range-slider-higher-color:var(--paper-grey-400);--paper-range-slider-knob-color:var(--primary-color);--paper-range-slider-pin-color:var(--primary-color);--paper-range-slider-pin-start-color:var(--paper-grey-400);--paper-range-slider-knob-start-color:transparent;--paper-range-slider-knob-start-border-color:var(--paper-grey-400)}#sliderOuterDiv_0{display:inline-block;width:var(--paper-range-slider-width)}#sliderOuterDiv_1{position:relative;height:calc(30px + var(--paper-slider-height,2px));margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}.sliderKnobMinMax{position:absolute;left:0;top:0;margin-left:calc(-15px - var(--paper-slider-height,2px)/ 2);width:calc(30px + var(--paper-slider-height,2px));height:calc(30px + var(--paper-slider-height,2px))}.divSpanWidth{position:absolute;width:100%;display:block;top:0}#sliderMax{--paper-slider-bar-color:transparent;--paper-slider-knob-color:var(--paper-range-slider-knob-color);--paper-slider-pin-color:var(--paper-range-slider-pin-color);--paper-slider-active-color:var(--paper-range-slider-active-color);--paper-slider-secondary-color:var(--paper-range-slider-higher-color);--paper-slider-pin-start-color:var(--paper-range-slider-pin-start-color);--paper-slider-knob-start-color:var(--paper-range-slider-knob-start-color);--paper-slider-knob-start-border-color:var(--paper-range-slider-knob-start-border-color)}#sliderMin{--paper-slider-active-color:var(--paper-range-slider-lower-color);--paper-slider-secondary-color:transparent;--paper-slider-knob-color:var(--paper-range-slider-knob-color);--paper-slider-pin-color:var(--paper-range-slider-pin-color);--paper-slider-pin-start-color:var(--paper-range-slider-pin-start-color);--paper-slider-knob-start-color:var(--paper-range-slider-knob-start-color);--paper-slider-knob-start-border-color:var(--paper-range-slider-knob-start-border-color)}</style><div id="sliderOuterDiv_0" style=""><div id="sliderOuterDiv_1"><div id="backDiv" class="divSpanWidth" on-down="_backDivDown" on-tap="_backDivTap" on-up="_backDivUp" on-track="_backDivOnTrack" on-transitionend="_backDivTransEnd"><div id="backDivInner_0" style="line-height:200%"><br></div></div><div id="sliderKnobMin" class="sliderKnobMinMax" on-down="_backDivDown" on-up="_backDivUp" on-track="_sliderKnobMinOnTrack"></div><div id="sliderKnobMax" class="sliderKnobMinMax" on-down="_backDivDown" on-up="_backDivUp" on-track="_sliderKnobMaxOnTrack"></div><div class="divSpanWidth" style="pointer-events:none"><paper-slider id="sliderMax" disabled$="[[disabled]]" on-down="_sliderMaxDown" on-up="_sliderMaxUp" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMax]]" secondary-progress="[[max]]" style="width:100%"></paper-slider></div><div class="divSpanWidth" style="pointer-events:none"><paper-slider id="sliderMin" disabled$="[[disabled]]" on-down="_sliderMinDown" on-up="_sliderMinUp" noink="" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMin]]" style="width:100%"></paper-slider></div><div id="backDivInner_1" style="line-height:100%"><br></div></div></div></template><script>Polymer({is:"paper-range-slider",behaviors:[Polymer.IronRangeBehavior],properties:{sliderWidth:{type:String,value:"",notify:!0,reflectToAttribute:!0},style:{type:String,value:"",notify:!0,reflectToAttribute:!0},min:{type:Number,value:0,notify:!0,reflectToAttribute:!0},max:{type:Number,value:100,notify:!0,reflectToAttribute:!0},valueMin:{type:Number,value:0,notify:!0,reflectToAttribute:!0},valueMax:{type:Number,value:100,notify:!0,reflectToAttribute:!0},step:{type:Number,value:1,notify:!0,reflectToAttribute:!0},valueDiffMin:{type:Number,value:0,notify:!0,reflectToAttribute:!0},valueDiffMax:{type:Number,value:0,notify:!0,reflectToAttribute:!0},alwaysShowPin:{type:Boolean,value:!1,notify:!0},pin:{type:Boolean,value:!1,notify:!0},snaps:{type:Boolean,value:!1,notify:!0},disabled:{type:Boolean,value:!1,notify:!0},singleSlider:{type:Boolean,value:!1,notify:!0},transDuration:{type:Number,value:250},tapValueExtend:{type:Boolean,value:!0,notify:!0},tapValueReduce:{type:Boolean,value:!1,notify:!0},tapValueMove:{type:Boolean,value:!1,notify:!0}},ready:function(){function i(i){return void 0!=i&&null!=i}i(this._nInitTries)||(this._nInitTries=0);var t=this.$$("#sliderMax").$$("#sliderContainer");if(i(t)&&(t=t.offsetWidth>0),i(t)&&(t=this.$$("#sliderMin").$$("#sliderContainer")),i(t)&&(t=t.offsetWidth>0),i(t))this._renderedReady();else{if(this._nInitTries<1e3){var e=this;setTimeout(function(){e.ready()},10)}else console.error("could not properly initialize the underlying paper-slider elements ...");this._nInitTries++}},_renderedReady:function(){this.init(),this._setPadding();var i=this;this.$$("#sliderMin").addEventListener("immediate-value-change",function(t){i._setValueMinMax(i._getValuesMinMax(this.immediateValue,null)),i.$.sliderMin._expandKnob(),i.$.sliderMax._expandKnob()}),this.$$("#sliderMax").addEventListener("immediate-value-change",function(t){i._setValueMinMax(i._getValuesMinMax(null,this.immediateValue))}),this.$$("#sliderMin").addEventListener("change",function(t){i._setValueMinMax(i._getValuesMinMax(this.immediateValue,null)),i.alwaysShowPin&&i.$.sliderMin._expandKnob()}),this.$$("#sliderMax").addEventListener("change",function(t){i._setValueMinMax(i._getValuesMinMax(null,this.immediateValue)),i.alwaysShowPin&&i.$.sliderMax._expandKnob()})},_setPadding:function(){var i=document.createElement("div");i.setAttribute("style","position:absolute; top:0px; opacity:0;"),i.innerHTML="invisibleText",document.body.insertBefore(i,document.body.children[0]);var t=i.offsetHeight/2;this.style.paddingTop=t+"px",this.style.paddingBottom=t+"px",i.parentNode.removeChild(i)},_setValueDiff:function(){this._valueDiffMax=Math.max(this.valueDiffMax,0),this._valueDiffMin=Math.max(this.valueDiffMin,0)},_getValuesMinMax:function(i,t){var e=null!=i&&i>=this.min&&i<=this.max,s=null!=t&&t>=this.min&&t<=this.max;if(!e&&!s)return[this.valueMin,this.valueMax];var n=e?i:this.valueMin,a=s?t:this.valueMax;n=Math.min(Math.max(n,this.min),this.max),a=Math.min(Math.max(a,this.min),this.max);var l=a-n;return e?l<this._valueDiffMin?(a=Math.min(this.max,n+this._valueDiffMin),l=a-n,l<this._valueDiffMin&&(n=a-this._valueDiffMin)):l>this._valueDiffMax&&this._valueDiffMax>0&&(a=n+this._valueDiffMax):l<this._valueDiffMin?(n=Math.max(this.min,a-this._valueDiffMin),l=a-n,l<this._valueDiffMin&&(a=n+this._valueDiffMin)):l>this._valueDiffMax&&this._valueDiffMax>0&&(n=a-this._valueDiffMax),[n,a]},_setValueMin:function(i){i=Math.max(i,this.min),this.$$("#sliderMin").value=i,this.valueMin=i},_setValueMax:function(i){i=Math.min(i,this.max),this.$$("#sliderMax").value=i,this.valueMax=i},_setValueMinMax:function(i){this._setValueMin(i[0]),this._setValueMax(i[1]),this._updateSliderKnobMinMax(),this.updateValues()},_setValues:function(i,t){null!=i&&(i<this.min||i>this.max)&&(i=null),null!=t&&(t<this.min||t>this.max)&&(t=null),null!=i&&null!=t&&(i=Math.min(i,t)),this._setValueMinMax(this._getValuesMinMax(i,t))},_updateSliderKnobMinMax:function(){var i=this.$$("#sliderMax").$$("#sliderContainer").offsetWidth,t=i*(this.valueMin-this.min)/(this.max-this.min)+.5*this.$$("#sliderKnobMin").offsetWidth,e=i*(this.valueMax-this.min)/(this.max-this.min)+.5*this.$$("#sliderKnobMax").offsetWidth;this.$$("#sliderKnobMin").style.left=t+"px",this.$$("#sliderKnobMax").style.left=e+"px"},_backDivOnTrack:function(i){switch(i.stopPropagation(),i.detail.state){case"start":this._backDivTrackStart(i);break;case"track":this._backDivTrackDuring(i);break;case"end":this._backDivTrackEnd()}},_backDivTrackStart:function(i){},_backDivTrackDuring:function(i){this._x1_Min=this._x0_Min+i.detail.dx;var t=this._calcStep(this._getRatioPos(this.$$("#sliderMin"),this._x1_Min/this._xWidth));this._x1_Max=this._x0_Max+i.detail.dx;var e=this._calcStep(this._getRatioPos(this.$$("#sliderMax"),this._x1_Max/this._xWidth));t>=this.min&&e<=this.max&&this._setValuesWithCurrentDiff(t,e,!1)},_setValuesWithCurrentDiff:function(i,t,e){var s=this._valueDiffMin,n=this._valueDiffMax;this._valueDiffMin=this.valueMax-this.valueMin,this._valueDiffMax=this.valueMax-this.valueMin,e?this.setValues(i,t):this._setValues(i,t),this._valueDiffMin=s,this._valueDiffMax=n},_backDivTrackEnd:function(){},_sliderKnobMinOnTrack:function(i){this._x1_Min=this._x0_Min+i.detail.dx;var t=this._calcStep(this._getRatioPos(this.$$("#sliderMin"),this._x1_Min/this._xWidth));this._setValues(t,null)},_sliderKnobMaxOnTrack:function(i){this._x1_Max=this._x0_Max+i.detail.dx;var t=this._calcStep(this._getRatioPos(this.$$("#sliderMax"),this._x1_Max/this._xWidth));this._setValues(null,t)},_sliderMinDown:function(){this.$$("#sliderMax")._expandKnob()},_sliderMaxDown:function(i){this.singleSlider?this._setValues(null,this._getEventValue(i)):this.$$("#sliderMin")._expandKnob()},_sliderMinUp:function(){this.alwaysShowPin?this.$$("#sliderMin")._expandKnob():this.$$("#sliderMax")._resetKnob()},_sliderMaxUp:function(){this.alwaysShowPin?this.$$("#sliderMax")._expandKnob():(this.$$("#sliderMin")._resetKnob(),this.singleSlider&&this.$$("#sliderMax")._resetKnob())},_getEventValue:function(i){var t=this.$$("#sliderMax").$$("#sliderContainer").offsetWidth,e=this.$$("#sliderMax").$$("#sliderContainer").getBoundingClientRect(),s=(i.detail.x-e.left)/t,n=this.min+s*(this.max-this.min);return n},_backDivTap:function(i){this._setValueNow=function(i,t){this.tapValueMove?this._setValuesWithCurrentDiff(i,t,!0):this.setValues(i,t)};var t=this._getEventValue(i);if(t>this.valueMin&&t<this.valueMax){if(this.tapValueReduce){var e=t<this.valueMin+(this.valueMax-this.valueMin)/2;e?this._setValueNow(t,null):this._setValueNow(null,t)}}else(this.tapValueExtend||this.tapValueMove)&&(t<this.valueMin&&this._setValueNow(t,null),t>this.valueMax&&this._setValueNow(null,t))},_backDivDown:function(i){this._sliderMinDown(),this._sliderMaxDown(),this._xWidth=this.$$("#sliderMin").$$("#sliderBar").offsetWidth,this._x0_Min=this.$$("#sliderMin").ratio*this._xWidth,this._x0_Max=this.$$("#sliderMax").ratio*this._xWidth},_backDivUp:function(){this._sliderMinUp(),this._sliderMaxUp()},_backDivTransEnd:function(i){},_getRatioPos:function(i,t){return Math.max(i.min,Math.min(i.max,(i.max-i.min)*t+i.min))},init:function(){this.setSingleSlider(this.singleSlider),this.setDisabled(this.disabled),this.alwaysShowPin&&(this.pin=!0),this.$$("#sliderMin").pin=this.pin,this.$$("#sliderMax").pin=this.pin,this.$$("#sliderMin").snaps=this.snaps,this.$$("#sliderMax").snaps=this.snaps,""!=this.sliderWidth&&(this.customStyle["--paper-range-slider-width"]=this.sliderWidth,this.updateStyles()),this.$$("#sliderMin").$$("#sliderBar").$$("#progressContainer").style.background="transparent",this._prevUpdateValues=[this.min,this.max],this._setValueDiff(),this._setValueMinMax(this._getValuesMinMax(this.valueMin,this.valueMax)),this.alwaysShowPin&&(this.$$("#sliderMin")._expandKnob(),this.$$("#sliderMax")._expandKnob())},setValues:function(i,t){this.$$("#sliderMin")._setTransiting(!0),this.$$("#sliderMax")._setTransiting(!0),this._setValues(i,t);var e=this;setTimeout(function(){e.$.sliderMin._setTransiting(!1),e.$.sliderMax._setTransiting(!1)},e.transDuration)},updateValues:function(){this._prevUpdateValues[0]==this.valueMin&&this._prevUpdateValues[1]==this.valueMax||(this._prevUpdateValues=[this.valueMin,this.valueMax],this.async(function(){this.fire("updateValues")}))},setMin:function(i){this.max<i&&(this.max=i),this.min=i,this._prevUpdateValues=[this.min,this.max],this.valueMin<this.min?this._setValues(this.min,null):this._updateSliderKnobMinMax()},setMax:function(i){this.min>i&&(this.min=i),this.max=i,this._prevUpdateValues=[this.min,this.max],this.valueMax>this.max?this._setValues(null,this.max):this._updateSliderKnobMinMax()},setStep:function(i){this.step=i},setValueDiffMin:function(i){this._valueDiffMin=i},setValueDiffMax:function(i){this._valueDiffMax=i},setTapValueExtend:function(i){this.tapValueExtend=i},setTapValueReduce:function(i){this.tapValueReduce=i},setTapValueMove:function(i){this.tapValueMove=i},setDisabled:function(i){this.disabled=i;var t=i?"none":"auto";this.$$("#sliderMax").$$("#sliderKnobInner").style.pointerEvents=t,this.$$("#sliderMin").$$("#sliderKnobInner").style.pointerEvents=t,this.$$("#sliderOuterDiv_1").style.pointerEvents=t,this.$$("#sliderKnobMin").style.pointerEvents=t,this.$$("#sliderKnobMax").style.pointerEvents=t},setSingleSlider:function(i){this.singleSlider=i,i?(this.$$("#backDiv").style.display="none",this.$$("#sliderMax").style.pointerEvents="auto",this.$$("#sliderMax").style.display="",this.$$("#sliderMin").style.display="none",this.$$("#sliderKnobMin").style.pointerEvents="none",this.$$("#sliderKnobMax").style.pointerEvents="none"):(this.$$("#backDiv").style.display="block",this.$$("#sliderMax").style.pointerEvents="none",this.$$("#sliderMax").style.display="",this.$$("#sliderMin").style.display="",this.$$("#sliderKnobMin").style.pointerEvents="auto",this.$$("#sliderKnobMax").style.pointerEvents="auto"),this.$$("#sliderMax").$$("#sliderContainer").style.pointerEvents=this.singleSlider?"auto":"none",this.$$("#sliderMin").$$("#sliderContainer").style.pointerEvents="none"}})</script></dom-module><dom-module id="more-info-climate" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>:host{color:var(--primary-text-color);--paper-input-container-input:{text-transform:capitalize};}.container-aux_heat,.container-away_mode,.container-fan_list,.container-humidity,.container-operation_list,.container-swing_list,.container-temperature{display:none}.has-aux_heat .container-aux_heat,.has-away_mode .container-away_mode,.has-fan_list .container-fan_list,.has-humidity .container-humidity,.has-operation_list .container-operation_list,.has-swing_list .container-swing_list,.has-temperature .container-temperature{display:block}.container-fan_list iron-icon,.container-operation_list iron-icon,.container-swing_list iron-icon{margin:22px 16px 0 0}paper-dropdown-menu{width:100%}.heat{--paper-slider-active-color:var(--paper-orange-400);--paper-slider-secondary-color:var(--paper-green-400)}.cool{--paper-slider-active-color:var(--paper-green-400);--paper-slider-secondary-color:var(--paper-blue-400)}#temp-range-slider{--paper-range-slider-lower-color:var(--paper-orange-400);--paper-range-slider-active-color:var(--paper-green-400);--paper-range-slider-higher-color:var(--paper-blue-400);--paper-range-slider-knob-color:var(--primary-color);--paper-range-slider-pin-color:var(--primary-color)}.single-row{padding:8px 0}.capitalize{text-transform:capitalize}</style><div class$="[[computeClassNames(stateObj)]]"><div class="container-temperature"><div class$="single-row, [[stateObj.attributes.operation_mode]]"><div hidden$="[[computeTargetTempHidden(stateObj)]]">Target Temperature</div><paper-slider id="temp-slider" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" secondary-progress="[[stateObj.attributes.max_temp]]" pin="" step="0.5" value="[[stateObj.attributes.temperature]]" hidden$="[[computeHideTempSlider(stateObj)]]" on-change="targetTemperatureSliderChanged"></paper-slider><paper-range-slider id="temp-range-slider" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" pin="" step="0.5" value-min="[[stateObj.attributes.target_temp_low]]" value-max="[[stateObj.attributes.target_temp_high]]" value-diff-min="2" hidden$="[[computeHideTempRangeSlider(stateObj)]]" on-change="targetTemperatureRangeSliderChanged"></paper-range-slider></div></div><div class="container-humidity"><div class="single-row"><div>Target Humidity</div><paper-slider min="[[stateObj.attributes.min_humidity]]" max="[[stateObj.attributes.max_humidity]]" step="1" pin="" value="[[stateObj.attributes.humidity]]" on-change="targetHumiditySliderChanged"></paper-slider></div></div><div class="container-operation_list"><div class="controls"><paper-dropdown-menu label-float="" label="Operation"><paper-menu class="dropdown-content" selected="{{operationIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.operation_list]]"><paper-item class="capitalize">[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div><div class="container-fan_list"><paper-dropdown-menu label-float="" label="Fan Mode"><paper-menu class="dropdown-content" selected="{{fanIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.fan_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-swing_list"><paper-dropdown-menu label-float="" label="Swing Mode"><paper-menu class="dropdown-content" selected="{{swingIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.swing_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-away_mode"><div class="center horizontal layout single-row"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="awayToggleChanged"></paper-toggle-button></div></div><div class="container-aux_heat"><div class="center horizontal layout single-row"><div class="flex">Aux Heat</div><paper-toggle-button checked="[[auxToggleChecked]]" on-change="auxToggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-climate",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},operationIndex:{type:Number,value:-1,observer:"handleOperationmodeChanged"},fanIndex:{type:Number,value:-1,observer:"handleFanmodeChanged"},swingIndex:{type:Number,value:-1,observer:"handleSwingmodeChanged"},awayToggleChecked:{type:Boolean},auxToggleChecked:{type:Boolean}},stateObjChanged:function(e){this.targetTemperatureSliderValue=e.attributes.temperature,this.awayToggleChecked="on"===e.attributes.away_mode,this.auxheatToggleChecked="on"===e.attributes.aux_heat,e.attributes.fan_list?this.fanIndex=e.attributes.fan_list.indexOf(e.attributes.fan_mode):this.fanIndex=-1,e.attributes.operation_list?this.operationIndex=e.attributes.operation_list.indexOf(e.attributes.operation_mode):this.operationIndex=-1,e.attributes.swing_list?this.swingIndex=e.attributes.swing_list.indexOf(e.attributes.swing_mode):this.swingIndex=-1,this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeTargetTempHidden:function(e){return!e.attributes.temperature&&!e.attributes.target_temp_low&&!e.attributes.target_temp_high},computeHideTempRangeSlider:function(e){return!e.attributes.target_temp_low&&!e.attributes.target_temp_high},computeHideTempSlider:function(e){return!e.attributes.temperature},computeClassNames:function(e){return"more-info-climate "+window.hassUtil.attributeClassNames(e,["away_mode","aux_heat","temperature","humidity","operation_list","fan_list","swing_list"])},targetTemperatureSliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.temperature&&this.callServiceHelper("set_temperature",{temperature:t})},targetTemperatureRangeSliderChanged:function(e){var t=e.currentTarget.valueMin,a=e.currentTarget.valueMax;t===this.stateObj.attributes.target_temp_low&&a===this.stateObj.attributes.target_temp_high||this.callServiceHelper("set_temperature",{target_temp_low:t,target_temp_high:a})},targetHumiditySliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.humidity&&this.callServiceHelper("set_humidity",{humidity:t})},awayToggleChanged:function(e){var t="on"===this.stateObj.attributes.away_mode,a=e.target.checked;t!==a&&this.callServiceHelper("set_away_mode",{away_mode:a})},auxToggleChanged:function(e){var t="on"===this.stateObj.attributes.aux_heat,a=e.target.checked;t!==a&&this.callServiceHelper("set_aux_heat",{aux_heat:a})},handleFanmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.fan_list[e],t!==this.stateObj.attributes.fan_mode&&this.callServiceHelper("set_fan_mode",{fan_mode:t}))},handleOperationmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.operation_list[e],t!==this.stateObj.attributes.operation_mode&&this.callServiceHelper("set_operation_mode",{operation_mode:t}))},handleSwingmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.swing_list[e],t!==this.stateObj.attributes.swing_mode&&this.callServiceHelper("set_swing_mode",{swing_mode:t}))},callServiceHelper:function(e,t){t.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("climate",e,t).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><dom-module id="more-info-cover" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.current_position,.current_tilt_position{max-height:0;overflow:hidden}.has-current_position .current_position,.has-current_tilt_position .current_tilt_position{max-height:90px}</style><div class$="[[computeClassNames(stateObj)]]"><div class="current_position"><div>Position</div><paper-slider min="0" max="100" value="{{coverPositionSliderValue}}" step="1" pin="" on-change="coverPositionSliderChanged"></paper-slider></div><div class="current_tilt_position"><div>Tilt position</div><paper-icon-button icon="mdi:arrow-top-right" on-tap="onOpenTiltTap" title="Open tilt" disabled="[[computeIsFullyOpenTilt(stateObj)]]"></paper-icon-button><paper-icon-button icon="mdi:stop" on-tap="onStopTiltTap" title="Stop tilt"></paper-icon-button><paper-icon-button icon="mdi:arrow-bottom-left" on-tap="onCloseTiltTap" title="Close tilt" disabled="[[computeIsFullyClosedTilt(stateObj)]]"></paper-icon-button><paper-slider min="0" max="100" value="{{coverTiltPositionSliderValue}}" step="1" pin="" on-change="coverTiltPositionSliderChanged"></paper-slider></div></div></template></dom-module><script>Polymer({is:"more-info-cover",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},coverPositionSliderValue:{type:Number},coverTiltPositionSliderValue:{type:Number}},stateObjChanged:function(t){this.coverPositionSliderValue=t.attributes.current_position,this.coverTiltPositionSliderValue=t.attributes.current_tilt_position},computeClassNames:function(t){return window.hassUtil.attributeClassNames(t,["current_position","current_tilt_position"])},coverPositionSliderChanged:function(t){this.hass.serviceActions.callService("cover","set_cover_position",{entity_id:this.stateObj.entityId,position:t.target.value})},coverTiltPositionSliderChanged:function(t){this.hass.serviceActions.callService("cover","set_cover_tilt_position",{entity_id:this.stateObj.entityId,tilt_position:t.target.value})},computeIsFullyOpenTilt:function(t){return 100===t.attributes.current_tilt_position},computeIsFullyClosedTilt:function(t){return 0===t.attributes.current_tilt_position},onOpenTiltTap:function(){this.hass.serviceActions.callService("cover","open_cover_tilt",{entity_id:this.stateObj.entityId})},onCloseTiltTap:function(){this.hass.serviceActions.callService("cover","close_cover_tilt",{entity_id:this.stateObj.entityId})},onStopTiltTap:function(){this.hass.serviceActions.callService("cover","stop_cover",{entity_id:this.stateObj.entityId})}})</script><dom-module id="more-info-default" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><style>.data-entry .value{max-width:200px}</style><div class="layout vertical"><template is="dom-repeat" items="[[computeDisplayAttributes(stateObj)]]" as="attribute"><div class="data-entry layout justified horizontal"><div class="key">[[formatAttribute(attribute)]]</div><div class="value">[[getAttributeValue(stateObj, attribute)]]</div></div></template></div></template></dom-module><script>!function(){"use strict";var e=["entity_picture","friendly_name","icon","unit_of_measurement","emulated_hue","emulated_hue_name","haaska_hidden","haaska_name","homebridge_hidden","homebridge_name"];Polymer({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return e.indexOf(t)===-1}):[]},formatAttribute:function(e){return e.replace(/_/g," ")},getAttributeValue:function(e,t){var r=e.attributes[t];return Array.isArray(r)?r.join(", "):r}})}()</script><dom-module id="more-info-group" assetpath="more-infos/"><template><style>.child-card{margin-bottom:8px}.child-card:last-child{margin-bottom:0}</style><div id="groupedControlDetails"></div><template is="dom-repeat" items="[[states]]" as="state"><div class="child-card"><state-card-content state-obj="[[state]]" hass="[[hass]]"></state-card-content></div></template></template></dom-module><script>Polymer({is:"more-info-group",behaviors:[window.hassBehavior],properties:{hass:{type:Object},stateObj:{type:Object},states:{type:Array,bindNuclear:function(t){return[t.moreInfoGetters.currentEntity,t.entityGetters.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}}},observers:["statesChanged(stateObj, states)"],statesChanged:function(t,e){var s,i,a,n,r=!1;if(e&&e.length>0)for(s=e[0],r=s.set("entityId",t.entityId).set("attributes",Object.assign({},s.attributes)),i=0;i<e.length;i++)a=e[i],a&&a.domain&&r.domain!==a.domain&&(r=!1);r?window.hassUtil.dynamicContentUpdater(this.$.groupedControlDetails,"MORE-INFO-"+window.hassUtil.stateMoreInfoType(r).toUpperCase(),{stateObj:r,hass:this.hass}):(n=Polymer.dom(this.$.groupedControlDetails),n.lastChild&&n.removeChild(n.lastChild))}})</script><dom-module id="more-info-sun" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><template is="dom-repeat" items="[[computeOrder(risingDate, settingDate)]]"><div class="data-entry layout justified horizontal"><div class="key"><span>[[itemCaption(item)]]</span><ha-relative-time datetime-obj="[[itemDate(item)]]"></ha-relative-time></div><div class="value">[[itemValue(item)]]</div></div></template><div class="data-entry layout justified horizontal"><div class="key">Elevation</div><div class="value">[[stateObj.attributes.elevation]]</div></div></template></dom-module><script>Polymer({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return new Date(t.attributes.next_rising)},computeSetting:function(t){return new Date(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 window.hassUtil.formatTime(this.itemDate(t))}})</script><dom-module id="more-info-configurator" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>p{margin:8px 0}p>img{max-width:100%}p.center{text-align:center}p.error{color:#C62828}p.submit{text-align:center;height:41px}paper-spinner{width:14px;height:14px;margin-right:20px}</style><div class="layout vertical"><template is="dom-if" if="[[isConfigurable]]"><p hidden$="[[!stateObj.attributes.description]]">[[stateObj.attributes.description]] <a hidden$="[[!stateObj.attributes.link_url]]" href="[[stateObj.attributes.link_url]]" target="_blank">[[stateObj.attributes.link_name]]</a></p><p class="error" hidden$="[[!stateObj.attributes.errors]]">[[stateObj.attributes.errors]]</p><p class="center" hidden$="[[!stateObj.attributes.description_image]]"><img src="[[stateObj.attributes.description_image]]"></p><template is="dom-repeat" items="[[stateObj.attributes.fields]]"><paper-input-container id="paper-input-fields-{{item.id}}"><label>[[item.name]]</label><input is="iron-input" type="[[item.type]]" id="[[item.id]]" on-change="fieldChanged"></paper-input-container></template><p class="submit"><paper-button raised="" disabled="[[isConfiguring]]" on-tap="submitClicked"><paper-spinner active="[[isConfiguring]]" hidden="[[!isConfiguring]]" alt="Configuring"></paper-spinner>[[submitCaption]]</paper-button></p></template></div></template></dom-module><script>Polymer({is:"more-info-configurator",behaviors:[window.hassBehavior],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:function(i){return i.streamGetters.isStreamingEvents}},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"},fieldInput:{type:Object,value:{}}},computeIsConfigurable:function(i){return"configure"===i.state},computeSubmitCaption:function(i){return i.attributes.submit_caption||"Set configuration"},fieldChanged:function(i){var t=i.target;this.fieldInput[t.id]=t.value},submitClicked:function(){var i={configure_id:this.stateObj.attributes.configure_id,fields:this.fieldInput};this.isConfiguring=!0,this.hass.serviceActions.callService("configurator","configure",i).then(function(){this.isConfiguring=!1,this.isStreaming||this.hass.syncActions.fetchAll()}.bind(this),function(){this.isConfiguring=!1}.bind(this))}})</script><dom-module id="more-info-thermostat" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>paper-slider{width:100%}.away-mode-toggle{display:none;margin-top:16px}.has-away_mode .away-mode-toggle{display:block}</style><div class$="[[computeClassNames(stateObj)]]"><div><div>Target Temperature</div><paper-slider min="[[tempMin]]" max="[[tempMax]]" step="0.5" value="[[targetTemperatureSliderValue]]" pin="" on-change="targetTemperatureSliderChanged"></paper-slider></div><div class="away-mode-toggle"><div class="center horizontal layout"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="toggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-thermostat",properties:{hass:{type:Object},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 window.hassUtil.attributeClassNames(t,["away_mode"])},targetTemperatureSliderChanged:function(t){this.hass.serviceActions.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:t.target.value})},toggleChanged:function(t){const 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){this.hass.serviceActions.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><dom-module id="more-info-script" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex iron-flex-alignment"></style><div class="layout vertical"><div class="data-entry layout justified horizontal"><div class="key">Last Action</div><div class="value">[[stateObj.attributes.last_action]]</div></div></div></template></dom-module><script>Polymer({is:"more-info-script",properties:{stateObj:{type:Object}}})</script><dom-module id="ha-labeled-slider" assetpath="components/"><template><style>:host{display:block;padding-bottom:16px}.title{margin-bottom:16px;opacity:var(--dark-primary-opacity)}iron-icon{float:left;margin-top:4px;opacity:var(--dark-secondary-opacity)}.slider-container{margin-left:24px}</style><div class="title">[[caption]]</div><iron-icon icon="[[icon]]"></iron-icon><div class="slider-container"><paper-slider min="[[min]]" max="[[max]]" value="{{value}}"></paper-slider></div></template></dom-module><script>Polymer({is:"ha-labeled-slider",properties:{caption:{type:String},icon:{type:String},min:{type:Number},max:{type:Number},value:{type:Number,notify:!0}}})</script><dom-module id="ha-color-picker" assetpath="components/"><template><style>canvas{cursor:crosshair}</style><canvas width="[[width]]" height="[[height]]"></canvas></template></dom-module><script>Polymer({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){this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){this.mouseMoveIsThrottled=!0}.bind(this),100))},onMouseMove:function(t){this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){this.mouseMoveIsThrottled=!0}.bind(this),100))},processColorSelect:function(t){var o=this.canvas.getBoundingClientRect();t.clientX<o.left||t.clientX>=o.left+o.width||t.clientY<o.top||t.clientY>=o.top+o.height||this.onColorSelect(t.clientX-o.left,t.clientY-o.top)},onColorSelect:function(t,o){var e=this.context.getImageData(t,o,1,1).data;this.setColor({r:e[0],g:e[1],b:e[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{rgb:this.color})},ready:function(){this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.drawGradient()},drawGradient:function(){var t,o,e,i,s;this.width&&this.height||(t=getComputedStyle(this)),o=this.width||parseInt(t.width,10),e=this.height||parseInt(t.height,10),i=this.context.createLinearGradient(0,0,o,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)"),this.context.fillStyle=i,this.context.fillRect(0,0,o,e),s=this.context.createLinearGradient(0,0,0,e),s.addColorStop(0,"rgba(255,255,255,1)"),s.addColorStop(.5,"rgba(255,255,255,0)"),s.addColorStop(.5,"rgba(0,0,0,0)"),s.addColorStop(1,"rgba(0,0,0,1)"),this.context.fillStyle=s,this.context.fillRect(0,0,o,e)}})</script><dom-module id="more-info-light" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.brightness,.color_temp,.white_value{max-height:0;overflow:hidden;transition:max-height .5s ease-in}ha-color-picker{display:block;width:250px;max-height:0;overflow:hidden;transition:max-height .2s ease-in}.has-brightness .brightness,.has-color_temp .color_temp,.has-white_value .white_value{max-height:84px}.has-rgb_color ha-color-picker{max-height:200px}</style><div class$="[[computeClassNames(stateObj)]]"><div class="brightness"><ha-labeled-slider caption="Brightness" icon="mdi:brightness-5" max="255" value="{{brightnessSliderValue}}" on-change="brightnessSliderChanged"></ha-labeled-slider></div><div class="color_temp"><ha-labeled-slider caption="Color Temperature" icon="mdi:thermometer" min="154" max="500" value="{{ctSliderValue}}" on-change="ctSliderChanged"></ha-labeled-slider></div><div class="white_value"><ha-labeled-slider caption="White Value" icon="mdi:file-word-box" max="255" value="{{wvSliderValue}}" on-change="wvSliderChanged"></ha-labeled-slider></div><ha-color-picker on-colorselected="colorPicked" height="200" width="250"></ha-color-picker></div></template></dom-module><script>Polymer({is:"more-info-light",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0},ctSliderValue:{type:Number,value:0},wvSliderValue:{type:Number,value:0}},stateObjChanged:function(t){t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness,this.ctSliderValue=t.attributes.color_temp,this.wvSliderValue=t.attributes.white_value),this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(t){return window.hassUtil.attributeClassNames(t,["brightness","rgb_color","color_temp","white_value"])},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?this.hass.serviceActions.callTurnOff(this.stateObj.entityId):this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},ctSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,color_temp:e})},wvSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||this.hass.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,white_value:e})},serviceChangeColor:function(t,e,i){t.serviceActions.callService("light","turn_on",{entity_id:e,rgb_color:[i.r,i.g,i.b]})},colorPicked:function(t){return this.skipColorPicked?void(this.colorChanged=!0):(this.color=t.detail.rgb,this.serviceChangeColor(this.hass,this.stateObj.entityId,this.color),this.colorChanged=!1,this.skipColorPicked=!0,void(this.colorDebounce=setTimeout(function(){this.colorChanged&&this.serviceChangeColor(this.hass,this.stateObj.entityId,this.color),this.skipColorPicked=!1}.bind(this),500)))}})</script><dom-module id="more-info-media_player" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>.media-state{text-transform:capitalize}paper-icon-button[highlight]{color:var(--accent-color)}.volume{margin-bottom:8px;max-height:0;overflow:hidden;transition:max-height .5s ease-in}.has-volume_level .volume{max-height:40px}iron-icon.source-input{padding:7px;margin-top:15px}paper-dropdown-menu.source-input{margin-left:10px}[hidden]{display:none!important}</style><div class$="[[computeClassNames(stateObj)]]"><div class="layout horizontal"><div class="flex"><paper-icon-button icon="mdi:power" highlight$="[[isOff]]" on-tap="handleTogglePower" hidden$="[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]"></paper-icon-button></div><div><template is="dom-if" if="[[computeShowPlaybackControls(isOff, hasMediaControl)]]"><paper-icon-button icon="mdi:skip-previous" on-tap="handlePrevious" hidden$="[[!supportsPreviousTrack]]"></paper-icon-button><paper-icon-button icon="[[computePlaybackControlIcon(stateObj)]]" on-tap="handlePlaybackControl" highlight=""></paper-icon-button><paper-icon-button icon="mdi:skip-next" on-tap="handleNext" hidden$="[[!supportsNextTrack]]"></paper-icon-button></template></div></div><div class="volume_buttons center horizontal layout" hidden$="[[computeHideVolumeButtons(isOff, supportsVolumeButtons)]]"><paper-icon-button on-tap="handleVolumeTap" icon="mdi:volume-off"></paper-icon-button><paper-icon-button id="volumeDown" disabled$="[[isMuted]]" on-mousedown="handleVolumeDown" on-touchstart="handleVolumeDown" icon="mdi:volume-medium"></paper-icon-button><paper-icon-button id="volumeUp" disabled$="[[isMuted]]" on-mousedown="handleVolumeUp" on-touchstart="handleVolumeUp" icon="mdi:volume-high"></paper-icon-button></div><div class="volume center horizontal layout" hidden$="[[!supportsVolumeSet]]"><paper-icon-button on-tap="handleVolumeTap" hidden$="[[supportsVolumeButtons]]" icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button><paper-slider disabled$="[[isMuted]]" min="0" max="100" value="[[volumeSliderValue]]" on-change="volumeSliderChanged" class="flex"></paper-slider></div><div class="controls layout horizontal justified" hidden$="[[!computeHideSelectSource(isOff, supportsSelectSource)]]"><iron-icon class="source-input" icon="mdi:login-variant"></iron-icon><paper-dropdown-menu class="source-input" label-float="" label="Source"><paper-menu class="dropdown-content" selected="{{sourceIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.source_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div></template></dom-module><script>Polymer({is:"more-info-media_player",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},source:{type:String,value:""},sourceIndex:{type:Number,value:0,observer:"handleSourceChanged"},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},supportsSelectSource:{type:Boolean,value:!1},hasMediaControl:{type:Boolean,value:!1}},HAS_MEDIA_STATES:["playing","paused","unknown"],stateObjChanged:function(e){e&&(this.isOff="off"===e.state,this.isPlaying="playing"===e.state,this.hasMediaControl=this.HAS_MEDIA_STATES.indexOf(e.state)!==-1,this.volumeSliderValue=100*e.attributes.volume_level,this.isMuted=e.attributes.is_volume_muted,this.source=e.attributes.source,this.supportsPause=0!==(1&e.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&e.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&e.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&e.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&e.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&e.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&e.attributes.supported_media_commands),this.supportsVolumeButtons=0!==(1024&e.attributes.supported_media_commands),this.supportsSelectSource=0!==(2048&e.attributes.supported_media_commands),void 0!==e.attributes.source_list&&(this.sourceIndex=e.attributes.source_list.indexOf(this.source))),this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(e){return window.hassUtil.attributeClassNames(e,["volume_level"])},computeIsOff:function(e){return"off"===e.state},computeMuteVolumeIcon:function(e){return e?"mdi:volume-off":"mdi:volume-high"},computeHideVolumeButtons:function(e,t){return!t||e},computeShowPlaybackControls:function(e,t){return!e&&t},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"mdi:pause":"mdi:stop":"mdi:play"},computeHidePowerButton:function(e,t,s){return e?!t:!s},computeHideSelectSource:function(e,t){return!e&&t},computeSelectedSource:function(e){return e.attributes.source_list.indexOf(e.attributes.source)},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")},handleSourceChanged:function(e){var t;!this.stateObj||void 0===this.stateObj.attributes.source_list||e<0||e>=this.stateObj.attributes.source_list.length||(t=this.stateObj.attributes.source_list[e],t!==this.stateObj.attributes.source&&this.callService("select_source",{source:t}))},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},handleVolumeUp:function(){var e=this.$.volumeUp;this.handleVolumeWorker("volume_up",e,!0)},handleVolumeDown:function(){var e=this.$.volumeDown;this.handleVolumeWorker("volume_down",e,!0)},handleVolumeWorker:function(e,t,s){(s||void 0!==t&&t.pointerDown)&&(this.callService(e),this.async(function(){this.handleVolumeWorker(e,t,!1)}.bind(this),500))},volumeSliderChanged:function(e){var t=parseFloat(e.target.value),s=t>0?t/100:0;this.callService("volume_set",{volume_level:s})},callService:function(e,t){var s=t||{};s.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("media_player",e,s)}})</script><dom-module id="more-info-camera" assetpath="more-infos/"><template><style>:host{max-width:640px}.camera-image{width:100%}</style><img class="camera-image" src="[[computeCameraImageUrl(hass, stateObj)]]" on-load="imageLoaded" alt="[[stateObj.entityDisplay]]"></template></dom-module><script>Polymer({is:"more-info-camera",properties:{hass:{type:Object},stateObj:{type:Object}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(e,t){return e.demo?"/demo/webcam.jpg":t?"/api/camera_proxy_stream/"+t.entityId+"?token="+t.attributes.access_token:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}})</script><dom-module id="more-info-updater" assetpath="more-infos/"><template><style>.link{color:#03A9F4}</style><div><a class="link" href="https://home-assistant.io/getting-started/updating/" target="_blank">Update Instructions</a></div></template></dom-module><script>Polymer({is:"more-info-updater",properties:{stateObj:{type:Object}},computeReleaseNotes:function(t){return t.attributes.release_notes||"https://home-assistant.io/getting-started/updating/"}})</script><dom-module id="more-info-alarm_control_panel" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><div class="layout horizontal"><paper-input label="code" value="{{enteredCode}}" pattern="[[codeFormat]]" type="password" hidden$="[[!codeInputVisible]]" disabled="[[!codeInputEnabled]]"></paper-input></div><div class="layout horizontal"><paper-button on-tap="handleDisarmTap" hidden$="[[!disarmButtonVisible]]" disabled="[[!codeValid]]">Disarm</paper-button><paper-button on-tap="handleHomeTap" hidden$="[[!armHomeButtonVisible]]" disabled="[[!codeValid]]">Arm Home</paper-button><paper-button on-tap="handleAwayTap" hidden$="[[!armAwayButtonVisible]]" disabled="[[!codeValid]]">Arm Away</paper-button></div></template></dom-module><script>Polymer({is:"more-info-alarm_control_panel",properties:{hass:{type:Object},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(e,t){var a=new RegExp(t);return null===t||a.test(e)},stateObjChanged:function(e){e&&(this.codeFormat=e.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="armed_home"===e.state||"armed_away"===e.state||"disarmed"===e.state||"pending"===e.state||"triggered"===e.state,this.disarmButtonVisible="armed_home"===e.state||"armed_away"===e.state||"pending"===e.state||"triggered"===e.state,this.armHomeButtonVisible="disarmed"===e.state,this.armAwayButtonVisible="disarmed"===e.state),this.async(function(){this.fire("iron-resize")}.bind(this),500)},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})},callService:function(e,t){var a=t||{};a.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("alarm_control_panel",e,a).then(function(){this.enteredCode=""}.bind(this))}})</script><dom-module id="more-info-lock" assetpath="more-infos/"><template><style>paper-input{display:inline-block}</style><div hidden$="[[!stateObj.attributes.code_format]]"><paper-input label="code" value="{{enteredCode}}" pattern="[[stateObj.attributes.code_format]]" type="password"></paper-input><paper-button on-tap="handleUnlockTap" hidden$="[[!isLocked]]">Unlock</paper-button><paper-button on-tap="handleLockTap" hidden$="[[isLocked]]">Lock</paper-button></div></template></dom-module><script>Polymer({is:"more-info-lock",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""}},handleUnlockTap:function(){this.callService("unlock",{code:this.enteredCode})},handleLockTap:function(){this.callService("lock",{code:this.enteredCode})},stateObjChanged:function(e){e&&(this.isLocked="locked"===e.state),this.async(function(){this.fire("iron-resize")}.bind(this),500)},callService:function(e,t){var i=t||{};i.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("lock",e,i)}})</script><dom-module id="more-info-hvac" assetpath="more-infos/"><template><style is="custom-style" include="iron-flex"></style><style>:host{color:var(--primary-text-color)}.container-aux_heat,.container-away_mode,.container-fan_list,.container-humidity,.container-operation_list,.container-swing_list,.container-temperature{display:none}.has-aux_heat .container-aux_heat,.has-away_mode .container-away_mode,.has-fan_list .container-fan_list,.has-humidity .container-humidity,.has-operation_list .container-operation_list,.has-swing_list .container-swing_list,.has-temperature .container-temperature{display:block}.container-fan_list iron-icon,.container-operation_list iron-icon,.container-swing_list iron-icon{margin:22px 16px 0 0}paper-dropdown-menu{width:100%}.single-row{padding:8px 0}</style><div class$="[[computeClassNames(stateObj)]]"><div class="container-temperature"><div class="single-row"><div>Target Temperature</div><paper-slider min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" step="0.5" pin="" value="[[stateObj.attributes.temperature]]" on-change="targetTemperatureSliderChanged"></paper-slider></div></div><div class="container-humidity"><div class="single-row"><div>Target Humidity</div><paper-slider min="[[stateObj.attributes.min_humidity]]" max="[[stateObj.attributes.max_humidity]]" step="1" pin="" value="[[stateObj.attributes.humidity]]" on-change="targetHumiditySliderChanged"></paper-slider></div></div><div class="container-operation_list"><div class="controls"><paper-dropdown-menu label-float="" label="Operation"><paper-menu class="dropdown-content" selected="{{operationIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.operation_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div></div><div class="container-fan_list"><paper-dropdown-menu label-float="" label="Fan Mode"><paper-menu class="dropdown-content" selected="{{fanIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.fan_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-swing_list"><paper-dropdown-menu label-float="" label="Swing Mode"><paper-menu class="dropdown-content" selected="{{swingIndex}}"><template is="dom-repeat" items="[[stateObj.attributes.swing_list]]"><paper-item>[[item]]</paper-item></template></paper-menu></paper-dropdown-menu></div><div class="container-away_mode"><div class="center horizontal layout single-row"><div class="flex">Away Mode</div><paper-toggle-button checked="[[awayToggleChecked]]" on-change="awayToggleChanged"></paper-toggle-button></div></div><div class="container-aux_heat"><div class="center horizontal layout single-row"><div class="flex">Aux Heat</div><paper-toggle-button checked="[[auxToggleChecked]]" on-change="auxToggleChanged"></paper-toggle-button></div></div></div></template></dom-module><script>Polymer({is:"more-info-hvac",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"},operationIndex:{type:Number,value:-1,observer:"handleOperationmodeChanged"},fanIndex:{type:Number,value:-1,observer:"handleFanmodeChanged"},swingIndex:{type:Number,value:-1,observer:"handleSwingmodeChanged"},awayToggleChecked:{type:Boolean},auxToggleChecked:{type:Boolean}},stateObjChanged:function(e){this.awayToggleChecked="on"===e.attributes.away_mode,this.auxheatToggleChecked="on"===e.attributes.aux_heat,e.attributes.fan_list?this.fanIndex=e.attributes.fan_list.indexOf(e.attributes.fan_mode):this.fanIndex=-1,e.attributes.operation_list?this.operationIndex=e.attributes.operation_list.indexOf(e.attributes.operation_mode):this.operationIndex=-1,e.attributes.swing_list?this.swingIndex=e.attributes.swing_list.indexOf(e.attributes.swing_mode):this.swingIndex=-1,this.async(function(){this.fire("iron-resize")}.bind(this),500)},computeClassNames:function(e){return"more-info-hvac "+window.hassUtil.attributeClassNames(e,["away_mode","aux_heat","temperature","humidity","operation_list","fan_list","swing_list"])},targetTemperatureSliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.temperature&&this.callServiceHelper("set_temperature",{temperature:t})},targetHumiditySliderChanged:function(e){var t=e.target.value;t!==this.stateObj.attributes.humidity&&this.callServiceHelper("set_humidity",{humidity:t})},awayToggleChanged:function(e){var t="on"===this.stateObj.attributes.away_mode,a=e.target.checked;t!==a&&this.callServiceHelper("set_away_mode",{away_mode:a})},auxToggleChanged:function(e){var t="on"===this.stateObj.attributes.aux_heat,a=e.target.checked;t!==a&&this.callServiceHelper("set_aux_heat",{aux_heat:a})},handleFanmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.fan_list[e],t!==this.stateObj.attributes.fan_mode&&this.callServiceHelper("set_fan_mode",{fan_mode:t}))},handleOperationmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.operation_list[e],t!==this.stateObj.attributes.operation_mode&&this.callServiceHelper("set_operation_mode",{operation_mode:t}))},handleSwingmodeChanged:function(e){var t;""!==e&&e!==-1&&(t=this.stateObj.attributes.swing_list[e],t!==this.stateObj.attributes.swing_mode&&this.callServiceHelper("set_swing_mode",{swing_mode:t}))},callServiceHelper:function(e,t){t.entity_id=this.stateObj.entityId,this.hass.serviceActions.callService("hvac",e,t).then(function(){this.stateObjChanged(this.stateObj)}.bind(this))}})</script><script>Polymer({is:"more-info-content",properties:{hass:{type:Object},stateObj:{type:Object,observer:"stateObjChanged"}},created:function(){this.style.display="block"},stateObjChanged:function(t){t&&window.hassUtil.dynamicContentUpdater(this,"MORE-INFO-"+window.hassUtil.stateMoreInfoType(t).toUpperCase(),{hass:this.hass,stateObj:t})}})</script><dom-module id="more-info-dialog" assetpath="dialogs/"><template><style>paper-dialog{font-size:14px;width:365px}paper-dialog[data-domain=camera]{width:auto}state-history-charts{position:relative;z-index:1;max-width:365px}state-card-content{margin-bottom:24px;font-size:14px}@media all and (max-width:450px),all and (max-height:500px){paper-dialog{margin:0;width:100%;max-height:calc(100% - 64px);position:fixed!important;bottom:0;left:0;right:0;overflow:scroll}}</style><paper-dialog id="dialog" with-backdrop="" opened="{{dialogOpen}}" data-domain$="[[stateObj.domain]]"><h2><state-card-content state-obj="[[stateObj]]" hass="[[hass]]" in-dialog=""></state-card-content></h2><template is="dom-if" if="[[showHistoryComponent]]"><state-history-charts state-history="[[stateHistory]]" is-loading-data="[[isLoadingHistoryData]]"></state-history-charts></template><paper-dialog-scrollable id="scrollable"><more-info-content state-obj="[[stateObj]]" hass="[[hass]]"></more-info-content></paper-dialog-scrollable></paper-dialog></template></dom-module><script>Polymer({is:"more-info-dialog",behaviors:[window.hassBehavior],properties:{hass:{type:Object},stateObj:{type:Object,bindNuclear:function(t){return t.moreInfoGetters.currentEntity},observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:function(t){return[t.moreInfoGetters.currentEntityHistory,function(t){return!!t&&[t]}]}},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData)"},isLoadingEntityHistoryData:{type:Boolean,bindNuclear:function(t){return t.entityHistoryGetters.isLoadingEntityHistory}},hasHistoryComponent:{type:Boolean,bindNuclear:function(t){return t.configGetters.isComponentLoaded("history")},observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:function(t){return t.moreInfoGetters.isCurrentEntityHistoryStale},observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1,computed:"computeShowHistoryComponent(hasHistoryComponent, stateObj)"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},delayedDialogOpen:{type:Boolean,value:!1}},ready:function(){this.$.scrollable.dialogElement=this.$.dialog},computeIsLoadingHistoryData:function(t,e){return!t||e},computeShowHistoryComponent:function(t,e){return this.hasHistoryComponent&&e&&window.hassUtil.DOMAINS_WITH_NO_HISTORY.indexOf(e.domain)===-1},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&this.hass.entityHistoryActions.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){return t?void this.async(function(){this.fetchHistoryData(),this.dialogOpen=!0}.bind(this),10):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){t?this.async(function(){this.delayedDialogOpen=!0}.bind(this),10):!t&&this.stateObj&&(this.async(function(){this.hass.moreInfoActions.deselectEntity()}.bind(this),10),this.delayedDialogOpen=!1)}})</script><dom-module id="ha-voice-command-dialog" assetpath="dialogs/"><template><style>iron-icon{margin-right:8px}.content{width:300px;min-height:80px;font-size:18px}.icon{float:left}.text{margin-left:48px;margin-right:24px}.interimTranscript{color:#a9a9a9}@media all and (max-width:450px){paper-dialog{margin:0;width:100%;max-height:calc(100% - 64px);position:fixed!important;bottom:0;left:0;right:0;overflow:scroll}}</style><paper-dialog id="dialog" with-backdrop="" opened="{{dialogOpen}}"><div class="content"><div class="icon"><iron-icon icon="mdi:text-to-speech" hidden$="[[isTransmitting]]"></iron-icon><paper-spinner active$="[[isTransmitting]]" hidden$="[[!isTransmitting]]"></paper-spinner></div><div class="text"><span>{{finalTranscript}}</span> <span class="interimTranscript">[[interimTranscript]]</span> …</div></div></paper-dialog></template></dom-module><script>Polymer({is:"ha-voice-command-dialog",behaviors:[window.hassBehavior],properties:{hass:{type:Object},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},finalTranscript:{type:String,bindNuclear:function(e){return e.voiceGetters.finalTranscript}},interimTranscript:{type:String,bindNuclear:function(e){return e.voiceGetters.extraInterimTranscript}},isTransmitting:{type:Boolean,bindNuclear:function(e){return e.voiceGetters.isTransmitting}},isListening:{type:Boolean,bindNuclear:function(e){return e.voiceGetters.isListening}},showListenInterface:{type:Boolean,computed:"computeShowListenInterface(isListening, isTransmitting)",observer:"showListenInterfaceChanged"}},computeShowListenInterface:function(e,n){return e||n},dialogOpenChanged:function(e){!e&&this.isListening&&this.hass.voiceActions.stop()},showListenInterfaceChanged:function(e){!e&&this.dialogOpen?this.dialogOpen=!1:e&&(this.dialogOpen=!0)}})</script><dom-module id="paper-icon-item" assetpath="../bower_components/paper-item/"><template><style include="paper-item-shared-styles"></style><style>:host{@apply(--layout-horizontal);@apply(--layout-center);@apply(--paper-font-subhead);@apply(--paper-item);@apply(--paper-icon-item)}.content-icon{@apply(--layout-horizontal);@apply(--layout-center);width:var(--paper-item-icon-width,56px);@apply(--paper-item-icon)}</style><div id="contentIcon" class="content-icon"><content select="[item-icon]"></content></div><content></content></template><script>Polymer({is:"paper-icon-item",behaviors:[Polymer.PaperItemBehavior]})</script></dom-module><dom-module id="stream-status" assetpath="components/"><template><style>:host{display:inline-block;height:24px}paper-toggle-button{vertical-align:middle}iron-icon{opacity:var(--dark-primary-opacity)}[hidden]{display:none!important}</style><iron-icon icon="mdi:alert" hidden$="[[!hasError]]"></iron-icon><paper-toggle-button id="toggle" on-change="toggleChanged" checked$="[[isStreaming]]" hidden$="[[hasError]]"></paper-toggle-button></template></dom-module><script>Polymer({is:"stream-status",behaviors:[window.hassBehavior],properties:{hass:{type:Object},isStreaming:{type:Boolean,bindNuclear:function(t){return t.streamGetters.isStreamingEvents}},hasError:{type:Boolean,bindNuclear:function(t){return t.streamGetters.hasStreamingEventsError}}},toggleChanged:function(){this.isStreaming?this.hass.streamActions.stop():this.hass.streamActions.start()}})</script><dom-module id="ha-sidebar" assetpath="components/"><template><style include="iron-flex iron-flex-alignment iron-positioning">:host{--sidebar-text:{opacity:var(--dark-primary-opacity);font-weight:500;font-size:14px};display:block;overflow:auto;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none}app-toolbar{font-weight:400;opacity:var(--dark-primary-opacity);border-bottom:1px solid #e0e0e0}paper-menu{padding-bottom:0}paper-icon-item{--paper-icon-item:{cursor:pointer};--paper-item-icon:{color:#000;opacity:var(--dark-secondary-opacity)};--paper-item-selected:{color:var(--default-primary-color);background-color:#e8e8e8;opacity:1};}paper-icon-item.iron-selected{--paper-item-icon:{color:var(--default-primary-color);opacity:1};}paper-icon-item .item-text{@apply(--sidebar-text)}paper-icon-item.iron-selected .item-text{opacity:1}paper-icon-item.logout{margin-top:16px}.divider{height:1px;background-color:#000;margin:4px 0;opacity:var(--dark-divider-opacity)}.setting{@apply(--sidebar-text)}.subheader{@apply(--sidebar-text);padding:16px}.dev-tools{padding:0 8px;opacity:var(--dark-secondary-opacity)}</style><app-toolbar><div main-title="">Home Assistant</div><paper-icon-button icon="mdi:chevron-left" hidden$="[[narrow]]" on-tap="toggleMenu"></paper-icon-button></app-toolbar><paper-menu attr-for-selected="data-panel" selected="[[selected]]" on-iron-select="menuSelect"><paper-icon-item on-tap="menuClicked" data-panel="states"><iron-icon item-icon="" icon="mdi:apps"></iron-icon><span class="item-text">States</span></paper-icon-item><template is="dom-repeat" items="[[computePanels(panels)]]"><paper-icon-item on-tap="menuClicked" data-panel$="[[item.url_path]]"><iron-icon item-icon="" icon="[[item.icon]]"></iron-icon><span class="item-text">[[item.title]]</span></paper-icon-item></template><paper-icon-item on-tap="menuClicked" data-panel="logout" class="logout"><iron-icon item-icon="" icon="mdi:exit-to-app"></iron-icon><span class="item-text">Log Out</span></paper-icon-item></paper-menu><div><div class="divider"></div><template is="dom-if" if="[[supportPush]]"><paper-item class="horizontal layout justified"><div class="setting">Push Notifications</div><paper-toggle-button on-change="handlePushChange" checked="{{pushToggleChecked}}"></paper-toggle-button></paper-item></template><paper-item class="horizontal layout justified"><div class="setting">Streaming updates</div><stream-status hass="[[hass]]"></stream-status></paper-item><div class="divider"></div><div class="subheader">Developer Tools</div><div class="dev-tools layout horizontal justified"><paper-icon-button icon="mdi:remote" data-panel="dev-service" alt="Services" title="Services" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:code-tags" data-panel="dev-state" alt="States" title="States" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:radio-tower" data-panel="dev-event" alt="Events" title="Events" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:file-xml" data-panel="dev-template" alt="Templates" title="Templates" on-tap="menuClicked"></paper-icon-button><paper-icon-button icon="mdi:information-outline" data-panel="dev-info" alt="Info" title="Info" on-tap="menuClicked"></paper-icon-button></div></div></template></dom-module><script>Polymer({is:"ha-sidebar",behaviors:[window.hassBehavior],properties:{hass:{type:Object},menuShown:{type:Boolean},menuSelected:{type:String},narrow:{type:Boolean},selected:{type:String,bindNuclear:function(t){return t.navigationGetters.activePanelName}},panels:{type:Array,bindNuclear:function(t){return[t.navigationGetters.panels,function(t){return t.toJS()}]}},supportPush:{type:Boolean,value:!1,bindNuclear:function(t){return t.pushNotificationGetters.isSupported}},pushToggleChecked:{type:Boolean,bindNuclear:function(t){return t.pushNotificationGetters.isActive}}},created:function(){this._boundUpdateStyles=this.updateStyles.bind(this)},computePanels:function(t){var e={map:1,logbook:2,history:3},n=[];return Object.keys(t).forEach(function(e){t[e].title&&n.push(t[e])}),n.sort(function(t,n){var i=t.component_name in e,o=n.component_name in e;return i&&o?e[t.component_name]-e[n.component_name]:i?-1:o?1:t.title>n.title?1:t.title<n.title?-1:0}),n},menuSelect:function(){this.debounce("updateStyles",this._boundUpdateStyles,1)},menuClicked:function(t){for(var e=t.target,n=5,i=e.getAttribute("data-panel");n&&!i;)e=e.parentElement,i=e.getAttribute("data-panel"),n--;n&&this.selectPanel(i)},toggleMenu:function(){this.fire("close-menu")},selectPanel:function(t){if(t!==this.selected){if("logout"===t)return void this.handleLogOut();this.hass.navigationActions.navigate.apply(null,t.split("/")),this.debounce("updateStyles",this._boundUpdateStyles,1)}},handlePushChange:function(t){t.target.checked?this.hass.pushNotificationActions.subscribePushNotifications().then(function(t){this.pushToggleChecked=t}.bind(this)):this.hass.pushNotificationActions.unsubscribePushNotifications().then(function(t){this.pushToggleChecked=!t}.bind(this))},handleLogOut:function(){this.hass.authActions.logOut()}})</script><dom-module id="home-assistant-main" assetpath="layouts/"><template><notification-manager hass="[[hass]]"></notification-manager><more-info-dialog hass="[[hass]]"></more-info-dialog><ha-voice-command-dialog hass="[[hass]]"></ha-voice-command-dialog><iron-media-query query="(max-width: 870px)" query-matches="{{narrow}}"></iron-media-query><paper-drawer-panel id="drawer" force-narrow="[[computeForceNarrow(narrow, showSidebar)]]" responsive-width="0" disable-swipe="[[isSelectedMap]]" disable-edge-swipe="[[isSelectedMap]]"><ha-sidebar drawer="" narrow="[[narrow]]" hass="[[hass]]"></ha-sidebar><iron-pages main="" attr-for-selected="id" fallback-selection="panel-resolver" selected="[[activePanel]]" selected-attribute="panel-visible"><partial-cards id="states" narrow="[[narrow]]" hass="[[hass]]" show-menu="[[showSidebar]]"></partial-cards><partial-panel-resolver id="panel-resolver" narrow="[[narrow]]" hass="[[hass]]" show-menu="[[showSidebar]]"></partial-panel-resolver></iron-pages></paper-drawer-panel></template></dom-module><script>Polymer({is:"home-assistant-main",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!0},activePanel:{type:String,bindNuclear:function(e){return e.navigationGetters.activePanelName},observer:"activePanelChanged"},showSidebar:{type:Boolean,value:!1,bindNuclear:function(e){return e.navigationGetters.showSidebar}}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():this.hass.navigationActions.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&this.hass.navigationActions.showSidebar(!1)},activePanelChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){window.removeInitMsg(),this.hass.startUrlSync()},computeForceNarrow:function(e,n){return e||!n},detached:function(){this.hass.stopUrlSync()}})</script></div><dom-module id="home-assistant"><template><template is="dom-if" if="[[loaded]]"><home-assistant-main hass="[[hass]]"></home-assistant-main></template><template is="dom-if" if="[[!loaded]]"><login-form hass="[[hass]]" force-show-loading="[[computeForceShowLoading(dataLoaded, iconsLoaded)]]"></login-form></template></template></dom-module><script>Polymer({is:"home-assistant",hostAttributes:{icons:null},behaviors:[window.hassBehavior],properties:{hass:{type:Object,value:window.hass},icons:{type:String},dataLoaded:{type:Boolean,bindNuclear:function(o){return o.syncGetters.isDataLoaded}},iconsLoaded:{type:Boolean,value:!1},loaded:{type:Boolean,computed:"computeLoaded(dataLoaded, iconsLoaded)"}},computeLoaded:function(o,t){return o&&t},computeForceShowLoading:function(o,t){return o&&!t},loadIcons:function(){var o=function(){this.iconsLoaded=!0}.bind(this);this.importHref("/static/mdi-"+this.icons+".html",o,function(){this.importHref("/static/mdi.html",o,o)})},ready:function(){this.loadIcons()}})</script></body></html> \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 3beed8e689bf725bfead5e238941af0db6a63c18..7e2be4a3b4c82f456f2cd25eea2f1be23794a764 100644 GIT binary patch delta 45233 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 N|3A?UiROh62mq=Y*Pj3Y delta 44468 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 diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 51bfd5b5967..f3081ed48fd 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 51bfd5b596766da35d99d09a4a92da32a514b299 +Subproject commit f3081ed48fd11fa89586701dba3792d028473a15 diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index 08fe313e5d8..6c01252b0d3 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -1 +1 @@ -"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","3ff5b66e1b53be97a95746db1546b6ab"],["/frontend/panels/dev-event-550bf85345c454274a40d15b2795a002.html","6977c253b5b4da588d50b0aaa50b21f4"],["/frontend/panels/dev-info-ec613406ce7e20d93754233d55625c8a.html","8e28a4c617fd6963b45103d5e5c80617"],["/frontend/panels/dev-service-d33657c964041d3ebf114e90a922a15e.html","cd3a429ec2f82fd58fd06b00e8d7df1f"],["/frontend/panels/dev-state-65e5f791cc467561719bf591f1386054.html","78158786a6597ef86c3fd6f4985cde92"],["/frontend/panels/dev-template-d23943fa0370f168714da407c90091a2.html","2cf2426a6aa4ee9c1df74926dc475bc8"],["/frontend/panels/map-49ab2d6f180f8bdea7cffaa66b8a5d3e.html","6e6c9c74e0b2424b62d4cc55b8e89be3"],["/static/core-5ed5e063d66eb252b5b288738c9c2d16.js","59dabb570c57dd421d5197009bf1d07f"],["/static/frontend-0b226e89047d24f1af8d070990f6c079.html","240db11c5b0aacdcbbb3286a0ad9e072"],["/static/mdi-46a76f877ac9848899b8ed382427c16f.html","a846c4082dd5cffd88ac72cbe943e691"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]],cacheName="sw-precache-v2--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},createCacheKey=function(e,t,a,n){var c=new URL(e);return n&&c.toString().match(n)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,t){var a=new URL(e);return a.search=a.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),a.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),c=createCacheKey(n,hashParamName,a,!1);return[n.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(a){if(!t.has(a))return e.add(new Request(a,{credentials:"same-origin"}))}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(a){return Promise.all(a.map(function(a){if(!t.has(a.url))return e.delete(a)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,a=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(a);var n="index.html";!t&&n&&(a=addDirectoryIndex(a,n),t=urlsToCacheKeys.has(a));var c="/";!t&&c&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(a=new URL(c,self.location).toString(),t=urlsToCacheKeys.has(a)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url,t&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var a,n;for(a=0;a<e.length;a++)if(n=e[a],n.url===t&&"focus"in n)return n.focus();if(clients.openWindow)return clients.openWindow(t)})))}),self.addEventListener("notificationclose",function(e){notificationEventCallback("closed",e)}); \ No newline at end of file +"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","8ab2c244274dcd7d80a34d37dcb5cb7e"],["/frontend/panels/dev-event-550bf85345c454274a40d15b2795a002.html","6977c253b5b4da588d50b0aaa50b21f4"],["/frontend/panels/dev-info-ec613406ce7e20d93754233d55625c8a.html","8e28a4c617fd6963b45103d5e5c80617"],["/frontend/panels/dev-service-d33657c964041d3ebf114e90a922a15e.html","cd3a429ec2f82fd58fd06b00e8d7df1f"],["/frontend/panels/dev-state-65e5f791cc467561719bf591f1386054.html","78158786a6597ef86c3fd6f4985cde92"],["/frontend/panels/dev-template-d23943fa0370f168714da407c90091a2.html","2cf2426a6aa4ee9c1df74926dc475bc8"],["/frontend/panels/map-49ab2d6f180f8bdea7cffaa66b8a5d3e.html","6e6c9c74e0b2424b62d4cc55b8e89be3"],["/static/core-5ed5e063d66eb252b5b288738c9c2d16.js","59dabb570c57dd421d5197009bf1d07f"],["/static/frontend-0a4c2c6e86a0a78c2ff3e03842de609d.html","5241dccfe1df6b25ef92830d64f90282"],["/static/mdi-46a76f877ac9848899b8ed382427c16f.html","a846c4082dd5cffd88ac72cbe943e691"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]],cacheName="sw-precache-v2--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},createCacheKey=function(e,t,a,n){var c=new URL(e);return n&&c.toString().match(n)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,t){var a=new URL(e);return a.search=a.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),a.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),c=createCacheKey(n,hashParamName,a,!1);return[n.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(a){if(!t.has(a))return e.add(new Request(a,{credentials:"same-origin"}))}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(a){return Promise.all(a.map(function(a){if(!t.has(a.url))return e.delete(a)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,a=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(a);var n="index.html";!t&&n&&(a=addDirectoryIndex(a,n),t=urlsToCacheKeys.has(a));var c="/";!t&&c&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(a=new URL(c,self.location).toString(),t=urlsToCacheKeys.has(a)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url,t&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var a,n;for(a=0;a<e.length;a++)if(n=e[a],n.url===t&&"focus"in n)return n.focus();if(clients.openWindow)return clients.openWindow(t)})))}),self.addEventListener("notificationclose",function(e){notificationEventCallback("closed",e)}); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/service_worker.js.gz b/homeassistant/components/frontend/www_static/service_worker.js.gz index 5a30e3ea2d56da9e55ed7b948435c0e919928b3f..ab7bcd26f7c4053d5d168f2d901b3ef024ac9826 100644 GIT binary patch literal 2327 zcmV+y3F!78iwFp7t_xTK19N3^c4=c}Uw3bEYh`jSYI6XkSZj0JxE1{?3a6`(HAMm7 z3$&`qWV4wzZC;bd<b#{dfWU>sgd$ZEvg6wR@4X-;S+b;LcRG_q3<=!FxsP)JXVt=> zHBBLnGtsL`nxd`-Etog@nUHf(ADgmu!Mka|teR>7!8f>WT`w?msNBtly#))gJpN(T zly~0U>8%x94c>#nH@IwTOI4df7gE^&Jimm>JQHP^3;E3j-*`8=XyEOtop0aXh;HAx z3Hk?L2_x`-yK(~i&NpKA@IAir&Giz_;m$v@CHQ(}c1%0a-C3ld1Ow>rW`|Fv)9IW1 zD@fC$*MIVY5~`V*4~OfevaT=^D{1OxyJ0xo6X$C%a_%}~u`I@&dDp?*EXsH+*ETr* z%>vB4R_D&!H}5|<{v3pYraial{-b|(Z=7d!Wf05J2Xxjscf@j8t{okJZR^VMpRQ0y zQ~bAkckcWOqJd`MoS8e%&uevk{?GSsUI#XFsu^0*2klZcgJlEKW)5o5#cX<U;fx*M z;UZ^}MG;FQB~_|85n-glRLMM+c?!;F9~bJTGfRz^qJpv=E4Unijrk~!Nv?SuMzM@y zD@8;^(Ku&m77IdHhXU$JvNV+}4)ZvVl!!T3xRD4U@P|<yoz_!Sx*mZ{Xc&=1LJEwi zEKE^Z7^*l<SS-2NsNul4h;VVLRgxuP9>tX4GQdRy-=0>}LUUP27^yH!;#6iyL?Ws} z$Tg)AWJF|)2^zyjlT@LISO$`5&a{fTRwT&@0gfnYs(+x#2m_-8ZPIB*rHqm^M$>4P z>o}vDhCCs0w9%7t8uOGVB8jsUG*4uRuIVV_u~d+;(|QanmZeptSeQkj79>oGrU_3e zh9Dy7Bq13U+ZZvaS;SD25D`EosnThbu|&xzjdRJ5tHQQ2iZV<iw2E?~d9FaDQfncS zB<CVV5Vu(XiOggg0R}x{QJydrNg2mE2cG2+_8Kfeg&fPefl&+?Jd%VeNg!u2!~9{K zry)l<OwlCx+M?VzQzFmfl*l+$Dq>W{G)vJtjGiK?-jlm=U_>xem`nh{B_ib*NgV<S zdBhYXBvTu!V}_X^r3M5fK_ftCjE6)ek<JL?Y)|?^6{9E-X`*?W3YqbU^DILLRmjoj zRMJFm1PdM|G9sKQ6(eAZb0JeEbHH#xlF?4^#%eUW9XEAen|l1N`x*UOmntwuqbL=! zOiG@IQIe!KWkrS+!mz*;%@Mz!iF#Sg=4LG`&10Q&t|`kwa*C;gY0YRZL&g|o(a&VP z0#&RQYhj352oX&QCO9H2W1hr>LYk(SCn)=8!rsAbRf=XUO<?w?S*GGV7aC)nXpz%c z<Xi!dLMnHq6}E4WwYanoBbu@A_%~QqGn5n2EF)o#p=M~1z^1|pdWgsqN@*IOloQ}T zxSwG6iF1WngORdsK~6DlESK1Iq~3$5Scn<4NCb`gvOs9W7c9$bXujmLwKdps8K;U$ z%0n!dkc%W@Xi63mNT?9I(ykzv^<r69*nQhkSr{btqUy^aCpu(8g)+su#l%t(PZAPp z1SV5F$9($icO39qEMV%iSECInj4s(|<ea%Jlsaf&Rv>|N4D;mRP0-33iP&*GznE26 zqd1AY6%9%>NMWx8@*q;r^kV$`$}GN&Kl?(d-;j_keO|w)6nvlF&lr}MEjfv)rNNKy zULs#j`mqD)3E5TzOJU{}N)K9OLU2da8xGz3*Jo4X`64j&`;PKl@6MMEVB71)q<_Kn zo;F|jRj*MVY8+I<q1=O$$N%DcUFWzZti%K6!J`}o8eIu?dAsAR3+<GUBnHi2=C zcjio;V_$^l`Et+PqJ4|Le41k@OGFve1AGeY+Cc@@V~N)r`-n{TfivC~lgW0pYwHCZ z8|lQ_w{AM{?X<9b(FIfOMe~nE3Z?akOg9hfSWz!%aegr_BITW)Ki6xfjvYdEQKIH! z3r&C{M{|Au$!tD7!p;RP4yYI8v)7HXCv|<Ti)y2)e@4?D<j-8R^A3Z)Oup>YiR0t2 z+<vGx$<=mfyh3gJ<JExy7CJMv2KoYj=x4vWkDT8XSy}gk9_Gh%KZV_D-zLoBM|6Y! zMYUtRFF)UT-lUU<b=S@1FKtp)h^bTH9E;<q<L_Dq!Idb?$I29?+j-CiSl^YqZ4{>u z28YLy2TQjR8oX`lMbScl>fNVJBd!Ca;f4DE&EQg$E5KvSg9wVQ3NXwFgRP)#IMGMf z!uSsUv$7c={2PQ$q1S~d+jFNy`$sq~&x*=HV7fmCC!HOC5VrPPvHXOH^vPg1_$T1B z=a@?j(>|Vkq&H`#lMYV)58O{2_QX-N#gCx0XTKdd`!fx$`6EEdvalS4zoJ<D1gh?M zw2u>7;r#vi2d8UoO&`CYKO(I`#7a5YncVw5=)#Yf$eWny!zlD=)HTp*ie-VM)X!v- zM|iTUEWu0<Xv?+S;hz%LOOsQitxkv>4xI{ThM7%}eIJn$hQoud?_M~+yY7=mZcp^L zc#^!eEup+!A5Feo)y+3(km_$2qAIkNWzU3IEZ$%Lf-}M%Bppj`Ta0|rVs=}`NsoME zkH8JINL}hvVdj56PkWYmD1QbW4<7EroPcdMPRzWiuLjSXrp65VFo!{>QVn{GS{RtQ zFoP@64&eI|1BIW8c7TcBqGN&6XRz!nIMR{qR9Ba_3Y*G({H$J;s`~e1Gq7mGy$)UD z>H3$}8Q6Z+TW~)3<;~-Lw2>XxR{-1BT|u0#h~=uCA88zu{yqVNhitLFsc3EK`Mdq% z$WS(~uU_w5i?~RMg()p<AHsZiH`Cp%IGF8FT!M1eS1|V0WqnIyf9%!E{cVf)qk7-H zp}{_3uRji5J$-}ZbbG8JU>)DP?~!HQf_w0~NA1W<8=f_sN5A!1zSE1>ZLLe)r`SN( z;9OywsIQ!3_hj8%<X02S6|6Zj5r5kZYZGyH=3!P>$oT>}-zvmrfGQnb%T<fLXHac2 xz6!c`t~bFQ8#8RJed<(imK}a^|6Di_zx{ChFc7%9i^H8a`5zO`4_%cM0057ia~c2u literal 2329 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 From 678f30def16ba4dde40dbc1862204b89cb594886 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen <turbokongen@hotmail.com> Date: Sat, 22 Oct 2016 21:01:12 +0200 Subject: [PATCH 145/147] Prevent Verisure cam to delete a file when it is None (#3988) --- homeassistant/components/camera/verisure.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/camera/verisure.py index cc98dc5f363..6e613b72298 100644 --- a/homeassistant/components/camera/verisure.py +++ b/homeassistant/components/camera/verisure.py @@ -4,6 +4,7 @@ Camera that loads a picture from a local file. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.verisure/ """ +import errno import logging import os @@ -74,12 +75,8 @@ class VerisureSmartcam(Camera): hub.my_pages.smartcam.download_image(self._device_id, new_image_id, self._directory_path) - if self._image_id: - _LOGGER.debug('Old image_id=%s', self._image_id) - self.delete_image(self) - - else: - _LOGGER.debug('No old image, only new %s', new_image_id) + _LOGGER.debug('Old image_id=%s', self._image_id) + self.delete_image(self) self._image_id = new_image_id self._image = os.path.join(self._directory_path, @@ -93,8 +90,12 @@ class VerisureSmartcam(Camera): '{}{}'.format( self._image_id, '.jpg')) - _LOGGER.debug('Deleting old image %s', remove_image) - os.remove(remove_image) + try: + os.remove(remove_image) + _LOGGER.debug('Deleting old image %s', remove_image) + except OSError as error: + if error.errno != errno.ENOENT: + raise @property def name(self): From fb352c20d9205778830196597c968eda495dbac4 Mon Sep 17 00:00:00 2001 From: jbags81 <jon@baginski.org> Date: Sat, 22 Oct 2016 16:59:20 -0400 Subject: [PATCH 146/147] Update wink.py (#3957) * Update wink.py added lambda and smoke detector call in component loading routine to fix broken functionality. * Update wink.py fixed extra space. * Update wink.py applied cleaner refactor per comments * Update wink.py fixed spacing * Update wink.py fixed lint error #1 --- homeassistant/components/wink.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 2e5e7ebcfb4..22c6c992838 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -49,6 +49,10 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) +WINK_COMPONENTS = [ + 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover' +] + def setup(hass, config): """Setup the Wink component.""" @@ -78,19 +82,8 @@ def setup(hass, config): SUBSCRIPTION_HANDLER.set_heartbeat(120) # Load components for the devices in Wink that we support - for component_name, func_exists in ( - ('light', pywink.get_bulbs), - ('switch', lambda: pywink.get_switches or pywink.get_sirens or - pywink.get_powerstrip_outlets), - ('binary_sensor', pywink.get_sensors), - ('sensor', lambda: pywink.get_sensors or pywink.get_eggtrays), - ('lock', pywink.get_locks), - ('cover', pywink.get_shades), - ('cover', pywink.get_garage_doors)): - - if func_exists(): - discovery.load_platform(hass, component_name, DOMAIN, {}, config) - + for component in WINK_COMPONENTS: + discovery.load_platform(hass, component, DOMAIN, {}, config) return True From 6040a40af25a60210e57276e54c70b9d7cfabad0 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny <me@robbiet.us> Date: Sat, 22 Oct 2016 15:09:05 -0700 Subject: [PATCH 147/147] Update version --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 763d3639b68..efb11cdffbf 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 31 -PATCH_VERSION = '0.dev0' +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2)