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;ugH?JE81RS?%% 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

>OtB$S?6nfc+;9OF)(sdw)zwNXm;txfb5 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 literal 32200 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,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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 delta 17070 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

\ 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 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 HGcW)EkW+c$ 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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

YBXF 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 delta 35956 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 S*c0*Qf4%*OX2+M%_ZId diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index d92c374406b..099e51fe6a1 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=[["/","8d89b35f10257827112f606055f6b9d9"],["/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-4be627b74e683af14ef779d8203ec674.html","b79c88170416821dee7b9339b6456a34"],["/frontend/panels/dev-template-d23943fa0370f168714da407c90091a2.html","2cf2426a6aa4ee9c1df74926dc475bc8"],["/frontend/panels/map-af7d04aff7dd5479c5a0016bc8d4dd7d.html","6031df1b4d23d5b321208449b2d293f8"],["/static/core-9b3e5ab4eac7e3b074e0daf3f619a638.js","a778d967944d0cc06b8b1e477e7afc1c"],["/static/frontend-5854807d361de26fe93ad474010f19d2.html","ee9c864599dcd4838c75bcd4a9d44622"],["/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))}).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;alRKFy7=eC0%^TiULZM7=Dx3BX5!H>@s zL`>f9bp0;>cX#z;o7LLQ5q)+K(&iSs>r8)IwfOP++*r$hmqocUyZ)CYKR>r#K`Mjo z+{~Sy|Lh67{``N<|7WEM%FAah6y1AS&(%1>OdY@^`$i$L?aky(30M5nxsIQW!f&3X4JZ9AWt_^t5C5ZdUXvQWvS zKf*0A;L5^=s!KhFk{2zOS1w|C8uRc_07qkg$BQRh<}JGPl_SOLk&2J1L5I`SqdZfO zZd_#2yW&aG;x^_ym4hdJR-1S~wpplhB(q_%lZ@@!Wv$OPO)#jRq?7FFWckFXPgrPW zkD6q&&S}Zz4%?)=SeL4(E362!@fT)2P^46_^wgFxdxmwUhYo1C&NeySF~#$g#w@o@ zF_(KXgg%=rIqAm#xTWY=@Wdb!=g%R>c9_mhnd8M8SrXOXcy!a2RUsnjo@-nsHU~_e zV%((X;iR_N;LL^p(xDTt)CUSJR8tgc@DW-XImvb9B+kVVh50;j&zy9YX$tn_`7 zr!Je|ZgS{Hr$xGEUVT%kit2-6$S_!UW0mNYG$ zWvrHRO!%B(g~hrDJJQuWSD81l`UlNyu?gx@O6V$9jBJT)f7fK9JoiEG)r=DByjOe7 zzW#{)F7Y>Nx|V_4ZJWNuK3?;Vd(JYd_w`UR$Xm=I>@!j3iL>7@Hc9W2ic|0=i zxF#BQzbr(It#j{+UnVDgY!o2?cvM%irJYvTxRxT=q-Co7Qqn zJ9I{SirkhHEf4zYJIrNveLtKY{P?Gm!Pnn?pJLvho6ehX^YyJI^#{MsGmHNbx;{`Y zOD5#-!aEaNWGAbBJ+*spY3c=~%cp+cEcyNGh@_QRoAHX2wGSTe7C&#+SJ+$r%x7+L ze9QBnMd$AhrCi}UHXdkuu+t=2u6)#w` zRQUDY(z}iMyIY@jpPQL=es=wtV2!ozDZ45oe|J?lcK_`z6x4iQ@_*+%ZzC=FIMO`t_O3tDJo1GX0LoahI*XZp5+9CDATyna15b z_S}Y_u{&=&75+*nsF0~ND1E)~;O%8`BDyRq?$zEhd1U1v6}N-`0*{{SP9~dUHXjze zsNckJx5wZH&#hg;-(N&Y+&@?!X2#S~-+V#yPO0LlZ+9{;{uA7Gdh?VVFxW3l?Ry<> z-a~hvC%$LCEENA`@QN$L^yn6cpjXwKPAc2n44sy+jD5?~sCOUyIbSb5sd*_(-G#5+ zuhsVO8MYJuj^ZdVTub8xFtLnl%vGTtAVy>Pw&zSHkUwo0) zmZ_=dTW09oi~rwLZ+l2iMbN!g#rIHFLh{d=B`> zV0p49xqF z_OklPAKbM0frsm+*x1-xIyqY3KW?rHUG?gI|JehJc)wef^e%rLA61{8@H#aA;Lius z&HKM!bSsOAXUu8e-C{9U@N}rPeUOFP-&!Us{+mg)!542``CR_`=)`3?aq)Za?G!7V zD8wBcb<3{h=ZCyyyQ2#YZY|uw@^sDmgg0wX|C)Bp?#kncQ zXRcU3C;59yZ2XgO;U3X5I}4QlZOgo-_UO%-!z_gFSO$*L#WlRX{*Ju&o8*; zQX^WDyw3cfsI+m;y4qJE`O9SM3jfcs7T8}~{^x?A#qQ6C=bHbNf8wBHHdTay0RRP% BYT5t* delta 2266 zcmbO(^i+^TzMF$%kIc{Ti5#l+9t~lpGh^N@^PRo@jNWC--9h{bwhKxVm;~; z|Ns0dr1jZpN~+$Ia`BqScV8@OEMHx=Pj;{1-Cf1PU-b70xjm@gUBqPlOxm#8qR)BX z&bULCcYeB0W4XWXOkXDZF6(ZQDG3bUcSk;Kzx?t`?myloFE75fuXN`JeklOg2B z`B+d{BZE|)J?b$MM^o}k&8Im0VKpjE3| zQmZ?8s!HY*4?VLi3zwLHIcK(bZ8kZV>Etwr%|i3Y#miYy8TESHoXRJS+%gX-PA~NF zT(YSpjc1cix((x|y8$|rTAr}DED}8*yow>hk7ZrR%xhe77gJgq1Mx4`FVk0&y z;6hJomwRH*?9`IQ>gh9|%-Jwu(iHb4!IO^`wrNccInvLNp0H78t(NFf$=G3Xt>jwDb9v6ngtEz?}yoxGcqP(S5E%clt| zEnFiOhd7IIyGrtM9-6XJS5J()uONNqq^?ghyt&&KDFshi7Pv*dc43#GFRQm{y3dK8 z;B$r*7As~b@A8=xlF!2J7wqXQroKbn(wZBxX&7juez7s;ro zOIRgx2p(+dQ2HYAcjB7s3%af*>H08tHYWWsnsn0Rbjxy2j|RhO+737Eg}%w9tqwBD z&U899YoYuk8*wYe$W^X>26j7`{2p&zeEkwv^!DRXF}J1*o?LYQqj^M_@f2szX|09l z>IIcdJQ4yQD!vn#$j_;$W2vcrzV{3Uvd_Zq99_l_YtM|h%C zn%rt*6-*v!FMDcg694(;Eh(SJ9m1|E4+|GPn$bPQEyGK!eS(l;+0xj=#8u*_cO80l zyYJK?g^l8&^Vup_JoZ=-VRR;%M~CN{NU)!>K}3CIQO|Pyy5o%bN0^rRRh!|MgwXO@?WE*ZKYuk9nW@xi$W))YFB>98G0T z$$H)}j0{@8=XP{Bo37E?IrDwDuRlL$VRE8Hv)45)yS?>4lO9W4uSh?4#qyhL#XSA* zXLN6s@0fnRxFoexqq(ZVe_`zH6$f_deymEK-!i%OLC^N1DVuWN*59gPJRW7W@m}~H&`VR3eH}*@2PyFQfeg139;NxZscWDHE-q@*j zVA|rH`~Ihx&7YU>v`I|*O81BE>svRem)5TC&RV>x-u<%Nk@GQIKJzu(`R;r7r1Np| z<~wz3%nx2I%h=^znJ8objRPVKKFjonl-=O_#Y?ll0THUHDB26 zqwprj|BG%J-kTo(&L;oT$-{eI|N0bewnFR370nI(N6W5!EZ1#*;3e`bIJN82+p-I6 zGwx5_tt+yepRN7)ME2nAa#DM;U%6g%YIt?`xv%m%<|PkXe>0qY5Hv;mK=K)8{w_W4 z3-xC?n$Ip+YhBpCuJxnspY&8^hact~a}ULeFUmPTSM05P;%*ylE%wHjb$otS*1sKZ z+?(r31RO26lNSrt}JESHjBq<@;P?< zy|uggS&Od!YS<_H?$FngJ$m79|9C|C|GI4F)V;N)w*SJdBOl5)|MWOrZ?b;qwWs_c!Q%`}D}e zF#r4SE$&KXbKK~CfA`plO?d}xCEf*E8)e+x-1hm!c00Cmt*5(Qlr(;oIelAdj<(OE zU#Bd~y04b%)|noRIM~CX{wRNTSeB>KxpOvaoJEZ`Ik;`(G7Jq|x;T4h) 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 * + * * * * * * * * * * * */ + + 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 Apa1{> 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 * - * * * * * * * * * * * */ - Nk;dFS(W#fxTr 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 C9rii^ delta 2561 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 ySgx8;9TFRHAu@mUR$bjg8qc@3*op-9{ 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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

-xi;%X)2(Tm$I4Z%@)k{Lu8-ys>05b)Q{tp{ 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 Y4S)BX7I;|G)H)mWjPy){3u|YnBNZMK#F=&y3#g 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

n;n#866IP!_dycSzUjTo3pd%)9ZqBGq0Av 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 PKSPu2FTQuo3>*vqRl+5H 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;aCPu!z-c=t+>iOtTfVrP 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 delta 2295 zcmbO(G+n4(zMF$%XA?sN6LWEDQCVhkYJ7QqQFdyPURE)~(umyR+dBmRX>%=KKEc#g zP{pw!%yedq-sNRyZ$C->bntAdLxQ5=LXW8}Z+2|^UoE~w#e3S6JHF{Tli3>Y9^I^e zp6T4GR>$mFPEzTM!l#EF@tLmYqd!~GJD*C?m)A1^#%e%;%m-TMEDNe^C6y=nN?p*($J zlMv5?I&w?G%m3Y7{n%!;c5_6Z z-Gj8b#qK)OpH?k?ygoP9^511quFS6gWy#OatyhrBU^_Q+=jT6r!mdC6U-Q5IS!sgu z@>vT-_g>a>HI6V-$bGVaE%wYZaq)9EKFYefs6=Wh=~-MhVd7Ncaa$6Q03?}I$dpcP@G3pZ*TG^u}*{pL~a=F7c=`Pl#D(VU= z!fgD7Sq~H`6)ZiqCCr{-o#~+i8m_ZVPIpZ4Jf$(qZBxwUo(!SSCQDAb@jq@UIu<-J z$i%t+bI7qBrgKx~c(F#7MD;fw-Lz#@h)BBU8dr(U0h6Z~H|cpesckkmbK$>q=)^05 zLJQRtg&KT>mPSr;T{($!@y;j5LlYw|F3o{Q%^H?4`2F3n*+U(`{}E4IL?FZJ$$4kjyopZesf%O<#+9Qx5|k#6X2IZ-L| z!&cXwCc#SQH4iUH^->iKoRY#*K0SMp)3WbR^2`iQdFliQ*tAJFi~e6VAzdOVYC))C zvh-@fSeb9R~qr^Jz)gH62KVrX2{EeEfW#D$(rf;#2 z*SzDNvy6N_lnn9~a|ruPl==93=8EY@bMmfjitsBvw^J>UZ;~*N>>3WigDoA4I$Tbk zPua^9x<)KJQObjdGf|OkN6(feHcJ&5k4YTkJoRtphH``Gsf!}L>xCu$>YNriTfFe% z@s(nh3zeR8RR{_1)Ly%xw0h0arL~e1b$<2jKe%bu!Z|G|krK~k6rGw`#6;yD@d^H% zAaLk~rzPXRgI^WH*#kOKTvPuE04YGfg1&kGbVCKeNAn=(4jwL+Vu-H zC)2aDUFNvV(NN;-H;hfvyQE_56uilzWeUee?YCUq!FwaFiH6-T3lU@M+`HnJ$q8p! zcc-J167MA?tZZ4HDUr>hc37$P{cBHMpym&|z3&Zm^rMS}g1wvN@9%ZlH|t9-`z7H` zYdNMJI-@;BZp(?52YnspGP}MXP7i+kQ_0}#@4inl@6S!=O}P2`){^>zU+0;{{|H?l zD3>J@Qh#{iorx{7lU2W-+P$|l^@7snQ$KH({Qh-B(n_q&cty(E2ak7)pEv6(>@9!h zGdDTD<@wK|^LG~2c31y2aSxx?VYGt%hsdq1y(+`F ztXV4jdT;67#{Au_PrJ{}%sM~2{!FmOTKAM)m65-@Djd82)^`^QYQ8V|zjK~(X^!x! zZXr|o*%?eZma|^}_D}o#QZP&}W256<4*Tj@?dqWG*LSTjTlK5lw6AQtA15o3u+TU-dx8E3N4wuw_dfrlckcX7@V%`po84PCj#)en;fE%hq2v;#lXBXcx9j z<8B^%Zo|*mowuC|e#XF_+k z^rydnd3NT@Cv58f+25)2RX@_W^~gQo*5>Opk7H2zph$ z>7=sF&CqEH%hM&gX!CJXUfxG3|zow=?F1_s56Y~hP!4}4d(;<6Wg%fP(Ld*YQ#539m!*5B_{G-Ms(%MYpn;c*dOe-7OY#1y6@s+Xq>w z{jFuP;=h?x8+`HBmCxm`k4{{c6Bobt-cGT?i9+1LQMc?`etyVXwmZ7e;MT$&EKk?0 zPk6KT^si~h?A}~G{WrJ!`{J7Wdlw$GN?dQxx4r5Y+sT*LBAgH8)x6*5KCQHl``y8U z{#z3rU(Yk0#1wG?=FvUxc(p}apsEkbCSQO#Ku1f7w!=~v$H_y-?q$a zYLDKWIlN-sgwL!+`$9X8GK5;~nzmXj`}~4iE;XVh$?MDyib@;jtgC$$lD|y0uJHdH dYk~c><$o>+TI~LOc&_ZBN7y!s^TeAQF From a1239077d98ef996d13311da51b2cec754b8b2d5 Mon Sep 17 00:00:00 2001 From: hexa- 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 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 ZQ=6YV2mkFq^WtYcqvy%Z-5Vtt7yw7j;kW<* delta 2534 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 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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 x^=35`>(unZ*IBklT<`0t=n40| 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

w2mip`W@DK!QfVZ&sov8stzvi_OZPC>5J2( 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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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 Q%zyg>r#{?l;bLR}05=){(f|Me literal 2842 zcmb2|=HS>Z^D~@@IU`ZGATcjBM>i$4Ot(0-s4O!%RWG9?H-}-XRCe+09Us?U@Od?< zc!|n#b93FV9>VIG@1ITDy>k8EM^#1>I3g{aS(uw9iOT-3F8{c|p>x~pXI7IXg?}@Z zZS>ceJT>a`%@kKpyV+}2DjiI6O>wE+teWL*7VT-zx6Eiu zG`PQX_AGsmgLc6Y%u#DDZ#|Li$2`s7SqT+PX)>Hdrdr$rT7;|FIsW*%E@M|56wA1sPxA~D-HKWIcN5x*J zCJA|Vv7WuddFxZZs0HS_&ajgVDGEu45GCz(FpEw(H-@1_h- z|7@XidEH`HR$Z;%T{t9 z-X$cnW%m{y4VZ>6VeE8GW-4E9aeqfYrSpG`Flwc*O5A(CEQDnUMC~U3Cme@>@o!vdfpJ^tVN z(M{yljqd#|r>8Z=Yg*j(4e|;MS}C!1)89FwmIc>MZg*F@sxCRK^XG<>e)y6dODkur zi1pm8t*Us_j5A8_cJ_t#D%<5oH!}pqJJlEKJ#_gpNhj>UeU*eg^)?60s#?x|*E^l< zmssh%F(%6BZQ8VFTLtD!IwUkV*2MF5uB@i-BgZH4?E zPm9$G`9V8g@3oWB(C>Qxkz1rljM3d;L%rn0FfG3PNzqj&b_MR49&&XP$Ca+o=I6}q z877ggmaz$}O!5<6IX-fn{;uPD?!^D%a~!U!Tzxsu33%2ST6;?A2Rt9^UFJ;cjil1eFeX(@$ zqaMBkP7nU1aV}aA7v|-3^bLpe;-q@jhi(TAt}D7MHLAMNVlF**{^iD#494<|(P49R zGfcc{3g&G6P?2ME!z(cC)RNY|WK)gTS)z?Qqw8h=XhjINPTnbV?$D3fAO74tTE5tD z$rRT~7c(mC_Qa|`w2Yko*ubvAnfYG`_tnP# z7Z1$;`%+)_!;yx{=@n@Uwo7qMW)2Sg@m{INIJxAl)mv59dz}{r|EzkX{cnf5oyNM^ z6Z~1^_PiEg_7!&aPmgMRJae^5K>z-XYeq6w>^8OuFHf$zxZ%aEEuH3t-AAwe>1j+^ z>9J7C@7a{Ydk!sFIbHMagR*6{`O!i7#p`=@Tvk;EODyj&vR(W`Q1QLMf@tS`s=H4K zNF*(}RCTp$-=zrOH8+)tSUzNzzKJ?(#=7n8va`p(T-3X;H0*BsEC0J!UOL~*jQl(& zv*X^IEhqQw+jiq*K>ZoDzqNBE*OaD~*Y3!7?zVigEpx~H=;a^%cHO`2-(t7o#{ZS} z_A{C5zPJ6Y&pO<@Ye@u?q8@}(qSDH3MZ&m$- zi!S*Z^Xl$tA2?k5P~*#wBbRUN_2;X&waP~RMf8W;@n5|Tek(pPGjPRaq3ub}b{j1G zxH2wY)uy~?PRSfjyH`sZBag>jIGJ!k^iRZ3=Wgf2!AmU!OIO9m=dS))$GcY2Bx&)t zpZQk`wztg+@P`SZ>%xY>EhME#SVt}1in?hxfY?$c*|cabhpnsVT$ zM1q!V$+En?g4rE?TAQ+NEY%EX6TS9OoMX?XZx)kgu(*Y2-(0jYXXDKl7Hv;ONgLx# zo~RSMr#%r0f3Q2@@FbB56+J3GmQn^K1>X0S)SPSXIee^pQawLorM@TE-QC}c4~d2cipBOjxxXLW;Qb)#d2r?e(=#vCY%AUdra1rJ_@LD9ZN7lF-+Si2Pvy=l zUSbwl+Ub1zv7FpVUxD=V(@sB?kWa|IaUyH#MkW@4d;f$kbRXV+U(UsN1OJ`h-;THC zpZ?7zW7@hiKX-DV)>-C1 znp8g5X;=8720i{+eQPs6f8aLu-P$yby;Gp4`}>F7IEUSLp3Rl4Jvv9SyjbqtJP+&H z{QS>9-&--idZvJMgExzlk%@ZO*K_xp&318`*=+VcRO!IzU>$9 z#cz+~dJ~eQbU%CA$K{*1nQu?L+j7@!W%#764aeUyJy-lzmi5AmKXS#z^J@9LVjq0TXyL4M9P3$o60Y|cx);}U)O&|H=Bv+DL*761C2CvHFD zl$3YxW667m_&ST~t%^O##t+w>xAa*a^I&HG<tg;LD!6b})p3+Cr|E9S)AVa-&w)x*wLH&GR0hU!{)J?H$7#1 z_+WqF53%+O3^O%T-8{9=tS_A#WUt9`32YH&;M#vvS0u4 o`zbT`bH)4>o>Y5Ga%%hCxFdi5w=ewnU+6#MruH;hGergl06BYty#N3J 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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 Q-1i?l_v?j$Q$-jU03bqoPXGV_ literal 2327 zcmb2|=HSruWQ$;8E>0~f%S=vs+5rpps` zS>%$1@ZMERG+$+1XsTR1pVN~4>qgCJ@jbKI&a7I=CQ#G-`r20}%|!{fDUrO#ioQt8aK;qnyTn|FQP{82yJb@BS!9H?0p(JHCba?p#+cFN21E zd1XHymRQwJu4m1x`tr*^F2Kpu`18Ypy&=~`-I7#&@8+2Ev{yf>;dMI~o31nM=nmue zRx@V1&g^-5i|zRS$cCBMU)`zv{p>IM@%haG7q&bXyIcR|{JY-IYu523S^jD`XZ5@< zLhtm}#gEtL##;WnEXtMH^}p=>xw-L5$!8cHI-ub?+vIe|6q8iWS#FzR)Fm&fJj{Ni6sPJuHT37WZq>cW;C94Xqj_m9 zT{_;HCs(q}YwvnC$1Td+?b}lQ5JRU%ovU*U6^;EYk3V_1AgRyJ*fVIappJ#FDsSbI zkfsJr9m|tSoliZI9j4BG(0etb#5(WQ9<#4MV!s>z+Nu|t(6i0#SfHBf`Q%y4e1x-g z!#rA8mwEL4`0I1!bds5QcBGDf$+?|sHzZWlXILe22p(+eSk&S2C4Ey|qgGh=Rs&uY zHm`^dPQ8t5Rcw5nn0gG)2v7NIocKOro0jY5X=(<)Bd4uYIp2CrVtLdP6+z4J1DQTg z)@?cT?d?Xf>E9AvHodU^$2;vR&(nZOsfjtx5(^!fW^?ghbat^*Y1!~($sC6N8*_zs z`%lt77@!@oT|r%9yT;YU3L1H~ZeM(BToP4*Z%rq1~#lrAzhjuA@hSrYz5t z$YxPHtk(MT%gy%|54J`exRSZvO*%*-)@|{`;%D0eUM*;hY-G7)cxH05xubTrO49L< zf0DuydmpKM3Nz8TsL;As=tQ%J?d9%|@3lAoF#7j~dug4s)3z^_e-{OPsI9V3j)>d# zL}l4DHv4_moi(N}kMk{2Pl*=PX`LCaVVg0L^PsF?zD3FV`1!uQ&$|y?d2fBHc&~N5 zSwrr6>B;|CuUFryc^JOet#U0zg?T^*zKeJJpJ<9$HM6^A|%c& zn6$O2|9zazZI)xE_huTG_SFi`dw$xsxci&#uTPnM3v~oj1Nc8^ZpmgBjp$d6%YU=D zU=#nGkK1PWY+D=~t^eSLhx)aBrI#D?m$yFc)|-)~KdW9RNMo&g%C5?Y<6RYw-JiP) z1vTG)`Li?5xb%&{s%{}u`REKL8OvF(fBUC>ekoX{mr>}jm&3mLu10mx_3OJd+phbhxR+Z5UGmo*koHCwWYr0 z@uxK_WLTE{E-tuq^CNp=_I}aR{v96kdCHpWnf$8zb{;W!`}n4NRdJft%Lh;XANYGD z(O-C~*7E{hQJ@cQdxR@tJy`;^d`FAdvbPRst& zOx$Z?>3Zu`%ZY~x?uZ?*2bCK#yf=G zGVIeg4EV7!gMG#iT`Mt*TDH^Ynv`Wv`jwVuuHLZn&hhiFLRYS;w>M+%HoNosg6X_< z^_g=U--hpHecl{@&*q-H^v%Lr)(y3HMUTAfidc8+=fq=%FW(CrRGbidJ-h4Lw0Zk) z>-X`be*X39>$$BR-BTiFT)Om4@l1QU@!h*!%{Tdus%oUyvL@%pUfXW+?RebYJNLI$ z*suD2S6b?g@V8%&FI>HB?%}unxD?a1k9&X1MPAe0!M?uwO5dI>4D)P+Gq1QT;}xDqx8C3h zSh_fSXV%8=Jga<62a&p_+t^RXgo!xX;|J{6U_D9?0`{db}!pjSHd{+C< N{LMP*$W##q1^_JLe*ypi 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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 ZOaCvN5&PJv#Y*nK{YBo+XRQJp3;?Pc*Pj3Y delta 44468 zcmdn^p1t7}JG*>02S@ZHu15B)?2LS_^$grgJZ^f)T-b1I<J`{k$NICiXP#VY`O8s7 zLuna<9>c95mAdc2p?~vCJiH3eow;|m&-Kgh)w@@(UiG^1qvwLa>_vT^o^F0`6$~w^ zg+G?qu{N%|GdpNg?yAVhgL&a8A9%f9_-)Pl_>o)QGe+`I@a^3_Ng>wLw<f=j_S$L| zQ~JIBY?$T2{jzUXF&?*D74<z}Yux45Qc3<p%M0IY)Nsd~e*e*QpSYX${_+ZuZ)}SH z8o9+CCzWl!b9hdBd!oI}iYG~-$yQOFU;5-;9(5I4tz=}V6*!$EPJh;q<_|$j3?EHb zwG-`paiu-z-l049Ha?$zedG6s%muM`AM?vE`z>$x<wO1ZztVr+BziUMjQi2HMCtvd z&*Ia)?+YZ<{diFQd;RxD;qCwbeo$<%ew|#?FgIMj=1p7Fns@C#W*@6P>{i8`e7J}I zpyLm@1>bobe%jda)c45qyq4Q5kQL4o=W#%x=1FFu;I<7{Bs6bSdM@M(zFjMP{_CFU zNmK7nJ8F4sN$>hEZ|b)fN6fESRsOljb6?tvuOhX__HQ_M>ehUQ<C5D>^O$~{EcZP9 zxN$=G1EqaCJ(Zn#R<MZ1+@I88eXwLf-UV;HnIDC|PVv=yJzI53mWJ3x)nbohQ+>{I z{uT)_kIK5toK&zk=+Lz*CDXz=4r=bRXx|($We&rC*8fK&9h1+_d+C+aQ16(q_Vn`< zpDPpJPM3+l`K{)U?W7ZKKLUAetJ>Qwm)?um^<81+s-GeA9tT&-UH&{VL(ZbvB7C3v zuftCY9r>TV;14Kkbhgl|e#5<2ZsP+x<}a+;li4T!nJM$bb-H}EOt#y>s6U&IA6;tt zU+_ip^wtz1A@4`#6RvAqJteqj<rim-dfk&g+Q!NGA011B|0T<|tkh#O-o7;8_Z0rN zo`Uo4rR4_CbQMl7s8?<ew3ocITm96x-btUd-)SFMUhv0wuf_)c1HHEE+Lu;cJ?Fmk zgqY2dxR@2;><>;P96Xih<vp$c(qETvQ{NtzN;y~?!L1>{b6LWoX-CBR>%3*s+oZ$) zRMpp)7KZ7(yPI5Z85zXDf1+&ClKs`2H|=0>%TEzKB7W8+t#u>EEca+GY0Y<sO!N%A zotXEYTBLErtnJhnb>^yjo=Y!B)a-X;YI`5puzQM%zyy!&E!@5hmv5e4CBIpQjZ6Mr z_t*WGw2i*M*&w#{^;D^qRXTbljZ7yWoqRB@ch1tU&+AQC{dP}&a{1hX=bSkW#R1D5 zd=7gr3-14sr$5stOuVJdu(9;?yr=W_SAKP}Vg3@g-_o-7|LNP?^Y6{wQ&IQp(cRq> zt}Nek-SuzX)*~LvUru3r*($E%6w@-d#O3~%7Y9<3rfR%j%XuW#>*K#Ywiox>W`8VG ze17p@L90W($ZxrGQ|i^Cjxfn91mE4^l@P1MpT*-nKj!-B%V+0_8Jz2~33;S$&KIH@ z!5(s`Y8L0AoZda3<K$;NTxW0bLizh``E^tB4tUMk!#QV9=;tTC8L4XhtIvAJmrgQL zwb1KY<Ru<Hx2xf%<?ctj7hSL9*YwZ`KV35Etcz9dMy+`U6{;C(8b?^`8zwZw9u#oX zdp3iA>Y9S1#~)WT2=Cj;xJ2w(w%W#hVRpKUCoZ>(i#t%~z@0i%smHkI$oGtwYt{F7 zPtOdJ_B>O?^>nY>rkV#Kp6A7GUh;9!pMB3FW#9e6hA)qoL~NQ9pgKWWz_xLxx;Upu z`H?j)o@u6DM%tTh=%x8iOAAZQOskLO*^_wfh{$HSWgDWBX01`Rj0`{gmf1kjE&gpn zF~d>QSX17Mddp6vUF9)5e<|lttkK!9sdi>Mhl9SRE-{QcnKR+7Wt8_O>!P1lS@F-e z-A(9|nyb@u>)VWM@mW_Rbyi$F=KCt@rCP<&X)|>%d1nax|05e9Fy-L<_s>(O)jyH9 zuV=XD?2vGwOoyw!&PQ((Yu2oX?DmJhOy-xb|KG*7t|aZ_YkAH7;D#HM*_|Z1XN0_H zn9uIsBowtk{^!@`j)e=7m>o+6r&T}s7@u_iqraidKh0-*TB5m3z1-yfAN}=Ieo^v2 z&FW|O*St1eQ1yNGmDk_*nXiAHT2k=g=jZ45p0n?*Uu0u1QS<-rclPO@e<d;MUR)e% z(HW>Q!TlbCb8XC$4R`gF-ZS?9HvB13=`PZmz-DnR?o@_Ql(%N^6}btXN%I|=BG$jS zwR_K-u!2~<Jtux|`_IecU0Ywya{r59W&ghaZoPc5a@7x+_AWnkTyeeI{eawpBSImk z6sN_vXED26e^WoHZ%_MQyJO|iJ`1gX?Bx6E+B9*|^zQGUSvow{OiteN`)R8CpZ*zo z&)CFPB~9tSwy5_}c1mxz%-oFmA}-EJ6T*-2a&tMw?Jd0Yz?wC<w3qkg`wtr*w#3z^ zTe|5hFz{QrI~YDN{V$`%a95!&E6?D>zMrBFIqxQ?Dg2gvW6v<D{<6PZxJIekp|8)5 zg)sefuex?|N<v`7=J$q^RvJ%jR>_(uVsl}~n$J5m?3EU>?djUyKQE%t<V+O9uP;>- zW--?{^QZ|}F^l|Il-}@c%H?mGlQ%mDCp%8L#v$+aWl7PAO2#LCb@QfmRi!MBJlvJ> z+R-E=T7~W3lG$C?SLHtab5Wvx#*Ys?XAjL-?fjo7H{DbEaocUFq&X*Nr*2wxN&2J6 zLhhaUGoIb!4VK!YSjoFusU-aC_0DC#xbh7A>W^_U%<Ek!zozl)P3y-x*KP&OJ5<gU zyl>$Rr-a=zPi3uLy=K<0jurKbHriFchz`7P?TxdcNOhr}?VsX(vah+<P0oHDDD=Dj z?AAX9tPyLzPPlUNgZ!6#u}AzT>!fczD!O<n&~Z*=J^us6pSNevW~@^`mDQbI6WI1l zS0`?oj+MOhmL10~ubIvNd+DW_*IV>eHAUFGE}Js9#dLU1DL?M=fw!UV!6MIZ?224X zTAWWLYdq_eVvbBMkUQVDAyJ4c;^<DnA8z8BtLr89@~R(r)hWlPSlZV=?V&=S`jUW6 zO-h#61=7P(4&=IbPpF$Oa?xf<?U}^}uai9+MZ{NXXG?RujD0fY<<BcU6Yl7=dZkEC zdi^tXV?vf=M(oK=aj#z1Ne0#4idFo2wkPD;q>HD`y9&2(mInXL?tSB{^^Rj%(9t9F zw>i29?taHp|0SF&Lavo1H0IYU#*qE;CO4hLLZmA;zTqrUn*QXTt<8y=&})2e6#3Jy zo&D-LF~)D*)T!*&0<QUgHQq<+Z+Yq{$P>IUN9b9R?t|qUeNvVlI2+0HWD?8nMWQCx z=jk+TSL@%OedP1QE7N|O1<s8L&<p**zpGB)!h(P9>psPny2$!NBJ)&5e}(3I<ShKl zrP?g2`IoWnko=pJ9)<dx42Ld8$yKjsuG@94Frv+(r}TisQg0sP*o8{cawYs#rBPSy zvXtN5+*M~EtNT3DFMMZ2H{T<}*sY&GOj!NHS0TC4*mSDJOd+W)@-1?wHW*!M*GP1{ zBA%6T!vC4@=AQ7yDQm9QH~KdJQqqa^ysuW+c{|gjPfKm@JATH~XXN}@64ICq8g{vD zHdH7zNt|_>Urg6)RfFEUyOZ}nU-O7n+gjpxcY)th{fJ{Ge${OA9#l`Cm#Zh^9vJSp zs^440>H2Y36W8CDw>rH|TWoiM#esjPi+aHJ7<MO{O~(!!&((5XGr!?qRQ;0F7@nDn z?rl|F+hH>|f_L^O;RSj7Un(rTo3JC$bsmSvIkugVw>pmQ&3V`&yR%0reNN}m-rdz9 zKAmx%g@@loNj+W^Ufs_q81VOhz*5eAH||bQ<T7}c+{OGxXLJ6fg&U5)DcE#k+QfGK z-F6|I*2nA_pKLxjhw&TZvdB*nb7C*;sFy$XwIkrdn!+x9<@)Pe%~BU8EvZO5So$)? zaDMxvo?aHU9T$QF*cA7<TI8*Jqqn%SJ6|$tR=B{!6*rf2Xg%hwGSHpN&c18Dp8w2K zo-A?x>0c!d@aD}Hzig3b>^gsP(!)xpmCWKhJ!)2-b}j5(o0F=cX*VsyVOBy#Tt@57 zMQru<si9@lbUApwNUQSMEHKYzidd7G>^YNJK8|H#%j9E;cb;#v4%nBw<u%jN6`Ph_ z=J<3er+%aAWD$ksOAe*K-=g;_MNz&ccpcBox66ZOPSQ|!<TSkGxp>xxdECN&x0zD8 zHy3I<i!yR~#F$9Li5TDgkj->9ENjZ<doQy(^R!~T>f;@rFZwI4y!%m>?70hlVj6tA z9jn+Dyr~S@+rDx`#l&@H7bkl7x>knF?5UHU5EaQ8p){MR&_237^Sx<FTC<mzWQnkO z%HN9{nX8vh2=<#DJZ*7u-{+ue2Z_%0xmlMse_+ts)oD<2!{Pd>qyN3<-&h=#m$BtC zgIBW5a}BEv3w-ONE1hNeXK2@FosR$Uc+rEqSGAR%KU(`uQu<lttU|AAr<W~R(P?iW zvuw)_)mqaV)1JvatGZ_5F5=*0uC3Hk+07^%5K%VkN}!r?v&{|v_r|@Sji0q$Uvt-E z%GJ-u_bz^H<u!9lt6)^2&YW2qQd?d4zwk+)o^)}CL7hUV^OK#0>*|@(p3d5l^*%H# zc7put%;O6G8+Gl}8@-M`@te4P{<`eR8C|?G)69EWy|(85?OE+FAphV<!-0bfqbJr( z+T<QNLn!*Co8z;HX=Rq*TbFwlJ(|Lv{=mB7(vDYZE8D6L{hD0)KmFVMOABXxn!R#T z#bdYrZRWm_v-Zuj<Jrmi-qzOAw!ZS$yZ8EjS9aXHe)6c1+eMiLr_UeQdZ1O&wYKnZ zDL31h&rZ+&X-)Q1F;?mI-@NjYx$%PZ=HmKz3!w`Msy(-s1bCO9^inc3HQfF*y6u=K zOUBdVK{KlV@9>yX?w$E$_16PI7MyF3<|ak0PEuXS-{<E$H>^#i_Tfemhm9B57W#g! z&-I!Zd~)?FwhZUk8I=}HUJrsV&z)rV@%Z2B@4pUdFS>U8qn@+t;rX$rZuYYCCU}>7 zEmB<gVfEp-DqmA^&8r(08##v=YMfXtAmAoHf2E+ywbG5^<_p&NY~ISNRugLUw5w}Q z+PkeHxjCEEYxzPSZ|Z*1l%1^>I(@5{^sKHGiRJZ|w^>&-1XxdBnfz+)qHpPY;@iCx zg%8^=6%F2CdhXm=-ryx0`DT9Fqunn%e|d!X+WmR87g~HaWKK5!Jmc58rlyYzWee&a zs5Y$iWytQYjH&rz<IGmEYRhp~3-c&;OVeu66t?CyTGv}vb9&VZ&i;@sxp6<|hBWDe ze~%^JH@m(&qyDt?(w)oa+a7;5-KU|IyNxd;X7|M%#~q%3eD<`O^;Bzs{<fL#b_@Ja zD9IIj{2{kdc=-k6i;Z<}jNh$Uw)i23>t?HOYdRHK?p>Z$BV@gy?T)XQUj7xg)&6!0 ziyPavZ8JD>fqNydBSUH8f@HSeCpsmUoVMFmq|>ovueSLEgZz_q^%WEEo!NMUV`6Vl z|J3YhC$z&YcSU_Y@l@~96Fv7OYCjmm_!c!)Ca6peIqqe1>^RTcg#q=KKkkh$TC&%w z^kQQ23y;l=7YmoWtWK*eEWMDdDzM%-=-z9M*^CQI+_VqgYi!w6{rQ$*_{7Sp%wJMr zA`|nSgO2;3^5&e~yhrrRXUUfO`jeT>2Ok^EUYyUjtuv}$mi^o;7WoK)t?`c56Q|ZD zY|6Cho0#SCFHgnkM$y4nOLvw1me#+TYu@;FUHmy$p^Sa5zaqWw<}2>Lx+!<zhVtca zbc-hE9?KPelK)tG<>TKQJ65^nn0GJFXffQ%;n}|5>QmD32otf6QwOy??9*+|+^=7z zH-%Zt|5i&^Mr+vWDhKyv+Ap<^HqS_T*l=g>qV|R2I%~G+U75fUymtOOrO<dsMQNq! z(mo~^BQ9*<J2WlOzpG|aL9_G0RN4NX^Mxi-?9K}I8&)QTzTRhLy!@2Y**RrhH%=+; zJ=|7ne(qIqXsWv1<1;O19Zfun8hdJ<pRLfT|2VO1*=}9~S(D5iD|mmKW*&|8IHXXr zNuT#qbT0Q6(T5_g^OjwH#nQZT=9I=I5)YJiKP#^=;rQ7r!+UMg{mYHr6P8{r{CUcF z@hi`))>Gd3Vuxa<WiedLGZu{~p0djHlIF6IpA$RXZvEPJyu)sT4Bw_e<H(N-E_~A3 z?|oZwR;xt)TK>zn7wznKY0BP}y}$V8%{e^kD)TF9Zf&l(_v1>+Re#=fvrNRiv`yD_ zOf?c_{*pe&j9-~e^<aD8m-*6a1_xcw#QCqCp7&Ppz$DhvU~AX<&(rK)ez}_0b#=#t zvYFF<)HF@MySMrlJJ+VZwfXiPzkcnmD6lPkELF3ef4S3%o7d}Wk6rQM^F0`+cfnb~ z{N;3`p2W;a91mlrXcfqD{&O~tTKKH5=66BU%!e#N&m9_f?O56Ww<X@<kwW0MTXUaW z*;z34;`a^m0h5j}F4@Y=^EDu1#|b{eMN_rReRw(j<enY>#MBe^Ny~-fQT{&%JJ-*( z+Nzv~e(tS~-fQk0?Cp}WxLzx!_xhXI>dDWaPv~P?#hRn_w2J5E{p&Gd9HmXw-^1$U zT7=gvVGO@7;k)S&@4D5!+thW_SJ<cN$$v08daUGQmNrk}0?+@8%g&os*S`$!(^rps zHfuxRWsb9=h4Zy9x~#eTds_%!sF|r?u=LVtix&wS9}=}_@?*BP*u;8#^Cg-33wH00 z7EIq;=qR+PyY#k2Qa8`7=t<56e-%0=Ts(JLQP^X$^QKRxolE_M4xTZ3r=`x}CXsh* zZLMeZS(nqFngX^rw{G7$yXk@+`x}w%R&)D;qaG!#U=yEjwNB)%m8aw^v0`;|yOl4G zv0vU?d?P_K>!(cm3i(KJd-3_LO~Eam59>9Y)g4!FUZq#NaGprd^=&_Mq?UVizlxV% z^_zX+bI%9Swf!qnOXQ!dFgAOWJ2S07HvLSz+G8D)=Lb46+b=EVS*McM>{1faqGMuv ze8vivu6uz(FQh)0eo($pwPX67I}4_(`Iu^LYI=R_?Q+W!-r@tUJA2k~r`NrI;9M$P zWUKS3enM68W%tm%iRWss{CZKzZfxY>b?JlNgOwSE*WNDTN-KLW7s6>gMVR%;y09bJ z-=9wU@^!h{!avbP@)Z|Xgmh1TJwf+PC&PlrB^|bW&2K#G=F|#55B%plhxcmP&iQgb zZti=h>i+r8wM;3WOPjLu{uW%d+~go^@%de8HN#`EBUWDZ*5{NRuFTx0qkaFvQs<*L z1?0baiGGbRPF0otmsL7(-jth1SyxS2td)DL%<tefr$*`4-y0@#rOe=9NQ&<CS|F+Y zPT<`$?|EzQe99Axva|9HVhk-=+WvBbNPEw^oi`tMuKV0JuQmOC$0h^sA3~zXa@e>H z^ZYz@G8Rnz;V?Z``(u5fg_zX(#VTu@syU^%ZTMxh)=hJ<NTfi3uGI5F-r2gRt|uQ> zjD8WNTos^pi!)d8_hGM<4q=BD7dPjgce^@+w?Oo9{(iOKH-Ve>^BX*Py*bG}w&uYW z?vIU^j}>oU{@5>$UwPxySL*98?>T;<);{K0o$51Nr{`g8M$@{!%zGZRq~8B~a!LP- z$$Wc1eK3n#%^OwYb1{6;-sF?A%>GGf{S&6G-m-m3uZjO^o}<>O8UnVJ=^7q?X6npv zXA5I|D*2JC)@Dh8`#!caQ~gp83-f+0N>tMR-Y=y0v?NPh{N|MxXD(G3yweE&?HKNM z=^1~x^R)2HS_binmCXy~45RKb8BVO{UAxL&c+UMP`In2F-d%sYDo63h#uGOZw@E9u zn#gv_{(kXTS}3`!nN{$4zsmwP8UD7}&(^wE?C@OOn|XOkVe;KSIyOJ9GtQ6VR58AH zxNUyUorn$fz1HCikGWNAZV{I&sA-#ZDdX{)yls3^zg~y0v6*%_gsJuK)3~>jyzigm zG_KcHlwe!QdFl1j)Rv15E1$mc-#TY!blO7x4E6;^etzF>R99=pFRaMdPqsH*qa4T= z^;+Qcu{wi0$^2~v0`4X?!S?J-I_qb|7Y9lOOpr3GlSyh=p?FWgO3!P{`^LW60<srV zGPbNpI?{gDWKre$Et1-SS|Y7&6^uuP@APs0W?fS6%U92LJlpfMir<lzh4VRcj?4Xh zSuyeY#~p3{E3<#3{4TWA5(xczT%v)|ckPqb9`oOJT2CIX`H*C;saE<^@}29B{dr|c zS#@4+FFKa@b1U%+M8Cc8mHl^N%!*x#f6t!vI(x)u)&8BDe}sDNGk7ZQPO~u$U-?LE z?g{M@wO*GwFX|^fo4p`T(LDX~#`G7hvNcjgT4l^?8ctg-?0plqB>j|fS-1WT)!AE* zn{4!n{nK&o&|mh(B^&m}$%l3{9x~(1xtqN9OW4e|Pc4f&4w;lHgmrU#3oNg%c~d{* z-v0~SeHZ5jmj2K7`+4mUdqKPPz7@@EKTm#F?@i1Jm>M&yFMrad`h3j`yMD|&KIiI= zO!ur)3o|_@E5=sXe`mkp(=qAN#;4z}FAQ(HCb}p0ibJW!oYXo!(R0Ug^Kvs2vP4Rb z>jxNf=jT38({MI?7!Z+LZd}rP;qMvWO%wUwoZIPGvSF!gPOElovdfRkO+J;yUu9-| zt=u>xFGq!Q=F0N#F*mCYA5*DMe((EL>&^C<bHXJbpB}u>eg5h(F5wj)+@~Ksv$QDX z-%-=EUY?5#>@^MvUX@(+(D?Rci(-wxrgv=>Q#GfoJJ~$>o9f()3sxJ<m7b_K?eV`t z*@fAU9*4Zl6Mp(^j;OTcgVu@5r|c`YJpbY$$E&y>9vzGN9X5IQ?_K{w<F)4@(T&sU zkNt?-do9Oob5?8SnsCl_H;-EzMLbw{$s#mlfv2J6^NM*_I?ufIeV83#@bymGUKx$0 z?2nrdFJ8Rx*zd)s({|{-IIsBc<k!iQU%%Tu@w%~uu==sdH&)4e$*|R4(mcLe^w<KP z@Xq9Vhc_0HQ?hO-D);?uIHf&HW?xeE{GWeV9zW!&kNkN-`}}#K`?viiy;-E?@=h_B zvpv53KIgl==O=&9*184NCBGhqC9>O#eR?hw{ycKx<JO1O?U#MG`^dfe7q(0I@Kd+x zk{fQU%*ak!K5=i$+z4^+nI?vFwN8}my1MM;k_YP_>&*4r?N@m`ugz_Dfbq$0gI5~o z-X8XER56+3bALm<^0`OGb(?$^hQIjA$UKMf=AJ!Yszl$sJbA~TDa8Jv2=_!e=Ut5M z|34l7zJK3O@hGPc+3$5Y)@{tyynbj8|G$Hl$HJ1_nVh3|z8ZDDJ~b<#!s61}Uq^4R zzwY(Sw0CcO!erIGt0EYSo2_~JHeOp=tDXFOy{#+fi+QZ^)xR%aus&WtgX{cOC3g8$ z7wgLZEIhO;{|t}MrwK7qvU~m&+^_Fro6P?2_3Y;dU;Jxt{+Rvq=d;5M_4OZKzGj{x z-Tu7b$2s})ES_R#+@2@vX>s?}#&&gBF8prFme%g&Sh8=w|Eag<Ii0<g#6*j%gb!LR zn?I?w{@EN&hr)B|ui9E04*#`tYpr(<?D}$f>d6MS$ro#Q!h0f?e7#kow9Zv_!FLtq zH?CO~*=k`eVpAh8ahv|K3Fs^k_WW5Ww?Js7mWpibbl&<@rEVLEZx>I`iS>AETyo(6 zmuZK(T>7c3xZwTrD`xl|*gD~`>CqdlwOeGjZ%mVVGd<(<`K4}g>H8Pox!rwi9#5Zb zy^VQ?S7Eigru)Lp5`9m7s|qK_-Sc^7uKMTKm!Hor|9K{?Q1e5*@k#gW?r!NHQP<2* zcd}O>pAf&|>AZ_Yliy}|H`cyq+dC;><r$4q1K;!cr&6kl`~E+;!GEqZ)FG<1Q|{5{ z3qPhU>Zst8%+qw8ljB_R*<s5s6Z`lDTOE@=FD<P1ig@<@ZSUO0n=c#@Q`o@t$M<=; zh~ES256_b1J)boQJXm_6>tpVusVdA#_m1kztS{mgoYT;$8RYBJ*q@P|kRmKTIkklE zx!5;>4Qp?AD=|NNXb|bhlYTnn-;s(&IsZG?pRp#NyRx;@u>0e=EKU3N!rnF3UpGJW zIjQvgYxgy;<a+V7Q#-`hdMIW6>Di-exZE?RWTE->x$31)#brc3JDaHFWqmyRFK<@r ztr@Ck7ntl#?zAxzwiaIgXickRuhyN_LSYN+Gk)A!J7eaOFQ2d28vnH`tt|W(DrL85 zgZ!U{crL9EiO#OOET1l0yh?cDfoj3|OJ53heP=dsNw41V{(<+D`a~N^=?M!a8W}kp z)nBEt#$z|<tcj04G0b>!rE~s`6F*;cNv|!sG<$a2A5mXlex7yF)k+2}C;oYym{q%3 zRJQUT_uGa17plMVr8bsvH7(l_t<EI*>E_z43{ld>nb~#k?>WCyzRKg1(dFSiS2i(i zyG{q&|CzG=-+ujYy7OhpMCSVX8N5sLi$zNsYx$2hPtlFiIUE$%#aiI;W^1NxpG=U4 za?M1wsN}HcDM9Qy9Q6u!6?R;I5Yn=AO3203&(4LOlekv#%D=d~s<-vLXIMkqd+RqR ze9yOQ2?a^NZF00rJN{DR_I>$eo)uqjy}f@p!dvP8#0w@b>JFwEe=a?&xK?EPq59ks z2fMBI@7W!$^!*d<c`qC(?REMDPwdhU$IIj-C%iB(3D<H;I%<<^A8ehkQB%tk&{NsC zv1ua1+9%Q~^>3_XxY+{~Et^&^;MCC+iIQ7?W~~mp{b51B#FpTv-pN`|7M}iK64$%T zW3K-LezP4t{{8FcyZNq~^keaq=GPT^e?Hd>SUP2GTCeP!COoerpZjWy{cOqC7k9o) zNPRP(?P96W*~2!o7S`<U6U$EFTGGFI=^WL?UDJ=eDHi&<t8Y?))Y%K_{hxNPy}8xv zcgXXP7h6``k8--QOvd!1>lcMBIn(oUx@9uEY-=R;m8BfH8L%MW%UZrA#WQ2=u6rvw zWD4(;I$A&XO@@i67t`8at@>@JytizSKm2_8<BJ*m-xL$JZoKW!nR_R5$|d;&US~F& zIPUGgxA7>umuzo%;M>mk`@=nbzq;`s3t|1gY_akZ(<R$;?P}~*TU~8rG-VhM{CN37 z(Jo8*B>Rem98(UsS{;t=_to5Z`tJD`pR#Ha^q!uIy|jPN&iY+HA3r;2V)0KZ%Vx%_ zQng)ME41&tzw5kK{Yw9##dA-IciFBCD_$$~cM_}RIVT=R*$BnF%Wd-)aQ?J<y|7vT zmm%w{yQ>?*98$~zZz;JS{HVZWqk8@X%XtsxgLyp*cixb3E?#(VQ{dg+b@M9UTv9a9 zP!tO}>En2rXTe2*DKTEB>+R%xJP*n#>z%rD<&0~{5z`e9<_F6ik7mfz4|7o0|82F% zw*O|OoYUXp8<kg%S=t>;vR1yB%8}Wg{A2&thr3plwjMIx=r;evO5r~{gu1`ZT<UXJ z*x_IL;}5KVe!3{n`<?!9`GV9b%4_<pqJFR+J013Q&OGVqkC^XE`R{C7Y+JUhp8X(8 z(#PcqLi0Uamk4VVyuZi&Yx}43O&05FBslI^eqdBSUh`k8VPUf5|KbHX8~qme-?*;- z;yhQ^`=i?XONF*Gn)cta?F>&6V7BaF<a64`5%*}5{n7e+kL>p!UiIhS53e<+-Y13L zJK9@YXZowQCTE|q$xFQ*TK9iO82#b-&vLll`-k~4$rk^c`UgVuk8(X=){md<z0<(| z>MEZ@+XI^3>6xv%{!v6>6HCG#vHl&G3tqY$<71U=Oj;|WI@?Ni=?jMLDgH~C`rB-- zE?hKe#aELbV)8dd-!A-ew_;+1tlbO=@7v!S{+;RgH|3L@{)A03i%i80>o3nee*N+n znfllx*Vojq&3LkR+5cu<1Mc)MW;*{ig|mI^&A<0TWJa&Uwc!8H6YC-uJY;@i_w~r} zr5{UEYyKwIbfy`)9uAuD!+43ns-sK?rls2Tik*)=SDc|`lkQ;o=DEcK)^#b3jO$O; zonorbVEs~Js52|!tKr|CX1_fXJU2%O%~tVVRQEhYwrJg%IrWy=Gh)PbC06)lSuSLq zJ>ksJH2;csO+`2T7k6q$W*nTIVBhOhFCY@xH`iU*#`346`VP6g8uK-wJ+Bj%haU)u zFzJ0&_~6>pW8G`7e%G#?pJ-@t%<!geq3eRL2A{V+Y&olOXJL6@rr}OOow(fg2Xb4F zK3MjsO~z5AcD1Tm*Uh)}`*Kq@s)wJQ*>}1y(9h`ctli;}%(u6s-+J%GRa^We^r@Bk zi-%SVw%MBRI=DKY-FiYp<O`FlpQXMjW|wW;I`z8kl8ASc_f623Fxxw~=HMUu|J&yr zWGLKuR{2sv`E<`&!q0ndewnzCQH{U*p62Yu@0ylQSS@#2VTxx=K?cuJ{@R`OLRU9b z%uxHxeou5|`{J+OdVAU==0>sg=mbj{Y&!4D*7H-1`IwJ{LE4>$iEo#1sin+b#y8vZ zqu;evHNp!Qdp`fuves|u!TDcOcd#scVYwz^>5`LECCz<ft}6HlhiB{Ps;vvtynAG- z--}anRUH$XYNpG7HQUqku;;}u=d{&&>vz<f^u?XfpQdeLw&ZPt;sZtBme&8GwG6+5 zyxHPKJZ=QaRWs{1&36u&A#CwFYKMW0Xrf_UyOxCIF{_hR%UVn7Zl8O+Bf|eetk}6S z+tWo2C$F0Rluu^(;%T&DI`{2%{ffTQ-VOhEUz|~qacjyWqn$NxgKc@)1+wCd_rI&Y z-K%X}e|MJt<~2W!cn)!xgj$;R*l5n^&s%?3<NjkNv+yToNvD|Ceb{)?Be$H}OmW4h z(pZ(dTZN3*g)eUNEu9#u*(c|#e(3HgrAwF2oLC~7aQOTIvCqEigx+R(o7ZHUs%<-z zWEQ?8apy7K%@-zYPc5tZy|U|C++XF@&lmaJnYPjJj^O+HODxI7H+=JXXN#R>D^5PP zRyimx?AxPTFWcQ!FM0TWcCPfxneF6Ow_<wL@{bA2B>Dd-+Xfd`&yBlwMoiX!Chx`o z`}jL^n|IH7tLqxgv1Mo4_358_jxzlzD&BQygHH*c|NU1lj_k_V9_*6dHz#hRgX*_k zd26R%&^_XmqQuFb$yEQ5TPr*!aiMxv<-@Nmd$r~qKFZXR$dbJA%;leVCY!EVvP)-! z_Ugb~l_*COMW5KPmuuBo7VBBuWiNX9{hb}l()+e4^Gn_rY`Jde7N>lm{ri=YBhAxY z-F?<b>ibnZm>7IM@8w>B(j;DXb>DK$dj0OJL8>b}Gvor)KIa(L-g22)zr3Pzp<QE~ z#I73czVtJFIT|sKw;aE;ZTEB)rxlB(cwM*tQZw=1w$tf?yX3NI+4{n+47bhqK6-nw z??!Uj#hkQ)i}K#b?JG>~Wwt(j@KvnkYl6yNe@!n5zOs!!rp(HH!CGqWd{!;DTzPlj zPxsAx?qBMY+UauP#uURZZozI3cRj2(UYp8xOwQ=~F~*>Wi;iSXu_@mAcE`5UK5q@2 zdTpi&Ubt9YeT`36?8*5>4?nwD%}Y%8-B5VRmn$k@XRPMdygFUEzv)%q^}aHv%iUCX z;#VB>PR8-~0t=2CCL5Au_RgADH&tKns)oU>m+9;*jnhhZaK6(l=ldbCf2oPij{TK2 z@9Qm-syANF;h1?hQ!AwSHc!aH13Q>4;x+CZW9ahHo5sD%?v2{KwX1gRxZo)8$WAK5 zPu(}Y^-^MaU5@3Klgjgg<;*#51U;Ggz2WrI1(8e|B6iZZ_SIeJYpA?ryhmn%>Gc2B zjB?xAU73}==UYD!Z?X)Rw^~1c8H4iV6kCJHpF3shg*H6dx-lTyT!JY>{u`&(3Eh>F zFE1JhODjhh+X}yX#mx}7^lP`bLR#bDQiU12IQbuc;@zTg?C_V3e7&6WJ1RbVe*exK z{!c?z`b+!od&`2>ZB%NNoVVy$VC*`zug05iEo(JZ-}Jc3Nb35lqvm2>XYXviacINs zPto&Ob*0^Qu%4`6yZNfa-DTD%W9Be6?=oMmKH2#@-<kbS_)2nYSKFV?mk@WG(eZdk z-n{y|mscOwjR;x&s=BQDcOREU_q?b61<9=MK6|fOan`x}i>{vY(=3_W;>*_v8O9uu znsrkz<mjvdoANhm;U_u^xfkc|eB~6cxi)nw^Lnu^ZcBDYubnoBcGj=dbH1Y}(~_(3 z<IAU&?>l6_e`z;=@3&<}q5t^?`$RvdKx?P_lNUO^kK`*hpWLPEz@c=X=j)0J4aO6E zMmr*Bmi;ULq*-aNxj9w$la<iwh3>(Ex0tTa(>RgKoj+-2{l!(%TmJNUulvjqeSP=Z z5A)-54$gX$8<{hCX`%0*^-*aL>#uAS4fWXGf9CNPop|ebgIABYz1sLv#@A-^Md=2; zx26RZS8he=w&_<ietWoCDCx(ZKkp82k6-cc&WFnEt)~zEQFGJI``|E3JU{sr@64#T zW!CqDcI=%UA?1DbV4>_p*?_5Yl`Bi;bB0x|pZD+cm+Q;z|9^6N_n^2YMN9PjnG>SB z>icW0FLf8bE!$jboxAs<<{qg(Zr?xbJ{uo<ss8I5)1rj>+jq<>JI(dKp8fa8{LPLn zXCwFQuJsJrenv1oV_LlTwX-M7m%bG!_<cDq(kpt&bphvSzrS<)?q1otb>WuBk0!n5 z-+uSG%If>8Wmil#^_!6Stn!%BiNe_rZf!`8imBMmSpSUckmA!Nxf@+f78Ff;5T@P` zccb#`*1ZLPuT^fX{By5r#nGwP%}%9-=ko2`{@6b_<m!to<;~9}JZ3+B(|i5cGUqeW zcT!tZ0!vG>m!DWt`y<+>;<Najh0*4R&L<T+{L-EC%8T`wZcklMYm7nmx(T)_mloeS zDqbNxsaGmuLY!ws{eN@w6DN0y$HoU0t$dPptu?ZgTRUFouft3JmSaV3dfYO>DT=Jz zLDLW4{Hvzfv*xCf{zU8C)pu5@EpiAJJ(;Jged60ib2aJoq!wGAD9_UAB6{VXxiX7h zEu3H(<Z`E1P~<kV(KE-0MVF6HsMc@KJ^bj#6vqV;?CSR-)-TPzy}W)_O!&XNS^<-{ z|17i?bX<LBaBby70}sj9$tq0`z1{`YwXUtYYBPJ``mmMr6<i%8HW#edx790L?zOEV zV7ftJ@cp?@9!4*DFfHezW}r@u^CS<(YZ5==*NScJoE%cUc;jj11vh*a^T?i=w`swm z<4>OU{ALj7oaCE!$#SO5srgwOV(Jf{nz%TIdqJ~8WW(NmksrropGsu}u2`OE@~@@) z;`YCHDp&99Qtr;PO$#W0@$IgL{(bki&r;n!e?6wN{o~0g%iOOq<tPRnHdSVOC-(Jb zb!K!*P>KKI%Fb6WvKD!%tvV28<ZqNY(|3md^T|ywSr@!6^R(xRKi{Du=9;&oe%q<7 z^_87sM^iLSypve}7(EeLAL!<GSh?j@(VogTi+?qBX`GWwS@SjJ;*tJcfAd343OBp> z+k9Ga<57}dkydc0?}MnmTgH(p+?B=kAzLH5I<K3v?7Et`is{0FcFi9*?=Vi&63%S3 zxH2Oqsb$+vneETEoR5EysCXcuKvXS8ckbzrzZ~m>Pd}=Sy3W9KGmM@0&JC;2v#+wa zIDcNG9;)vmJn8S{0MEyTWtTIb*67MEcDrNj#i}H8zVqvyB6S9v<~9L_V@tH3n}iyO zUeDGGXLoDuf2NryQ@ZszldjGB?9<!x*p{T)M;*0W<>R<*)~SM9sZS@XeTm5MQ7znU z_L47a$=5u=`VwjVK%e=iL|wN(3oO|g_jT^w%!ywQ7ChhnlWph1iu`Ry%*{hR^q#41 zh_&I`c|a}MFHvKazJ1J(hTN=}|68|&cs%ZUV}J7Wjtk!&8Hxp!oSoPIvgp<t-Gq;> zpJHzoTBz{8z7cyn{>jo=KQnB$q)HuAIC444pW8kB^zwj)VEexM@JY!9*$<{(>MzyZ zcEm;F&aG3|4*RYuUzjdlo^fw!NqYBs{}pR<<rc@9<XB#}eW>kG9pz>p#yGh)Xn%(G zw$)bo5$hX;%d=J;`Sy`3dg@_b-PrcKKF)_Xq#upAvA{-q3Ck<Tm>hdQdqJ+vak=FQ zff>`i6S-KcjP?a)P3c%GT%RPfXjSaR%{)@~{BNH0Y2&=yzC=LCPyFTXmgA1kV^(r| zY!%unmA`qmTw3ejWgY8xv|kGDtxWYl&8ER7lIyv24KttgzTZMK_?Hwc{jNKq>%yYW z&l&&bJD*RKH~gYjBW%thrs($Ca9hBPBYK$=HI`gtJgno_7b_Wf>w@p2@BOdpyAA(n zryUL18u(=DhyUp-L!T8~*(y43%bpDr!X^nQ3S2G?7i607x02((!A?iHfaoKRj%+6T znp&P%I5)2g@G<K65M9%n8QjUN#msW;aOXl(F~2o`+c>9j<!#*Gzoa{)V{Xe%`-#7< zKDjxwWV@`@>Qx_?&236jU@g@TDm$k9N}>MsDvRISc4WkhHf;VTbJ`>(VYNd^;kyUi zGuLz!Kg(cL{@QYSits)+!LEzf4|%3p7frgEH%DcsVNSZ5VYRmL$-nYPtLkkNja26g zt@(1};6c{x<p#%h%(^4Kd`fMm(!9B=rnYsJgxR*nG4f8G7`?6e>rq4B_B(ecd`aGW zU0&&sczq7Hzl=t0bo8awDqjy*NoY8HlW{0<xWE?QVcq!M?$73!nRjMS)SPy;l1n$L z-}y=C<Rz{0EXOkEADXvW<x<#&Szm5cPkVBrci|nI`DH>1-9HzVJovx5#PG9HU>aw0 zj`oqm-%luh6tHzN_FuClK6!`SF)gbtc6C)hAKkrNe!lKk{o5~hyZ?WmF8;ohcfx$5 z1E-&+)P3!Xo#i8`(z1G~*SX6Y!FC}s&JVdB1tlEM5R>svNn2B-=CpLr!d`Qx%WVzw zqBsirb{v-Zw$Fy)$zhWx4tLfbN>Fc``*Olm(e?S$Ho2@-lX3fN$iJd7cR~L0+vX3o zF3EP3NgrZ)Tk-zLY43IQy7g~fb>^CU)mU?EYJcSKx&(>nmy@!1>t&TUuc`elp`v=o zMc6jzm+p`M7dlf+ZB9sPx!w9H)y8wEH_b1X_YiO51I>hkb2eK1c786jJI4Hq#FVB* zqFEbx67;p#c`)A*pD}Cm*8NFw8_nc>7KRtjxn?H%cgI3@nWE&5MV>y#Pt_+hIX#!? zaapFl+Vg4N;nfkRnkJ;KT^|@Ye~Pt{^JnH-ncu2E_h<FiWdz)1<UU-bZncBw&^rd@ z1BOl!kqV3TU*$Z`-u`dVUDxjSx|63)Y&w*3SlB}&+5VBfuh0ySiIp)ed~e?#(PY~s zHuvLEzUmoPYfQ7Z?Q@oIopHoBf17kk{o~kIH#SyfyV$?cHu3qg{`$h(JQchjs%ru! zUV1e7_@DQ;lL}dntgoLNDtPNGbKJ{QJ1ZYXTKU_hx<<Mx$8Gv<rCVgVMZUt0EhA~s zyel7>`udEPP2-CF?Hnf7w<MY&|G`@4$JuKpAGe=&^r-Qx89uj+pLs1ietPj^M&$;p zJ07g{>*{j(Ch?wM_nJ{Jqrv<8g-2^wZDIDF>6_*MqbQom^waqj@{jJ{zrY==`^UKV zm^0(S>8TU#*|)I2(5tuC>^}M^bdq!Hq<ao6LJ!X$eWL6WXi>d!-T&5!(Y<q4zH(~& zd-zgZ*c$g7+uSVe8K-of3AF9<mh$Y_e1Y>&^jw3$+3NLIjV(UEobpv!t#QXey^r4e zHk@L37qwucw7?~w;#~g>>j~zEUwY15Cb2}Z@%EgKTLpz{p6N#~dbU)bf1U2+?c$t) z@#6K`9FiXjn<IWcuMqmq`1qS@^|LshgR%*FuV1A0ohVDv%DXZ3sKo7OD|wP)gIy|j zE{KjwTb_Mj+xfzX&-D*h@7t|Xue$!D>YsDYyydNmtKT;%6@`Yy_9#7<;GMW?VX=nI z>AlmH!&fwH^IefUNzZ6y?(F@01D_w;I=fU>VC~5X-m!))VS>|~<#c^zmN<O8=CNEq zsd4?XCz(sw!<{R?%6;I-+F>87Q^WsyQTp2kck5`5T{ZRl6}I-@$w+RjcRU}U@x#V> zOJ$or>w3n|kJhC(x)on_SZU$$eCK(?J1gwceAxnSS~vYZnJIB3Q*QB*d8?is%`$vC zZ{dw=i!4kTTX(0QJU{cd#;%PsTfQubIM@`TbYR8Qo_DQ`XCK@Z(7$niLhb)!`|G1W z^WMI5#y9OFkL~l#O*Kh{(QO;N^O`&A*Bw$<J<FSG`~I`dRKM*qdncr1JAdtFT6fC+ z=CRM_>*ErQ*Ic@Ldu5^Li-T|cw>9o4DHeXcLDczWx0ZF0rujX-FXzPa_-oUR%cB|6 zgSW>we@iQiV*9?ZZ`Q;r-6`Q>tlwqa-#rg)uYR&u+9KF(@q`-&=Niu>GdFUr5kI2C zbF@BESHJNmOIK-r;;)PMUa#(xXh^y^*|R{j%wPX=<ie&k?;>{^M9Z}C+h}p!fBj_s zqvQD;^Pg4l-q2vwy0fs`!gR{FyGN1^95wsc)M9dHp==xLVNO0a365DO-U-i<D%TA& zkg9oMSkfAxSIzLQXq$!|^K+k|Xzh#ox96??%T(%DU!KRMY_?&>m4i{4UTwS*QsP<{ zdP*kVxudh?M21SF$2qNfMwf+uRvb0G`6Vl668qzVLwBS6AN{HLxT8Mi+l?3gT~8<1 zEvdB?+h6Y$8Pk6+Wg_F1o9Q9hrS*R4Tf-#nQ{UDnr0_+~+-Fk%ZsE?IQtx`jUSx@F z4+`nFJz6&9$G`esrkYBf^UBw+X$4p-E_ohxt;t-d{PnzPlApfZKl9~&i1*3Vut;AX zk1hR@$1TJbNQfPna&j79?9EM%&*#qD{jTJu{Qf(;E%)C^_5U;Nx#a%N+N}!lHC?+| zmR#7Yu}u5Q)7i)3*m#o4Pg%OGpBxezrF^JkZ`}I_Jco4+W@)(Z`c{8HqxOMqWz6ye zPt;1eVzlQqt6$u+;#KxT2isW-t^a)upHq<>sB_9s<YSBa_w`37c6^v}L$%d6v+mtG z<MYcdo5UY}vf}gc%Ws5zPvx+-ofQ7!!+&2mK<B~NBQ3j>UY~r-eDU^U<_wX4SJig4 z+L|AcQLBhG;(Kyo;-!|o=WX`AkgWG-Z(jZ1YrU4@Zv&(I3=(1|LzT}@n6)SRX+fjr z&67b@k9yZVcb)mS(dFGE&9>#8H=H^9YfqY_Ra}^7G*@wrU)OTe<;xcD{bwceF!$E# zV2L@?ZW#!e`l&@d%r#i4%lUP6N%2I<nA@Ve&DYM^bD_sD;u^o}MvuvJ`1xLbPhtH1 zqW<ivb(=Jon?-eR(*E!JPbc!wK_l<~C%!ucpFi<n4cF#PZi0&!{cj4EHi*)1Hw`(o zQEzj>C#Cly$_bgLPoFtu%kV66>a>jW!o4=@l8<~p9xdqVG{=i=?S|wvaW^)WHJ_?? zcAWBsNk{d=u`{oxe)RNOqm;f@Sc#=^ef6K1tsl}v>YaFQ+g!bNwQ13`!;yT|3>tMF z%lKwHRj%~pPkgm(lIMo#&A)i3PupAlEBTDvj7huH%ywPOn)<iV|EGz<xtlLN?}W*O z&U&-vhGi8`^Mq|*j#hZYYS$Uxj56iS$Yy4L7#5g#UhEmi;@`nG2VZ`<DN|cy!hX8Q z(qma<E6e>a5wqLt<z4DdZ<!+}zVk=uldw*SDR&;(bbqaRlmB3KStHNloY@+W?(KbR zVBA_ABdPc4f9+|}oL$y&hn`+Dp1#?2`os{94+mfIie3|++vx4SyXLKMF=J+BpKgmn z-k+099s(^K>m&tYnivGWvHhr6WY*R0Tgh-^ZSiIHyUWZDaoZk_eqNtvyqNnnw{P#Q zT}SWS*bx2v*&Yk2^s>VXcCJ77Y{$>c%Xeq)xxC;lcjJzEN7-I)Zk%bp%67@KpTY0E ze0*x(%cY;!sX4So_HJpD*_!<)Z|Y}vuX?d-UD6`el?zM?gt^$IG)`;IUCAPRVm*st zVrl(WvCy9A&uzEEIJr5etof=czrOzVVyzQ)sS`IRRV}^L{4Aj?SCrd&_R@=+f?qRy z?<=^=*te0jzd+pJ*teR}JNLQveOq@c@$!q6xxBZew>R99slM!Qy!@!_FEiIS-fI#> z-9=9x`0(Pr(ub8H9b95xA3JsEXPMlQxtcub@s5|v7IG^ezi!dDBG;woa7WbfP1B6G zchtXRn|5l|@;MGq!ky!;+<fyYwyQAq<s0Foe}bLi(KB1PZ)_9Z_M+vo!rCWCS<V=n z*2Zi|`y;!K*L1-&?_^ntNzKfvRt5WCo}ZL?>+C1}Z!6|=p8fkKtpC@_e=F|a{~BmN zxo*}E=6_Y)Y|Ll3MO^)V(2D2pSu39Hv)&mo7A@emPOiUdtKWUz=H0t^(@Kl~lAAj0 z6?l1+>)nzLms`YhiC#*+d!Nm1joTD?nRnHvy;~-GB;McN5VAwiNTlV*eKv_#f6WRO za#=-G9Gc$jWvajV&*T3AJ0b#JGA;NOFJtmwvYnH8g&wa!sKk2}Rn2uF^UfSfyP>`Q z{>$oTYqrZeHzjJ+M|ass+P^X1FaP5B<o9Q$ZMu8--zTFT%8NF)JiK*i&e~VIZs-Z* zU&#>f^55KL6Ps$;s%C%q`mKdirc8g?^yn6U<&%#uE`BOHad?LN--q{a@ZNoEX2o;u z${u?mp=pH$wvkgq11EXZT4;)W5Z=ZdBIdeFWc8e)CSKpupAO!dkQ|?1zv|Fwme*|& zv7xJ<ai)}9cKy|P(wu+!=>v@1av}3tC-w6&Dy=B(I@S4e%WD5MwKB1x$}L5zGV?#J z$~NCD$mNux$&kxd+W+uOPqSa~Hn)i19OW+qSl9ROV)i)@|6}pe%&_Y>t@6tjn4X@z zi+Q#7o2tK|yzXCZbdTPP5R-hxVN!qJuflRYv+?@LswyJSpLd5;yJYU0-C=dMTsZlD z!0%UUI|VmxEU!IVxAyuxwd7|CAKa9^4Q%DAWx4$VP6(tWPkb3VFD6Aev`PI0Q&c$X z$<1f?&FH!P!bjBD^>KuT{w`L*o8D*6bGsGl8*-;FKlw>=-?1RRHkQ-E%8%AOND8c| ze;)Nl@>P82stpVuSz_~+aQ|GMBsy>7(?doo>5ffeth%ws{wCgB_J`HGc-ijLQO5!w z-PmArUh&u?U%w`~CZ~H#&mLG%ay8XRD(3&?%SzX0Oi8ynoLoJLB_Qi|Mt({Czrgc1 z=5DWg^4MyJfBgQMuV0=#oqc`z`MRGEpKiV^ANQxeUO#?M_nwolc(&b({N>J8UiaSd z#_xX`Z)97W4{%>xy<}C{r%zYAFNeQh-2V5U)f6j5UZckQZn^K<rRQ^gJ?{GXZ@=vB zY3%DK7;eb0(Z4tC!%t6f!NaP5^Xr_hoZn)}Uz_n}%BLE+C-Y42g>MdVzPWLS&EvA; z*0wf>8UoW?cW>QVFEDqdxuvAzg0mITJ9G6{=ANIlI6LjrvxGa6vZAq*f9k{-{|#t6 zZQZh$>xty*T~mMFyl~;c3i)N8tv55z*G{=*oi*vq?6yC5FMgYM?6qv%oN}h6s-hE^ zFRQ(q6Cu5LOJZ6%OQQDuixS#vw!Jvmynlzk4cqGIJ#($!v6SDmYfCb&f3NmAQvQ5t zsJ9LSJC9Y{&WT6443s=)s@|;(((P8QQC_~m>HGIzx2+QYw#?W3w#?5XWJ${!mffBq zcGj!!OtstZx$i=^x$?#+xq$wuB|$bz6WC6Pcy7zQF63f8U9nC#-zl<}({{GM{U(L? zJ#y(+uFMwWvJw(j@?O2_NNn#ZvHGINlYH0i`(0@1dbsB0#(TH4y^Piz6-zyIb&XGL z9g|9?LLC3hx7#-?D-(OVzECGf;ox@5O%Haxz4!L!);mJi4YoenE#`5jWs>uDlc%fF z4*Zn9wP5GEbg56N&#zdo;)zO~_+D1bi&ePv|H<}+OK0=hY%RF)S@48Fh*fFj{}jjV z59{Ss1zzrTHQ3KI<qPvfi;T2uYDLR}XD;-Ojw)a6|6KM+*2cX79rqe|&irdwbGY5% ztE1-vmCKP|pM12u*yY*zojvS@kn*P|hUYdjTdv>tn&(&dV!l7uZGSOqx}9mXe{La? z^{8Q%^@ih@l)MbwT66L_&zKv=T<u%Ly13O|H%4J!{d|i>94j{^PInb`cUy1WzB<A2 zI^*Ltp2rT|<$Dv;xB0xJR9)H8&#eFZa*pnO_`EWVdG$sgwah8v8Fx;&X`Ei#?YeEv zCaxPdy~}hTz5Ran!<rp$jC^NxoaRtS&Dglj%s;>3lB(IwkhsaMAG$0{J_}Y9he>>U zT{Q7j>4`+a(1Y*ll`Or*V`l^f-8P-zT+hN&rCiwXi7#g93i~H(Y)`d#33Yun_v<qk zUL&p3BBO9$G3LgWrIw1Bm&D4AH>}{8F7;4z=f1Tfr?T>z_Id}l9$PNSk@Q>ek5l`) z$xkOA&6@SD=*;{RiHGgZTYIcMBKQ2c0l$zSW8`ZW;T;n)3e`K0_tkHjytYZOeNVFk zO9;2pt9V|sljpnV7Z(5TsJ344?dPYU%a01)DlHKZVmf$ecFFWJY@1H%e3+xYL)FV? z)}p|gjpg2v?a_P-Q{sM|T&nH-|4N@v`yA1V9j|R~I68bxV^R4x*)w!f=0yDqDmz5` zTr`uFW|vyka_*jdjiESkwqH<5{fGI}BHVr-xg%0Lvt*~X@R{W;;csShHrlC&iyjW) z$u``OpryO<&50vfIks2*4hU`f)pY&hx(|H+9^2SWthFs)bv0*NER)BV>Tsr64%#YK z^=>LxESk%&|0+wIXLgg<`hoA&_hEAjdRCqDj9$LydH5Y4&)(lh*M7Tu!+hBd*ORsN z1?Kx2?|S6Sj640LBU;O#FK_xX@m|M(0D+B#Yh`LrM@9NIPJ0#7watuUOWwgNuYT|K z?YO42eG~tcHoLM3s&AEAls6r`9eQtD;HI>K%MOA<?KP7UbQk=qKk=ehQ1b27P0s2T zNm;WJqkNUtUAuAY*kRk^Fa}+9i^`fqLVY3&Wb0K{-<nzD%pP}teNAPpE$`Oru~v7h zHpn#h%LKh(-^-bKeD<~XTT|}qi2e~3Vpf0tztlgFCwgY>+868I>`PwMbM{SY&b(`_ zdz`s#N+fSP_=Wv?pV@k9;fZO_gTLLIqq8H?L8{<&ZEtQ`)wHum%PSN<>`9beemt{Z z;KEtn;#RG1^-JCdZW2#mi@Ozbtl`fm*0!o|DHWf7h1*!2%AIcNZS_d$RR*{0Q={Yl z{k!=WyPvV>+hC>jb<51R+8fWyUH4o1qR@#)#qRyx$92Xpw&Y)Wc%6?)++|~No6$Mf z*0v4jf}P?LwVC@$VlI0xC|TZ+d~y9%wX>VPng87V;e2k6vHhZY4kJfCn?SQP+g<_R z<=2fp*u-X7tbhOO-SvvV9jDJ7diG7!vHb3*9^IKLr}*#2T%H!ER5fRJUKgX;YtewU zvg(I+bxe{pxYMP|!Y1tcVDlQjE#kjBqTkyc-pJoNN$NT0<WI^zAA{TW*qrBT+cW(| z{onP9>92OHRIKXQf6wZN9&dg2>nH)8M9rR^9S^_q?cb~XxRSfB*xjtyxcub=wwXTv z7C%j0zJ9%XaQUI2?i(u%FK#M)+Z7$+%M{GqSR8qlHTeDB?WX5)DuZ)XPk2gbay-8q znlMvE_~PDmsnYSgU(QK7IX`-m@uYQ=);+Vz$<^Lxeg1*W^@?sj5!v~>A4IQtQGaB+ zmUT(qCALYsP89vKOU*Hont6&_{?InQDGIBNf0R}3yjHcDHMHgQ>Z><pEG9H$OBj|c z+OVk1ZQrc3Y0kc%GCTvm&U5owWy!bmPJcaHTR?&S?gJSjo_Qgr@1^fG%#@JW9>A>h z=~p7_A>)L(Qtan6BqfrkiwC_3P|u!Il9f@vT>F$Ii>mk4wgVUR-_NUM*}il4iqyV; zvb^r;FL^HPxoPLW=kD31cfEGUBw62y`E_A;jz_s@c~WrwyA`*#_c4p^$d{Gw=+eDf z#&~}A5{}8o-Wl*OXZDhch`H|gSgAmvzG(vYPji8%6?>I?qZALl^_x_5>PX;nCxw-3 znYDx0>eYV;yYp{?b41};t+}d~KYy9^B<Iadx8QfmZynqBoOD{hhs{=9#<oH07S|~~ zt2gC^->f^uKR!Nt^S#Or>kD0+3zuD-A0oacA!LR2oH@q5O?{3M+%q3-o#M22;cNlb ztwC`bRqqZiIwV+iF3st2!1waiNi*I|FnK1iUv-CJyoqc;c6}13m82O<c9-2`;VI{w z{8D*TCx%z_-@QB4Oit%tipe4S#~+j5R?KV<=Z&kAFvzigweIenZ7+Sq`5BZ?>pM-e zKR?N}@Sc{RQ1P}`Gr7~c`ldLX)lq$#bw+mCHlc~idpzDYY^thxcX0<(wF2)sQRl{L zooOo#_Psxux@JqpTaMZFa(&jbC(Mt0!~2+bPrB#6^ror)Z$l>JU4P>q(w;MWUfSgo zZ+e#QpR=y;!n2JXKJx3s^!BdUKJ&4_((iogx}Ar!YfqN$%%1%%YIcm!i&bl@^2JXs zUCooFnz?k9RjgI_%T|x0C+;p(Q@EC8@i*p2i29oY4<62cV)Q2AxyJp&9|R}YmtXNL z-p$!x-~A*>^ND$^S94nv^R(Xg=Ztutns3^Y&1^jTn$Gl6Q}=6oCI%eaI%7|J;3938 ztm*aUci4`dUa}@=lE~tk%>MTVGv-^E#E4I=Qp!5|Jv&!!Ra53oM$LZq<1ZWSFLb@> zt_rM*Uv|nx;<@^_9TWa#WgmQLXInbKE3kg@P21`tNr#pmopS2fndQc9$=4?<uUqb9 z`oYF~@|k7z{K+Nz)Or`&J~%A4cm09$m$on-T6p`AsP4}8SF)Tsfi=f(&)Y27UVG(! zxxl_@*B8s~>{)+AV;*NN&unEO)+zhi4vQVW=;yIR@`S|=sU0#gMUNibOyO@zXP(9@ zt-{$@@Vj2NE_A6u)5>{I-W_yZ``&J!7^8<U*QE89`dy!0Z?(Qz{jRw#KBM7_$7faN za1O`ihSK3PkFl&@n#8Nm79w)|Y52s6?PdPX;hIxlXLx57TBX0zkofkie^tT{)>#ET zZ7-*l9o=~M)}m0ks{z5uIq}OJ#qW4rEPLIvG33Vf2c^4L)$f*JHm|;QL+)-<oO_4s z3j?<79fq4PI)yMM`TT$Ry?LSb+v05zCF(BuD|4^UIB<>m*duKVzwlY*{U0hU6*jE( zjXZq!jl<k1-whXce!Z}?WpOEAcn(+JzC3%)o41$Ul+@syu>WFqahICkp1iv*U*hiU z>h6iT*mmVzXOCS&rjB*}xt}wSs)Y;Bn0Q2~J@kU3Bmb?9&t6z9^J}lK<S%Dgyd$b5 z<;3S({_4z8+9_w><SuZPe_=Z9fA+)Qsk<Zp`}%mh#lJPV_IcO;v!&W~QY^BWjDP&) z{lA+Z{FU)tsvvHk^)A<!&#X+=$z6W*`a|H2-`@=0*#6LaQ)|z0&En8&-WT=Pr0$e) zbc){OSaz@6U2FkgKSKb2Ui|ZG3>6g;-xygE-v@5^_s{I_M#a3g>ATPU%8%Z5`Q7b| z?Qb{N-u|=l?WWUaH&d(cq(#5kCHrm{Z{FrVyY;FMH|;#gQK7ZIkh5C$m_w~^*1F;= zzczLM*d(;E$EV~(oLuPiV{a8cOTV8Sy|lhcZ1K%+9+@ZVbH3VT|M6qw?z;FTGI#2s z1rGxDe~J>hXL8jl?dq4H^{rj0f5P@oJvQgwnpDrnH=PuB{t0?{`k9vXv;!QjuJ6*L zE{Y`xNbJgX^OaQ(dm-tyJg{ZOc{`uhv%Omr16WkP?~XA)`)p;3kXVgWdi%lK`uq=O zys2(9uNV2)lES^wBT?du%d+GR$G9zow>;51sv#$FUasrCoLTFN&5U_G)@uxeJZ_2b zER|?qJTZ#Rre%$H$mFC6cHwTCOd6B!P2IQpuM3+`?(CrA>YVk-&5kXpxm!e+D=l8g z?^<o3Z&xjGE+=^*_m>Spx!O|}v`@+XmYM6@VYR_oSGQh2=VGU5Y*&VakNVwr2}`3M zaKG$xG6{<iUpVpH*G=BXoKvh_Ry~xCQ<{CgfB*VbR<l!IZuy%UBz~;$@FLC4<&%$G zSas;5VW@l6wbqsY9(t{v)p<5(yUE6)O_$l^Pb#EuS|xkZYTm(RFYB(mKPuxkew%gW znaA2Pd5hbnMd#ODyIY@A<~~Jf-S>yj^vY&9YgfO0<I$!vXVzMaBXiySZdkfxE?;>~ zVXDNn_>G6WEN+YZ$-1S!$5^KOKSNns`uxo)v)#p6jxigA%FDcs`@5i6BU<AsN8W9h z<V`V-8)c>Tmdu(cbz|$lC$%>|R5p9Qmb-Jwt7<jF%sHV)nF?epZ%^D-QeT@Gt<Li* z=VujvyxIJSUacQ@#cI5M98W3OdvNipW+snof&DL!e_NJXRQK*h#A?57tyQW*;a{dj z%5CBQe(~<KSL_LMTk=G8%j56&N$)lM=ytQ=Lqul8nZhF7jk{NCMjP$tb~5ODKCN?o z^&e@o>+TEGqPGd${>^XNu&d8@UR6~6kJ~*5?o7VRShc5U)~>t|t*lq~BaE}Trii~> z8r#9W)rnh!FEV`hD$cVF_m_Nl9k#$=ZoTZ=9e00d{N($&s((|UVRL_F;j@TfzT!4n zsftbBxA#?dPd@jonq8(g{ms5>^JdPI*t~V)xyAol4_HNQWRtsQtm>8ZXn~ghw5yZX zwo2DG$(ZuU>RN5;Z7eb7Nj;R$^z5e(v#F9wgxV*z42Mf6ZlrPt#!p)i8|WRAEV8Yb z{hPnp+dCUsYm<fVzOZ}o?#kSG+=tt&%(D|e8$Z(9`ee33T+(d0CpV<b@-=z={AIlF zDE<7*zIXAl(;})}r>neoTd%h}>RcQfZN1(uyHS5;{gqDheC^}!`dZvx={d7TY<suK z)Tv6^AthhRC|`N@j8_UdVZ!_E133Ogw?2GXo;<(&$p2q`%a3nz*s8hz_lJyG^Vns# zZQFI?a?R8`OLqwGd$#mh!%?>*sw=l0GYpK(o3qW^H)_k=ZJ*YD@7%XoR$B0P>hY52 z^U7nKVm03J?~9JBZ;Sm?{yJ*g)!T*1vt77eiT;Y=eK}3~PxQ`%@-k7<F`HQ*yf0nr z?a+00j@rz#X$L1{TBX-7d83qlxZ*AQg4E;p(-b9pq9(4rSW<k|Y|13|{+(Qj53CPW z-jYk$X3>-Bu;bws*Y_(<CU~trk<ET^&$ONKPaaKp#PaytcF8HRdK2!}M`cBBXAiE? z;QUzMe5^=vX4b#|#`*q-lh55<vGdvEiCY_OmZhDGYd3hMUbpv{xOqH-{u`Z-vW*!J zH%yL=S#sIug~BIuwlamoeF}Yt`xaeSoPOGZ>Gt)GgQvYyix1xNVpoj6E$4K_rlIz` z)tRVy=c3QF?6%Na!&1NDk86=<{K<ONx`n6IgKPb#x9t}!&I$?PI(KBjjCdjMkTY|4 zFPK;TX6~UiJO1C_v%6`YL7jKhe{mh*&z3QV%#`ozssA`VdDW*y+#gq63%GYa&ETtK zFT?diZOcw7yj$}3{OP=$lg0Z!MRGoSmsD&j{afSN`Gqa_w=)X+KQ3{f<zFeSZuHjG zuC(6Id2YeJYw_0Nx2rDQ*~XF-{5nkN2}^rZ-y{A0--l1WJh^e_d$YHJ#cX?aUi-Wu zk$v&wq$+jBf{pT1TqWf%Ywwe|a<G1B`1wr7ob%>!hsArh372j3c=TSe{L{PLC#89e zGfvrWQqVv5YWBT}9|dwt`S$QlkKE_H@^0MHyMFD`rS(&Hi)}o6>+c&W?>gydW|NYN zqp5MXbCfLFZ~t1dcJHEY>xAWpUd_6(@<*%To#m-BV<abMe~z_|+pViDaNA;y_6^_T zJvvVBr*L&#FLO&t-o#lsF@IT*<27;it?L)ud$IZN+_*UknSOV-*Iu0PrF+hW<dT$M zhWdIZ{=PPuJEf%ILH&e-6*KZ4XL+(d<bM#PGxcedW=PwE`MlaJXSW>8<=nXbjjq9! z=W}nreDyX%d)dtiEtxY~e=Mrq`?uBd?$cIF?y`yt)9M0PR=fUQkyz>2rvK|%W<x_| zQtIT?8#C`Mo@>4OCHr*wEy-&&BR6N8N!=7M->uMZlqYkAhyC^i#sdAs`o%KmdL9ZE z3z}ZH)YrIu|Mlk$<vns4okG8DjxsC~?$KT~>#NCPiHo-yrdVuQYSeq#<>PcVkDA+k zo^?swMlTd+ncqya@DrBd)@N}FbPM!}S<1@4=@OHo<&%BidA}c3zV_#y?LBTG^CLbn z$5z>dEBrj8e2n9p=#0g69sf;tO;f9n`gJGD_Y}|UjlZ|%*&SVJx;X2r<!bff`-v<P zT^%3qeEC>4DP*Pl<&R1$7a9HhAs1ZpWIe;pdYK^2QwCmtFKang_?~9}CGeKdd9Ckp zw;wXn7Z}+VAHU=Bk9lUzzAJAJ*j*KW{Kcqby^#LVV#bo`h1aJ}jOpT<FZ=V@vyDyb zyL#&v&2N8h(>q_EZHpR*OX$0ZlTl1(CH$qBE;;B1JItCI_26Zy!ju_45^t6}i=E0m z{`uG}iw6OmCTBHX|4GYP6T0Vg;hub_$hFru6t-xItvOlS#}bjaeyaq(ng5@H%Sl}e zw#>d>ykOCuqB)v9%+Y~$5!tUwN}VVAS^Q7B-c@GKS-<aWVTp*?vc<X97x_X3JU%5J z`}rh#{=tRMc6jSw5@XQmuHDJ2znkYETM~2Y)Uqj8<E$o@Xv&FQ&(-H>)!g6O<E^oU zQK*i4ZOEOO(yx7r*De39HqB_|x=)?&)g+!2y<vLBHaTZz+UEt9g~ip|`T~NIn`$?3 z8ae!8s&+_q{MlE3%;|UF_NQletxP;J+h*m%GVi3jH|Jz9I<FJF`d+;xm2rCcIg`N4 zr&8Ci`JJ<#x#8X-wxWYi6Z59@U3Yx{ohe|}^@Y889EWGwu<~zQ-hW{Kv|an#j$1ua zh-ArG@LlruNj0Aywo_`#$2n_dyk}>ftGj4EeXFP0=>s>doR=DiPd;4l88*rE@cwHq z1zB76KX#fh?|aRzkizvH%&ZG+w+Zq}UD*7yBkk!rlWqE%#mjbo^SN{8+B()Hsk`6C zW<Q#cc7Bg<oz;zWZnrC~>d|-Q9JV_wyz<g|(yMKG+G<~>2Hkpg^Ntd8B4feVbtgVE zcR%KBez311W}j+S-J8pMqNeWI#PF!zW~B>L(1PQ?5-fk7DouUB)M@tA$bGpRXGvSP zLCfU*`*tVKTK4L#*>Mw<JIn=FE|{oRoN^YZT;cR|Vc(yl^Ro4JRHq*HouK!2*R}YY zC(@^%=ARRk_{wAIUB1hapITne(wQ&1c3yVEio2RStrO>MoB3M!?o!?`aUn0WExnW6 z^}Or%q~)1i$nDkL9+Q;oyh%f5+KV@<uD>#H_Y%ID@h-{b(zZACz1b3Xe{-L24^dsb zd2W|UY=_02)*1g6SI&}Zc(s7ne52N-Tpx`O^G{uvb5>!w7}t|GdJ|)~F0!xNzM5nD zksV>Tb?@BlPPO(`H$AAk_sR!RvAC=i%=%C6C4|hc-&wzTRvC}W^zWLFw%1FuB|T#l zQEIh+!e8Z{&Bn5P@d3}mLdLV39;oD%r=}nMl9iwp!LRd{K}d65bw;K0zK>-uPgPtf z`EhB<{a2OclXMyrB2(&@MA<L4`l*p-Wwz|Ez}+@Y$t!c+Qm={WW&U5A5mSHnP|D|n z-iMC9&S+Tpe37kX{Y>E#$J>^^->{wM#|*u@?X4SB?;m2hu$H~*&!l4wm+DvYN#&f$ zy8m&;HtnYR-=8nvkK6a}_sidtm#gdFb63tk$;OmkKU3lAqh9CNLI!^G3OVmwm7FnW zU+BccIbQSbe7x=*7_c+$^OoxhEp0Eo)?7SPy?4#Mt}mKuzYDBHopsU=)vtLN%p*`( zt}y$u*qsM=O!nV-cGckm>zmF!%-IunuerQu-pkKd-967(mG?f0U9&3ZP{$9q-!W5P ziX=XLW#1^e_SK2i_Mx92eb(#!v2ry>jOhmB8zoNZbwQ#MT5D%c)!V#k#xjRr%YJXX zUitp*_G|aQ$5|9t3Nt!b8*O4Votw00eSLCz){{!>I46mVj1f<Na-CiLUpQa!_pJif zlaJIS+VB6}eKL)G-&6gE8!o44D06)+GCW(6G5u<g;C|PCtA0%Rx9P|SLAN8)m0Rj# zbj89&3@?4VS8?G*R`Gj}?JsQix97k4{!RCzfT@$M^K4F|jOb^d*qTLh=AVq^JrVu= zSNs$4`k=#yimLQ%Y+iiNopWUGE3bnK?}|j1h9_H_EPcC%?d#sD%eE;zUpV2X$m8y~ z_fB@w(GR&x{buK0fA{m;zt6FkC*E9n{qBZ*Q_s&|?rYUPNc_-KQQ~8wvogmo;q|+1 z{lCuGy=c6|Q1{_(ecPEPW%b53mnoaSv%FOIpFelcAJGiC`o<)kH$~=q4uyTxKdgRC zWZTDi50CZMGM#wj`)+sn+jq0Yy#M^qxPET3UbEx$pR0cGPd=I(^rmD-@;sHFnwKjU zEPHcuD}VYtgWAwPr`-~-oBEw-OMLs*K<{~`YvRx9ZF_I^zhc{a>Gs^HSI@mS)V~hk z{bT>2;o9dddo&Cl-uJqYUoXJ7^};Xf6FoPWPk$B5ybvYeaQ**cR+F4fv);5G{b|)$ z>V2B!aKp=%;@~%j8Xs(oSnWS=<N2eZR|FfwUfvh8IpldF>T6qVKri3wjJln(BcDdr zMxF7~Z#W;j(cJ9jnfVibMYKoW{uKYrVLM~Z>iK27F~8%EC%uwAaO10wFw;Nt`nk{l zf8hSa9bZ_VCBG^4*XoRecfXx_{)I>2&2fAAyv6OCme~K~dvNH=ZI^G((I)me_X~C( zd3|Hw#0rJ}CC2)fZ(e81bhY0gxbEkpx$|^|TJ-Ke<4{gZc5$e`B)9(YN5`V)y{gx~ zoK7&ePFUFyp4B=xXK}(0-97448wxVMzpFi0UzBh<@`~qi?l=vF?V7WklOHMdoQyvf z_p<8Wmoklz&l@LiP`<@$?RCYD?`zXCD`72T&wvEs{a03;KXK^zrwKFnPcRdye#T=f zbf|p3ZSD6xuixGlU3od3p+i_=i+-m2i)G>B`*j$V&I|E`Ng2(!k~M28ljp8T1;qk| zhG_|T3ViiGE8bP@Hpvq*oA34G!o+`F8Xl6$Os&<jB^u5y3g69+>4sgm<!QU}<(9t% z`(}Z17GIuCZ~q3Abk`ZKxX<@9^wx(pCyrgbDeC#|K-wp@#9Xhk?Xl6t(=W7aD@fe( z`&+i}rmCiX(KC;gq*my~2W)3F|Dfd8-OJ+l+i2^V2OU}p^-I1T$(e9*R)NYHvvLFb zIpXvG_m%o(-1{}-$I4Yq=IgWWURP_nKI>*<Z0<hKpyRvUZL?<C3LU)1c2SKh`;1$D zySz<ulTO{FT}!?kyR}y5>9rkaeJ?vO-oBvA+?f}-m2WF+rt<Qafg7F*8k)X8y+ZEm zaS1jnM|lBFR)_EWe*EX^otG8fyz%;Mxr;;Yo1UyC8p;kkoj>-eFW&GW-0C+=mE(+c z&b7I+=cfj}_t#7+wtjl*xmj<HdyyjBv4DFE`@a2aH2wBUPA75Kj`Dujy%!JE7C+uH z{Q$e~s-iRhgA%ed{X5>IrJPfnn<NygP!q>g(ekD#^X7)IKO#T!w^%2?YOa6Ca8+Gb zCgCk3OY*ttN$2mcPTchJEbjzWksV$xD?fGJ^zn9>d&tHwG2bVAgXx^ztMX!2OjdcG z;SsxRrCJi_Z^s$-k~zB#jC*vr{!W@bf3E)7wUZ{6CUKb>HZAfrT_4W<@Y74dV@Dr~ zuiWJ#c5~}Xo)3cebYya4W;Z%kmIT}=t}ieV3;QAK?J+s-Md3F&(^@{M6wBjIvSss< z-pjva;>)@9lk?wA<Bu!4GIl@YTNrEZtIy@qq3+twKl9dWo3&aN_MUO=bBm_B_n-IT zX}9oo<=?a6d&*2*`4g*FD9-7a6~X@dMagcD{bk{j#{%a_m7nm>oLMIHBffR+w9L>o zr#$PUN?$E%7JPe>NoDRgx%FbZ{hxkR63$_NvwBxt*u{g4eQUJaubu8<&XAwFY4;xf z8&S`WSRarNDcB*|-I#lC>)q)*RxM5(A*#2ZT%DgQb?)@5oQX_I8V0hvtPF##8fJKJ zw7RnYRnYF!@2*+rY>F&9_qRmWWX4YQ^A&b|hgsg{KCfR}`6jpQ^OxXBx*rtxEbgjJ zJW#gX_bO-E;!C;iCjR2y*Hc#aTb$*dQWaa;Q1>puF*k6#jL072x>L(udT-_a`7%At zZ^`K^jwXlaw7oEHti7Tr^Wn&h$oE=1BR)*N>-lNr1@DGsL2+@%-z-`Zv*w<-%8ps# zC&Cu3<`gjBeLFsXXZ^ks-W|6;)PymVJxOXj`X)z2eUr(;&W2f){$<@2Yipdo>ORkG z_r31<;+<K!HP1DfuP1zr*Y$)ky}R|<Bx)-6{1xwViX1erGBQ04X}|sQW@Y}&`O<zO zlCK4xvi`sND&gXkmX9|N9`zF6Y3HlkJ<nl_jbZA;#xpI;pB?rq4%B{8&${Pu|GvL& z-_I^fEv@R;x>&`pX6%^Io%cm;pZtnMo0owbU-$AIol>9pviwKh<o$P7UD8|h@TKuH z!<B{Gj7q*boU>lN%b}nvvh&YAv%4)y^LAC%PkUap{u&p1_3A0l(>cu#UOVCVwM*40 zOZ(wbWkusm@wHif*W{cWf7j0W>JwOh&(y&*_KR$&uSH>j$+O15J_c_ShU+gUhCS6| zO3S^n*V(i4QqAwV?M(-CyIr@wycx)RIiQN)^~LsFn|Zr4-)lCkG%S(&BmMp7w+{=0 zY!BRjo#yDyQQD|;q{=*7^Sj;ka{_Pf)JIh_E44I#@MW*|IC@4zd0SAqbMCFSEUT)k z_2MbF%d%xRoBfpS4`5a}ST*J8{7qWU{Jz(}C`T)Z+`PM}l|R?6<Ym)#`HW`n-+pDj z9&6p2pZ;vQwXH&O^L)OaBK9iM@*-chC)s#KM10eFusHk1>zmuS{zYcm9gWhF%FWTU zEwp``d$BY&$%0!|cQ*6Ktsau<|E8s$6ipE0xK-~E?#)u~dP;EfOg$@aabvlKPL}c6 zfeK44bFZ}jouRusLvF6<>a6yMd!D+UY-Q#1x?UK1baPk#%{jYP?A^RecfL`4NI!qf z%v6W=9e?L<Tw2As^BG%+@S>k@V&0Z*Hk!S=JIrXElb@Srboc{Pk=@c?;wMX<Vsm}$ z>pVxS^y=T1`jTHNkvDV~?|ZMUC%gNy-QK^yH&32--?QPiNy@|t#=cV0VH)|amS>q< zjccZy)!2MIwRm~%#*&+R%rD-)sq}YBM$81!$h)#vVrMVDoH8j_QshF~Rqu#^DQh={ z`#D{`V-$JUM1P{KfRU&7&sJ?N{mq=Onq{g_ge~wo@okgD2`-QNa@FT%>%^w7m@OFY zwBW7zGeHh(5x4aV(<jItW8A#ltBEt=Q-Ph@6Iag4{U_@e+<kqa@8)FY#&|Zh@Lz9> zjCXF$z9D@;TO>hyVQ#{eZRXQ9%VsVqUE}si%=bv&P2N^}#ut~&lon~FI3GK}aruGk zLfII()C(+gEZ^_H#n4i^;zIrTysjp$$xoSXE{twaTX%Eq&fQ&KZi@(A%*}h@@^|}O zxkQ7UW`=doG~S)Oy>0uI{Pe4H+zRh(I~o=Gb-OJ;!?U#>#~0bDZto}vu6a4VsaL7i z**B_MZ^MnfWtDqhzr4O6MYAUIoT${EWjgWu-E?*1cdXRkv`Q&@@pJwA`>THbtF3oF z`F)+!*4~EwkLRA)^=l`?i7h|3Z2970@y4RW#5i)V<`w14O7Yx4-6KIAojhg<vU&?F zqkl$NOw8Kx_%fTo`>(z2yF&DNwIdJ8RA)wtEq%ZDlv--zKa-Q8Po6Jnl3w^uXT@B( zm|*936WJ!d@-O)KYo5UT*cYa|_cL!>sV^26Q6Id)ML3`@{2))P_d@1Q#an@CP7gia z*_?Q+U*l%KH#LY!Y~nq>O>&iA{=R&2^?V`U-$mjVR=3$sU{X=y+3Kt7^RR58i^r6; zJq`;C5|@@{DV=|N%D?7yx%%ftD{`Do&s5I4;yL>`=fw4{%v&d2NtyJ(YUkWlm;C*A z->bVcyP#I3KA1K0zOt&CzR$Kqcc&^fsVj26|F$T-)|q*Qb?26{l}n?8(tT&F+`ruD z<sD_UyL09(zq(b((bnqMGRfBrA3r9QHs1KQN!#YnYrTKFrw0Y`t}(v+%pjy;mj9|7 zfA7CYdh;V}^Fh<uy_WatqHjBVvy%GtuR3Ri<%j%@?OTIaifeAHKdZlW%MO_gU;cM9 zc6rzSwBx$XTF=dUmic$|ThSAVZ#U2G$y{OK_T(DVwN=|Jbn;i3O7BvMjTPDyyS#(9 zt8t#&)C*rb=WkoXaoK9iX7=5tzNRU&D?Ur=p3;4EvN~Qty2|(Up^5%SB%&FYh2K8< z{I~9wKi^G`ykRu;5cE^uYA#a$a&FXq<&SIETj;rcwWzuff7)oJ*5iqjxXany?zCi@ zpMPW!A+DXUeg4E7$v1L+t+Wgk5*)60elfTb`R;||4X26)&z~yo3+tcYDp|_^d+pKP z&C1K%S@-`+WcuHBraVsa>*1`a%+c1q7qdDn`;NR2Ox)ygsykS3v30Ck<*o+C(E7~2 zRn6{-cRqMlmY&+bKbAjsyYJS^j~{!#J-I3L;<Wdf;{J6Rnth%tnfrc*tGzM29os&C z)0bjzG2WWVcKXb+tImhn$xpjr(X#(q%b`BIo(+pjdzQ?)Hf`m{+3l>y7VEvctG%;d z>FJZe)lXW@b}lSGo}E?8HGi*(r`vaqtj=f7^|9=2tERdI%si}P@TsIgx8E&o!i;V6 zosVw&we`u@4-?Kdmo7LLzNm%2HbKX$UUK>2r;k_PQ#yP8_0NKx`{iTOjsItDk+HWs zwBow=v;1;j&G5w~JxZMwl52hj7-ZVFb*}UaGdbT>+VXJo<^t0<?~fLq+TEyjs=|a% z`;U6}@5Ed6%^UZ4s1@H|a_h#<<C#y6*WHX>x5_f5<!0TSewTL^9e=z|-*TTnGhor9 zxweg$CDdP>310T){oUD|CU#N1k*Q^M%N8n3QS#3SuX@A~(zYw5u$b9hox}V2gYp*+ zXB<8nn63#uaN(LtOxxv%m%DWzO<MW)Z^6_22|Er{Jp1_7YR=L6>lIHIRo9kzyDi>m z^K|h?t36CNKFeNcmkqdZ>9pf!!Tw{86-Vp7F)u8Qe0jk8&*BB5b-OmTJP&EnsP>ri zu<DI(XHCOH|0_$XwN>(Fn)?K~@SIeA^83sFzYEwqHeO3ys41|>Ecrsr)H!cYIPF;F z`Jw1!zrE<qEAs=tABa7er}d~_>Qvj5IGfJIlB16u|5qG4DL5;y-|(lB?wLQVRm)qe zic&OJ{WcC?<|wf8wT``*>()u@bdQ{S$>J9*$#d5|#V%-hZO@)WfpsP<0?V>EBqD7) z!s_;wu3?;~u=Ci)TOw;c*tgBMh>8%6=-j$rv#F{x|1{sUCc*UCS0i-ZIbIHAte@5X zS!zyhft&9B*e9P&*A!3Jme_pJR9<I)Rbk8jLPo#2b3&KRWY}lNohk6_)3?AQipt#X z@qd3j?%&3BfJ@S}q-5QRU&jh2TyWm~B59+-JSE%nTt)XhuK6A+@(+EzWV2)m-<F5z zO9IkXBzOJ_Kky{PqpEstWLc-a(Vj3~ar2(~Nh{8Eo5XV-4DjPinQ$m&zsFViJ_}Ky zSqbYOY*(?{daGwk>b^4p4o!O_FKO=yeRznyt-nL3<l<@`akIT&ro3F^vwG+CnwPc5 zi)P%IJu@ohYJaBfOk??sZ<{YzUle-G80oX{RW0v##Ws$;lbPiwRp>l_Ar+cmmOtr< zy>RYL8Mb=w`fVq^cmzHBJb7aPN7REXHTjFqt1ogMoH9S;>;n1kDebunTjog_YKavy zcwR_$JG$Q~^^t!A%lkv$-mEe@QC(onCuJk+Gfh{maqIohTvf+q*Xos9p0eq>RN&V& zIjJ++`99kY?v%f=72315S;p_Zy}tEs+o2_or#0q0YFx?MTW^0*bdevY)DPA9_0ipO zCpKB{&dta^J!4YZxfO+lQUY^N8u{F8bds;Jk_caxb(f3dLvOW5O6k)B$^6=-KOb*Q zDq7u_Gx3{hlEmC~7y7<ecbk9M>UU8s;Hg32s=VV%Jmr==3~-NWkTCa8{*^mfA&GO> zE$hAKlqJ@bm+roMi>0x?diB44_O5-uuY5E5CcFP_-uG<@LQ+*ZM!pTN`edFZEW2rY zJ#Y1?_q(@ggnQjPZScrGgTv>*>)fQ}TkZVJKfILC@VS>3#(g4hU!3oQ?cbV~-7Nh2 zq4VQ=&8jm@JWA1#$&)|r+g&<4Byg*ibd}>rZ_iU__4h`b+Im$x%<>Pq`l){XkDv4Q z@-6rHVsx=r>w{0ns+(%xKc3}2%6lfdbit0Al=Dw{-rYTYJ-Nm4@svAaBGJc_H+V@L zbld;IXKHtJ*)sDMh4AD5&CV*FS=f~k;;yvL@XnnIxyHT;GX+n-4f|`Lbiva5)|cBB z2e&91#ANsI^<GfCeT(mrSj3(OOw2s4$LnQ2`^)}&{pPUz_9d^+*PL1Zy<UG+#g_&5 z`S-W}ujT(QE%WX7<9n`~Ow`UVnfItuw{i0yuBY?%JwN>N@p)F+eEXvs=8fu-zhw?{ z{XMgn=Z9`H*P%O;;u2T1oxEchER#1mv-RUrn<r<l?YkDl>fpak-p4hD<ITBk+9u~d z*PUCy$Xze;%{4@^aQ?{)Zp{X7f1Qb%oLHmxO!&uK9c9tL8#2E3XO{M}?%J}Wv!egH zQupkD>SwNnDY9u<3)l<A%3l9|S!@4Rj_tSQ0tGpL)`q|s2L1SB8$Klp+PB+YJKcTW zu5I2)SN@|$K99CUcWQ2u{bh1qk3)3N9H-wwQRWYs<6rjH>plwIE}=JX%JHbhhUJIe zWf}Ato4mckb^RaH;cu;~J!XI3heoaZWIwH;=TGOp{s42Q?zlvQ1YJ9Yho^-4w;Au* zFwdzpGBkPCovSao1i#MA=M9a19=7P&MK9MHA*G||dM%o)^hNI6d9wPb(v!l4CO!v^ z+AO1vefqoA@htnr-j(%_nsa~b%-noCe9;rN<*$z<AOA5aKzH+<3*DPUq<fTAj@lfb zms$ASb?3r})IC-+tYhsyEHL14efIM#Z*g1KA&1v%@BW?khvlo_I^E42(_iL(3H|@! z;^ODciQStWPt<?-@>^b>Z}-}l+(s%Jis~yqJbH0FeS)`;w05!EfA*OtS?1SEPHlMp z+x_ppYm+Z4HD1lB+TF3wO6Ebm^LGC&FIK<j`)EIB#f2%?i_b6nA^)`QREkT{j1${` z%op;1srEkBVd~_5O|}O=Rvh^BH?nHW(mg-+_=>7>Y(8b4v|x9x*fy5J`zwX!v_wx) zlb`=mS8GM-<<}P9!WS>=S&(<dWSY?H4;Mbv7jTyK|N8r4^~;-w+xhoN&oX?h`TXI- z#fxt`Pc4pHmU(W|ug&k5yZ47&3*F`5%W-P9txffbZ5z6h>swxO{#(A>Ih%juvF-Qg z&8=Nsl)=J(lVj52nNd#{9a%j$^=#V7^7i|DOCPe&UTX5}6)#`AuAAb7pARl>IQxRD zs9%onE}PYa`tL8SD{n>r5}A3{FLl}K%kQNkT%^{0{q=f3XXBUSU$WZ7ldnFE__^!< z*{vr+oE~m+=n|Drk;%+9k;qy9<-v>JnN{yroHV}ndjDVdo<-`v#Bx=dN;)e1o^N=S za^U%s0@m^iuN{N<$~Bj0o-hfSvt!<*Z*q!%S?`?HZ<!L;GOOWReU-ViPVDQ?3lpDg zzb5s1M$y;56DO_g?{)8R-V!sLf6n2hk7fm*H52qcZOkY9RbhqL{52IU+pEt9o;O!k z+bZXMRKKR{qRy_WCy{K6JC{D)T2Rhdop)9<Z(^IEW9^w2CD!+uSai-WoRu?&zkZdZ zR%Y#=t@(<+A8q9wFBf&M*4<cdKl|y$>LsjQOl%*+4z8b~x-zuv##h((HtcTa)-6ik z{a5^l@2jOh#N2l;68@R{%H@U6lze54jD5iw-5*_c$?k2wbN<%MdOwC0oF8Vib#Og= zmn+DZw{F+&2Nwb(8&-v;tXLnEt+=ed#9GpW&mlSMyOYqNvMbR!*=ui$CN?d*y{f)` z_e~)+0fFFdMmC+8g=V5YN7#S$tvx*F(q*%#w=atJKBy|68W|FLOtEdrY`^Fex1XQ& znj6k_^O2``U660{duHF71=p?%C>P9l%>4I%-TUA<;l-Dh&D(xfeAWho)9=1ED=58X zt+i^9TK(&$O|*~YVa-c34cdyLv=*E=e6QMfXMH<Ic&8B8-nkB@E?f^a9ks5-S{Aab za@L(Xq09H<X*Ju_gNY9H$84{>mYuUO>cProo7YUedvW`!PuvdvPu3p0vg#pQw|L#x z2Lk^$*R07ht=M>z;Xikq#zx^|&zI!rs8$@fu5~y4(~~O)z3VE%Vw8e=Ixi{BTYgU1 zf+=u+F-K~D{Ry7<&0F8e)=68$lodFO1SM^d$b0u<g7G!y=6lHnSrZ!O-fPyHq9-F4 zvWvB{TG7*sf40fep4)$=<GvIe<u`wtTM%6^EyRK4;-=p(-Y#hJ6^)5zaMbD&pP6cU z{U_f#({$Oa#vFEyTE^4+GMwVeza)ry_%k%0HGaGO%%=5%7WJQ+VpY-$4%+Qpw!vqK zqM!JxBCi#7ne7W!UOnw+d2;QV+hMX+f4f9~JWv&=$bX>LQ2b<H7ULxS&$|rX%a>Ui zu2~zaw%qlfvi7<)A)>WkrcC+WTh4o}uieE!L{#j+Th^PCoGxmX+drwwuAX2Q(O#<j zPCB3IWaIzK_pWPfpD2@Azt3y)+l7p)85^ZC%Xl~*WvTQoy*trFJ@d`o#|96?E~wmQ zx@b`nyog8f(Y$AJZ{|JuwEW-dT^AWvS{^@kE3tdUy7_C?tXi{X%^Lk~brG|NUmuyi z*k3R$#XE)f1Aj_=O{l2Iz3pN$Czu$*R;xvyY0x|%s{PMApy5T@qhF!!a?_maKQW*A z`tM2buBehn`#WRmnML(yrJfEoJKEi?;-vm=|HCb3((08v^es-ExbbDqnY9Tm94hmj zWGw^xRD~}dJ?~{9(ck}$@6~!wwUuioro|-rEj#Y+K3`s6?8&;_ue=`{EiMscadB@D zkGka=b4lUoV?(1I7Tp>8QkPdbY>6#uTVl%|@gjd#=}i^Q!*7mG-cc=+a9{pjY|6wL zXXY>d!C$`0y8giPT@&0G6F4?_+3jumyW)TQy9rVU;(`jEPJUG~J!&(fE&E5dWvSb$ ze5cRb%qYXBwtDAe*S4wKpHDC??YEwOVl$%?WB7FLEsSRM(_bptmcJ2H(#<$$_#*cI zV)xf|6-%}UKQ`W~I>l!WlZ4mo?_qVt{=%EtRYY`tR|kvyOOZRFv)*x4^7h5K8@kQE z<(z!g-}X;W+(vTNx&vyxr+68wex35DpE*_aLf$;#z@*ucAJ)8B#(Mb1|9$>FOO&0K zzg(O4KRv~odChF^j6Gs*wP!4DuL(cUmU!kP`=cdZ_dnNkRJ%{VwuMoro-0e{k%#D? zX)NCM+ROjXt1UZSxGM6|`pVLe%yaV6wD-I|`o-UkOQ&@6_F2zt9@Z|K+#USSUiqu1 z^X`Hheoa?b^QZa#S+HQ`uD1N7D)Yh>ZBr7md`s1e?MycvU3K7zLhj>7H$`&z6;$p8 zw8SuF#KuNF&d*TMx_`s}*QeCIo`337%JL3r9k<L?KgP6CYT~ImxzkSUuD!!CDal4c zl;2p^Me?oMVkUX}kHUZ6EU}P%DiJGrWT_C(j<wO8vWiMokM3GFx3Cv;97?|N<mjf{ zq=@uv-OiOaRCn^XD&EUos2|k)L)u*S@+94&&n4Z8ZUV7!ODa@7r893{(o^m_)w_0W zeXHA?4Ik&o1#)oaHf+q%-BfkmCb-^Ge$$N=jvKpYDQZbg;JvE8?erHBqoDUMcF&Yw z&@L@3*5!V9q0QF+g}Dwj$tpb)*~Bk?<66wn)ZC>b)qdo(X-j3>GYym5k-9CbWX=Vf zudm>fKP4A?hg)Hv*W(i_{&i`c5vr_zy6w{Q-&5<2E==p(tNGvkn6azN+l1Nscst9p zmvcN>J$s&+?~nTqbFaNW|9xlO<&d{qRzx+hu#2y2OUp2m&8?P|Gm!Ii)lRVG>X1{N z7MFU|$#Ux6org|qul+1_D`d}!7aZzE-+rh_O>RrQ`cb+5Mpe|K#mjY45AnTpsj=Jj z>&~MmSJrKMy{BGCeFekOxTWTnlk;o$rk@k|nw4R`bKU8!Q{TLMH8DGKW=`=>9=-}5 z^$C5N2OcEoPMn~6<dghAgDlP}o%hq7cXz&RTD<l+w<CAprRgP&3{%yT)dIVHml)VK zFAg$p<f^Esm-;>Hdv^8m4;5!DUmsbwj$O<4G;cUZpv<K?+~r(V_31v6oLg5+K6EHG zz2fLD&#bw3e|+9-d9);lV|&^K^%?Dpoc|m(3%I=Xx?$tLdkGb91@)p6ay%pJCNBx? z4c?l(;$rm9EoZDkul~4K6sC5j?73KH+~GIdj;~isPF{Po^T>)Io(p~l`q`SaR&R-u zTX^W`P014zpPMX{zjpQPeCMgZ>$BHRJY;>A#YQTBA%jGk(VgVkrpJ%fU7D`{ZmXtT z;050wp@x&cIc{#)vUql8?bdv=`q1Zdb*pN>U#w8*dtT;SvtGleST3EZ=G)n(^d0@3 zZ`H4Cob|}$_R5?S`+~XclI8OL@$yBSp8x5)_J!+LKBnh#wOH2Yxb%9T*!8uNV|i_T z%<1*wpU;=HDxaDp++&c}s@ML)NI9l8SyxE(-=-in<+)AQl<b@5)o5#mfBMABC37%3 z)=BQ^#c1~pE$i5M_-s6%7vJ&jiF(<4sL=h;nU8-bYWVfpUyPjf_@A`;>VK-nj$Nt| zLH0j&UcIQf<QFvkvcW^?$Ro_Fm^CwIZ2fY`r~b>to*(bbUvQR&m*0=sS+jD<smyJi z&zNIA$-VYD%3if2PW$`O9~!qWZWFxK!x5ai$8Y-2(<PQM`9hAXEPc2-^{48IPQTf| z`jOD8w`-2n3WgU<n&-79W~xD4%KSxpCaY<$a7&tSs{hlj-kweKtu+{WZ>^QoFgrMv zkEQ(6>krp=*YB_U{QBei_Wj@LW&hv$ZEAV+M#1DA)#~#cq%%I9J^rla@N=f0GY#u3 zb@*F)Eg$f+&%Lwhk;cCLK8_O>D7-FXRlKF#HYa_>?DG<u8~totGQXTP-*ha0U#EkS zbGo3QmA>-93a6VFAG00G4e1TPAkP-nv(R~GQlr1pp`r@mv>4U;oa6Q%?dEdrtTvr{ z=+K8{vL`$n+h&xTFP71Nvx@1cz@h}nvwX>$lM4?WSyU5j8qs`mN8jcoFLAHhecMDQ z%{GhG5GY@Gu*g*Q@Q$yJuhsRW^LKaLWIL+7ZR<*}#H6)$pEpa(h(x}euCK)&<>xka zqhpeU$%K3M6Y}mKyg5l?!p!;%<tMer+i#g{lKlG8_k`c~xl1g%@5*#({A}s4dhMN( z|H|{@$w|VGCp`=}GR<P~bl*!4*k@n7Iw@~QB^QgJ;mJmy$|<pDR~zoFymEPA{JGDW z$MV;GF@5;W?dbfp#XU15uln8aU8MCTBh>p=y+()7k%)C^={XE*j|lV4J~AojpGLhw z_r98oSLLs7GcDmz-JopYp1a1m=!Jlj`0;x8jhBQ!Excy0rc(1J+gdkK<lC-;DKpb@ z3Y9XoOIB7qGdzFp^2_IPg=wYMC2cjyDGr<b=i7bVnd;)P`}cRDCI2_Rxp-reuk`Bh zg<cDF1p98Y&pY;DyIo76_P^De+zVE`@z=i;S|2*;bLmw5t9#$?J*XgJzr^m1sgK}< z$obVF@#|0N8`tDLkTI85NJv-vlRati)A{G?qidVe`3iD+?Rk|Sa8EvEp*Gv=PQke= zDhtXpjkpspo>D%ib#(epZMFwf?a$<>v8Xh@f4XPk84aNo!m4Fc9;tm>_T!4>%1M%G zcBw8~bUZ)Si!V8A8-F=3sN=AxfYl_f=65Xd9KFvr^f}JRuD`O*!|>?t*(<HJ`1l3) zt-J7{RI;<6@Vef!kH`1yIaJ{*v*q2~8O%+4O0pSSe%H3UHMK~5Xt}3aUh!w?`=Ck9 zd1pTqe_Xa(_i;2=nasKNaDT1qt8UHeo^xu~$);V6{H@lm$pZD^b$)Y$m5#Bb<y~T_ zib<*rk#P-GI8+?_zA10>rmngtFUoxDOW(^JY<D-3X7paoHR)!>1d)TspDkfKyl3WS z`N)HOJ$zD$R%%MR8|DaTBrcmX>Ddcomu&fZnc#rv_5Gf#Q6^hv>U?nBdZJ&Y<fV+o zvMj4-({+xlShq^OeV);Q`b4+We3PCAPiN<UcW3|i<B>|K=L>q@32f-8bNYV1C_=-+ z<wAg5{PP#(k!$oWXGz%0$Hg_hc~NF=J$LQfP+iNtlXf%}e3Mwpzd4YLh5N;^kVM`! zGAF|Ktx{$9An!OyIb;ow?wslCFNMqUv`(CEe<@h+N8uUumuJHQYlIDZ3=Qhn{%v~@ z@Zf&sw<#ZDvgf9K6MMiJ_&2A}Px-}>$N$nr-X>k}G>G?9?CJ6La#pZ@t0iuCH?oq; zbka7jGxHUMgVjYGW3DdUx$63Ys|(uK>|^RN^x$yZm9jdu@!FGcgMP0qM=zK@I<KW{ zXW2cuzkhw<C7uL>7m+m^S4P^|6!j<e)H|>{J+Aq4SHbUwu+6{A%pu8}UO$!T(_H<~ z>gn-6hbK$SsQS0=$9&dVn#r4<P1ZlW`;jl3k-#O3!{=Wr+4<U~-28O(5&M?6!be}4 zE@WJ7eavE~&-#t)#Jdya@Ak9YKWo`t^U^qa%KEzzC1!7f%L7G>bmTkFY+1kS+VOe4 z@A=o5)QdSh{_*>u`Npq|5w>*<nm3uYZxugLRHf7skk9VUk+iU7-yOY{?@C*vZyiu; zYmY2&X8f7@)?fR_(r*)6_x9_YsZB|Iqx|zm{#BOqOJ(_7{y%&e;;=-?aQTaAMyxBh zK1vRKzT)+jNlSiJUiPf2irM(XY0`48-C}&FrQ6Od-P)q9`@)4O`&*x1BDbb$(wm$Z ztKC;cdX>cAZspPJ)IYS_E>C&S=fz6R(e*L0DXULUm)_4P$;Em`@>X;9zVqGw)9v;% z%E*>yc^CXmYvefDAuYuz!gwkv#N6MZ>#&rK#2mHo>E-(wwGw8DaQxcE?cKBQZrbsl z^}Hg+dp(z$tk-rqH}~AfcUr&SM}EJ*`Q7%}rKVSu=NcaVbi9Vg!g12-D28;kxz4vT zq~0VS>e6gbj(DCTc*AE`m0y!neA%)O%oFDwckkG(Qr7W!I^zLG39-%EdzRZ;N~&zQ zc+m5S#=`9z{>Z)hDmh*M0HYWq<8-$JjD}2d`qL{9Fxu8HswvyK#;$nH+UGuOQ)@#F zzO7jFmZ$XIzON1&555ySnV`|KbKco|QeVHPs&TE4`sQ`1w_0X_vx1HEt4$3G85wJM zmt}YGIXdr9zrt<frg<RzkE3F+>vavm`a4_X@3cSDFo@oh+oXNjs${nE8yma8&Br(< zKApcJc&C@txjTtdnCtajUUJXem~1-NWfgBvu$06m-G`z7-%P)t%MlZ6dGfLDvKEGm zY47@iXEw!c-po4xxs05g<_6PHJ%?(I4bp8#ZyvC#F0#6G>zZTP9<^>^8HXufUp^@@ zev|Swk#|x}$i~pAnu@cv+Ds$QFuCuONdLdYVs=cLrq%5SxuJ7zFZ^au|LXXU2MzZO zq^ukksI17|*<(MY`-SLK$<>ULkE^>~dRg!={q-i>D;!!(yUx!@XK!1nVePv88OLt5 z%R#Q&rYo{JHm%i9RC_k7o%_iC60^>Q{F#bog?AXNHg27I+_OeR{dKC<-w8n+6C&>W z95@n{x+TPL$-?Ka6?q)8JS`-IrS<Fi5;RV&ouhobsC-ia+f^%>YcD^t&zv}OTh}bf zwGWwRUr;oN%F>UYBWhbS?c}?HZ=W4zE_~RypyPbz!wszySa;{$@hj5%TUM!CYr6lh z_yzSZpAWg)KRoR;W83rRk{Nl<M<gcMt=n?l<x|AE4?E^699R+V7sASwbz&9E_V2Se z<}1}-)0$pCU2pn|iF>L)KRRDu_w5+>*PVx-2|W2#7ib&m5Hb0LRLO;$uNN799O3*o zZL-xyPj?*&w$m;wf#=c>7d*M+%l$&oZDaD4ebeMFrcJEc8~W?stevU?9IZMfbMpT_ zXZy9X@q4FeY_C;@sge68W(IYZf93TCD>Sdk$zIfIdtQIOc2+hwf2L?w=l2&=IUjg! zTXs>$+ko#)%BHO?2ZS;+N<LfbeU(~r@Kxr&LZg7?b88x%_8*`3#U}fV)*sKO;bkHf zZpn+*)N)TY*d;vk>M6ygF(>yvE!}Rti7Dn({H5-@6=@~~hqCxo9Q7~1-g>#=7$2|U zma--j-$idPEa2U|vR*VO<8xPLr|hm&M%L;;^UB$2mDv?n-%j4Qa@8%%duf3yG=B>f zDu0(`aK4oL^uPT}pWo|k5B!T3a(|F`V$yjTzg2qYCw|PFHh-?86zlJU99JG!C{3Do zNZ)H;)T<-$(`3Xrnk!yixz5cMu5X_iBR98IApAnJ)1>G2Zwv*NyeapuSK+Ch6~CbH z*b9S`<%<1|ia*%A_Qc=p=#~-BTK~=cbW@&BwTR1Z6$`)IcqKOJJ#2!q?n0{5-?)Bi z=UAgsv9{>;ov@Q9-fucJZOw-?Z=Kqi!6ni^+Uu5H|DwJ3SbPfi0s*TZ$G!coaEpid z-O#r5>D5rYvCumI#{Q13`}4|wCe@4Id)lq={uAq`JVxI71uHy%8&BN)=7@U!G&$Dv zmw^@2s;({n^*nakrr75jf3O+1#)|HI62CIoQAYpu!ef%ZRQGFNnO#%$)$#AG?FJw0 zS@g^@0*&_awJ}I-(a=6#dVbl{Teo)C&S^P#NcVOfSMck$C&8i*^|q`~np^K>KV9O< zgZk$+8^2Dypcc99RH)vi)SF84Mc8v!sRTcWp7r$h=IeU{9j9x*<lsAgbN)}WC5P|* zwzT}&^TgR*v-ZQXt~;fn9n&rLFL`h~^xEQW7rlBK*9%=<F!}Q7dHXv!xs<OoPAQ%k z)TP4sLn~{sKj&Uvr~QtT+c??#{+3@*v%0qZ%dLNEiuHTW@WgLqmJvGg-u1e=%88Tf z4_ju3$;+8-*d)7qA5*o|$}jAqVhyv_tvem{H(2aO-4EGr(W|Y6Cnq0xCVx?F{~kG4 zsih*eOutHMzx}y;dj7G(eF75Q?>D{H@>}y<=N-4$Z{@wb?aplX=5{?d+WFPt>$|-Z zAF^ny)6)FI=T<(+zdrtKzx{>AsJin{5BpU8(r#mVy7+(CS&_L`F+%MQ=60%|H18b| zxxSV46D#Yw!sU&f5}`A1EM}L;&=7xLq@O8dBVxbcZP1g0$LrJPc|CW0U$95jLgVP+ z4Z^;HLCJrZdNWj%{>eOdwvM*a>tDTaYNbl`Ons|WyB9fmOb*<0DZ8Ye{d@7Ug>$a_ ztC{mnOqsRcw*JNE?>mx1R^HgS<MDHs@RSvLoL8Oh6=doMtgxPA|7m$+&612|4_!F~ z9-H&-m2C4YPd~2uY5R8LmkZs>)oqV3$ZQf`@bXH8%8KS09l<-_xxDLJqUjxZ@SJeA zmEf$Yt8UKvx9eHW!YsEY8%3EFl412(d6(aWt&_WQibwWv;9(WcOZ_2M$>FxCy75`; zb55+D@uaiXY1=Ga`=?jGED}DjmT98OqU?>A&qy&R98clvV9ny3d*J0=rjVl+;mW66 z<=4dgfB2-Kc;{DHzo$Rem>Is>@?uK-Kji~kUN7e4oBPA?fPA^g$IaP_>s75@)cg`N ziLd{7OmfLx={N7syWT8J_ei={7yf5Kuva+S+Qaz<TXsGDax2^N_SV!1;%RT=PA{`L zyTxYexjR`};pbS++&VvVUCQ=WAMeEpvK4#Ju8iaL$T4o-yWxnnVsPLzj)J(Y=U#^- z=F}{@le;S^UagK(;-;*2!$!sDc?!=@+OVrW;C-_6LVcaA>8|U0d-iPDU9K&eF5vdW z@29)!*J`gHJxea>{W$%A`L3^-{^k?HKBhI!(M|RGIj<6rpAF^xD;Tw~c(=WSwb_%t zuNxi<CvBNhk-YYU^Wi0uN@r$UIoZsbylHXEGRub{TKY>ix+hgc{h6@K_>_oooo<W5 zANS2mk4)kFm-B_Y{>44EV+ZH(*16Y5&iWKL%R1IEi<2R}^6(Y*&XlPFzx?z(o+ewp zl~{3hlCIZYIh9zmzTHlfrwE<9sI=2<1yi`EpZAmgtFphC=eIm*WPcR-_1X*r#=?bs z$NY~TjCr)|gZ@vQ`*zz4=Be^5VOnRvUaaX}p;^vc{6uP>Q{*OPj-GmkKbQ62KkazT z{4rSi$Q5B(uH8SxBI|^%-xoi&JdSr+&GJugly{x}Qoi4ld!43nrCG*>)rC1RXFERg zGfw-L^v2||@X~azm6fkDY%F}O=Sii=EIEAq^#{X6w^x|6s=M!1T^z~rY2$Rx;${1- z>`FhiZ~7m<?@9j@uao^+oawHzi|ehXPTaofQ&?Esig58wq1WE|)huB$deC!QP_yrj zAnV*vg`=khCvfeX{`thYnQ^DLr<Yz{@+@P9NlR|(lZwsUhE^4qZ|q=aKY6Krr%d&h z6VGEyJ0@t=uFgE;+cKr*#5WJ~#;dQ2pS|;4;PrC5qdD)+c}<rhUgyu9R(L>H&}MPO zw|aIx#ng54qfgy0%v<%)=<5vmJ-jDHHRQI2TrFt4*xM##{V1!Q`Ih(5^4%r3H!9VZ zOP)LXI8mzlZ=vD7Qya}L_bt637`@~7iYfQme?@oBU2WFxYfx;vp_KL36|Y#{)IWE= zUi08Cy`rwm=kf6EtJ;DOdnP8l<9V%dbG_ucv!CL#>km!4YFGW~ZD_W+r+q}R!j;91 zs@YaMTWdD@7^JSOf6}d5wRYu#9bc+X_?wr+EXmw;M*B<aS+m8y@z4F%uTvL~j8aXL zdUK#7V*bp|^p{CZ1{2b59le@rcY5{tv?#~^o`jv1J5T!?Y>Hd;QRgIAL1&`Bw`-h; z%FH5<q{RjN2L1I*Y`MO%7oV8ta`0Xv-|qN8=YGTO@3KP<GoDG^mwEe;&_|c!%k39c zPC4BEE0E*4VMfUP>q5pdix&k-Y;`ob!NG7bbmI~3@~-QdE<c+lHeJkko4TY~`u&sZ zOAl$3Pvkzjr?h(41HH2+Z=AV%_I-hcj&A+l$=Mkz_D9ckeyx%JT&{jq$N#f;oXwqb zzK0!MzLitM?#7p`FERzZrkPLvzl}>+;MU4F9{wqHVW~&jdDqRqu`WP(rc2`f!o39n zH7m?|*wdR-*q+v~``RwgH%t6uTD4qLaG%V)<=kiWUMlO>1Uj`GRIhrFX|a-J^U~{D z(Kl|VM{C*yYMak={NMPHRah&l-ay*>cMsFIloRZUCI&Y}0?$;>Nj<=R)IU)A`Sb*Z zdELGCuBO(x$3Ndr&El=D;$3U;zIAGH;QEE0-z7KcwXBlgUbepT?vq!Wwa@oD$vyU6 z_G@3sJ*6qTm+$rX%JigUXTZGfI}1ZMrT$5A);)IN%qixIC2U&?C(UNH{pWdWyU&gK zwKAK(=fv+=+f});=INTV7K>I)Ti_#>*;>1B{^K88*91&mbY_uPXtRTudu3yniHz{$ zuTg@ZnG7G8&E9eL^trUguDwQ1`N^7BZMO;@>HlH+{N>6g^8Yr;-+Zxeg}j(`)Q6}n zf9sE*kCzDUZHZ0fFLSCkPhFyNGx4GBC+!oOYgOyt9a+6SA}2mCaJKo=>1uz>pUtqU zvGAU8Pju$T*RC$r;cBOtYRqbQwhGGYUOb`k{?pZ4UzBFoIb54NC*kpX38s0%vJ+G5 z%2}p{rgxjFoKM_-`RwY~FDyf@ZJHi_y?5@F`M19pexH7CUt+@g={0Fr>_5gjFSuD` z@np$O<BykrcrK_H`eVUe8XLs1bq(v0xv_Ejr>B`t5ZLhg!?WdYyE!H2EG*rZEq9IW z?3#-zCl|b0aQB7Hp}Mz0=6#9ZWvmq}Z|yqJx^7*e<Q5HeUhzneb)Re3$t*53=y&@5 zZJ(0mqoT;ri`JW>H*J5EEGd0_M*QQpKM%LP{XA*Gft6R5I(w%rb39+ay3luD!R<eH zGp?`TxOJyW*)fdYLTur!#DyB_{2w07zcS~`8cFp6$=A!SK78mXyf5f-`juazyo_t> ztR>BJR_7Y;+Wt#&kJWniuHQw*UGF6(M%AzUv@`F3#jo^}o@>mntAt4h2+el?rxKRl zuTnPUl}gyGcp>RkXEk}sKXaD6cvQb4ELl=+R{lY^sXXWRJe{HQ?om(pjq{s-3f*lv zeTJ!U(T7v7u5Y;2(($D~$I*+`?TU4CLfIXjdaw2FMz2cuAD#H^gF)_W>E*KyNaY`A z+`Tk$>W;0v!DnYYh<wxLtezhHdB;o3?bGT%byP})H^^*^zA|Y+`J9l+dS4Wk>~hu4 zMbw*Se>MMn_UhDc3;o_@R|_ql_A%<;$tA5<1m^D%tjg(eW%ztgF!t#6Nm}9mJ<Dd9 z@a?)2Jlk{n&W4ho>U_Hv*)Hk0yMcY1!|hxA)%AJrdgqG&N>PunEe_o+{mrZU(OTO} zyX%VcOYB~B{!^@aC~9QY@J=x_UNNEPFo%Svbq#N6y~%;B-&b9?z4$TX=rp(68-?C} z;Cm|&yR75zx2^e_qHh+|uuaNPI8?K4^7R+-Vci1m-xKnD0~h2O&kwENviGJvuTRO$ zzxfg}i&{8?7pYvorN@5ApwdJ_!}@WRZ?np2DfcO7kD2>PrQdegsH=9|fnUeZ(_3eW z>*;{_4@m)Xhd$Ql&+yw^Gh^KYjs6cJJrRQ1Z{{R57MgC{RsPbbPr^pBcVaik?W_7# zx*vASuYdOW{H6uQY~{9Avpk*6t0vFe{^9mg_RBNo-F^Lhov{S_!txotflmUqn|j^3 zRm*3!OPD$8obj6C^E;o3=cYcocX@)-q}SihYLs@LHA?BoG2OFivzg-!MJ>s>o0gp| z6xb8$X%)MzZ9+k1wx_w=5iRi_TRvV0J-G0+aE)iLisf6j$k@}1-tJaE&wlH|%lziH zwGyG7RSP1f_g-d{s^^G#UHOP}LBQJF*IUdQ?h2k%Q8&!leDGA+f|O3(hVS{cU0dH= z-m~%MoBLMp5`;fKZi~o?+QF5vFFK4z`K!1xtFX+{<vL0`lKYz)+-(+m+8Z0(X8Lv0 zLM)tJzEGdRZtE^?A>*hPrhxO)a`*B@81!BB*>=^YIppF#r*7kQrf=$v<TstKC_Wiz z@Fh6&s*`yB?3mo19}P#(^*+C<@<?>8r^pJW4F|W~D9m=g{^9qI$q_!ARaG`gzRj7M zUU2QV&A|oJK1g5X|Dn!eKkLZNr=r_zXUD{(p7W3Vr{sH@|7DA5{om+1UA@iUSJ#xy zle8?6dTJ$m^#yNH)^Cg5jCYRglB^eMdu?Lq<NNaZ+xZ8z*n^ms8p*b8wmx?G{jzR_ z4V}jJOvf&22(a`#+Hq6(h~-+YoYLl9lf4#RD%x;(Ps)mGq2DWxHash2D&-H4Za8Mz z&&g83-Vso@{{HM`t8d;qwqE%|!QZPLfA*C%zO$L4&FK24MOJCvC2NP0?gyq9KCRj@ zrCzC_&T_ep>-46@$K*dPo3*gJ`snZdt8blnubKK@>(BL;>1N$+Vz1(&jQU$RKP6`r zC05M8wqem6ozIu%cwS)m_=;<B>^i3GCre%_XX-a>vQH0fO;l`6I&ko&P^S09_@CEL zm&|P0nAfkX>io8jMc$|2caGpejguQz_?i1{$avRKZ?Y`I^VCZT-CL&w8_M)1&pGsZ z?J;)ujtLG1r-W|q=>E9Jt7{^g<cqD*>eHeN*I4~dy7tt6mv*623TF>%&Xl>P2De2t zI&RuXJmpV~Ui|0WTs^ns)Bcmcd@vCGn8(K-{z!kz+EqvRf^N&M?TcMo_f0Y--A?qv zkw5cNRpo_PrU%yx?XUU!Eb{%))$;M*|E!jLbkE!H(ffpH4L&c!kLr8R$nn2q^twb? zw<?_Ttj*HD2^u}E;u|;g3G_rQapISg(_3?)<=FCVZ5uyjeY~AzzxL|X>l|;dM?O_$ z-jHq(o4B<@ex7PpzUb4$oj&4+7@v9WdXjGV!CX`P%UZdt$Wu0F&ewnB*qOk)rD*N8 z=A#j(19jW4&bs~L!{*9whNe1EicL;o&wQq3U)mMLoinFgH)OI)|9`QHzvfc{S8p-2 ztzLCYwC=R!*-KuUkGh;^P3Tfh^L*}F7VA4Br`Ac7_f16ii-vV=*Ianzel33UL*?0m zf(unOTt)K#mwqninKjMuAahDUeVol%x#M>pYRDaHdZqE~(vcI(7RiW5_RpyKt>fOf zy70=@s>j@~+awZv5`s^rhHW+ft$*&*=hdN6qVacs?VqCWdgEmMPmTRH=6xk!^ipo7 zryJP!@YF22ap}>-%kSsal)wL7ecw5&^6NjN^-P87Evo<j39jf1^AY<g@N^&B<mLP8 zbxodDn_DEja?rKhw>6D#|NPIl8<vW8si}CRN%l*3PO{2OPVJIg7M(r!c{l&$)mP_) zoDVGv<8ID>l3aYrV&0zl%e(nYmjrOy<ScyP&FZo*MVvQk&s3{ZVb{vT>$Yn@pV(La z@P*2LpYxR}3IAN5W<?$EC{WC)_s_p$GyA;P<@(K6>e|u|JoqfGcOiPcW|*&?^7*i$ zX`<0d@kVon<%0~By5iFx`b)S7ywQ5Z`)@&qx$)E~yZ24FvMzmN`HHRIOidN`NF=Mf z9MU@Ly=Tt`eWNe81SZaS(#jF;RA|%LP$03)fz^iBr9=I>^wAunOMg6k4YKYO?3%KB zhw#?K%EK1*_NGPU%Y!3(r49%NHhZjkWUJ7AW@VUU#<#c$^B)wRJtCmFa)C{sr07TH zo4jFpUAx|XtT=OLT38ZqZ0-)F>@{!de&6bzc|EIp*)Q&of35~J?3nxYSPxH1>G|{^ zo#RuObf48cyZEcuHE+VvWrvxb&hxSSJzalAhD+7C2TvOo+_kQs`0Ns^y7+WP<%jz; zthv(jBR(cqdQON~efD@|%<8<K*HSL8%YTw|>IkQw`JRc1J-rWQlkYs3mX=q1RN{=^ zCn<T(<qMTX&xZTOJ`6tQTOa3>x;s76bZ>>t{PG<B^SNK&KP*jbY59BW)bzsFS++-9 z@9U}P3HQCb)}3X%h{f*hU5g#>muGu_e*E~atj3z<anbi&x~|8aSUz1$#eAv9lA`~) zNt3T{|CsllO`mTs^ODo+%lH-Rn%(8D=1+felhJ^=BVTN~_$@}I`h#I1VI1Nwtv02# zw-*+cT|4I+z9;*^IiDr$dCwCr$fhk|IFKN}Wu@Dj%sN|lmU$`5@3BojTz4pR(_gk< z=AUw&@a$j9yZvHJfwY+ySJ`pqEsLj4T6E`Q=E1GO_l`uJ^vNrpe#;<3JhR_#(@h-_ zro87Lju?H~utzF|x9!gBIo{6oe~c#mc&wz6xa-#f0qrBp-?=TGcj5I_#i~y;y)OMN zu$2mPysDzm*>mD#TE_b7@7ZhoU2OZ_hRhGjyd#o&=%qm4@!)OpwtH6ZyUG7rmg&)k zsH-ARQ}+JTs9}07^Sf+@@4pw(moud-UL8y<e5D#&RsDbI_V@K)%qom0Ie!*WWlpKj z?|i;AR_%pJY>m(9uV1o4#a2Ho37zuPb=LYX8pUlLQ;Jhe|2n4eZVJ2<YWVkP^QoKV zX&YVlo=q@XbL-Z1b>$u1Pv#oTwA$BOEVz?FYnRTtQ#|r^{E4@#I_))1_&L1{_n!Le zv{75`*Q_eX2UlkN;j0(y*z#kNZkm=Xv%|veQ|eb;yua_r@#!2V;yp_|^tUiwY@FW2 zBzH#RYA7#@LCjCJ!sDk(raL{kbY1s}kn$R_5Y4+y%s*oM^H<;cFx~&%(l<A{Kixgb zrJ}AA+S)GhW7)Yo2B(*?SaS*YEWEBEV9=Mp^5CVz@AVFgMjtE@wm4b#^~7JcBlpFQ z*4=eBzf^1}R<9pcu=ToPOuxg+CG-D(+@MgB9l)G=YO$gET}_G4;q|}#Pw>p1HnE{n zk#*B2CGlG+8#lE3UAgnPOE`3o`_h|>jczPmGGU=c%jr){>vK1R>t8s~mw3c5xM+$@ z>B;~8iUq;7bH8SJ_=!CKaQ<TEC%Jz+%e9oI2J}tvHdXO<@u)je@9imM!*u1^)t|o? zp6kBas{HeW+p%l<#SycQ*&7}UEQx1V_w=dTq5Q99vh~NkGg<uRti9x4AGg#v#oEbz z_WoFDIU^JAPcAKzzm9x3vgTcP;Jc9ItoFCdTFi7x-ng8xH!Pi|Y4GkSlRD$l11|5p z)|5rgoao-7chqO6?GqW(XX^DAtS?P{bwWXsVJ$OPi1F-?B2ApEFAaMFpWka{pOAQ= zcQ4Q1#qZDPeE7f6=l;FNKFz;$zVDwNxnbEXpZj;sjags*jGG)h{rQR4Q;sg!EaMj| z@>Tb{{e@NOd8r)v_xrXopOQ(+U8QmKzpSF6-@bp%4#ymW-SP~lJUwx$@XCd+Mc(z- zZ_j0T{^!OE?ftpc{@;&r3q0rK7TUJyi+${qP_v7CXG-7Sa<lsJG~jvnFaA>+-rg$Z zk5}ZM%XF`BvzNFK`t2R>-OIcaXNH$+Y_f7c|0;xCdG+QWXI~VAT>t(4y?3(FnHJ8g z0z3B_Z@Rv-`u#5%|8ixu^{T)32zDwTv-s!u+_?T;!OKf~*&jKutUh4D^OEOWv*zIo zubvtDtdnze)R8^;=9%1uEMwm1Y3?^Vif4v$y?*!NVC}`l?BBdMtlFpk>2{y3L7Gdk z_`fbm<*b`3axD(meuu0{E2!Ad$9H&n|5umO`MDisYAT`0tN4;k>Yi0+mHO;%ml0WR z5z)JdbMvy<^*5s;=G@(P*T$l{*-*~B(^Nm}Q^a9`bDNK*^?eIYe6p-2D4stkYxV}- z#-q_5q5oz4PV0x2+FeUCeU|tA+JU6J<2&*?%x=X+uDIm8a*{;n)*lx*;+1>%1bdj7 zZLzv+(zolb2D?>U=_WmmNk1NV-`Mmz=j8hZ(d*Bjvr61uP=2HSoPchaV~@|QE!8^9 zt*y`eaIG)3-uo|0<n>1BOR6o(d;k69FncB*tn(_p>8`SroVS2^V!YBO7vC=rJ@3Ug zcGdFzudYAkFF!ShZ{MYbd^IPg`<Lu{-0!NFC$nNpf-&1n$+9z-m++{uo>hNV+kNqD z-=o4yENllOPIxZxO{^>Ms6W!Y#QEvvH?@p^nR6|b<1ai~^@(Lt<W`H{cezf=mAcQH zlU?oaywOX2#rc114kA|~`mJs>7`(MzXz%zzVb;XuVmIonj;^j|&H1}=Mtp4AKkj80 zdB4^N&Di<rq|NUcXO-_xS@pwi)u~gD?(282zG{;E-j;RqU;SIRr^Z;zX^2O1*KfY0 zmvhfwCQ(`4=f}#YMnx0i^}0i+>aB{taQ*1gh`XUPByOwSX;pPvvp9W$#P<7}A1l1E zUv^xP`O1gCzs_%R;ZU1t_s^lXWW_WUt@YDxJe;&`YwPFVZ`R25I%Xd0l{<2&o@cQ% zqqRZ6<cbeLUR()bdyf3nxwLy(cc#P}rm$RoS>>xwj)uN0dYC>nukvbF+{yJK6N}Q9 z@LrW&uC2dp`nd;;VvOO_pFUu;H`BkkM58U_F3(Mq0?Q?SciCEvDnb^C?B8{#`9l1( z-xGeG-Zb$<yi?DS&Rw~GZ+~8VcaOye_J6t4qaHHKF%=z}UiFYsfpPov*$)|Ks{Z+S z`{e0#|9G2uduqS`xVw4#;mH*zKlAt9_LkWt=~!M9@!(zct?7}E7!~Tz2%h8A31XRE zlKw)XY>Iu<gTv2$dJ4I%KIJy)-098}m7VfO3#UiCckm0I^&`*k&qoH1*D|uVc3Qfd z&EBx0@$M6$YqR!Pf4jyKUi6=BYQp^;)82nybu`Lzk^lX-p_8LhV@_}X{%uuj_yUC| z_rCwW&iHwX*3Rz_c1v#vUlZ$i{=vQaI_K1NIgVE!)V<!f&{5-1P~_P(?eOIrnXf#% zYGJa=W&7{avv+U$J<DVHY8Jlnzy#s*TLRv&2^`7oT=MC*R<1zo^BcAjytb_OnVhz# z?&{s8eBr`s{rH~ciH(bEcSsz%zAC9vdCSY6iL)%HKC%6_ROEZ#k-aY@91}mye{?tD z218Q)#TirB4)d*g=$I+Gxoq9r+G=eR|Md-xm0ME3u^DNoBptr+*z)I$W9fSr%WD7r z9Po$rv%^(&*OcVcjTdxX?|HUPy0GG~+_|t&$BCT%b5^mX=FHy2GXKGZgq78dCC`{o zYyEl3-92~Nb>3qK4)oMpH2kl=qP4wYv*P(BW<R$(I|SFq$^VmVoL{)k*Mj+x-RC*R z6IWkdd9$4J^R`<7!fi^7ycV;LemD@~WahVcoAj!O*57~cGfC}fd9{T5@79P~hj{;s zJWTefCl4N2d8=$8Z&+XCwlI6;=l2|#=bRT7o*U#RD`W6rde7GGwWUwPQjIqJ++^w? zwXH=zZqIIx-#o$n@%8@KGwdHd+?o9N-{kFA7auGv6&E@EFd@YH28+oiv+XMrcWw+k z!rYM)@N>HU>$5BN=;{>yc9_lhKbooefZuMf@`k*}-^HcQJ+|J}{-W5{_K?59{kf}l zGqNNGDBdl(7olYAv9@vF#OW{eS68;{tyH?6#PgVS?t)MD2j^$(T$uj9t=_Qdrh37j z8wFeMGuJ&kU)PXXFyXoh-|dWG&k4K-=Zb7Q7FiZ4bG-cH{N3|6eRs(I|NivniU`M~ zU1#1KecT`_dVRk7pH2VsYTOS`xZByk`Vq5|=ADi6#r-ZuS32Eq`%;nVA-l7B-u&Ks z%TvGI{rlu>!oTO&{hy2X|3Cg%UhZ-Iy!slc<8Pnc5q;eAwn@C>g0hfc+K0D~?f+KX z-&I=m#UM1?b3!oF{Fsodn`G|o?T(me>k_8@ao?)>;^pThyO+B^yPH!s-J_{xx1AW* z;zh3dVh0Xr$5r*z6)}IF{N#Y%j4AmE|Cz4s*Lw9p<E&sz;j@!W#q~coiL2k@O|$Y3 zpDvtOAIGYj%2@R)eD~G6MX4DKGd!GMh{Wq;O*b^!sXXOQkIb<Kmmj)5_cy<BKNP9t zAaf`v(#iWp<1?wYbV)|pKdSo0ZofBv@GEIc=l0t2=GhFn4gS~5)idt1A7slZfBT!= z*eh-8-p*b7mlQiSS04GggF%Cr*Ixfmb}RD=rpx6S-|IiVGCHJWy`_i$p|fuCuRh&m zLH;jK*-l$-VavO}BInFqtJC=jf3n}UFLca4_2%PFv(395wxx@3FEv!wKeub%YQuG` zOMZ65H!eT1zwh8_l}lTN@64TPxM0`PWR_Lcljqv+j%)fcHDFiw>Yp<G9`XB@)K1vB zz`xA=t<TC`mAAv1U)Bq^)p>`wO<tdz6D-$OqU)D-@OB|ja>s(x2G80WS-!FP9XG#Q zX63Ioqblr`(c*B=GNlVOb$vWvb@nz&?zC35-X=Ng=Hr0!kh1(d_RaU3a?eb>cUHYZ zOxuTJj^VN|59Eqx?w+wK#N(M+=%fD~zoWH9ownyLH7VhpY9`+mRH#w!mH#?GWXkEt zN!J{NB#!hsE_=i0*l^ys>_QfAufxu;r`!Pvo8uV5(xt!6Rn&`lvvcm$g>S#^mYTHs zfl1Dj{W=?p?}g2qZZFET;hA9iO-IWgngNR=ANn`G%6l@qU-|F<<nHO*!G;?vGvjn; zT*`g@XSvzb+|{d|9#!l=HDlH8+@JNgbP|db6F%J*GB_jnB`;51LC<d4<@IkrpJJ1` zoE^F-dIIyBFR@>nOV$56EGRM#bn`H|oZ{eZe74UyqweCyKW9RiCmb&_6xB#++N0sI zsX18e4db(kTig%+OW?aXS>uNFnZ)nfSuYHqU90WQvS`@Zv(G&`;!w3y!#|IoyjT03 z>Mi;m7rWUAzS#Tyo$h*N7N1VGjz_P5KaBWLWYw~}$S&>2{pgOh;*YxK9_3ZoFTEfp zWBcS6k!&hn2D2S56fJnpvS?0G!EK>$*N<l0=C0Ly@hQ#!ahcEL#-i^JX5_43y?$m* z-khCo`)0D*E@N%}^X{Obx>?DEsRn%2FI#0lh3vRqZ(H-}&5W1k1*bPxf8A>Pxv3}A za9erez5bU?3Ig9B%<ZYDXI|$yX}{e5ra4AV6W$-v?%KCofBTBF($@7_hw792GCOvK zWz3$a&9GnV!!q&LyG4Jb?U-+*^=)m*=T}x$d{ei+S<1&L_hyN(;h)vaJ(HYje?K>f ze$=)nzS@1Bc&<kMBJ)G$%dM{_9XD~$kK3vieeJ=q`+eJ!XQ{p0vF*6c*&{#Ki0v@R zX3%R*D^%L9p)UGQM`h;CyPif>GG~O#BqlAqc{DpEly!sY>id>EZL25sNPlX-z0G+A zH`g4mSgS2LqIz*zMLpS{u3C7lT=0BR>|&*t$-lSOGR#et`T20kt2y;dygHX6I1j6A zD@aIQ`Xy*bK=5quq@}GY8#kVP*A%we#v^C@#K6VHlN=L@8>*x)USnEwS3{|J*E?R# zDL&b1{+r8m|EC<<<E!V;*HU$Zt8n&`StXoXyN@hCd!)57D_H6L&3WGz`f)9??F`;r zs{23XlG7pk2eZVLtqhLMQI4<JQor)*WG(fdVe>6DS)ilf%b(ha-};w#gSq-{-rIKF zfZ5f_&s*(oa)zD1!~3;u?<Jx1-R6shE}z|d@#lq&fqw<|e&DiPcJbA=GYi-BCeGd7 z{d14$F|N5Od9l1K=LC<;dcVt6ZS6jTGrh^1&K!+)%)8|tf5-Gx(o@&SEza7n>i31O zRd#y*a*xIKo~v`N6ihuTc`g0ILb+9JGI`x+TkhATH}D7S6LvcKOk!$sS*W$B+0ien zw9I5wMRx^=u0CIJBqH=_)KQ1&hOF-YUrcN>wvozQ?vi(gfAcE`Q-xojPAn_?y#9%& z<iRa{2_M~MV_(*~#JpC!Cf<1T14H5IUD@6B+=qIsBr=Uxc5*Xvr{6kwWQBMwJ8!xB zs_gWirDm6Za4AcEEOVK_(|qw1>$HZ0I?q1$ZreJm<-(3r7ynEN7q>03KOSt#x;G+? z+wOO<7|;2>>5a#3{JCfKqF8F~8~t}%dz!ePGBp+`OK{{(k^j!V_)(HH?+VVRt{-1o zo}ZvDG_`(a2Dk6L6J8-#IzPDtdhT2Lg1?<-!uF0SN}CkRR!Sbdo_DBkT569||8b63 z!wiQn{wq#RPF-8c@wT#c>(e8-H-Bz=zcAWo*?OUr3U7yp8Qu;LeIIYUC-W{g>aN(a zh>)2-RQdTF-pvmY>9XMTUHbOp=08SrBirikZC;*rRBhs_+x3e$ii=mQ-L*dVQS;0@ z=g)AJ%-9g{wIn=t*XrWyY?cgD@;pP2#9dubQo#JeX6^BLPS%olj~v{uvF_Ps`Ck6r zlOCO8R8TYEz8Ch==C<GI<+?o+&*e92q&FU#%dnhxe`OKxl*voiDf(_axB7aY`SVxu zGVOs=>+fH0T5(?2W_G$nK)v%m*M}~TJL3~}wm9wkb^eXby*A~eTwhO|<;~i&evNJI z-%lUA!}HbaYd^mG`Q+;Blb^-&@9(YqJio27wfxvS34ey!M`XVy&Nb3eoaJ0n;FkaB zmD19^3TscK<r|rBUO)KWG<&tyJuBM>nMUlEci(tC$TIb>@%ZvCKza7ep9Uqp_2yr5 zRDbPg6v=OEi?R!d34Qb4WS^y$_ttl+<>`|5XMWgJzUa6jTbZn-`(0f{^#=XmfW{vO z-}h<lsBYAMbi2-feR0b#?JbWP9oe15Ixkso_?n>g*v0<lmy5MWnAms!-z)Aj{XyN! ziQ#oW`3ldiSpDg#+b{J$@54lY@1G#|_U?oFf<Fq%f;VM;C&qH@o6hjMD{kkRO)py? z-*&sR_sq+WZQ_4bMLbPo&J`Z~oV_)9`LF9i9!q%S_Q%bC+Fi1aeO^Roywa%!zux!> zbp3s&wra=ci~s-L|9^a+_O1Btwb3c&p=tTM_FDC9-uu64R_9N*S-VuO3gz<%^=~y_ zDR-=T>b`>dY?ZIq=Gf*=S$)gw&8mpfWhd0k*GF94oM_rpA6$R#(#vZV+w%V^$u15G zIpTY1_r^+vkEdsCTXu`hr}(bZ_cy1fyFRYt^s$&w=G^QOT&ZyHL@w_~27_ZRwlg2@ zGUYt}Z<<|5-~4HA7j_6raU{P`Ovr8c`|s}eAM@&ySQ0OtV{xok_j+lxa`RU&i#5tT z7HJyUHX8p;WA&cTx85rkzxex?qgCZk>K^YdSDt(Ok?z-5e_1t-+;dks;y-2XyH&ok zzkjJ+@sKz8N5iXg!O}8UA0I5=k;ule*eCXOrT+Ytx;%~=N$JtAJLme$H1Ke5s&&13 z{Nb7@6PsLi{#*Iq$jw0E;NAM0iC%dVH~u$%R>>GY^^e)|*;_p=iu>bF3MJ_{CBKuL zcJ9j~`I9%;zEAyoxu`AT^n^y4#DWS=X(Jhhtyb67;$pKqu1%Q7`26bBW-E?M4{v6% z)^KNwZL8<_<lC}yEx)PuUXK>3MNP)mRvmW>Zj`6Azr81SZg$=$>tmtjx$L(e*RMJ~ z@BW&!6-QUj(mKlU+;pOE=#=cxb1kLIO{&`^uGRa6ODtXSvnTl3<~OywoL%-#OSH7~ z)%yP<#z*JWyqrlg5889D3Cp+HX+GXp?P;({dD9AJmV41XRnt@7uX0&B^KepNL+*+< zf7|zP)$Gvu_i$;RgVNzG`p+D`-dgh1?!u+E`hVY#awXfH+A#g}>lHfB3MT!JH9PP> zZ_E6<w>aln-mqXQd;8YcS@KT6gUTGYd)Ev@`oA7#VP{IcHPvJNk%eC;B~F{WH2kyI z*Xi=cJs%Xx_8qFow7r<~&GujT)|xGnJMX@_Tr%<On(XQeHwv8p2F_o#$a(XvgtMJD za+jUZSwFk5yS~9KG~+~`>^|9VVO<RR-{fX6FsePg(Q)}}?U$?kyi-=3=t)1-z*su> z6?ZMW6#w%H7o${{-gKY!a%q*hLC3y)+1Svx*~gj^b6Il>ZMznAZTJ3RVN|74$gdNB z+@QqdO_+S4#f6ZB+<<P8Cok27cg=eG$~(fll;MC_VVl3+y)X66lXkqyl#kg}F08?2 z_4SF%KV2!aWn34yOARMY>*eve91zqwbKV(O4(Yq6W~}%0y3L{}&KPCJW1F~BN4E5U z)z#BKrR=M`OS>NLJ}O-H^X|I{orC8m9NXl)<X6G<T&okm*w#&5ExM0!X~61eD{U?o z=GWK0M2IZpT+SojIIsG8bG>zsh<@49^6vS%Y<j!Tot(hJd}E!tz3B6cuUD^HD7|d) zwrn9GBjssUb(jC@KF<1E6B=i6?$IWr`|~3oo8CSj8KHNyf5WC1qHaG~*G4_qdgYgy z^siM1(wi@>Q9gR>W98mBr%g|nWgT-W6TKq!{@d*rbAm1@9h)$*cC*Oc1G*c(-mO>M znE1j+Xh}@JyshgD?JH9jmuu|&yT@yV`Jr5M?}VPL=+tjq2dY|`CpFXv?Q<^(5#AK1 z-g52oJ42QaTeYtg$FXTRYO23X&|I}bz&^j7jptnigR^ErtGDa7iSr5tMAv-O)_QXO z_QEEnO|kLkYK5MNh==SEwl`IDJN5QLQyoYB4<XsGa{g!H^S0G)GMKFJlJ&3Viih%V zId6WS*;IIA=gX6mxBcK|`BW_P{p-E;bH1h4lw1qB!<4G=`CITarWZEqb*Cq*-PgLn z);sTJ!GR~SQ6YaSwtCs;e_#FK_0y<H3ZkJO^Yb4zbe<DAb}lfz@cu1Lkyp>$5@pV- zvgwzd7X4eld)|fDvnzdU?wC|a`58RB+*8Y}8McapsXBFu#PxnD(`m;K@yoEKvs$RA zEU?+5-^G8py7R+)i8^lMw3;HVk2w|3zS^hE_b$<IdHPLfZd}Qla(VTen%Oq(?cA?Y z*(dLso!9iSBh0O06&LUIN1S!>bHAx`&#)-i)4yqnguc_u_(`+=eAr-d&hO5q<$K>8 z(Ji++(_ig=_|cDy2*c8Tu|MnmDm8)?9y6RY6~A<0f5fu`(Q|J++10W79e+U6_Zw{? z8`&6t+z@1#bEsBk$3KOW$0gkw>U3i3t)DqAu#S?Elbde(h0$1ib6;7a*rwK?rFC8R zzQh{#+?wrh$Zt;b^!hK1GK}Wa=X_z5^m=9cj^+JIFK_m@JM1nR73@@xJ2v$xr|q44 zudY4{yJfpU`q~Wz3y;GKvR$e|V?DH<B^gM+trz>3z|a43n&O13uE)7{82n;(6?<tp z{r?w6`TFaj+kVMh{NJB^__pG&fAJUYDn9dfC}I^}d*m`({@t1*1=^ST8-p*fSFYTg z&!l=putQ=&`)Y%}OZG{hw7uSbIIePN^}g+E771*7rKRmSY1#($uPzC<8xLJN%zpB; zPsID9PW3bOc(?40GqcV=;g%!H6?o*xBg0VPRleGecHN7b>bH7J+?%w<Cc^W;q)CTg z{?wU!{^C2;Nt$;I40-Oa6aCzOLhtgaq!%VF-U@bKuT18N{G)uh<)R?lmTe_|b*Go^ za7yys?s`0f!Q+TmdYYlsr(cIxM4P96cZivJYwoVS+uqK4TBGt#+JDQ}kUN2{d?%V` zzdEJ3H(aZ=**B@@V_%*?{q53($D8i|NU>1J)_cEXi&=2um9AA!=Dd6N@Nhw|zWmRJ zH8xB3pY)nJBl`D*`U~t;KlX5}VmWgq`Q=Ja=05&)V*W4vUx%Hze~6dMnnixyEuI~n zU8gj=*Vs;+H(`_1<?cl9>J@!I7YAN;4$PmywPN`y2kvd&-IiV7y83T+yT<LU*Ez3x z^wBh)YnSv_|BU`(a7AmE;)HBl8@>xil|SD$(bk`~EARKL*=vk^r8jInYc}KMvnSbS zjqb#6R4jb^d=rbab2PhKvWy~c|F3yp9>w;h%&6G(@LlN>&%^VrR)}ssT;UYfGO4~} zLfJ0K=&HB6Cmyln%S{dA)1D|LyXf7noil6dg(n@-*x|XO==YcP)pwrlh+5$p{^g=z zn0(;;RaIt-7ACBfT3ssl>&?$V&i8+J6&lJciuv;8P=ka1nFOVG3$BYX+D>0lW4M)R z`v=CY64mC9XXzbZJfmOtP3p&*sRw7a%dtMaynFiQJ>vH-9({l0Z}+PrId#`Zq4Ij` nK8eTH$jf|_Tp7N%VEVt`N#7)=D_;5d;y)ut&-2wgnK>8$S+%z{ 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 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 Me2zbMst5xE0FRt=8UO$Q literal 2329 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 Q-1i?l_v?j$Q$-jU03bqoPXGV_ 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)