diff --git a/.coveragerc b/.coveragerc index 7cfcacaaa2d..d97befce096 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,6 +2,8 @@ source = homeassistant omit = + homeassistant/__main__.py + homeassistant/external/* # omit pieces of code that rely on external devices being present @@ -11,29 +13,37 @@ omit = homeassistant/components/zwave.py homeassistant/components/*/zwave.py + homeassistant/components/modbus.py + homeassistant/components/*/modbus.py + + homeassistant/components/isy994.py + homeassistant/components/*/isy994.py + homeassistant/components/*/tellstick.py homeassistant/components/*/vera.py - homeassistant/components/keyboard.py - homeassistant/components/switch/wemo.py - homeassistant/components/thermostat/nest.py - homeassistant/components/light/hue.py - homeassistant/components/sensor/systemmonitor.py - homeassistant/components/sensor/sabnzbd.py - homeassistant/components/notify/pushbullet.py - homeassistant/components/notify/pushover.py - homeassistant/components/media_player/cast.py + homeassistant/components/browser.py + homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/luci.py - homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py - homeassistant/components/device_tracker/ddwrt.py + homeassistant/components/device_tracker/tomato.py + homeassistant/components/keyboard.py + homeassistant/components/light/hue.py + homeassistant/components/media_player/cast.py + homeassistant/components/notify/instapush.py + homeassistant/components/notify/nma.py + homeassistant/components/notify/pushbullet.py + homeassistant/components/notify/pushover.py + homeassistant/components/notify/xmpp.py + homeassistant/components/sensor/mysensors.py + homeassistant/components/sensor/openweathermap.py + homeassistant/components/sensor/sabnzbd.py + homeassistant/components/sensor/systemmonitor.py + homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py - - homeassistant/components/isy994.py - homeassistant/components/light/isy994.py - homeassistant/components/switch/isy994.py - homeassistant/components/sensor/isy994.py + homeassistant/components/switch/wemo.py + homeassistant/components/thermostat/nest.py [report] diff --git a/.gitmodules b/.gitmodules index ae38be7c61b..ca0b1f024b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "homeassistant/external/nzbclients"] path = homeassistant/external/nzbclients url = https://github.com/jamespcole/home-assistant-nzb-clients.git +[submodule "homeassistant/external/pymysensors"] + path = homeassistant/external/pymysensors + url = https://github.com/theolind/pymysensors diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example index 125be6e2d05..524078b7765 100644 --- a/config/configuration.yaml.example +++ b/config/configuration.yaml.example @@ -28,7 +28,8 @@ wink: access_token: 'YOUR_TOKEN' device_tracker: - # The following types are available: netgear, tomato, luci, nmap_tracker + # The following types are available: ddwrt, netgear, tomato, luci, + # and nmap_tracker platform: netgear host: 192.168.1.1 username: admin @@ -65,7 +66,7 @@ device_sun_light_trigger: # Optional: disable lights being turned off when everybody leaves the house # disable_turn_off: 1 -# A comma seperated list of states that have to be tracked as a single group +# A comma separated list of states that have to be tracked as a single group # Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME) group: living_room: diff --git a/config/custom_components/example.py b/config/custom_components/example.py index a972e3ab576..dc18aae4b98 100644 --- a/config/custom_components/example.py +++ b/config/custom_components/example.py @@ -22,7 +22,7 @@ DOMAIN = "example" # List of component names (string) your component depends upon # We depend on group because group will be loaded after all the components that -# initalize devices have been setup. +# initialize devices have been setup. DEPENDENCIES = ['group'] # Configuration key for the entity id we are targetting @@ -115,5 +115,5 @@ def setup(hass, config): # Register our service with HASS. hass.services.register(DOMAIN, SERVICE_FLASH, flash_service) - # Tells the bootstrapper that the component was succesfully initialized + # Tells the bootstrapper that the component was successfully initialized return True diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 05bbb9b40ff..132482d4bfa 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -12,11 +12,8 @@ import logging import threading import enum import re -import datetime as dt import functools as ft -import requests - from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, @@ -24,6 +21,7 @@ from homeassistant.const import ( EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) import homeassistant.util as util +import homeassistant.util.dt as date_util DOMAIN = "homeassistant" @@ -109,7 +107,20 @@ class HomeAssistant(object): def track_point_in_time(self, action, point_in_time): """ - Adds a listener that fires once at or after a spefic point in time. + Adds a listener that fires once after a spefic point in time. + """ + utc_point_in_time = date_util.as_utc(point_in_time) + + @ft.wraps(action) + def utc_converter(utc_now): + """ Converts passed in UTC now to local now. """ + action(date_util.as_local(utc_now)) + + self.track_point_in_utc_time(utc_converter, utc_point_in_time) + + def track_point_in_utc_time(self, action, point_in_time): + """ + Adds a listener that fires once after a specific point in UTC time. """ @ft.wraps(action) @@ -135,11 +146,19 @@ class HomeAssistant(object): self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener) return point_in_time_listener + # pylint: disable=too-many-arguments + def track_utc_time_change(self, action, + year=None, month=None, day=None, + hour=None, minute=None, second=None): + """ Adds a listener that will fire if time matches a pattern. """ + self.track_time_change( + action, year, month, day, hour, minute, second, utc=True) + # pylint: disable=too-many-arguments def track_time_change(self, action, year=None, month=None, day=None, - hour=None, minute=None, second=None): - """ Adds a listener that will fire if time matches a pattern. """ + hour=None, minute=None, second=None, utc=False): + """ Adds a listener that will fire if UTC time matches a pattern. """ # We do not have to wrap the function with time pattern matching logic # if no pattern given @@ -155,6 +174,9 @@ class HomeAssistant(object): """ Listens for matching time_changed events. """ now = event.data[ATTR_NOW] + if not utc: + now = date_util.as_local(now) + mat = _matcher if mat(now.year, year) and \ @@ -305,7 +327,7 @@ def create_worker_pool(): for start, job in current_jobs: _LOGGER.warning("WorkerPool:Current job from %s: %s", - util.datetime_to_str(start), job) + date_util.datetime_to_local_str(start), job) return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback) @@ -333,7 +355,7 @@ class Event(object): self.data = data or {} self.origin = origin self.time_fired = util.strip_microseconds( - time_fired or dt.datetime.now()) + time_fired or date_util.utcnow()) def as_dict(self): """ Returns a dict representation of this Event. """ @@ -341,7 +363,7 @@ class Event(object): 'event_type': self.event_type, 'data': dict(self.data), 'origin': str(self.origin), - 'time_fired': util.datetime_to_str(self.time_fired), + 'time_fired': date_util.datetime_to_str(self.time_fired), } def __repr__(self): @@ -354,6 +376,13 @@ class Event(object): return "".format(self.event_type, str(self.origin)[0]) + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.event_type == other.event_type and + self.data == other.data and + self.origin == other.origin and + self.time_fired == other.time_fired) + class EventBus(object): """ Class that allows different components to communicate via services @@ -376,6 +405,9 @@ class EventBus(object): def fire(self, event_type, event_data=None, origin=EventOrigin.local): """ Fire an event. """ + if not self._pool.running: + raise HomeAssistantError('Home Assistant has shut down.') + with self._lock: # Copy the list of the current listeners because some listeners # remove themselves as a listener while being executed which @@ -474,13 +506,14 @@ class State(object): self.entity_id = entity_id.lower() self.state = state self.attributes = attributes or {} - self.last_updated = last_updated or dt.datetime.now() + self.last_updated = date_util.strip_microseconds( + last_updated or date_util.utcnow()) # Strip microsecond from last_changed else we cannot guarantee # state == State.from_dict(state.as_dict()) # This behavior occurs because to_dict uses datetime_to_str # which does not preserve microseconds - self.last_changed = util.strip_microseconds( + self.last_changed = date_util.strip_microseconds( last_changed or self.last_updated) @property @@ -512,8 +545,8 @@ class State(object): return {'entity_id': self.entity_id, 'state': self.state, 'attributes': self.attributes, - 'last_changed': util.datetime_to_str(self.last_changed), - 'last_updated': util.datetime_to_str(self.last_updated)} + 'last_changed': date_util.datetime_to_str(self.last_changed), + 'last_updated': date_util.datetime_to_str(self.last_updated)} @classmethod def from_dict(cls, json_dict): @@ -528,12 +561,12 @@ class State(object): last_changed = json_dict.get('last_changed') if last_changed: - last_changed = util.str_to_datetime(last_changed) + last_changed = date_util.str_to_datetime(last_changed) last_updated = json_dict.get('last_updated') if last_updated: - last_updated = util.str_to_datetime(last_updated) + last_updated = date_util.str_to_datetime(last_updated) return cls(json_dict['entity_id'], json_dict['state'], json_dict.get('attributes'), last_changed, last_updated) @@ -550,7 +583,7 @@ class State(object): return "".format( self.entity_id, self.state, attr, - util.datetime_to_str(self.last_changed)) + date_util.datetime_to_local_str(self.last_changed)) class StateMachine(object): @@ -587,7 +620,7 @@ class StateMachine(object): """ Returns all states that have been changed since point_in_time. """ - point_in_time = util.strip_microseconds(point_in_time) + point_in_time = date_util.strip_microseconds(point_in_time) with self._lock: return [state for state in self._states.values() @@ -849,7 +882,7 @@ class Timer(threading.Thread): last_fired_on_second = -1 - calc_now = dt.datetime.now + calc_now = date_util.utcnow interval = self.interval while not self._stop_event.isSet(): @@ -875,7 +908,13 @@ class Timer(threading.Thread): last_fired_on_second = now.second - self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}) + # Event might have been set while sleeping + if not self._stop_event.isSet(): + try: + self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}) + except HomeAssistantError: + # HA raises error if firing event after it has shut down + break class Config(object): @@ -898,44 +937,9 @@ class Config(object): # Directory that holds the configuration self.config_dir = os.path.join(os.getcwd(), 'config') - def auto_detect(self): - """ Will attempt to detect config of Home Assistant. """ - # Only detect if location or temp unit missing - if None not in (self.latitude, self.longitude, self.temperature_unit): - return - - _LOGGER.info('Auto detecting location and temperature unit') - - try: - info = requests.get( - 'https://freegeoip.net/json/', timeout=5).json() - except requests.RequestException: - return - - if self.latitude is None and self.longitude is None: - self.latitude = info['latitude'] - self.longitude = info['longitude'] - - if self.temperature_unit is None: - # From Wikipedia: - # Fahrenheit is used in the Bahamas, Belize, the Cayman Islands, - # Palau, and the United States and associated territories of - # American Samoa and the U.S. Virgin Islands - if info['country_code'] in ('BS', 'BZ', 'KY', 'PW', - 'US', 'AS', 'VI'): - self.temperature_unit = TEMP_FAHRENHEIT - else: - self.temperature_unit = TEMP_CELCIUS - - if self.location_name is None: - self.location_name = info['city'] - - if self.time_zone is None: - self.time_zone = info['time_zone'] - - def path(self, path): + def path(self, *path): """ Returns path to the file within the config dir. """ - return os.path.join(self.config_dir, path) + return os.path.join(self.config_dir, *path) def temperature(self, value, unit): """ Converts temperature to user preferred unit if set. """ @@ -955,6 +959,17 @@ class Config(object): # Could not convert value to float return value, unit + def as_dict(self): + """ Converts config to a dictionary. """ + return { + 'latitude': self.latitude, + 'longitude': self.longitude, + 'temperature_unit': self.temperature_unit, + 'location_name': self.location_name, + 'time_zone': self.time_zone.zone, + 'components': self.components, + } + class HomeAssistantError(Exception): """ General Home Assistant exception occured. """ diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index c5e775bd9f9..83a8c499718 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -7,6 +7,14 @@ import argparse import importlib +# Home Assistant dependencies, mapped module -> package name +DEPENDENCIES = { + 'requests': 'requests', + 'yaml': 'pyyaml', + 'pytz': 'pytz', +} + + def validate_python(): """ Validate we're running the right Python version. """ major, minor = sys.version_info[:2] @@ -20,13 +28,13 @@ def validate_dependencies(): """ Validate all dependencies that HA uses. """ import_fail = False - for module in ['requests']: + for module, name in DEPENDENCIES.items(): try: importlib.import_module(module) except ImportError: import_fail = True print( - 'Fatal Error: Unable to find dependency {}'.format(module)) + 'Fatal Error: Unable to find dependency {}'.format(name)) if import_fail: print(("Install dependencies by running: " @@ -72,24 +80,13 @@ def ensure_config_path(config_dir): 'directory {} ').format(config_dir)) sys.exit() - # Try to use yaml configuration first - config_path = os.path.join(config_dir, 'configuration.yaml') - if not os.path.isfile(config_path): - config_path = os.path.join(config_dir, 'home-assistant.conf') + import homeassistant.config as config_util - # Ensure a config file exists to make first time usage easier - if not os.path.isfile(config_path): - config_path = os.path.join(config_dir, 'configuration.yaml') - try: - with open(config_path, 'w') as conf: - conf.write("frontend:\n\n") - conf.write("discovery:\n\n") - conf.write("history:\n\n") - conf.write("logbook:\n\n") - except IOError: - print(('Fatal Error: No configuration file found and unable ' - 'to write a default one to {}').format(config_path)) - sys.exit() + config_path = config_util.ensure_config_exists(config_dir) + + if config_path is None: + print('Error getting configuration path') + sys.exit() return config_path diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index ecd5be36dee..787e0f80562 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -10,25 +10,27 @@ start by calling homeassistant.start_home_assistant(bus) """ import os -import configparser -import yaml -import io import logging from collections import defaultdict import homeassistant +import homeassistant.util as util +import homeassistant.util.dt as date_util +import homeassistant.config as config_util import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group from homeassistant.helpers.entity import Entity from homeassistant.const import ( EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, - CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_VISIBILITY, + CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_CUSTOMIZE, TEMP_CELCIUS, TEMP_FAHRENHEIT) _LOGGER = logging.getLogger(__name__) -ATTR_COMPONENT = "component" +ATTR_COMPONENT = 'component' + +PLATFORM_FORMAT = '{}.{}' def setup_component(hass, domain, config=None): @@ -67,7 +69,7 @@ def _setup_component(hass, domain, config): if missing_deps: _LOGGER.error( - "Not initializing %s because not all dependencies loaded: %s", + 'Not initializing %s because not all dependencies loaded: %s', domain, ", ".join(missing_deps)) return False @@ -87,14 +89,42 @@ def _setup_component(hass, domain, config): return True else: - _LOGGER.error("component %s failed to initialize", domain) + _LOGGER.error('component %s failed to initialize', domain) except Exception: # pylint: disable=broad-except - _LOGGER.exception("Error during setup of component %s", domain) + _LOGGER.exception('Error during setup of component %s', domain) return False +def prepare_setup_platform(hass, config, domain, platform_name): + """ Loads a platform and makes sure dependencies are setup. """ + _ensure_loader_prepared(hass) + + platform_path = PLATFORM_FORMAT.format(domain, platform_name) + + platform = loader.get_component(platform_path) + + # Not found + if platform is None: + return None + + # Already loaded or no dependencies + elif (platform_path in hass.config.components or + not hasattr(platform, 'DEPENDENCIES')): + return platform + + # Load dependencies + for component in platform.DEPENDENCIES: + if not setup_component(hass, component, config): + _LOGGER.error( + 'Unable to prepare setup for platform %s because dependency ' + '%s could not be initialized', platform_path, component) + return None + + return platform + + # pylint: disable=too-many-branches, too-many-statements def from_config_dict(config, hass=None): """ @@ -122,12 +152,12 @@ def from_config_dict(config, hass=None): if ' ' not in key and key != homeassistant.DOMAIN) if not core_components.setup(hass, config): - _LOGGER.error("Home Assistant core failed to initialize. " - "Further initialization aborted.") + _LOGGER.error('Home Assistant core failed to initialize. ' + 'Further initialization aborted.') return hass - _LOGGER.info("Home Assistant core initialized") + _LOGGER.info('Home Assistant core initialized') # Setup the components for domain in loader.load_order_components(components): @@ -148,26 +178,7 @@ def from_config_file(config_path, hass=None): # Set config dir to directory holding config file hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) - config_dict = {} - # check config file type - if os.path.splitext(config_path)[1] == '.yaml': - # Read yaml - config_dict = yaml.load(io.open(config_path, 'r')) - - # If YAML file was empty - if config_dict is None: - config_dict = {} - - else: - # Read config - config = configparser.ConfigParser() - config.read(config_path) - - for section in config.sections(): - config_dict[section] = {} - - for key, val in config.items(section): - config_dict[section][key] = val + config_dict = config_util.load_config_file(config_path) return from_config_dict(config_dict, hass) @@ -177,7 +188,7 @@ def enable_logging(hass): logging.basicConfig(level=logging.INFO) # Log errors to a file if we have write access to file or config dir - err_log_path = hass.config.path("home-assistant.log") + err_log_path = hass.config.path('home-assistant.log') err_path_exists = os.path.isfile(err_log_path) # Check if we can write to the error log if it exists or that @@ -196,30 +207,73 @@ def enable_logging(hass): else: _LOGGER.error( - "Unable to setup error log %s (access denied)", err_log_path) + 'Unable to setup error log %s (access denied)', err_log_path) def process_ha_core_config(hass, config): """ Processes the [homeassistant] section from the config. """ + hac = hass.config + + def set_time_zone(time_zone_str): + """ Helper method to set time zone in HA. """ + if time_zone_str is None: + return + + time_zone = date_util.get_time_zone(time_zone_str) + + if time_zone: + hac.time_zone = time_zone + date_util.set_default_time_zone(time_zone) + else: + _LOGGER.error('Received invalid time zone %s', time_zone_str) + for key, attr in ((CONF_LATITUDE, 'latitude'), (CONF_LONGITUDE, 'longitude'), - (CONF_NAME, 'location_name'), - (CONF_TIME_ZONE, 'time_zone')): + (CONF_NAME, 'location_name')): if key in config: - setattr(hass.config, attr, config[key]) + setattr(hac, attr, config[key]) - for entity_id, hidden in config.get(CONF_VISIBILITY, {}).items(): - Entity.overwrite_hidden(entity_id, hidden == 'hide') + set_time_zone(config.get(CONF_TIME_ZONE)) + + for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items(): + Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values()) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] if unit == 'C': - hass.config.temperature_unit = TEMP_CELCIUS + hac.temperature_unit = TEMP_CELCIUS elif unit == 'F': - hass.config.temperature_unit = TEMP_FAHRENHEIT + hac.temperature_unit = TEMP_FAHRENHEIT - hass.config.auto_detect() + # If we miss some of the needed values, auto detect them + if None not in ( + hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone): + return + + _LOGGER.info('Auto detecting location and temperature unit') + + info = util.detect_location_info() + + if info is None: + _LOGGER.error('Could not detect location information') + return + + if hac.latitude is None and hac.longitude is None: + hac.latitude = info.latitude + hac.longitude = info.longitude + + if hac.temperature_unit is None: + if info.use_fahrenheit: + hac.temperature_unit = TEMP_FAHRENHEIT + else: + hac.temperature_unit = TEMP_CELCIUS + + if hac.location_name is None: + hac.location_name = info.city + + if hac.time_zone is None: + set_time_zone(info.time_zone) def _ensure_loader_prepared(hass): diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index b5cdb9cae6c..b0d6faa2f49 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -15,6 +15,7 @@ import homeassistant.remote as rem from homeassistant.const import ( URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM, URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS, + URL_API_CONFIG, URL_API_BOOTSTRAP, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL, HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_UNPROCESSABLE_ENTITY) @@ -42,6 +43,13 @@ def setup(hass, config): # /api/stream hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream) + # /api/config + hass.http.register_path('GET', URL_API_CONFIG, _handle_get_api_config) + + # /api/bootstrap + hass.http.register_path( + 'GET', URL_API_BOOTSTRAP, _handle_get_api_bootstrap) + # /states hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states) hass.http.register_path( @@ -140,6 +148,23 @@ def _handle_get_api_stream(handler, path_match, data): hass.bus.remove_listener(MATCH_ALL, forward_events) +def _handle_get_api_config(handler, path_match, data): + """ Returns the Home Assistant config. """ + handler.write_json(handler.server.hass.config.as_dict()) + + +def _handle_get_api_bootstrap(handler, path_match, data): + """ Returns all data needed to bootstrap Home Assistant. """ + hass = handler.server.hass + + handler.write_json({ + 'config': hass.config.as_dict(), + 'states': hass.states.all(), + 'events': _events_json(hass), + 'services': _services_json(hass), + }) + + def _handle_get_api_states(handler, path_match, data): """ Returns a dict containing all entity ids and their state. """ handler.write_json(handler.server.hass.states.all()) @@ -190,9 +215,7 @@ def _handle_post_state_entity(handler, path_match, data): def _handle_get_api_events(handler, path_match, data): """ Handles getting overview of event listeners. """ - handler.write_json([{"event": key, "listener_count": value} - for key, value - in handler.server.hass.bus.listeners.items()]) + handler.write_json(_events_json(handler.server.hass)) def _handle_api_post_events_event(handler, path_match, event_data): @@ -227,10 +250,7 @@ def _handle_api_post_events_event(handler, path_match, event_data): def _handle_get_api_services(handler, path_match, data): """ Handles getting overview of services. """ - handler.write_json( - [{"domain": key, "services": value} - for key, value - in handler.server.hass.services.services.items()]) + handler.write_json(_services_json(handler.server.hass)) # pylint: disable=invalid-name @@ -312,3 +332,15 @@ def _handle_get_api_components(handler, path_match, data): """ Returns all the loaded components. """ handler.write_json(handler.server.hass.config.components) + + +def _services_json(hass): + """ Generate services data to JSONify. """ + return [{"domain": key, "services": value} + for key, value in hass.services.services.items()] + + +def _events_json(hass): + """ Generate event data to JSONify. """ + return [{"event": key, "listener_count": value} + for key, value in hass.bus.listeners.items()] diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 48d8476759f..2abae8095d6 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -1,8 +1,8 @@ """ homeassistant.components.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sets up a demo environment that mimics interaction with devices +Sets up a demo environment that mimics interaction with devices. """ import time @@ -32,7 +32,13 @@ def setup(hass, config): hass.states.set('a.Demo_Mode', 'Enabled') # Setup sun - loader.get_component('sun').setup(hass, config) + if not hass.config.latitude: + hass.config.latitude = '32.87336' + + if not hass.config.longitude: + hass.config.longitude = '117.22743' + + bootstrap.setup_component(hass, 'sun') # Setup demo platforms for component in COMPONENTS_WITH_DEMO_PLATFORM: diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 4740336767f..c53fff0e4f3 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -6,8 +6,9 @@ Provides functionality to turn on lights based on the state of the sun and devices. """ import logging -from datetime import datetime, timedelta +from datetime import timedelta +import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from . import light, sun, device_tracker, group @@ -115,7 +116,7 @@ def setup(hass, config): new_state.state == STATE_HOME: # These variables are needed for the elif check - now = datetime.now() + now = dt_util.now() start_point = calc_time_for_light_when_sunset() # Do we need lights? diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 6d4db7ad7ed..40dd6e5150e 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -8,11 +8,12 @@ import logging import threading import os import csv -from datetime import datetime, timedelta +from datetime import timedelta from homeassistant.loader import get_component from homeassistant.helpers import validate_config import homeassistant.util as util +import homeassistant.util.dt as dt_util from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, @@ -113,7 +114,7 @@ class DeviceTracker(object): """ Reload known devices file. """ self._read_known_devices_file() - self.update_devices(datetime.now()) + self.update_devices(dt_util.utcnow()) dev_group.update_tracked_entity_ids(self.device_entity_ids) @@ -125,7 +126,7 @@ class DeviceTracker(object): seconds = range(0, 60, seconds) _LOGGER.info("Device tracker interval second=%s", seconds) - hass.track_time_change(update_device_state, second=seconds) + hass.track_utc_time_change(update_device_state, second=seconds) hass.services.register(DOMAIN, SERVICE_DEVICE_TRACKER_RELOAD, @@ -226,7 +227,7 @@ class DeviceTracker(object): self.untracked_devices.clear() with open(known_dev_path) as inp: - default_last_seen = datetime(1990, 1, 1) + default_last_seen = dt_util.utcnow().replace(year=1990) # To track which devices need an entity_id assigned need_entity_id = [] diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 3d63c49209c..2c69746fab0 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -1,4 +1,35 @@ -""" Supports scanning a DD-WRT router. """ +""" +homeassistant.components.device_tracker.ddwrt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a DD-WRT router for device +presence. + +Configuration: + +To use the DD-WRT tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: ddwrt + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" import logging from datetime import timedelta import re @@ -20,7 +51,7 @@ _DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}') # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns a DdWrt scanner. """ + """ Validates config and returns a DD-WRT scanner. """ if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -93,7 +124,7 @@ class DdWrtDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ Ensures the information from the DdWrt router is up to date. + """ Ensures the information from the DD-WRT router is up to date. Returns boolean if scanning successful. """ if not self.success_init: return False @@ -111,8 +142,8 @@ class DdWrtDeviceScanner(object): self.last_results = [] active_clients = data.get('active_wireless', None) if active_clients: - # This is really lame, instead of using JSON the ddwrt UI - # uses it's own data format for some reason and then + # This is really lame, instead of using JSON the DD-WRT UI + # uses its own data format for some reason and then # regex's out values so I guess I have to do the same, # LAME!!! @@ -132,7 +163,7 @@ class DdWrtDeviceScanner(object): return False def get_ddwrt_data(self, url): - """ Retrieve data from DD-WRT and return parsed result """ + """ Retrieve data from DD-WRT and return parsed result. """ try: response = requests.get( url, @@ -154,8 +185,7 @@ class DdWrtDeviceScanner(object): def _parse_ddwrt_response(data_str): - """ Parse the awful DD-WRT data format, why didn't they use JSON????. - This code is a python version of how they are parsing in the JS """ + """ Parse the DD-WRT data format. """ return { key: val for key, val in _DDWRT_DATA_REGEX .findall(data_str)} diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index 637c48ddf26..6ff3d996a47 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -1,4 +1,39 @@ -""" Supports scanning a OpenWRT router. """ +""" +homeassistant.components.device_tracker.luci +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a OpenWRT router for device +presence. + + +It's required that the luci RPC package is installed on the OpenWRT router: +# opkg install luci-mod-rpc + +Configuration: + +To use the Luci tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: luci + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" import logging import json from datetime import timedelta diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index aac09536746..102bc78ff47 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -1,4 +1,35 @@ -""" Supports scanning a Netgear router. """ +""" +homeassistant.components.device_tracker.netgear +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a Netgear router for device +presence. + +Configuration: + +To use the Netgear tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: netgear + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" import logging from datetime import timedelta import threading @@ -30,7 +61,7 @@ def get_scanner(hass, config): class NetgearDeviceScanner(object): - """ This class queries a Netgear wireless router using the SOAP-api. """ + """ This class queries a Netgear wireless router using the SOAP-API. """ def __init__(self, host, username, password): self.last_results = [] diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index b221a815fb8..ccd04f6cda1 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -1,6 +1,27 @@ -""" Supports scanning using nmap. """ +""" +homeassistant.components.device_tracker.nmap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a network with nmap. + +Configuration: + +To use the nmap tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: nmap_tracker + hosts: 192.168.1.1/24 + +Variables: + +hosts +*Required +The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or +the range notation (192.168.1.1-255). +""" import logging -from datetime import timedelta, datetime +from datetime import timedelta from collections import namedtuple import subprocess import re @@ -8,6 +29,7 @@ import re from libnmap.process import NmapProcess from libnmap.parser import NmapParser, NmapParserException +import homeassistant.util.dt as dt_util from homeassistant.const import CONF_HOSTS from homeassistant.helpers import validate_config from homeassistant.util import Throttle, convert @@ -36,7 +58,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) def _arp(ip_address): - """ Get the MAC address for a given IP """ + """ Get the MAC address for a given IP. """ cmd = ['arp', '-n', ip_address] arp = subprocess.Popen(cmd, stdout=subprocess.PIPE) out, _ = arp.communicate() @@ -85,7 +107,7 @@ class NmapDeviceScanner(object): Returns True if successful, False otherwise. """ try: results = NmapParser.parse(stdout) - now = datetime.now() + now = dt_util.now() self.last_results = [] for host in results.hosts: if host.is_up(): @@ -119,7 +141,7 @@ class NmapDeviceScanner(object): options = "-F --host-timeout 5" exclude_targets = set() if self.home_interval: - now = datetime.now() + now = dt_util.now() for host in self.last_results: if host.last_update + self.home_interval > now: exclude_targets.add(host) diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index 265dcf84b57..1a189a08396 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -1,4 +1,41 @@ -""" Supports scanning a Tomato router. """ +""" +homeassistant.components.device_tracker.tomato +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a Tomato router for device +presence. + +Configuration: + +To use the Tomato tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: tomato + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + http_id: ABCDEFG + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. + +http_id +*Required +The value can be obtained by logging in to the Tomato admin interface and +search for http_id in the page source code. +""" import logging import json from datetime import timedelta diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 2a9cb64f6ec..819d45b5b68 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -1,11 +1,13 @@ """ +homeassistant.components.discovery +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Starts a service to scan in intervals for new devices. Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. Knows which components handle certain types, will make sure they are loaded before the EVENT_PLATFORM_DISCOVERED is fired. - """ import logging import threading diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index bf7aed7c7fd..a1c8f55f236 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "93774c7a1643c7e3f9cbbb1554b36683" +VERSION = "28c0680cf6ebd969dc5710c22d9c4075" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 5995df6e07d..a4c206a4ce4 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -11,11 +11,14 @@ window.PolymerGestures={},function(a){var b=!1,c=document.createElement("meta"); LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ThisExpression:"ThisExpression",UnaryExpression:"UnaryExpression"},V={UnexpectedToken:"Unexpected token %0",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared"};var ab=H,bb=L;a.esprima={parse:R}}(this),function(a){"use strict";function b(a,b,d,e){var f;try{if(f=c(a),f.scopeIdent&&(d.nodeType!==Node.ELEMENT_NODE||"TEMPLATE"!==d.tagName||"bind"!==b&&"repeat"!==b))throw Error("as and in can only be used within diff --git a/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-js.html b/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-js.html index 8d7cedcd778..6bd24e8767e 100644 --- a/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-js.html +++ b/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-js.html @@ -7,18 +7,6 @@ 'light', 'group', 'sun', 'configurator', 'thermostat', 'script' ]; - // Register some polymer filters - - PolymerExpressions.prototype.HATimeToDate = function(timeString) { - if (!timeString) return; - - return window.hass.util.parseDateTime(timeString); - }; - - PolymerExpressions.prototype.HATimeStripDate = function(timeString) { - return (timeString || "").split(' ')[0]; - }; - // Add some frontend specific helpers to the models Object.defineProperties(window.hass.stateModel.prototype, { // how to render the card for this state @@ -81,3 +69,5 @@ window.hass.uiUtil = {}; })(); + + diff --git a/homeassistant/components/frontend/www_static/polymer/resources/moment-js.html b/homeassistant/components/frontend/www_static/polymer/resources/moment-js.html index 70e14e72d7f..14742dd2d2d 100644 --- a/homeassistant/components/frontend/www_static/polymer/resources/moment-js.html +++ b/homeassistant/components/frontend/www_static/polymer/resources/moment-js.html @@ -3,3 +3,22 @@ --> + + diff --git a/homeassistant/components/frontend/www_static/webcomponents.min.js b/homeassistant/components/frontend/www_static/webcomponents.min.js index 474305f73fc..0fa8b57549e 100644 --- a/homeassistant/components/frontend/www_static/webcomponents.min.js +++ b/homeassistant/components/frontend/www_static/webcomponents.min.js @@ -7,8 +7,10 @@ * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -// @version 0.5.5 -window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){e=e.split("="),e[0]&&(t[e[0]]=e[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(WebComponents),WebComponents.flags.shadow&&("undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),window.ShadowDOMPolyfill={},function(e){"use strict";function t(){if("undefined"!=typeof chrome&&chrome.app&&chrome.app.runtime)return!1;if(navigator.getDeviceStorage)return!1;try{var e=new Function("return true;");return e()}catch(t){return!1}}function n(e){if(!e)throw new Error("Assertion failed")}function r(e,t){for(var n=W(t),r=0;rl;l++)c[l]=new Array(s),c[l][0]=l;for(var u=0;s>u;u++)c[0][u]=u;for(var l=1;a>l;l++)for(var u=1;s>u;u++)if(this.equals(e[t+u-1],r[o+l-1]))c[l][u]=c[l-1][u-1];else{var d=c[l-1][u]+1,p=c[l][u-1]+1;c[l][u]=p>d?d:p}return c},spliceOperationsFromEditDistances:function(e){for(var t=e.length-1,n=e[0].length-1,s=e[t][n],c=[];t>0||n>0;)if(0!=t)if(0!=n){var l,u=e[t-1][n-1],d=e[t-1][n],p=e[t][n-1];l=p>d?u>d?d:u:u>p?p:u,l==u?(u==s?c.push(r):(c.push(o),s=u),t--,n--):l==d?(c.push(a),t--,s=d):(c.push(i),n--,s=p)}else c.push(a),t--;else c.push(i),n--;return c.reverse(),c},calcSplices:function(e,n,s,c,l,u){var d=0,p=0,f=Math.min(s-n,u-l);if(0==n&&0==l&&(d=this.sharedPrefix(e,c,f)),s==e.length&&u==c.length&&(p=this.sharedSuffix(e,c,f-d)),n+=d,l+=d,s-=p,u-=p,s-n==0&&u-l==0)return[];if(n==s){for(var h=t(n,[],0);u>l;)h.removed.push(c[l++]);return[h]}if(l==u)return[t(n,[],s-n)];for(var m=this.spliceOperationsFromEditDistances(this.calcEditDistances(e,n,s,c,l,u)),h=void 0,w=[],v=n,g=l,b=0;br;r++)if(!this.equals(e[r],t[r]))return r;return n},sharedSuffix:function(e,t,n){for(var r=e.length,o=t.length,i=0;n>i&&this.equals(e[--r],t[--o]);)i++;return i},calculateSplices:function(e,t){return this.calcSplices(e,0,e.length,t,0,t.length)},equals:function(e,t){return e===t}},e.ArraySplice=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(){a=!1;var e=i.slice(0);i=[];for(var t=0;t0){for(var u=0;u0&&r.length>0;){var i=n.pop(),a=r.pop();if(i!==a)break;o=i}return o}function u(e,t,n){t instanceof G.Window&&(t=t.document);var o,i=k(t),a=k(n),s=r(n,e),o=l(i,a);o||(o=a.root);for(var c=o;c;c=c.parent)for(var u=0;u0;i--)if(!g(t[i],e,o,t,r))return!1;return!0}function w(e,t,n,r){var o=it,i=t[0]||n;return g(i,e,o,t,r)}function v(e,t,n,r){for(var o=at,i=1;i0&&g(n,e,o,t,r)}function g(e,t,n,r,o){var i=z.get(e);if(!i)return!0;var a=o||s(r,e);if(a===e){if(n===ot)return!0;n===at&&(n=it)}else if(n===at&&!t.bubbles)return!0;if("relatedTarget"in t){var c=q(t),l=c.relatedTarget;if(l){if(l instanceof Object&&l.addEventListener){var d=V(l),p=u(t,e,d);if(p===a)return!0}else p=null;J.set(t,p)}}Z.set(t,n);var f=t.type,h=!1;X.set(t,a),$.set(t,e),i.depth++;for(var m=0,w=i.length;w>m;m++){var v=i[m];if(v.removed)h=!0;else if(!(v.type!==f||!v.capture&&n===ot||v.capture&&n===at))try{if("function"==typeof v.handler?v.handler.call(e,t):v.handler.handleEvent(t),et.get(t))return!1}catch(g){I||(I=g)}}if(i.depth--,h&&0===i.depth){var b=i.slice();i.length=0;for(var m=0;mr;r++)t[r]=a(e[r]);return t.length=o,t}function o(e,t){e.prototype[t]=function(){return r(i(this)[t].apply(i(this),arguments))}}var i=e.unsafeUnwrap,a=e.wrap,s={enumerable:!1};n.prototype={item:function(e){return this[e]}},t(n.prototype,"item"),e.wrappers.NodeList=n,e.addWrapNodeListMethod=o,e.wrapNodeList=r}(window.ShadowDOMPolyfill),function(e){"use strict";e.wrapHTMLCollection=e.wrapNodeList,e.wrappers.HTMLCollection=e.wrappers.NodeList}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){L(e instanceof S)}function n(e){var t=new M;return t[0]=e,t.length=1,t}function r(e,t,n){C(t,"childList",{removedNodes:n,previousSibling:e.previousSibling,nextSibling:e.nextSibling})}function o(e,t){C(e,"childList",{removedNodes:t})}function i(e,t,r,o){if(e instanceof DocumentFragment){var i=s(e);B=!0;for(var a=i.length-1;a>=0;a--)e.removeChild(i[a]),i[a].parentNode_=t;B=!1;for(var a=0;ao;o++)r.appendChild(I(t[o]));return r}function w(e){if(void 0!==e.firstChild_)for(var t=e.firstChild_;t;){var n=t;t=t.nextSibling_,n.parentNode_=n.previousSibling_=n.nextSibling_=void 0}e.firstChild_=e.lastChild_=void 0}function v(e){if(e.invalidateShadowRenderer()){for(var t=e.firstChild;t;){L(t.parentNode===e);var n=t.nextSibling,r=I(t),o=r.parentNode;o&&Y.call(o,r),t.previousSibling_=t.nextSibling_=t.parentNode_=null,t=n}e.firstChild_=e.lastChild_=null}else for(var n,i=I(e),a=i.firstChild;a;)n=a.nextSibling,Y.call(i,a),a=n}function g(e){var t=e.parentNode;return t&&t.invalidateShadowRenderer()}function b(e){for(var t,n=0;ns;s++)i=b(t[s]),!o&&(a=v(i).root)&&a instanceof e.wrappers.ShadowRoot||(r[n++]=i);return n}function n(e){return String(e).replace(/\/deep\/|::shadow/g," ")}function r(e){return String(e).replace(/:host\(([^\s]+)\)/g,"$1").replace(/([^\s]):host/g,"$1").replace(":host","*").replace(/\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content/g," ")}function o(e,t){for(var n,r=e.firstElementChild;r;){if(r.matches(t))return r;if(n=o(r,t))return n;r=r.nextElementSibling}return null}function i(e,t){return e.matches(t)}function a(e,t,n){var r=e.localName;return r===t||r===n&&e.namespaceURI===D}function s(){return!0}function c(e,t,n){return e.localName===n}function l(e,t){return e.namespaceURI===t}function u(e,t,n){return e.namespaceURI===t&&e.localName===n}function d(e,t,n,r,o,i){for(var a=e.firstElementChild;a;)r(a,o,i)&&(n[t++]=a),t=d(a,t,n,r,o,i),a=a.nextElementSibling;return t}function p(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,null);if(c instanceof C)s=T.call(c,i);else{if(!(c instanceof N))return d(this,r,o,n,i,null);s=S.call(c,i)}return t(s,r,o,a)}function f(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof C)s=_.call(c,i,a);else{if(!(c instanceof N))return d(this,r,o,n,i,a);s=M.call(c,i,a)}return t(s,r,o,!1)}function h(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof C)s=O.call(c,i,a);else{if(!(c instanceof N))return d(this,r,o,n,i,a);s=L.call(c,i,a)}return t(s,r,o,!1)}var m=e.wrappers.HTMLCollection,w=e.wrappers.NodeList,v=e.getTreeScope,g=e.unsafeUnwrap,b=e.wrap,y=document.querySelector,E=document.documentElement.querySelector,S=document.querySelectorAll,T=document.documentElement.querySelectorAll,M=document.getElementsByTagName,_=document.documentElement.getElementsByTagName,L=document.getElementsByTagNameNS,O=document.documentElement.getElementsByTagNameNS,C=window.Element,N=window.HTMLDocument||window.Document,D="http://www.w3.org/1999/xhtml",j={querySelector:function(t){var r=n(t),i=r!==t;t=r;var a,s=g(this),c=v(this).root;if(c instanceof e.wrappers.ShadowRoot)return o(this,t);if(s instanceof C)a=b(E.call(s,t));else{if(!(s instanceof N))return o(this,t);a=b(y.call(s,t))}return a&&!i&&(c=v(a).root)&&c instanceof e.wrappers.ShadowRoot?o(this,t):a},querySelectorAll:function(e){var t=n(e),r=t!==e;e=t;var o=new w;return o.length=p.call(this,i,0,o,e,r),o -}},H={matches:function(t){return t=r(t),e.originalMatches.call(g(this),t)}},x={getElementsByTagName:function(e){var t=new m,n="*"===e?s:a;return t.length=f.call(this,n,0,t,e,e.toLowerCase()),t},getElementsByClassName:function(e){return this.querySelectorAll("."+e)},getElementsByTagNameNS:function(e,t){var n=new m,r=null;return r="*"===e?"*"===t?s:c:"*"===t?l:u,n.length=h.call(this,r,0,n,e||null,t),n}};e.GetElementsByInterface=x,e.SelectorsInterface=j,e.MatchesInterface=H}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;return e}function n(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.previousSibling;return e}var r=e.wrappers.NodeList,o={get firstElementChild(){return t(this.firstChild)},get lastElementChild(){return n(this.lastChild)},get childElementCount(){for(var e=0,t=this.firstElementChild;t;t=t.nextElementSibling)e++;return e},get children(){for(var e=new r,t=0,n=this.firstElementChild;n;n=n.nextElementSibling)e[t++]=n;return e.length=t,e},remove:function(){var e=this.parentNode;e&&e.removeChild(this)}},i={get nextElementSibling(){return t(this.nextSibling)},get previousElementSibling(){return n(this.previousSibling)}};e.ChildNodeInterface=i,e.ParentNodeInterface=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}var n=e.ChildNodeInterface,r=e.wrappers.Node,o=e.enqueueMutation,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=window.CharacterData;t.prototype=Object.create(r.prototype),i(t.prototype,{get textContent(){return this.data},set textContent(e){this.data=e},get data(){return s(this).data},set data(e){var t=s(this).data;o(this,"characterData",{oldValue:t}),s(this).data=e}}),i(t.prototype,n),a(c,t,document.createTextNode("")),e.wrappers.CharacterData=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e>>>0}function n(e){r.call(this,e)}var r=e.wrappers.CharacterData,o=(e.enqueueMutation,e.mixin),i=e.registerWrapper,a=window.Text;n.prototype=Object.create(r.prototype),o(n.prototype,{splitText:function(e){e=t(e);var n=this.data;if(e>n.length)throw new Error("IndexSizeError");var r=n.slice(0,e),o=n.slice(e);this.data=r;var i=this.ownerDocument.createTextNode(o);return this.parentNode&&this.parentNode.insertBefore(i,this.nextSibling),i}}),i(a,n,document.createTextNode("")),e.wrappers.Text=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return i(e).getAttribute("class")}function n(e,t){a(e,"attributes",{name:"class",namespace:null,oldValue:t})}function r(t){e.invalidateRendererBasedOnAttribute(t,"class")}function o(e,o,i){var a=e.ownerElement_;if(null==a)return o.apply(e,i);var s=t(a),c=o.apply(e,i);return t(a)!==s&&(n(a,s),r(a)),c}if(!window.DOMTokenList)return void console.warn("Missing DOMTokenList prototype, please include a compatible classList polyfill such as http://goo.gl/uTcepH.");var i=e.unsafeUnwrap,a=e.enqueueMutation,s=DOMTokenList.prototype.add;DOMTokenList.prototype.add=function(){o(this,s,arguments)};var c=DOMTokenList.prototype.remove;DOMTokenList.prototype.remove=function(){o(this,c,arguments)};var l=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(){return o(this,l,arguments)}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t,n){var r=t.parentNode;if(r&&r.shadowRoot){var o=e.getRendererForHost(r);o.dependsOnAttribute(n)&&o.invalidate()}}function n(e,t,n){u(e,"attributes",{name:t,namespace:null,oldValue:n})}function r(e){a.call(this,e)}var o=e.ChildNodeInterface,i=e.GetElementsByInterface,a=e.wrappers.Node,s=e.ParentNodeInterface,c=e.SelectorsInterface,l=e.MatchesInterface,u=(e.addWrapNodeListMethod,e.enqueueMutation),d=e.mixin,p=(e.oneOf,e.registerWrapper),f=e.unsafeUnwrap,h=e.wrappers,m=window.Element,w=["matches","mozMatchesSelector","msMatchesSelector","webkitMatchesSelector"].filter(function(e){return m.prototype[e]}),v=w[0],g=m.prototype[v],b=new WeakMap;r.prototype=Object.create(a.prototype),d(r.prototype,{createShadowRoot:function(){var t=new h.ShadowRoot(this);f(this).polymerShadowRoot_=t;var n=e.getRendererForHost(this);return n.invalidate(),t},get shadowRoot(){return f(this).polymerShadowRoot_||null},setAttribute:function(e,r){var o=f(this).getAttribute(e);f(this).setAttribute(e,r),n(this,e,o),t(this,e)},removeAttribute:function(e){var r=f(this).getAttribute(e);f(this).removeAttribute(e),n(this,e,r),t(this,e)},get classList(){var e=b.get(this);if(!e){if(e=f(this).classList,!e)return;e.ownerElement_=this,b.set(this,e)}return e},get className(){return f(this).className},set className(e){this.setAttribute("class",e)},get id(){return f(this).id},set id(e){this.setAttribute("id",e)}}),w.forEach(function(e){"matches"!==e&&(r.prototype[e]=function(e){return this.matches(e)})}),m.prototype.webkitCreateShadowRoot&&(r.prototype.webkitCreateShadowRoot=r.prototype.createShadowRoot),d(r.prototype,o),d(r.prototype,i),d(r.prototype,s),d(r.prototype,c),d(r.prototype,l),p(m,r,document.createElementNS(null,"x")),e.invalidateRendererBasedOnAttribute=t,e.matchesNames=w,e.originalMatches=g,e.wrappers.Element=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";case" ":return" "}}function n(e){return e.replace(L,t)}function r(e){return e.replace(O,t)}function o(e){for(var t={},n=0;n";case Node.TEXT_NODE:var u=e.data;return t&&N[t.localName]?u:r(u);case Node.COMMENT_NODE:return"";default:throw console.error(e),new Error("not implemented")}}function a(e){e instanceof _.HTMLTemplateElement&&(e=e.content);for(var t="",n=e.firstChild;n;n=n.nextSibling)t+=i(n,e);return t}function s(e,t,n){var r=n||"div";e.textContent="";var o=T(e.ownerDocument.createElement(r));o.innerHTML=t;for(var i;i=o.firstChild;)e.appendChild(M(i))}function c(e){h.call(this,e)}function l(e,t){var n=T(e.cloneNode(!1));n.innerHTML=t;for(var r,o=T(document.createDocumentFragment());r=n.firstChild;)o.appendChild(r);return M(o)}function u(t){return function(){return e.renderAllPending(),S(this)[t]}}function d(e){m(c,e,u(e))}function p(t){Object.defineProperty(c.prototype,t,{get:u(t),set:function(n){e.renderAllPending(),S(this)[t]=n},configurable:!0,enumerable:!0})}function f(t){Object.defineProperty(c.prototype,t,{value:function(){return e.renderAllPending(),S(this)[t].apply(S(this),arguments)},configurable:!0,enumerable:!0})}var h=e.wrappers.Element,m=e.defineGetter,w=e.enqueueMutation,v=e.mixin,g=e.nodesWereAdded,b=e.nodesWereRemoved,y=e.registerWrapper,E=e.snapshotNodeList,S=e.unsafeUnwrap,T=e.unwrap,M=e.wrap,_=e.wrappers,L=/[&\u00A0"]/g,O=/[&\u00A0<>]/g,C=o(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),N=o(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]),D=/MSIE/.test(navigator.userAgent),j=window.HTMLElement,H=window.HTMLTemplateElement;c.prototype=Object.create(h.prototype),v(c.prototype,{get innerHTML(){return a(this)},set innerHTML(e){if(D&&N[this.localName])return void(this.textContent=e);var t=E(this.childNodes);this.invalidateShadowRenderer()?this instanceof _.HTMLTemplateElement?s(this.content,e):s(this,e,this.tagName):!H&&this instanceof _.HTMLTemplateElement?s(this.content,e):S(this).innerHTML=e;var n=E(this.childNodes);w(this,"childList",{addedNodes:n,removedNodes:t}),b(t),g(n,this)},get outerHTML(){return i(this,this.parentNode)},set outerHTML(e){var t=this.parentNode;if(t){t.invalidateShadowRenderer();var n=l(t,e);t.replaceChild(n,this)}},insertAdjacentHTML:function(e,t){var n,r;switch(String(e).toLowerCase()){case"beforebegin":n=this.parentNode,r=this;break;case"afterend":n=this.parentNode,r=this.nextSibling;break;case"afterbegin":n=this,r=this.firstChild;break;case"beforeend":n=this,r=null;break;default:return}var o=l(n,t);n.insertBefore(o,r)},get hidden(){return this.hasAttribute("hidden")},set hidden(e){e?this.setAttribute("hidden",""):this.removeAttribute("hidden")}}),["clientHeight","clientLeft","clientTop","clientWidth","offsetHeight","offsetLeft","offsetTop","offsetWidth","scrollHeight","scrollWidth"].forEach(d),["scrollLeft","scrollTop"].forEach(p),["getBoundingClientRect","getClientRects","scrollIntoView"].forEach(f),y(j,c,document.createElement("b")),e.wrappers.HTMLElement=c,e.getInnerHTML=a,e.setInnerHTML=s}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.HTMLCanvasElement;t.prototype=Object.create(n.prototype),r(t.prototype,{getContext:function(){var e=i(this).getContext.apply(i(this),arguments);return e&&a(e)}}),o(s,t,document.createElement("canvas")),e.wrappers.HTMLCanvasElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=window.HTMLContentElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get select(){return this.getAttribute("select")},set select(e){this.setAttribute("select",e)},setAttribute:function(e,t){n.prototype.setAttribute.call(this,e,t),"select"===String(e).toLowerCase()&&this.invalidateShadowRenderer(!0)}}),i&&o(i,t),e.wrappers.HTMLContentElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=window.HTMLFormElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get elements(){return i(a(this).elements)}}),o(s,t,document.createElement("form")),e.wrappers.HTMLFormElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e,t){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var o=i(document.createElement("img"));r.call(this,o),a(o,this),void 0!==e&&(o.width=e),void 0!==t&&(o.height=t)}var r=e.wrappers.HTMLElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLImageElement;t.prototype=Object.create(r.prototype),o(s,t,document.createElement("img")),n.prototype=t.prototype,e.wrappers.HTMLImageElement=t,e.wrappers.Image=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=(e.mixin,e.wrappers.NodeList,e.registerWrapper),o=window.HTMLShadowElement;t.prototype=Object.create(n.prototype),t.prototype.constructor=t,o&&r(o,t),e.wrappers.HTMLShadowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){if(!e.defaultView)return e;var t=d.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);d.set(e,t)}return t}function n(e){for(var n,r=t(e.ownerDocument),o=c(r.createDocumentFragment());n=e.firstChild;)o.appendChild(n);return o}function r(e){if(o.call(this,e),!p){var t=n(e);u.set(this,l(t))}}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.wrap,u=new WeakMap,d=new WeakMap,p=window.HTMLTemplateElement;r.prototype=Object.create(o.prototype),i(r.prototype,{constructor:r,get content(){return p?l(s(this).content):u.get(this)}}),p&&a(p,r),e.wrappers.HTMLTemplateElement=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.registerWrapper,o=window.HTMLMediaElement;o&&(t.prototype=Object.create(n.prototype),r(o,t,document.createElement("audio")),e.wrappers.HTMLMediaElement=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var t=i(document.createElement("audio"));r.call(this,t),a(t,this),t.setAttribute("preload","auto"),void 0!==e&&t.setAttribute("src",e)}var r=e.wrappers.HTMLMediaElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLAudioElement;s&&(t.prototype=Object.create(r.prototype),o(s,t,document.createElement("audio")),n.prototype=t.prototype,e.wrappers.HTMLAudioElement=t,e.wrappers.Audio=n)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e.replace(/\s+/g," ").trim()}function n(e){o.call(this,e)}function r(e,t,n,i){if(!(this instanceof r))throw new TypeError("DOM object constructor cannot be called as a function.");var a=c(document.createElement("option"));o.call(this,a),s(a,this),void 0!==e&&(a.text=e),void 0!==t&&a.setAttribute("value",t),n===!0&&a.setAttribute("selected",""),a.selected=i===!0}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.rewrap,c=e.unwrap,l=e.wrap,u=window.HTMLOptionElement;n.prototype=Object.create(o.prototype),i(n.prototype,{get text(){return t(this.textContent)},set text(e){this.textContent=t(String(e))},get form(){return l(c(this).form)}}),a(u,n,document.createElement("option")),r.prototype=n.prototype,e.wrappers.HTMLOptionElement=n,e.wrappers.Option=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=window.HTMLSelectElement;t.prototype=Object.create(n.prototype),r(t.prototype,{add:function(e,t){"object"==typeof t&&(t=i(t)),i(this).add(i(e),t)},remove:function(e){return void 0===e?void n.prototype.remove.call(this):("object"==typeof e&&(e=i(e)),void i(this).remove(e))},get form(){return a(i(this).form)}}),o(s,t,document.createElement("select")),e.wrappers.HTMLSelectElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=e.wrapHTMLCollection,c=window.HTMLTableElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get caption(){return a(i(this).caption)},createCaption:function(){return a(i(this).createCaption())},get tHead(){return a(i(this).tHead)},createTHead:function(){return a(i(this).createTHead())},createTFoot:function(){return a(i(this).createTFoot())},get tFoot(){return a(i(this).tFoot)},get tBodies(){return s(i(this).tBodies)},createTBody:function(){return a(i(this).createTBody())},get rows(){return s(i(this).rows)},insertRow:function(e){return a(i(this).insertRow(e))}}),o(c,t,document.createElement("table")),e.wrappers.HTMLTableElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableSectionElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get rows(){return i(a(this).rows)},insertRow:function(e){return s(a(this).insertRow(e))}}),o(c,t,document.createElement("thead")),e.wrappers.HTMLTableSectionElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableRowElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get cells(){return i(a(this).cells)},insertCell:function(e){return s(a(this).insertCell(e))}}),o(c,t,document.createElement("tr")),e.wrappers.HTMLTableRowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e.localName){case"content":return new n(e);case"shadow":return new o(e);case"template":return new i(e)}r.call(this,e)}var n=e.wrappers.HTMLContentElement,r=e.wrappers.HTMLElement,o=e.wrappers.HTMLShadowElement,i=e.wrappers.HTMLTemplateElement,a=(e.mixin,e.registerWrapper),s=window.HTMLUnknownElement;t.prototype=Object.create(r.prototype),a(s,t),e.wrappers.HTMLUnknownElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.wrappers.Element,n=e.wrappers.HTMLElement,r=e.registerObject,o=e.defineWrapGetter,i="http://www.w3.org/2000/svg",a=document.createElementNS(i,"title"),s=r(a),c=Object.getPrototypeOf(s.prototype).constructor;if(!("classList"in a)){var l=Object.getOwnPropertyDescriptor(t.prototype,"classList");Object.defineProperty(n.prototype,"classList",l),delete t.prototype.classList}o(c,"ownerSVGElement"),e.wrappers.SVGElement=c}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){p.call(this,e)}var n=e.mixin,r=e.registerWrapper,o=e.unwrap,i=e.wrap,a=window.SVGUseElement,s="http://www.w3.org/2000/svg",c=i(document.createElementNS(s,"g")),l=document.createElementNS(s,"use"),u=c.constructor,d=Object.getPrototypeOf(u.prototype),p=d.constructor;t.prototype=Object.create(d),"instanceRoot"in l&&n(t.prototype,{get instanceRoot(){return i(o(this).instanceRoot)},get animatedInstanceRoot(){return i(o(this).animatedInstanceRoot)}}),r(a,t,l),e.wrappers.SVGUseElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.SVGElementInstance;s&&(t.prototype=Object.create(n.prototype),r(t.prototype,{get correspondingElement(){return a(i(this).correspondingElement)},get correspondingUseElement(){return a(i(this).correspondingUseElement)},get parentNode(){return a(i(this).parentNode)},get childNodes(){throw new Error("Not implemented")},get firstChild(){return a(i(this).firstChild)},get lastChild(){return a(i(this).lastChild)},get previousSibling(){return a(i(this).previousSibling)},get nextSibling(){return a(i(this).nextSibling)}}),o(s,t),e.wrappers.SVGElementInstance=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.CanvasRenderingContext2D;n(t.prototype,{get canvas(){return c(i(this).canvas)},drawImage:function(){arguments[0]=s(arguments[0]),i(this).drawImage.apply(i(this),arguments)},createPattern:function(){return arguments[0]=a(arguments[0]),i(this).createPattern.apply(i(this),arguments)}}),r(l,t,document.createElement("canvas").getContext("2d")),e.wrappers.CanvasRenderingContext2D=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrapIfNeeded,s=e.wrap,c=window.WebGLRenderingContext;if(c){n(t.prototype,{get canvas(){return s(i(this).canvas)},texImage2D:function(){arguments[5]=a(arguments[5]),i(this).texImage2D.apply(i(this),arguments)},texSubImage2D:function(){arguments[6]=a(arguments[6]),i(this).texSubImage2D.apply(i(this),arguments)}});var l=/WebKit/.test(navigator.userAgent)?{drawingBufferHeight:null,drawingBufferWidth:null}:{};r(c,t,l),e.wrappers.WebGLRenderingContext=t}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r(e,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unsafeUnwrap,i=e.unwrap,a=e.unwrapIfNeeded,s=e.wrap,c=window.Range;t.prototype={get startContainer(){return s(o(this).startContainer)},get endContainer(){return s(o(this).endContainer)},get commonAncestorContainer(){return s(o(this).commonAncestorContainer)},setStart:function(e,t){o(this).setStart(a(e),t)},setEnd:function(e,t){o(this).setEnd(a(e),t)},setStartBefore:function(e){o(this).setStartBefore(a(e))},setStartAfter:function(e){o(this).setStartAfter(a(e))},setEndBefore:function(e){o(this).setEndBefore(a(e))},setEndAfter:function(e){o(this).setEndAfter(a(e))},selectNode:function(e){o(this).selectNode(a(e))},selectNodeContents:function(e){o(this).selectNodeContents(a(e))},compareBoundaryPoints:function(e,t){return o(this).compareBoundaryPoints(e,i(t))},extractContents:function(){return s(o(this).extractContents())},cloneContents:function(){return s(o(this).cloneContents())},insertNode:function(e){o(this).insertNode(a(e))},surroundContents:function(e){o(this).surroundContents(a(e))},cloneRange:function(){return s(o(this).cloneRange())},isPointInRange:function(e,t){return o(this).isPointInRange(a(e),t)},comparePoint:function(e,t){return o(this).comparePoint(a(e),t)},intersectsNode:function(e){return o(this).intersectsNode(a(e))},toString:function(){return o(this).toString()}},c.prototype.createContextualFragment&&(t.prototype.createContextualFragment=function(e){return s(o(this).createContextualFragment(e))}),n(window.Range,t,document.createRange()),e.wrappers.Range=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.GetElementsByInterface,n=e.ParentNodeInterface,r=e.SelectorsInterface,o=e.mixin,i=e.registerObject,a=i(document.createDocumentFragment());o(a.prototype,n),o(a.prototype,r),o(a.prototype,t);var s=i(document.createComment(""));e.wrappers.Comment=s,e.wrappers.DocumentFragment=a}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(u(e).ownerDocument.createDocumentFragment());n.call(this,t),c(t,this);var o=e.shadowRoot;f.set(this,o),this.treeScope_=new r(this,a(o||e)),p.set(this,e)}var n=e.wrappers.DocumentFragment,r=e.TreeScope,o=e.elementFromPoint,i=e.getInnerHTML,a=e.getTreeScope,s=e.mixin,c=e.rewrap,l=e.setInnerHTML,u=e.unsafeUnwrap,d=e.unwrap,p=new WeakMap,f=new WeakMap,h=/[ \t\n\r\f]/;t.prototype=Object.create(n.prototype),s(t.prototype,{constructor:t,get innerHTML(){return i(this)},set innerHTML(e){l(this,e),this.invalidateShadowRenderer()},get olderShadowRoot(){return f.get(this)||null},get host(){return p.get(this)||null},invalidateShadowRenderer:function(){return p.get(this).invalidateShadowRenderer()},elementFromPoint:function(e,t){return o(this,this.ownerDocument,e,t)},getElementById:function(e){return h.test(e)?null:this.querySelector('[id="'+e+'"]')}}),e.wrappers.ShadowRoot=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.previousSibling_=e.previousSibling,e.nextSibling_=e.nextSibling,e.parentNode_=e.parentNode}function n(n,o,i){var a=x(n),s=x(o),c=i?x(i):null;if(r(o),t(o),i)n.firstChild===i&&(n.firstChild_=i),i.previousSibling_=i.previousSibling;else{n.lastChild_=n.lastChild,n.lastChild===n.firstChild&&(n.firstChild_=n.firstChild);var l=R(a.lastChild);l&&(l.nextSibling_=l.nextSibling)}e.originalInsertBefore.call(a,s,c)}function r(n){var r=x(n),o=r.parentNode;if(o){var i=R(o);t(n),n.previousSibling&&(n.previousSibling.nextSibling_=n),n.nextSibling&&(n.nextSibling.previousSibling_=n),i.lastChild===n&&(i.lastChild_=n),i.firstChild===n&&(i.firstChild_=n),e.originalRemoveChild.call(o,r)}}function o(e){I.set(e,[])}function i(e){var t=I.get(e);return t||I.set(e,t=[]),t}function a(e){for(var t=[],n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t}function s(){for(var e=0;em;m++){var w=R(i[u++]);s.get(w)||r(w)}for(var v=f.addedCount,g=i[u]&&R(i[u]),m=0;v>m;m++){var b=o[l++],y=b.node;n(t,y,g),s.set(y,!0),b.sync(s)}d+=v}for(var p=d;p=0;o--){var i=r[o],a=m(i);if(a){var s=i.olderShadowRoot;s&&(n=h(s));for(var c=0;c=0;u--)l=Object.create(l);["createdCallback","attachedCallback","detachedCallback","attributeChangedCallback"].forEach(function(e){var t=o[e];t&&(l[e]=function(){C(this)instanceof r||M(this),t.apply(C(this),arguments)})});var d={prototype:l};i&&(d["extends"]=i),r.prototype=o,r.prototype.constructor=r,e.constructorTable.set(l,r),e.nativePrototypeTable.set(o,l);x.call(O(this),t,d);return r},b([window.HTMLDocument||window.Document],["registerElement"])}b([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement,window.HTMLHtmlElement],["appendChild","compareDocumentPosition","contains","getElementsByClassName","getElementsByTagName","getElementsByTagNameNS","insertBefore","querySelector","querySelectorAll","removeChild","replaceChild"]),b([window.HTMLBodyElement,window.HTMLHeadElement,window.HTMLHtmlElement],y),b([window.HTMLDocument||window.Document],["adoptNode","importNode","contains","createComment","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createRange","createTextNode","elementFromPoint","getElementById","getElementsByName","getSelection"]),E(t.prototype,l),E(t.prototype,d),E(t.prototype,f),E(t.prototype,{get implementation(){var e=D.get(this); -return e?e:(e=new a(O(this).implementation),D.set(this,e),e)},get defaultView(){return C(O(this).defaultView)}}),S(window.Document,t,document.implementation.createHTMLDocument("")),window.HTMLDocument&&S(window.HTMLDocument,t),N([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement]),s(a,"createDocumentType"),s(a,"createDocument"),s(a,"createHTMLDocument"),c(a,"hasFeature"),S(window.DOMImplementation,a),b([window.DOMImplementation],["createDocumentType","createDocument","createHTMLDocument","hasFeature"]),e.adoptNodeNoRemove=r,e.wrappers.DOMImplementation=a,e.wrappers.Document=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.wrappers.Selection,o=e.mixin,i=e.registerWrapper,a=e.renderAllPending,s=e.unwrap,c=e.unwrapIfNeeded,l=e.wrap,u=window.Window,d=window.getComputedStyle,p=window.getDefaultComputedStyle,f=window.getSelection;t.prototype=Object.create(n.prototype),u.prototype.getComputedStyle=function(e,t){return l(this||window).getComputedStyle(c(e),t)},p&&(u.prototype.getDefaultComputedStyle=function(e,t){return l(this||window).getDefaultComputedStyle(c(e),t)}),u.prototype.getSelection=function(){return l(this||window).getSelection()},delete window.getComputedStyle,delete window.getDefaultComputedStyle,delete window.getSelection,["addEventListener","removeEventListener","dispatchEvent"].forEach(function(e){u.prototype[e]=function(){var t=l(this||window);return t[e].apply(t,arguments)},delete window[e]}),o(t.prototype,{getComputedStyle:function(e,t){return a(),d.call(s(this),c(e),t)},getSelection:function(){return a(),new r(f.call(s(this)))},get document(){return l(s(this).document)}}),p&&(t.prototype.getDefaultComputedStyle=function(e,t){return a(),p.call(s(this),c(e),t)}),i(u,t,window),e.wrappers.Window=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrap,n=window.DataTransfer||window.Clipboard,r=n.prototype.setDragImage;r&&(n.prototype.setDragImage=function(e,n,o){r.call(this,t(e),n,o)})}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t;t=e instanceof i?e:new i(e&&o(e)),r(t,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unwrap,i=window.FormData;i&&(n(i,t,new i),e.wrappers.FormData=t)}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrapIfNeeded,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(e){return n.call(this,t(e))}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=n[e],r=window[t];if(r){var o=document.createElement(e),i=o.constructor;window[t]=i}}var n=(e.isWrapperFor,{a:"HTMLAnchorElement",area:"HTMLAreaElement",audio:"HTMLAudioElement",base:"HTMLBaseElement",body:"HTMLBodyElement",br:"HTMLBRElement",button:"HTMLButtonElement",canvas:"HTMLCanvasElement",caption:"HTMLTableCaptionElement",col:"HTMLTableColElement",content:"HTMLContentElement",data:"HTMLDataElement",datalist:"HTMLDataListElement",del:"HTMLModElement",dir:"HTMLDirectoryElement",div:"HTMLDivElement",dl:"HTMLDListElement",embed:"HTMLEmbedElement",fieldset:"HTMLFieldSetElement",font:"HTMLFontElement",form:"HTMLFormElement",frame:"HTMLFrameElement",frameset:"HTMLFrameSetElement",h1:"HTMLHeadingElement",head:"HTMLHeadElement",hr:"HTMLHRElement",html:"HTMLHtmlElement",iframe:"HTMLIFrameElement",img:"HTMLImageElement",input:"HTMLInputElement",keygen:"HTMLKeygenElement",label:"HTMLLabelElement",legend:"HTMLLegendElement",li:"HTMLLIElement",link:"HTMLLinkElement",map:"HTMLMapElement",marquee:"HTMLMarqueeElement",menu:"HTMLMenuElement",menuitem:"HTMLMenuItemElement",meta:"HTMLMetaElement",meter:"HTMLMeterElement",object:"HTMLObjectElement",ol:"HTMLOListElement",optgroup:"HTMLOptGroupElement",option:"HTMLOptionElement",output:"HTMLOutputElement",p:"HTMLParagraphElement",param:"HTMLParamElement",pre:"HTMLPreElement",progress:"HTMLProgressElement",q:"HTMLQuoteElement",script:"HTMLScriptElement",select:"HTMLSelectElement",shadow:"HTMLShadowElement",source:"HTMLSourceElement",span:"HTMLSpanElement",style:"HTMLStyleElement",table:"HTMLTableElement",tbody:"HTMLTableSectionElement",template:"HTMLTemplateElement",textarea:"HTMLTextAreaElement",thead:"HTMLTableSectionElement",time:"HTMLTimeElement",title:"HTMLTitleElement",tr:"HTMLTableRowElement",track:"HTMLTrackElement",ul:"HTMLUListElement",video:"HTMLVideoElement"});Object.keys(n).forEach(t),Object.getOwnPropertyNames(e.wrappers).forEach(function(t){window[t]=e.wrappers[t]})}(window.ShadowDOMPolyfill),function(e){function t(e,t){var n="";return Array.prototype.forEach.call(e,function(e){n+=e.textContent+"\n\n"}),t||(n=n.replace(d,"")),n}function n(e){var t=document.createElement("style");return t.textContent=e,t}function r(e){var t=n(e);document.head.appendChild(t);var r=[];if(t.sheet)try{r=t.sheet.cssRules}catch(o){}else console.warn("sheet not found",t);return t.parentNode.removeChild(t),r}function o(){N.initialized=!0,document.body.appendChild(N);var e=N.contentDocument,t=e.createElement("base");t.href=document.baseURI,e.head.appendChild(t)}function i(e){N.initialized||o(),document.body.appendChild(N),e(N.contentDocument),document.body.removeChild(N)}function a(e,t){if(t){var o;if(e.match("@import")&&j){var a=n(e);i(function(e){e.head.appendChild(a.impl),o=Array.prototype.slice.call(a.sheet.cssRules,0),t(o)})}else o=r(e),t(o)}}function s(e){e&&l().appendChild(document.createTextNode(e))}function c(e,t){var r=n(e);r.setAttribute(t,""),r.setAttribute(x,""),document.head.appendChild(r)}function l(){return D||(D=document.createElement("style"),D.setAttribute(x,""),D[x]=!0),D}var u={strictStyling:!1,registry:{},shimStyling:function(e,n,r){var o=this.prepareRoot(e,n,r),i=this.isTypeExtension(r),a=this.makeScopeSelector(n,i),s=t(o,!0);s=this.scopeCssText(s,a),e&&(e.shimmedStyle=s),this.addCssToDocument(s,n)},shimStyle:function(e,t){return this.shimCssText(e.textContent,t)},shimCssText:function(e,t){return e=this.insertDirectives(e),this.scopeCssText(e,t)},makeScopeSelector:function(e,t){return e?t?"[is="+e+"]":e:""},isTypeExtension:function(e){return e&&e.indexOf("-")<0},prepareRoot:function(e,t,n){var r=this.registerRoot(e,t,n);return this.replaceTextInStyles(r.rootStyles,this.insertDirectives),this.removeStyles(e,r.rootStyles),this.strictStyling&&this.applyScopeToContent(e,t),r.scopeStyles},removeStyles:function(e,t){for(var n,r=0,o=t.length;o>r&&(n=t[r]);r++)n.parentNode.removeChild(n)},registerRoot:function(e,t,n){var r=this.registry[t]={root:e,name:t,extendsName:n},o=this.findStyles(e);r.rootStyles=o,r.scopeStyles=r.rootStyles;var i=this.registry[r.extendsName];return i&&(r.scopeStyles=i.scopeStyles.concat(r.scopeStyles)),r},findStyles:function(e){if(!e)return[];var t=e.querySelectorAll("style");return Array.prototype.filter.call(t,function(e){return!e.hasAttribute(R)})},applyScopeToContent:function(e,t){e&&(Array.prototype.forEach.call(e.querySelectorAll("*"),function(e){e.setAttribute(t,"")}),Array.prototype.forEach.call(e.querySelectorAll("template"),function(e){this.applyScopeToContent(e.content,t)},this))},insertDirectives:function(e){return e=this.insertPolyfillDirectivesInCssText(e),this.insertPolyfillRulesInCssText(e)},insertPolyfillDirectivesInCssText:function(e){return e=e.replace(p,function(e,t){return t.slice(0,-2)+"{"}),e.replace(f,function(e,t){return t+" {"})},insertPolyfillRulesInCssText:function(e){return e=e.replace(h,function(e,t){return t.slice(0,-1)}),e.replace(m,function(e,t,n,r){var o=e.replace(t,"").replace(n,"");return r+o})},scopeCssText:function(e,t){var n=this.extractUnscopedRulesFromCssText(e);if(e=this.insertPolyfillHostInCssText(e),e=this.convertColonHost(e),e=this.convertColonHostContext(e),e=this.convertShadowDOMSelectors(e),t){var e,r=this;a(e,function(n){e=r.scopeRules(n,t)})}return e=e+"\n"+n,e.trim()},extractUnscopedRulesFromCssText:function(e){for(var t,n="";t=w.exec(e);)n+=t[1].slice(0,-1)+"\n\n";for(;t=v.exec(e);)n+=t[0].replace(t[2],"").replace(t[1],t[3])+"\n\n";return n},convertColonHost:function(e){return this.convertColonRule(e,E,this.colonHostPartReplacer)},convertColonHostContext:function(e){return this.convertColonRule(e,S,this.colonHostContextPartReplacer)},convertColonRule:function(e,t,n){return e.replace(t,function(e,t,r,o){if(t=L,r){for(var i,a=r.split(","),s=[],c=0,l=a.length;l>c&&(i=a[c]);c++)i=i.trim(),s.push(n(t,i,o));return s.join(",")}return t+o})},colonHostContextPartReplacer:function(e,t,n){return t.match(g)?this.colonHostPartReplacer(e,t,n):e+t+n+", "+t+" "+e+n},colonHostPartReplacer:function(e,t,n){return e+t.replace(g,"")+n},convertShadowDOMSelectors:function(e){for(var t=0;t","+","~"],r=e,o="["+t+"]";return n.forEach(function(e){var t=r.split(e);r=t.map(function(e){var t=e.trim().replace(O,"");return t&&n.indexOf(t)<0&&t.indexOf(o)<0&&(e=t.replace(/([^:]*)(:*)(.*)/,"$1"+o+"$2$3")),e}).join(e)}),r},insertPolyfillHostInCssText:function(e){return e.replace(_,b).replace(M,g)},propertiesFromRule:function(e){var t=e.style.cssText;e.style.content&&!e.style.content.match(/['"]+|attr/)&&(t=t.replace(/content:[^;]*;/g,"content: '"+e.style.content+"';"));var n=e.style;for(var r in n)"initial"===n[r]&&(t+=r+": initial; ");return t},replaceTextInStyles:function(e,t){e&&t&&(e instanceof Array||(e=[e]),Array.prototype.forEach.call(e,function(e){e.textContent=t.call(this,e.textContent)},this))},addCssToDocument:function(e,t){e.match("@import")?c(e,t):s(e)}},d=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,p=/\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,f=/polyfill-next-selector[^}]*content\:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim,h=/\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,m=/(polyfill-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,w=/\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,v=/(polyfill-unscoped-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,g="-shadowcsshost",b="-shadowcsscontext",y=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",E=new RegExp("("+g+y,"gim"),S=new RegExp("("+b+y,"gim"),T="([>\\s~+[.,{:][\\s\\S]*)?$",M=/\:host/gim,_=/\:host-context/gim,L=g+"-no-combinator",O=new RegExp(g,"gim"),C=(new RegExp(b,"gim"),[/\^\^/g,/\^/g,/\/shadow\//g,/\/shadow-deep\//g,/::shadow/g,/\/deep\//g,/::content/g]),N=document.createElement("iframe");N.style.display="none";var D,j=navigator.userAgent.match("Chrome"),H="shim-shadowdom",x="shim-shadowdom-css",R="no-shim";if(window.ShadowDOMPolyfill){s("style { display: none !important; }\n");var P=ShadowDOMPolyfill.wrap(document),I=P.querySelector("head");I.insertBefore(l(),I.childNodes[0]),document.addEventListener("DOMContentLoaded",function(){e.urlResolver;if(window.HTMLImports&&!HTMLImports.useNative){var t="link[rel=stylesheet]["+H+"]",n="style["+H+"]";HTMLImports.importer.documentPreloadSelectors+=","+t,HTMLImports.importer.importsPreloadSelectors+=","+t,HTMLImports.parser.documentSelectors=[HTMLImports.parser.documentSelectors,t,n].join(",");var r=HTMLImports.parser.parseGeneric;HTMLImports.parser.parseGeneric=function(e){if(!e[x]){var t=e.__importElement||e;if(!t.hasAttribute(H))return void r.call(this,e);e.__resource&&(t=e.ownerDocument.createElement("style"),t.textContent=e.__resource),HTMLImports.path.resolveUrlsInStyle(t),t.textContent=u.shimStyle(t),t.removeAttribute(H,""),t.setAttribute(x,""),t[x]=!0,t.parentNode!==I&&(e.parentNode===I?I.replaceChild(t,e):this.addElementToDocument(t)),t.__importParsed=!0,this.markParsingComplete(e),this.parseNext()}};var o=HTMLImports.parser.hasResource;HTMLImports.parser.hasResource=function(e){return"link"===e.localName&&"stylesheet"===e.rel&&e.hasAttribute(H)?e.__resource:o.call(this,e)}}})}e.ShadowCSS=u}(window.WebComponents)),function(){window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}}(window.WebComponents),window.HTMLImports=window.HTMLImports||{flags:{}},function(e){function t(e,t){t=t||h,r(function(){i(e,t)},t)}function n(e){return"complete"===e.readyState||e.readyState===v}function r(e,t){if(n(t))e&&e();else{var o=function(){("complete"===t.readyState||t.readyState===v)&&(t.removeEventListener(g,o),r(e,t))};t.addEventListener(g,o)}}function o(e){e.target.__loaded=!0}function i(e,t){function n(){s==c&&e&&e()}function r(e){o(e),s++,n()}var i=t.querySelectorAll("link[rel=import]"),s=0,c=i.length;if(c)for(var l,u=0;c>u&&(l=i[u]);u++)a(l)?r.call(l,{target:l}):(l.addEventListener("load",r),l.addEventListener("error",r));else n()}function a(e){return d?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&l(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function l(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",d=Boolean(u in document.createElement("link")),p=Boolean(window.ShadowDOMPolyfill),f=function(e){return p?ShadowDOMPolyfill.wrapIfNeeded(e):e},h=f(document),m={get:function(){var e=HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return f(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(h,"_currentScript",m);var w=/Trident|Edge/.test(navigator.userAgent),v=w?"complete":"interactive",g="readystatechange";d&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)l(e)}()),t(function(){HTMLImports.ready=!0,HTMLImports.readyTime=(new Date).getTime();var e=h.createEvent("CustomEvent");e.initCustomEvent("HTMLImportsLoaded",!0,!0,{}),h.dispatchEvent(e)}),e.IMPORT_LINK_TYPE=u,e.useNative=d,e.rootDocument=h,e.whenReady=t,e.isIE=w}(HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(HTMLImports),HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e){var t=e.ownerDocument,n=t.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,n),e},resolveUrlsInCssText:function(e,r){var o=this.replaceUrls(e,r,t);return o=this.replaceUrls(o,r,n)},replaceUrls:function(e,t,n){return e.replace(n,function(e,n,r,o){var i=r.replace(/["']/g,"");return t.href=i,i=t.href,n+"'"+i+"'"+o})}};e.path=r}),HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(){if(4===i.readyState){var e=i.getResponseHeader("Location"),n=null;if(e)var n="/"===e.substr(0,1)?location.origin+e:e;r.call(o,!t.ok(i)&&i,i.response||i.responseText,n)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,l=e.isIE,u=e.IMPORT_LINK_TYPE,d="link[rel="+u+"]",p={documentSelectors:d,importsSelectors:[d,"link[rel=stylesheet]","style","script:not([type])",'script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(HTMLImports.__importsParsingHook&&HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(r){t&&t(r),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),l&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(){r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r["import"],r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e["import"]?!1:!0}};e.parser=p,e.IMPORT_SELECTOR=d}),HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,l=e.Loader,u=e.Observer,d=e.parser,p={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){f.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);f.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n["import"]=c}d.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),d.parseNext()},loadedAll:function(){d.parseNext()}},f=new l(p.loaded.bind(p),p.loadedAll.bind(p));if(p.observer=new u,!document.baseURI){var h={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",h),Object.defineProperty(c,"baseURI",h)}e.importer=p,e.importLoader=f}),HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||HTMLImports.useNative)}(CustomElements),CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){a=[],i(e,t),a=null}function i(e,t){if(e=wrap(e),!(a.indexOf(e)>=0)){a.push(e);for(var n,r=e.querySelectorAll("link[rel="+s+"]"),o=0,c=r.length;c>o&&(n=r[o]);o++)n["import"]&&i(n["import"],t);t(e)}}var a,s=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),CustomElements.addModule(function(e){function t(e){return n(e)||r(e)}function n(t){return e.upgrade(t)?!0:void s(t)}function r(e){y(e,function(e){return n(e)?!0:void 0})}function o(e){s(e),p(e)&&y(e,function(e){s(e)})}function i(e){M.push(e),T||(T=!0,setTimeout(a))}function a(){T=!1;for(var e,t=M,n=0,r=t.length;r>n&&(e=t[n]);n++)e();M=[]}function s(e){S?i(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&!e.__attached&&p(e)&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function l(e){u(e),y(e,function(e){u(e)})}function u(e){S?i(function(){d(e)}):d(e)}function d(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&e.__attached&&!p(e)&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function p(e){for(var t=e,n=wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.host}}function f(e){if(e.shadowRoot&&!e.shadowRoot.__watched){b.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)w(t),t=t.olderShadowRoot}}function h(e){if(b.dom){var n=e[0];if(n&&"childList"===n.type&&n.addedNodes&&n.addedNodes){for(var r=n.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var o=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";o=o.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",e.length,o||"")}e.forEach(function(e){"childList"===e.type&&(_(e.addedNodes,function(e){e.localName&&t(e)}),_(e.removedNodes,function(e){e.localName&&l(e)}))}),b.dom&&console.groupEnd()}function m(e){for(e=wrap(e),e||(e=wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(h(t.takeRecords()),a())}function w(e){if(!e.__observer){var t=new MutationObserver(h);t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=wrap(e),b.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop()),t(e),w(e),b.dom&&console.groupEnd()}function g(e){E(e,v)}var b=e.flags,y=e.forSubtree,E=e.forDocumentTree,S=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=S;var T=!1,M=[],_=Array.prototype.forEach.call.bind(Array.prototype.forEach),L=Element.prototype.createShadowRoot;L&&(Element.prototype.createShadowRoot=function(){var e=L.call(this);return CustomElements.watchShadow(this),e}),e.watchShadow=f,e.upgradeDocumentTree=g,e.upgradeSubtree=r,e.upgradeAll=t,e.attachedNode=o,e.takeRecords=m}),CustomElements.addModule(function(e){function t(t){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),o=e.getRegisteredDefinition(r||t.localName);if(o){if(r&&o.tag==t.localName)return n(t,o);if(!r&&!o["extends"])return n(t,o)}}}function n(t,n){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),e.attachedNode(t),e.upgradeSubtree(t),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(l(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),u(c.__name,c),c.ctor=d(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&w(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t=0&&b(r,HTMLElement),r)}function h(e){var t=L.call(this,e);return v(t),t}var m,w=e.upgradeDocumentTree,v=e.upgrade,g=e.upgradeWithDefinition,b=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],S={},T="http://www.w3.org/1999/xhtml",M=document.createElement.bind(document),_=document.createElementNS.bind(document),L=Node.prototype.cloneNode;m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},document.registerElement=t,document.createElement=f,document.createElementNS=p,Node.prototype.cloneNode=h,e.registry=S,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=l,document.register=document.registerElement}),function(e){function t(){a(wrap(document)),window.HTMLImports&&(HTMLImports.__importsParsingHook=function(e){a(wrap(e["import"]))}),CustomElements.ready=!0,setTimeout(function(){CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})}var n=e.useNative,r=e.initializeModules,o=/Trident/.test(navigator.userAgent);if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t()}(window.CustomElements),function(){Function.prototype.bind||(Function.prototype.bind=function(e){var t=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.slice();return r.push.apply(r,arguments),t.apply(e,r)}})}(window.WebComponents),function(e){"use strict";function t(){window.Polymer===o&&(window.Polymer=function(){throw new Error('You tried to use polymer without loading it first. To load polymer, ')})}if(!window.performance){var n=Date.now();window.performance={now:function(){return Date.now()-n}}}window.requestAnimationFrame||(window.requestAnimationFrame=function(){var e=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame;return e?function(t){return e(function(){t(performance.now())})}:function(e){return window.setTimeout(e,1e3/60)}}()),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(){return window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||function(e){clearTimeout(e)}}());var r=[],o=function(e){"string"!=typeof e&&1===arguments.length&&Array.prototype.push.call(arguments,document._currentScript),r.push(arguments)};window.Polymer=o,e.consumeDeclarations=function(t){e.consumeDeclarations=function(){throw"Possible attempt to load Polymer twice"},t&&t(r),r=null},HTMLImports.useNative?t():addEventListener("DOMContentLoaded",t)}(window.WebComponents),function(){var e=document.createElement("style");e.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var t=document.querySelector("head");t.insertBefore(e,t.firstChild)}(window.WebComponents),function(e){window.Platform=e}(window.WebComponents); \ No newline at end of file +// @version 0.6.1 +window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){e=e.split("="),e[0]&&(t[e[0]]=e[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log&&t.log.split){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(WebComponents),WebComponents.flags.shadow&&("undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),window.ShadowDOMPolyfill={},function(e){"use strict";function t(){if("undefined"!=typeof chrome&&chrome.app&&chrome.app.runtime)return!1;if(navigator.getDeviceStorage)return!1;try{var e=new Function("return true;");return e()}catch(t){return!1}}function n(e){if(!e)throw new Error("Assertion failed")}function r(e,t){for(var n=W(t),r=0;rl;l++)c[l]=new Array(s),c[l][0]=l;for(var u=0;s>u;u++)c[0][u]=u;for(var l=1;a>l;l++)for(var u=1;s>u;u++)if(this.equals(e[t+u-1],r[o+l-1]))c[l][u]=c[l-1][u-1];else{var d=c[l-1][u]+1,p=c[l][u-1]+1;c[l][u]=p>d?d:p}return c},spliceOperationsFromEditDistances:function(e){for(var t=e.length-1,n=e[0].length-1,s=e[t][n],c=[];t>0||n>0;)if(0!=t)if(0!=n){var l,u=e[t-1][n-1],d=e[t-1][n],p=e[t][n-1];l=p>d?u>d?d:u:u>p?p:u,l==u?(u==s?c.push(r):(c.push(o),s=u),t--,n--):l==d?(c.push(a),t--,s=d):(c.push(i),n--,s=p)}else c.push(a),t--;else c.push(i),n--;return c.reverse(),c},calcSplices:function(e,n,s,c,l,u){var d=0,p=0,h=Math.min(s-n,u-l);if(0==n&&0==l&&(d=this.sharedPrefix(e,c,h)),s==e.length&&u==c.length&&(p=this.sharedSuffix(e,c,h-d)),n+=d,l+=d,s-=p,u-=p,s-n==0&&u-l==0)return[];if(n==s){for(var f=t(n,[],0);u>l;)f.removed.push(c[l++]);return[f]}if(l==u)return[t(n,[],s-n)];for(var m=this.spliceOperationsFromEditDistances(this.calcEditDistances(e,n,s,c,l,u)),f=void 0,w=[],v=n,g=l,b=0;br;r++)if(!this.equals(e[r],t[r]))return r;return n},sharedSuffix:function(e,t,n){for(var r=e.length,o=t.length,i=0;n>i&&this.equals(e[--r],t[--o]);)i++;return i},calculateSplices:function(e,t){return this.calcSplices(e,0,e.length,t,0,t.length)},equals:function(e,t){return e===t}},e.ArraySplice=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(){a=!1;var e=i.slice(0);i=[];for(var t=0;t0){for(var u=0;u0&&r.length>0;){var i=n.pop(),a=r.pop();if(i!==a)break;o=i}return o}function u(e,t,n){t instanceof G.Window&&(t=t.document);var o,i=A(t),a=A(n),s=r(n,e),o=l(i,a);o||(o=a.root);for(var c=o;c;c=c.parent)for(var u=0;u0;i--)if(!g(t[i],e,o,t,r))return!1;return!0}function w(e,t,n,r){var o=ie,i=t[0]||n;return g(i,e,o,t,r)}function v(e,t,n,r){for(var o=ae,i=1;i0&&g(n,e,o,t,r)}function g(e,t,n,r,o){var i=z.get(e);if(!i)return!0;var a=o||s(r,e);if(a===e){if(n===oe)return!0;n===ae&&(n=ie)}else if(n===ae&&!t.bubbles)return!0;if("relatedTarget"in t){var c=B(t),l=c.relatedTarget;if(l){if(l instanceof Object&&l.addEventListener){var d=V(l),p=u(t,e,d);if(p===a)return!0}else p=null;Z.set(t,p)}}J.set(t,n);var h=t.type,f=!1;$.set(t,a),X.set(t,e),i.depth++;for(var m=0,w=i.length;w>m;m++){var v=i[m];if(v.removed)f=!0;else if(!(v.type!==h||!v.capture&&n===oe||v.capture&&n===ae))try{if("function"==typeof v.handler?v.handler.call(e,t):v.handler.handleEvent(t),ee.get(t))return!1}catch(g){P||(P=g)}}if(i.depth--,f&&0===i.depth){var b=i.slice();i.length=0;for(var m=0;mr;r++)t[r]=a(e[r]);return t.length=o,t}function o(e,t){e.prototype[t]=function(){return r(i(this)[t].apply(i(this),arguments))}}var i=e.unsafeUnwrap,a=e.wrap,s={enumerable:!1};n.prototype={item:function(e){return this[e]}},t(n.prototype,"item"),e.wrappers.NodeList=n,e.addWrapNodeListMethod=o,e.wrapNodeList=r}(window.ShadowDOMPolyfill),function(e){"use strict";e.wrapHTMLCollection=e.wrapNodeList,e.wrappers.HTMLCollection=e.wrappers.NodeList}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){O(e instanceof _)}function n(e){var t=new T;return t[0]=e,t.length=1,t}function r(e,t,n){N(t,"childList",{removedNodes:n,previousSibling:e.previousSibling,nextSibling:e.nextSibling})}function o(e,t){N(e,"childList",{removedNodes:t})}function i(e,t,r,o){if(e instanceof DocumentFragment){var i=s(e);U=!0;for(var a=i.length-1;a>=0;a--)e.removeChild(i[a]),i[a].parentNode_=t;U=!1;for(var a=0;ao;o++)r.appendChild(P(t[o]));return r}function w(e){if(void 0!==e.firstChild_)for(var t=e.firstChild_;t;){var n=t;t=t.nextSibling_,n.parentNode_=n.previousSibling_=n.nextSibling_=void 0}e.firstChild_=e.lastChild_=void 0}function v(e){if(e.invalidateShadowRenderer()){for(var t=e.firstChild;t;){O(t.parentNode===e);var n=t.nextSibling,r=P(t),o=r.parentNode;o&&$.call(o,r),t.previousSibling_=t.nextSibling_=t.parentNode_=null,t=n}e.firstChild_=e.lastChild_=null}else for(var n,i=P(e),a=i.firstChild;a;)n=a.nextSibling,$.call(i,a),a=n}function g(e){var t=e.parentNode;return t&&t.invalidateShadowRenderer()}function b(e){for(var t,n=0;ns;s++)i=b(t[s]),!o&&(a=v(i).root)&&a instanceof e.wrappers.ShadowRoot||(r[n++]=i);return n}function n(e){return String(e).replace(/\/deep\/|::shadow|>>>/g," ")}function r(e){return String(e).replace(/:host\(([^\s]+)\)/g,"$1").replace(/([^\s]):host/g,"$1").replace(":host","*").replace(/\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content|>>>/g," ")}function o(e,t){for(var n,r=e.firstElementChild;r;){if(r.matches(t))return r;if(n=o(r,t))return n;r=r.nextElementSibling}return null}function i(e,t){return e.matches(t)}function a(e,t,n){var r=e.localName;return r===t||r===n&&e.namespaceURI===j}function s(){return!0}function c(e,t,n){return e.localName===n}function l(e,t){return e.namespaceURI===t}function u(e,t,n){return e.namespaceURI===t&&e.localName===n}function d(e,t,n,r,o,i){for(var a=e.firstElementChild;a;)r(a,o,i)&&(n[t++]=a),t=d(a,t,n,r,o,i),a=a.nextElementSibling;return t}function p(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,null);if(c instanceof N)s=S.call(c,i);else{if(!(c instanceof C))return d(this,r,o,n,i,null);s=_.call(c,i)}return t(s,r,o,a)}function h(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=M.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=T.call(c,i,a)}return t(s,r,o,!1)}function f(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=L.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=O.call(c,i,a)}return t(s,r,o,!1)}var m=e.wrappers.HTMLCollection,w=e.wrappers.NodeList,v=e.getTreeScope,g=e.unsafeUnwrap,b=e.wrap,y=document.querySelector,E=document.documentElement.querySelector,_=document.querySelectorAll,S=document.documentElement.querySelectorAll,T=document.getElementsByTagName,M=document.documentElement.getElementsByTagName,O=document.getElementsByTagNameNS,L=document.documentElement.getElementsByTagNameNS,N=window.Element,C=window.HTMLDocument||window.Document,j="http://www.w3.org/1999/xhtml",D={querySelector:function(t){var r=n(t),i=r!==t;t=r;var a,s=g(this),c=v(this).root;if(c instanceof e.wrappers.ShadowRoot)return o(this,t);if(s instanceof N)a=b(E.call(s,t));else{if(!(s instanceof C))return o(this,t);a=b(y.call(s,t))}return a&&!i&&(c=v(a).root)&&c instanceof e.wrappers.ShadowRoot?o(this,t):a; + +},querySelectorAll:function(e){var t=n(e),r=t!==e;e=t;var o=new w;return o.length=p.call(this,i,0,o,e,r),o}},H={matches:function(t){return t=r(t),e.originalMatches.call(g(this),t)}},x={getElementsByTagName:function(e){var t=new m,n="*"===e?s:a;return t.length=h.call(this,n,0,t,e,e.toLowerCase()),t},getElementsByClassName:function(e){return this.querySelectorAll("."+e)},getElementsByTagNameNS:function(e,t){var n=new m,r=null;return r="*"===e?"*"===t?s:c:"*"===t?l:u,n.length=f.call(this,r,0,n,e||null,t),n}};e.GetElementsByInterface=x,e.SelectorsInterface=D,e.MatchesInterface=H}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;return e}function n(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.previousSibling;return e}var r=e.wrappers.NodeList,o={get firstElementChild(){return t(this.firstChild)},get lastElementChild(){return n(this.lastChild)},get childElementCount(){for(var e=0,t=this.firstElementChild;t;t=t.nextElementSibling)e++;return e},get children(){for(var e=new r,t=0,n=this.firstElementChild;n;n=n.nextElementSibling)e[t++]=n;return e.length=t,e},remove:function(){var e=this.parentNode;e&&e.removeChild(this)}},i={get nextElementSibling(){return t(this.nextSibling)},get previousElementSibling(){return n(this.previousSibling)}};e.ChildNodeInterface=i,e.ParentNodeInterface=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}var n=e.ChildNodeInterface,r=e.wrappers.Node,o=e.enqueueMutation,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=window.CharacterData;t.prototype=Object.create(r.prototype),i(t.prototype,{get nodeValue(){return this.data},set nodeValue(e){this.data=e},get textContent(){return this.data},set textContent(e){this.data=e},get data(){return s(this).data},set data(e){var t=s(this).data;o(this,"characterData",{oldValue:t}),s(this).data=e}}),i(t.prototype,n),a(c,t,document.createTextNode("")),e.wrappers.CharacterData=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e>>>0}function n(e){r.call(this,e)}var r=e.wrappers.CharacterData,o=(e.enqueueMutation,e.mixin),i=e.registerWrapper,a=window.Text;n.prototype=Object.create(r.prototype),o(n.prototype,{splitText:function(e){e=t(e);var n=this.data;if(e>n.length)throw new Error("IndexSizeError");var r=n.slice(0,e),o=n.slice(e);this.data=r;var i=this.ownerDocument.createTextNode(o);return this.parentNode&&this.parentNode.insertBefore(i,this.nextSibling),i}}),i(a,n,document.createTextNode("")),e.wrappers.Text=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return i(e).getAttribute("class")}function n(e,t){a(e,"attributes",{name:"class",namespace:null,oldValue:t})}function r(t){e.invalidateRendererBasedOnAttribute(t,"class")}function o(e,o,i){var a=e.ownerElement_;if(null==a)return o.apply(e,i);var s=t(a),c=o.apply(e,i);return t(a)!==s&&(n(a,s),r(a)),c}if(!window.DOMTokenList)return void console.warn("Missing DOMTokenList prototype, please include a compatible classList polyfill such as http://goo.gl/uTcepH.");var i=e.unsafeUnwrap,a=e.enqueueMutation,s=DOMTokenList.prototype.add;DOMTokenList.prototype.add=function(){o(this,s,arguments)};var c=DOMTokenList.prototype.remove;DOMTokenList.prototype.remove=function(){o(this,c,arguments)};var l=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(){return o(this,l,arguments)}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t,n){var r=t.parentNode;if(r&&r.shadowRoot){var o=e.getRendererForHost(r);o.dependsOnAttribute(n)&&o.invalidate()}}function n(e,t,n){u(e,"attributes",{name:t,namespace:null,oldValue:n})}function r(e){a.call(this,e)}var o=e.ChildNodeInterface,i=e.GetElementsByInterface,a=e.wrappers.Node,s=e.ParentNodeInterface,c=e.SelectorsInterface,l=e.MatchesInterface,u=(e.addWrapNodeListMethod,e.enqueueMutation),d=e.mixin,p=(e.oneOf,e.registerWrapper),h=e.unsafeUnwrap,f=e.wrappers,m=window.Element,w=["matches","mozMatchesSelector","msMatchesSelector","webkitMatchesSelector"].filter(function(e){return m.prototype[e]}),v=w[0],g=m.prototype[v],b=new WeakMap;r.prototype=Object.create(a.prototype),d(r.prototype,{createShadowRoot:function(){var t=new f.ShadowRoot(this);h(this).polymerShadowRoot_=t;var n=e.getRendererForHost(this);return n.invalidate(),t},get shadowRoot(){return h(this).polymerShadowRoot_||null},setAttribute:function(e,r){var o=h(this).getAttribute(e);h(this).setAttribute(e,r),n(this,e,o),t(this,e)},removeAttribute:function(e){var r=h(this).getAttribute(e);h(this).removeAttribute(e),n(this,e,r),t(this,e)},get classList(){var e=b.get(this);if(!e){if(e=h(this).classList,!e)return;e.ownerElement_=this,b.set(this,e)}return e},get className(){return h(this).className},set className(e){this.setAttribute("class",e)},get id(){return h(this).id},set id(e){this.setAttribute("id",e)}}),w.forEach(function(e){"matches"!==e&&(r.prototype[e]=function(e){return this.matches(e)})}),m.prototype.webkitCreateShadowRoot&&(r.prototype.webkitCreateShadowRoot=r.prototype.createShadowRoot),d(r.prototype,o),d(r.prototype,i),d(r.prototype,s),d(r.prototype,c),d(r.prototype,l),p(m,r,document.createElementNS(null,"x")),e.invalidateRendererBasedOnAttribute=t,e.matchesNames=w,e.originalMatches=g,e.wrappers.Element=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";case" ":return" "}}function n(e){return e.replace(O,t)}function r(e){return e.replace(L,t)}function o(e){for(var t={},n=0;n";case Node.TEXT_NODE:var u=e.data;return t&&C[t.localName]?u:r(u);case Node.COMMENT_NODE:return"";default:throw console.error(e),new Error("not implemented")}}function a(e){e instanceof M.HTMLTemplateElement&&(e=e.content);for(var t="",n=e.firstChild;n;n=n.nextSibling)t+=i(n,e);return t}function s(e,t,n){var r=n||"div";e.textContent="";var o=S(e.ownerDocument.createElement(r));o.innerHTML=t;for(var i;i=o.firstChild;)e.appendChild(T(i))}function c(e){f.call(this,e)}function l(e,t){var n=S(e.cloneNode(!1));n.innerHTML=t;for(var r,o=S(document.createDocumentFragment());r=n.firstChild;)o.appendChild(r);return T(o)}function u(t){return function(){return e.renderAllPending(),_(this)[t]}}function d(e){m(c,e,u(e))}function p(t){Object.defineProperty(c.prototype,t,{get:u(t),set:function(n){e.renderAllPending(),_(this)[t]=n},configurable:!0,enumerable:!0})}function h(t){Object.defineProperty(c.prototype,t,{value:function(){return e.renderAllPending(),_(this)[t].apply(_(this),arguments)},configurable:!0,enumerable:!0})}var f=e.wrappers.Element,m=e.defineGetter,w=e.enqueueMutation,v=e.mixin,g=e.nodesWereAdded,b=e.nodesWereRemoved,y=e.registerWrapper,E=e.snapshotNodeList,_=e.unsafeUnwrap,S=e.unwrap,T=e.wrap,M=e.wrappers,O=/[&\u00A0"]/g,L=/[&\u00A0<>]/g,N=o(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),C=o(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]),j=/MSIE/.test(navigator.userAgent),D=window.HTMLElement,H=window.HTMLTemplateElement;c.prototype=Object.create(f.prototype),v(c.prototype,{get innerHTML(){return a(this)},set innerHTML(e){if(j&&C[this.localName])return void(this.textContent=e);var t=E(this.childNodes);this.invalidateShadowRenderer()?this instanceof M.HTMLTemplateElement?s(this.content,e):s(this,e,this.tagName):!H&&this instanceof M.HTMLTemplateElement?s(this.content,e):_(this).innerHTML=e;var n=E(this.childNodes);w(this,"childList",{addedNodes:n,removedNodes:t}),b(t),g(n,this)},get outerHTML(){return i(this,this.parentNode)},set outerHTML(e){var t=this.parentNode;if(t){t.invalidateShadowRenderer();var n=l(t,e);t.replaceChild(n,this)}},insertAdjacentHTML:function(e,t){var n,r;switch(String(e).toLowerCase()){case"beforebegin":n=this.parentNode,r=this;break;case"afterend":n=this.parentNode,r=this.nextSibling;break;case"afterbegin":n=this,r=this.firstChild;break;case"beforeend":n=this,r=null;break;default:return}var o=l(n,t);n.insertBefore(o,r)},get hidden(){return this.hasAttribute("hidden")},set hidden(e){e?this.setAttribute("hidden",""):this.removeAttribute("hidden")}}),["clientHeight","clientLeft","clientTop","clientWidth","offsetHeight","offsetLeft","offsetTop","offsetWidth","scrollHeight","scrollWidth"].forEach(d),["scrollLeft","scrollTop"].forEach(p),["getBoundingClientRect","getClientRects","scrollIntoView"].forEach(h),y(D,c,document.createElement("b")),e.wrappers.HTMLElement=c,e.getInnerHTML=a,e.setInnerHTML=s}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.HTMLCanvasElement;t.prototype=Object.create(n.prototype),r(t.prototype,{getContext:function(){var e=i(this).getContext.apply(i(this),arguments);return e&&a(e)}}),o(s,t,document.createElement("canvas")),e.wrappers.HTMLCanvasElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=window.HTMLContentElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get select(){return this.getAttribute("select")},set select(e){this.setAttribute("select",e)},setAttribute:function(e,t){n.prototype.setAttribute.call(this,e,t),"select"===String(e).toLowerCase()&&this.invalidateShadowRenderer(!0)}}),i&&o(i,t),e.wrappers.HTMLContentElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=window.HTMLFormElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get elements(){return i(a(this).elements)}}),o(s,t,document.createElement("form")),e.wrappers.HTMLFormElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e,t){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var o=i(document.createElement("img"));r.call(this,o),a(o,this),void 0!==e&&(o.width=e),void 0!==t&&(o.height=t)}var r=e.wrappers.HTMLElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLImageElement;t.prototype=Object.create(r.prototype),o(s,t,document.createElement("img")),n.prototype=t.prototype,e.wrappers.HTMLImageElement=t,e.wrappers.Image=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=(e.mixin,e.wrappers.NodeList,e.registerWrapper),o=window.HTMLShadowElement;t.prototype=Object.create(n.prototype),t.prototype.constructor=t,o&&r(o,t),e.wrappers.HTMLShadowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){if(!e.defaultView)return e;var t=d.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);d.set(e,t)}return t}function n(e){for(var n,r=t(e.ownerDocument),o=c(r.createDocumentFragment());n=e.firstChild;)o.appendChild(n);return o}function r(e){if(o.call(this,e),!p){var t=n(e);u.set(this,l(t))}}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.wrap,u=new WeakMap,d=new WeakMap,p=window.HTMLTemplateElement;r.prototype=Object.create(o.prototype),i(r.prototype,{constructor:r,get content(){return p?l(s(this).content):u.get(this)}}),p&&a(p,r),e.wrappers.HTMLTemplateElement=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.registerWrapper,o=window.HTMLMediaElement;o&&(t.prototype=Object.create(n.prototype),r(o,t,document.createElement("audio")),e.wrappers.HTMLMediaElement=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var t=i(document.createElement("audio"));r.call(this,t),a(t,this),t.setAttribute("preload","auto"),void 0!==e&&t.setAttribute("src",e)}var r=e.wrappers.HTMLMediaElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLAudioElement;s&&(t.prototype=Object.create(r.prototype),o(s,t,document.createElement("audio")),n.prototype=t.prototype,e.wrappers.HTMLAudioElement=t,e.wrappers.Audio=n)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e.replace(/\s+/g," ").trim()}function n(e){o.call(this,e)}function r(e,t,n,i){if(!(this instanceof r))throw new TypeError("DOM object constructor cannot be called as a function.");var a=c(document.createElement("option"));o.call(this,a),s(a,this),void 0!==e&&(a.text=e),void 0!==t&&a.setAttribute("value",t),n===!0&&a.setAttribute("selected",""),a.selected=i===!0}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.rewrap,c=e.unwrap,l=e.wrap,u=window.HTMLOptionElement;n.prototype=Object.create(o.prototype),i(n.prototype,{get text(){return t(this.textContent)},set text(e){this.textContent=t(String(e))},get form(){return l(c(this).form)}}),a(u,n,document.createElement("option")),r.prototype=n.prototype,e.wrappers.HTMLOptionElement=n,e.wrappers.Option=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=window.HTMLSelectElement;t.prototype=Object.create(n.prototype),r(t.prototype,{add:function(e,t){"object"==typeof t&&(t=i(t)),i(this).add(i(e),t)},remove:function(e){return void 0===e?void n.prototype.remove.call(this):("object"==typeof e&&(e=i(e)),void i(this).remove(e))},get form(){return a(i(this).form)}}),o(s,t,document.createElement("select")),e.wrappers.HTMLSelectElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=e.wrapHTMLCollection,c=window.HTMLTableElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get caption(){return a(i(this).caption)},createCaption:function(){return a(i(this).createCaption())},get tHead(){return a(i(this).tHead)},createTHead:function(){return a(i(this).createTHead())},createTFoot:function(){return a(i(this).createTFoot())},get tFoot(){return a(i(this).tFoot)},get tBodies(){return s(i(this).tBodies)},createTBody:function(){return a(i(this).createTBody())},get rows(){return s(i(this).rows)},insertRow:function(e){return a(i(this).insertRow(e))}}),o(c,t,document.createElement("table")),e.wrappers.HTMLTableElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableSectionElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get rows(){return i(a(this).rows)},insertRow:function(e){return s(a(this).insertRow(e))}}),o(c,t,document.createElement("thead")),e.wrappers.HTMLTableSectionElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableRowElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get cells(){return i(a(this).cells)},insertCell:function(e){return s(a(this).insertCell(e))}}),o(c,t,document.createElement("tr")),e.wrappers.HTMLTableRowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e.localName){case"content":return new n(e);case"shadow":return new o(e);case"template":return new i(e)}r.call(this,e)}var n=e.wrappers.HTMLContentElement,r=e.wrappers.HTMLElement,o=e.wrappers.HTMLShadowElement,i=e.wrappers.HTMLTemplateElement,a=(e.mixin,e.registerWrapper),s=window.HTMLUnknownElement;t.prototype=Object.create(r.prototype),a(s,t),e.wrappers.HTMLUnknownElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.wrappers.Element,n=e.wrappers.HTMLElement,r=e.registerObject,o=e.defineWrapGetter,i="http://www.w3.org/2000/svg",a=document.createElementNS(i,"title"),s=r(a),c=Object.getPrototypeOf(s.prototype).constructor;if(!("classList"in a)){var l=Object.getOwnPropertyDescriptor(t.prototype,"classList");Object.defineProperty(n.prototype,"classList",l),delete t.prototype.classList}o(c,"ownerSVGElement"),e.wrappers.SVGElement=c}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){p.call(this,e)}var n=e.mixin,r=e.registerWrapper,o=e.unwrap,i=e.wrap,a=window.SVGUseElement,s="http://www.w3.org/2000/svg",c=i(document.createElementNS(s,"g")),l=document.createElementNS(s,"use"),u=c.constructor,d=Object.getPrototypeOf(u.prototype),p=d.constructor;t.prototype=Object.create(d),"instanceRoot"in l&&n(t.prototype,{get instanceRoot(){return i(o(this).instanceRoot)},get animatedInstanceRoot(){return i(o(this).animatedInstanceRoot)}}),r(a,t,l),e.wrappers.SVGUseElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.SVGElementInstance;s&&(t.prototype=Object.create(n.prototype),r(t.prototype,{get correspondingElement(){return a(i(this).correspondingElement)},get correspondingUseElement(){return a(i(this).correspondingUseElement)},get parentNode(){return a(i(this).parentNode)},get childNodes(){throw new Error("Not implemented")},get firstChild(){return a(i(this).firstChild)},get lastChild(){return a(i(this).lastChild)},get previousSibling(){return a(i(this).previousSibling)},get nextSibling(){return a(i(this).nextSibling)}}),o(s,t),e.wrappers.SVGElementInstance=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.CanvasRenderingContext2D;n(t.prototype,{get canvas(){return c(i(this).canvas)},drawImage:function(){arguments[0]=s(arguments[0]),i(this).drawImage.apply(i(this),arguments)},createPattern:function(){return arguments[0]=a(arguments[0]),i(this).createPattern.apply(i(this),arguments)}}),r(l,t,document.createElement("canvas").getContext("2d")),e.wrappers.CanvasRenderingContext2D=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrapIfNeeded,s=e.wrap,c=window.WebGLRenderingContext;if(c){n(t.prototype,{get canvas(){return s(i(this).canvas)},texImage2D:function(){arguments[5]=a(arguments[5]),i(this).texImage2D.apply(i(this),arguments)},texSubImage2D:function(){arguments[6]=a(arguments[6]),i(this).texSubImage2D.apply(i(this),arguments)}});var l=/WebKit/.test(navigator.userAgent)?{drawingBufferHeight:null,drawingBufferWidth:null}:{};r(c,t,l),e.wrappers.WebGLRenderingContext=t}}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.GetElementsByInterface,n=e.ParentNodeInterface,r=e.SelectorsInterface,o=e.mixin,i=e.registerObject,a=i(document.createDocumentFragment());o(a.prototype,n),o(a.prototype,r),o(a.prototype,t);var s=i(document.createComment(""));e.wrappers.Comment=s,e.wrappers.DocumentFragment=a}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(u(e).ownerDocument.createDocumentFragment());n.call(this,t),c(t,this);var o=e.shadowRoot;h.set(this,o),this.treeScope_=new r(this,a(o||e)),p.set(this,e)}var n=e.wrappers.DocumentFragment,r=e.TreeScope,o=e.elementFromPoint,i=e.getInnerHTML,a=e.getTreeScope,s=e.mixin,c=e.rewrap,l=e.setInnerHTML,u=e.unsafeUnwrap,d=e.unwrap,p=new WeakMap,h=new WeakMap,f=/[ \t\n\r\f]/;t.prototype=Object.create(n.prototype),s(t.prototype,{constructor:t,get innerHTML(){return i(this)},set innerHTML(e){l(this,e),this.invalidateShadowRenderer()},get olderShadowRoot(){return h.get(this)||null},get host(){return p.get(this)||null},invalidateShadowRenderer:function(){return p.get(this).invalidateShadowRenderer()},elementFromPoint:function(e,t){return o(this,this.ownerDocument,e,t)},getElementById:function(e){return f.test(e)?null:this.querySelector('[id="'+e+'"]')}}),e.wrappers.ShadowRoot=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(e).root;return t instanceof h?t.host:null}function n(t,n){if(t.shadowRoot){n=Math.min(t.childNodes.length-1,n);var r=t.childNodes[n];if(r){var o=e.getDestinationInsertionPoints(r);if(o.length>0){var i=o[0].parentNode;i.nodeType==Node.ELEMENT_NODE&&(t=i)}}}return t}function r(e){return e=u(e),t(e)||e}function o(e){a(e,this)}var i=e.registerWrapper,a=e.setWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.unwrapIfNeeded,u=e.wrap,d=e.getTreeScope,p=window.Range,h=e.wrappers.ShadowRoot;o.prototype={get startContainer(){return r(s(this).startContainer)},get endContainer(){return r(s(this).endContainer)},get commonAncestorContainer(){return r(s(this).commonAncestorContainer)},setStart:function(e,t){e=n(e,t),s(this).setStart(l(e),t)},setEnd:function(e,t){e=n(e,t),s(this).setEnd(l(e),t)},setStartBefore:function(e){s(this).setStartBefore(l(e))},setStartAfter:function(e){s(this).setStartAfter(l(e))},setEndBefore:function(e){s(this).setEndBefore(l(e))},setEndAfter:function(e){s(this).setEndAfter(l(e))},selectNode:function(e){s(this).selectNode(l(e))},selectNodeContents:function(e){s(this).selectNodeContents(l(e))},compareBoundaryPoints:function(e,t){return s(this).compareBoundaryPoints(e,c(t))},extractContents:function(){return u(s(this).extractContents())},cloneContents:function(){return u(s(this).cloneContents())},insertNode:function(e){s(this).insertNode(l(e))},surroundContents:function(e){s(this).surroundContents(l(e))},cloneRange:function(){return u(s(this).cloneRange())},isPointInRange:function(e,t){return s(this).isPointInRange(l(e),t)},comparePoint:function(e,t){return s(this).comparePoint(l(e),t)},intersectsNode:function(e){return s(this).intersectsNode(l(e))},toString:function(){return s(this).toString()}},p.prototype.createContextualFragment&&(o.prototype.createContextualFragment=function(e){return u(s(this).createContextualFragment(e))}),i(window.Range,o,document.createRange()),e.wrappers.Range=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.previousSibling_=e.previousSibling,e.nextSibling_=e.nextSibling,e.parentNode_=e.parentNode}function n(n,o,i){var a=x(n),s=x(o),c=i?x(i):null;if(r(o),t(o),i)n.firstChild===i&&(n.firstChild_=i),i.previousSibling_=i.previousSibling;else{n.lastChild_=n.lastChild,n.lastChild===n.firstChild&&(n.firstChild_=n.firstChild);var l=R(a.lastChild);l&&(l.nextSibling_=l.nextSibling)}e.originalInsertBefore.call(a,s,c)}function r(n){var r=x(n),o=r.parentNode;if(o){var i=R(o);t(n),n.previousSibling&&(n.previousSibling.nextSibling_=n),n.nextSibling&&(n.nextSibling.previousSibling_=n),i.lastChild===n&&(i.lastChild_=n),i.firstChild===n&&(i.firstChild_=n),e.originalRemoveChild.call(o,r)}}function o(e){P.set(e,[])}function i(e){var t=P.get(e);return t||P.set(e,t=[]),t}function a(e){for(var t=[],n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t}function s(){for(var e=0;em;m++){var w=R(i[u++]);s.get(w)||r(w)}for(var v=h.addedCount,g=i[u]&&R(i[u]),m=0;v>m;m++){var b=o[l++],y=b.node;n(t,y,g),s.set(y,!0),b.sync(s)}d+=v}for(var p=d;p=0;o--){var i=r[o],a=m(i);if(a){var s=i.olderShadowRoot;s&&(n=f(s));for(var c=0;c=0;u--)l=Object.create(l);["createdCallback","attachedCallback","detachedCallback","attributeChangedCallback"].forEach(function(e){var t=o[e];t&&(l[e]=function(){N(this)instanceof r||T(this),t.apply(N(this),arguments)})});var d={prototype:l};i&&(d["extends"]=i),r.prototype=o,r.prototype.constructor=r,e.constructorTable.set(l,r),e.nativePrototypeTable.set(o,l);I.call(L(this),t,d);return r},b([window.HTMLDocument||window.Document],["registerElement"])}b([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement,window.HTMLHtmlElement],["appendChild","compareDocumentPosition","contains","getElementsByClassName","getElementsByTagName","getElementsByTagNameNS","insertBefore","querySelector","querySelectorAll","removeChild","replaceChild"]),b([window.HTMLBodyElement,window.HTMLHeadElement,window.HTMLHtmlElement],y),b([window.HTMLDocument||window.Document],["adoptNode","importNode","contains","createComment","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createRange","createTextNode","createTreeWalker","elementFromPoint","getElementById","getElementsByName","getSelection"]),E(t.prototype,l),E(t.prototype,d),E(t.prototype,h),E(t.prototype,{get implementation(){var e=j.get(this);return e?e:(e=new a(L(this).implementation),j.set(this,e),e)},get defaultView(){return N(L(this).defaultView)}}),_(window.Document,t,document.implementation.createHTMLDocument("")),window.HTMLDocument&&_(window.HTMLDocument,t),C([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement]),s(a,"createDocumentType"),s(a,"createDocument"),s(a,"createHTMLDocument"),c(a,"hasFeature"),_(window.DOMImplementation,a),b([window.DOMImplementation],["createDocumentType","createDocument","createHTMLDocument","hasFeature"]),e.adoptNodeNoRemove=r,e.wrappers.DOMImplementation=a,e.wrappers.Document=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.wrappers.Selection,o=e.mixin,i=e.registerWrapper,a=e.renderAllPending,s=e.unwrap,c=e.unwrapIfNeeded,l=e.wrap,u=window.Window,d=window.getComputedStyle,p=window.getDefaultComputedStyle,h=window.getSelection;t.prototype=Object.create(n.prototype),u.prototype.getComputedStyle=function(e,t){return l(this||window).getComputedStyle(c(e),t)},p&&(u.prototype.getDefaultComputedStyle=function(e,t){return l(this||window).getDefaultComputedStyle(c(e),t)}),u.prototype.getSelection=function(){return l(this||window).getSelection()},delete window.getComputedStyle,delete window.getDefaultComputedStyle,delete window.getSelection,["addEventListener","removeEventListener","dispatchEvent"].forEach(function(e){u.prototype[e]=function(){var t=l(this||window);return t[e].apply(t,arguments)},delete window[e]}),o(t.prototype,{getComputedStyle:function(e,t){return a(),d.call(s(this),c(e),t)},getSelection:function(){return a(),new r(h.call(s(this)))},get document(){return l(s(this).document)}}),p&&(t.prototype.getDefaultComputedStyle=function(e,t){return a(),p.call(s(this),c(e),t)}),i(u,t,window),e.wrappers.Window=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrap,n=window.DataTransfer||window.Clipboard,r=n.prototype.setDragImage;r&&(n.prototype.setDragImage=function(e,n,o){r.call(this,t(e),n,o)})}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t;t=e instanceof i?e:new i(e&&o(e)),r(t,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unwrap,i=window.FormData;i&&(n(i,t,new i),e.wrappers.FormData=t)}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrapIfNeeded,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(e){return n.call(this,t(e))}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=n[e],r=window[t];if(r){var o=document.createElement(e),i=o.constructor;window[t]=i}}var n=(e.isWrapperFor,{a:"HTMLAnchorElement",area:"HTMLAreaElement",audio:"HTMLAudioElement",base:"HTMLBaseElement",body:"HTMLBodyElement",br:"HTMLBRElement",button:"HTMLButtonElement",canvas:"HTMLCanvasElement",caption:"HTMLTableCaptionElement",col:"HTMLTableColElement",content:"HTMLContentElement",data:"HTMLDataElement",datalist:"HTMLDataListElement",del:"HTMLModElement",dir:"HTMLDirectoryElement",div:"HTMLDivElement",dl:"HTMLDListElement",embed:"HTMLEmbedElement",fieldset:"HTMLFieldSetElement",font:"HTMLFontElement",form:"HTMLFormElement",frame:"HTMLFrameElement",frameset:"HTMLFrameSetElement",h1:"HTMLHeadingElement",head:"HTMLHeadElement",hr:"HTMLHRElement",html:"HTMLHtmlElement",iframe:"HTMLIFrameElement",img:"HTMLImageElement",input:"HTMLInputElement",keygen:"HTMLKeygenElement",label:"HTMLLabelElement",legend:"HTMLLegendElement",li:"HTMLLIElement",link:"HTMLLinkElement",map:"HTMLMapElement",marquee:"HTMLMarqueeElement",menu:"HTMLMenuElement",menuitem:"HTMLMenuItemElement",meta:"HTMLMetaElement",meter:"HTMLMeterElement",object:"HTMLObjectElement",ol:"HTMLOListElement",optgroup:"HTMLOptGroupElement",option:"HTMLOptionElement",output:"HTMLOutputElement",p:"HTMLParagraphElement",param:"HTMLParamElement",pre:"HTMLPreElement",progress:"HTMLProgressElement",q:"HTMLQuoteElement",script:"HTMLScriptElement",select:"HTMLSelectElement",shadow:"HTMLShadowElement",source:"HTMLSourceElement",span:"HTMLSpanElement",style:"HTMLStyleElement",table:"HTMLTableElement",tbody:"HTMLTableSectionElement",template:"HTMLTemplateElement",textarea:"HTMLTextAreaElement",thead:"HTMLTableSectionElement",time:"HTMLTimeElement",title:"HTMLTitleElement",tr:"HTMLTableRowElement",track:"HTMLTrackElement",ul:"HTMLUListElement",video:"HTMLVideoElement"});Object.keys(n).forEach(t),Object.getOwnPropertyNames(e.wrappers).forEach(function(t){window[t]=e.wrappers[t]})}(window.ShadowDOMPolyfill),function(e){function t(e,t){var n="";return Array.prototype.forEach.call(e,function(e){n+=e.textContent+"\n\n"}),t||(n=n.replace(d,"")),n}function n(e){var t=document.createElement("style");return t.textContent=e,t}function r(e){var t=n(e);document.head.appendChild(t);var r=[];if(t.sheet)try{r=t.sheet.cssRules}catch(o){}else console.warn("sheet not found",t);return t.parentNode.removeChild(t),r}function o(){C.initialized=!0,document.body.appendChild(C);var e=C.contentDocument,t=e.createElement("base");t.href=document.baseURI,e.head.appendChild(t)}function i(e){C.initialized||o(),document.body.appendChild(C),e(C.contentDocument),document.body.removeChild(C)}function a(e,t){if(t){var o;if(e.match("@import")&&D){var a=n(e);i(function(e){e.head.appendChild(a.impl),o=Array.prototype.slice.call(a.sheet.cssRules,0),t(o)})}else o=r(e),t(o)}}function s(e){e&&l().appendChild(document.createTextNode(e))}function c(e,t){var r=n(e);r.setAttribute(t,""),r.setAttribute(x,""),document.head.appendChild(r)}function l(){return j||(j=document.createElement("style"),j.setAttribute(x,""),j[x]=!0),j}var u={strictStyling:!1,registry:{},shimStyling:function(e,n,r){var o=this.prepareRoot(e,n,r),i=this.isTypeExtension(r),a=this.makeScopeSelector(n,i),s=t(o,!0);s=this.scopeCssText(s,a),e&&(e.shimmedStyle=s),this.addCssToDocument(s,n)},shimStyle:function(e,t){return this.shimCssText(e.textContent,t)},shimCssText:function(e,t){return e=this.insertDirectives(e),this.scopeCssText(e,t)},makeScopeSelector:function(e,t){return e?t?"[is="+e+"]":e:""},isTypeExtension:function(e){return e&&e.indexOf("-")<0},prepareRoot:function(e,t,n){var r=this.registerRoot(e,t,n);return this.replaceTextInStyles(r.rootStyles,this.insertDirectives),this.removeStyles(e,r.rootStyles),this.strictStyling&&this.applyScopeToContent(e,t),r.scopeStyles},removeStyles:function(e,t){for(var n,r=0,o=t.length;o>r&&(n=t[r]);r++)n.parentNode.removeChild(n)},registerRoot:function(e,t,n){var r=this.registry[t]={root:e,name:t,extendsName:n},o=this.findStyles(e);r.rootStyles=o,r.scopeStyles=r.rootStyles;var i=this.registry[r.extendsName];return i&&(r.scopeStyles=i.scopeStyles.concat(r.scopeStyles)),r},findStyles:function(e){if(!e)return[];var t=e.querySelectorAll("style");return Array.prototype.filter.call(t,function(e){return!e.hasAttribute(R)})},applyScopeToContent:function(e,t){e&&(Array.prototype.forEach.call(e.querySelectorAll("*"),function(e){e.setAttribute(t,"")}),Array.prototype.forEach.call(e.querySelectorAll("template"),function(e){this.applyScopeToContent(e.content,t)},this))},insertDirectives:function(e){return e=this.insertPolyfillDirectivesInCssText(e),this.insertPolyfillRulesInCssText(e)},insertPolyfillDirectivesInCssText:function(e){return e=e.replace(p,function(e,t){return t.slice(0,-2)+"{"}),e.replace(h,function(e,t){return t+" {"})},insertPolyfillRulesInCssText:function(e){return e=e.replace(f,function(e,t){return t.slice(0,-1)}),e.replace(m,function(e,t,n,r){var o=e.replace(t,"").replace(n,"");return r+o})},scopeCssText:function(e,t){var n=this.extractUnscopedRulesFromCssText(e);if(e=this.insertPolyfillHostInCssText(e),e=this.convertColonHost(e),e=this.convertColonHostContext(e),e=this.convertShadowDOMSelectors(e),t){var e,r=this;a(e,function(n){e=r.scopeRules(n,t)})}return e=e+"\n"+n,e.trim()},extractUnscopedRulesFromCssText:function(e){for(var t,n="";t=w.exec(e);)n+=t[1].slice(0,-1)+"\n\n";for(;t=v.exec(e);)n+=t[0].replace(t[2],"").replace(t[1],t[3])+"\n\n";return n},convertColonHost:function(e){return this.convertColonRule(e,E,this.colonHostPartReplacer)},convertColonHostContext:function(e){return this.convertColonRule(e,_,this.colonHostContextPartReplacer)},convertColonRule:function(e,t,n){return e.replace(t,function(e,t,r,o){if(t=O,r){for(var i,a=r.split(","),s=[],c=0,l=a.length;l>c&&(i=a[c]);c++)i=i.trim(),s.push(n(t,i,o));return s.join(",")}return t+o})},colonHostContextPartReplacer:function(e,t,n){return t.match(g)?this.colonHostPartReplacer(e,t,n):e+t+n+", "+t+" "+e+n},colonHostPartReplacer:function(e,t,n){return e+t.replace(g,"")+n},convertShadowDOMSelectors:function(e){for(var t=0;t","+","~"],r=e,o="["+t+"]";return n.forEach(function(e){var t=r.split(e);r=t.map(function(e){var t=e.trim().replace(L,"");return t&&n.indexOf(t)<0&&t.indexOf(o)<0&&(e=t.replace(/([^:]*)(:*)(.*)/,"$1"+o+"$2$3")),e}).join(e)}),r},insertPolyfillHostInCssText:function(e){return e.replace(M,b).replace(T,g)},propertiesFromRule:function(e){var t=e.style.cssText;e.style.content&&!e.style.content.match(/['"]+|attr/)&&(t=t.replace(/content:[^;]*;/g,"content: '"+e.style.content+"';"));var n=e.style;for(var r in n)"initial"===n[r]&&(t+=r+": initial; ");return t},replaceTextInStyles:function(e,t){e&&t&&(e instanceof Array||(e=[e]),Array.prototype.forEach.call(e,function(e){e.textContent=t.call(this,e.textContent)},this))},addCssToDocument:function(e,t){e.match("@import")?c(e,t):s(e)}},d=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,p=/\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,h=/polyfill-next-selector[^}]*content\:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim,f=/\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,m=/(polyfill-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,w=/\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,v=/(polyfill-unscoped-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,g="-shadowcsshost",b="-shadowcsscontext",y=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",E=new RegExp("("+g+y,"gim"),_=new RegExp("("+b+y,"gim"),S="([>\\s~+[.,{:][\\s\\S]*)?$",T=/\:host/gim,M=/\:host-context/gim,O=g+"-no-combinator",L=new RegExp(g,"gim"),N=(new RegExp(b,"gim"),[/>>>/g,/::shadow/g,/::content/g,/\/deep\//g,/\/shadow\//g,/\/shadow-deep\//g,/\^\^/g,/\^/g]),C=document.createElement("iframe");C.style.display="none";var j,D=navigator.userAgent.match("Chrome"),H="shim-shadowdom",x="shim-shadowdom-css",R="no-shim";if(window.ShadowDOMPolyfill){s("style { display: none !important; }\n");var I=ShadowDOMPolyfill.wrap(document),P=I.querySelector("head");P.insertBefore(l(),P.childNodes[0]),document.addEventListener("DOMContentLoaded",function(){e.urlResolver;if(window.HTMLImports&&!HTMLImports.useNative){var t="link[rel=stylesheet]["+H+"]",n="style["+H+"]";HTMLImports.importer.documentPreloadSelectors+=","+t,HTMLImports.importer.importsPreloadSelectors+=","+t,HTMLImports.parser.documentSelectors=[HTMLImports.parser.documentSelectors,t,n].join(",");var r=HTMLImports.parser.parseGeneric;HTMLImports.parser.parseGeneric=function(e){if(!e[x]){var t=e.__importElement||e;if(!t.hasAttribute(H))return void r.call(this,e);e.__resource&&(t=e.ownerDocument.createElement("style"),t.textContent=e.__resource),HTMLImports.path.resolveUrlsInStyle(t,e.href),t.textContent=u.shimStyle(t),t.removeAttribute(H,""),t.setAttribute(x,""),t[x]=!0,t.parentNode!==P&&(e.parentNode===P?P.replaceChild(t,e):this.addElementToDocument(t)),t.__importParsed=!0,this.markParsingComplete(e),this.parseNext()}};var o=HTMLImports.parser.hasResource;HTMLImports.parser.hasResource=function(e){return"link"===e.localName&&"stylesheet"===e.rel&&e.hasAttribute(H)?e.__resource:o.call(this,e)}}})}e.ShadowCSS=u}(window.WebComponents)),function(e){window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==p[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function a(e,a,s){function c(e){b.push(e)}var l=a||"scheme start",u=0,d="",v=!1,g=!1,b=[];e:for(;(e[u-1]!=f||0==u)&&!this._isInvalid;){var y=e[u];switch(l){case"scheme start":if(!y||!m.test(y)){if(a){c("Invalid scheme.");break e}d="",l="no scheme";continue}d+=y.toLowerCase(),l="scheme";break;case"scheme":if(y&&w.test(y))d+=y.toLowerCase();else{if(":"!=y){if(a){if(f==y)break e;c("Code point not allowed in scheme: "+y);break e}d="",u=0,l="no scheme";continue}if(this._scheme=d,d="",a)break e;t(this._scheme)&&(this._isRelative=!0),l="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==y?(query="?",l="query"):"#"==y?(this._fragment="#",l="fragment"):f!=y&&" "!=y&&"\n"!=y&&"\r"!=y&&(this._schemeData+=o(y));break;case"no scheme":if(s&&t(s._scheme)){l="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=y||"/"!=e[u+1]){c("Expected /, got: "+y),l="relative";continue}l="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==y){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query;break e}if("/"==y||"\\"==y)"\\"==y&&c("\\ is an invalid code point."),l="relative slash";else if("?"==y)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",l="query";else{if("#"!=y){var E=e[u+1],_=e[u+2];("file"!=this._scheme||!m.test(y)||":"!=E&&"|"!=E||f!=_&&"/"!=_&&"\\"!=_&&"?"!=_&&"#"!=_)&&(this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._path.pop()),l="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",l="fragment"}break;case"relative slash":if("/"!=y&&"\\"!=y){"file"!=this._scheme&&(this._host=s._host,this._port=s._port),l="relative path";continue}"\\"==y&&c("\\ is an invalid code point."),l="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=y){c("Expected '/', got: "+y),l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":if(l="authority ignore slashes","/"!=y){c("Expected '/', got: "+y);continue}break;case"authority ignore slashes":if("/"!=y&&"\\"!=y){l="authority";continue}c("Expected authority, got: "+y);break;case"authority":if("@"==y){v&&(c("@ already seen."),d+="%40"),v=!0;for(var S=0;S0){var o=n[r-1],i=h(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=w.get(e);t||w.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=w.get(e),n=0;nh&&(p=s[h]);h++)a(p)?(c++,n()):(p.addEventListener("load",r),p.addEventListener("error",i));else n()}function a(e){return d?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&l(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function l(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",d=Boolean(u in document.createElement("link")),p=Boolean(window.ShadowDOMPolyfill),h=function(e){return p?ShadowDOMPolyfill.wrapIfNeeded(e):e},f=h(document),m={get:function(){var e=HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return h(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var w=/Trident|Edge/.test(navigator.userAgent),v=w?"complete":"interactive",g="readystatechange";d&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)l(e)}()),t(function(e){HTMLImports.ready=!0,HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=u,e.useNative=d,e.rootDocument=f,e.whenReady=t,e.isIE=w}(HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(HTMLImports),HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=i.getResponseHeader("Location"),a=null;if(n)var a="/"===n.substr(0,1)?location.origin+n:n;r.call(o,!t.ok(i)&&i,i.response||i.responseText,a)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]), +this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,l=e.isIE,u=e.IMPORT_LINK_TYPE,d="link[rel="+u+"]",p={documentSelectors:d,importsSelectors:[d,"link[rel=stylesheet]","style","script:not([type])",'script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(HTMLImports.__importsParsingHook&&HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(r){t&&t(r),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),l&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r["import"],r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e["import"]?!1:!0}};e.parser=p,e.IMPORT_SELECTOR=d}),HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,l=e.Loader,u=e.Observer,d=e.parser,p={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){h.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);h.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n["import"]=c}d.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),d.parseNext()},loadedAll:function(){d.parseNext()}},h=new l(p.loaded.bind(p),p.loadedAll.bind(p));if(p.observer=new u,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=p,e.importLoader=h}),HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||HTMLImports.useNative)}(CustomElements),CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;c>s&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),CustomElements.addModule(function(e){function t(e){return n(e)||r(e)}function n(t){return e.upgrade(t)?!0:void s(t)}function r(e){y(e,function(e){return n(e)?!0:void 0})}function o(e){s(e),p(e)&&y(e,function(e){s(e)})}function i(e){T.push(e),S||(S=!0,setTimeout(a))}function a(){S=!1;for(var e,t=T,n=0,r=t.length;r>n&&(e=t[n]);n++)e();T=[]}function s(e){_?i(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&!e.__attached&&p(e)&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function l(e){u(e),y(e,function(e){u(e)})}function u(e){_?i(function(){d(e)}):d(e)}function d(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&e.__attached&&!p(e)&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function p(e){for(var t=e,n=wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function h(e){if(e.shadowRoot&&!e.shadowRoot.__watched){b.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)w(t),t=t.olderShadowRoot}}function f(e){if(b.dom){var n=e[0];if(n&&"childList"===n.type&&n.addedNodes&&n.addedNodes){for(var r=n.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var o=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";o=o.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",e.length,o||"")}e.forEach(function(e){"childList"===e.type&&(M(e.addedNodes,function(e){e.localName&&t(e)}),M(e.removedNodes,function(e){e.localName&&l(e)}))}),b.dom&&console.groupEnd()}function m(e){for(e=wrap(e),e||(e=wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(f(t.takeRecords()),a())}function w(e){if(!e.__observer){var t=new MutationObserver(f);t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=wrap(e),b.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop()),t(e),w(e),b.dom&&console.groupEnd()}function g(e){E(e,v)}var b=e.flags,y=e.forSubtree,E=e.forDocumentTree,_=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=_;var S=!1,T=[],M=Array.prototype.forEach.call.bind(Array.prototype.forEach),O=Element.prototype.createShadowRoot;O&&(Element.prototype.createShadowRoot=function(){var e=O.call(this);return CustomElements.watchShadow(this),e}),e.watchShadow=h,e.upgradeDocumentTree=g,e.upgradeSubtree=r,e.upgradeAll=t,e.attachedNode=o,e.takeRecords=m}),CustomElements.addModule(function(e){function t(t){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),o=e.getRegisteredDefinition(r||t.localName);if(o){if(r&&o.tag==t.localName)return n(t,o);if(!r&&!o["extends"])return n(t,o)}}}function n(t,n){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),e.attachedNode(t),e.upgradeSubtree(t),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(l(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),u(c.__name,c),c.ctor=d(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&v(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t<_.length;t++)if(e===_[t])return!0}function i(e){var t=l(e);return t?i(t["extends"]).concat([t]):[]}function a(e){for(var t,n=e["extends"],r=0;t=e.ancestry[r];r++)n=t.is&&t.tag;e.tag=n||e.__name,n&&(e.is=e.__name)}function s(e){if(!Object.__proto__){var t=HTMLElement.prototype;if(e.is){var n=document.createElement(e.tag),r=Object.getPrototypeOf(n);r===e.prototype&&(t=r)}for(var o,i=e.prototype;i&&i!==t;)o=Object.getPrototypeOf(i),i.__proto__=o,i=o;e["native"]=t}}function c(e){return b(M(e.tag),e)}function l(e){return e?S[e.toLowerCase()]:void 0}function u(e,t){S[e]=t}function d(e){return function(){return c(e)}}function p(e,t,n){return e===T?h(t,n):O(e,t)}function h(e,t){var n=l(t||e);if(n){if(e==n.tag&&t==n.is)return new n.ctor;if(!t&&!n.is)return new n.ctor}var r;return t?(r=h(e),r.setAttribute("is",t),r):(r=M(e),e.indexOf("-")>=0&&y(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return g(e),e}}var m,w=e.isIE11OrOlder,v=e.upgradeDocumentTree,g=e.upgradeAll,b=e.upgradeWithDefinition,y=e.implementPrototype,E=e.useNative,_=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],S={},T="http://www.w3.org/1999/xhtml",M=document.createElement.bind(document),O=document.createElementNS.bind(document);m=Object.__proto__||E?function(e,t){return e instanceof t}:function(e,t){for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),w&&!function(){var e=document.importNode;document.importNode=function(){var t=e.apply(document,arguments);if(t.nodeType==t.DOCUMENT_FRAGMENT_NODE){var n=document.createDocumentFragment();return n.appendChild(t),n}return t}}(),document.registerElement=t,document.createElement=h,document.createElementNS=p,e.registry=S,e["instanceof"]=m,e.reservedTagList=_,e.getRegisteredDefinition=l,document.register=document.registerElement}),function(e){function t(){a(wrap(document)),window.HTMLImports&&(HTMLImports.__importsParsingHook=function(e){a(wrap(e["import"]))}),CustomElements.ready=!0,setTimeout(function(){CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})}var n=e.useNative,r=e.initializeModules,o=/Trident/.test(navigator.userAgent);if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t();e.isIE11OrOlder=o}(window.CustomElements),function(e){Function.prototype.bind||(Function.prototype.bind=function(e){var t=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.slice();return r.push.apply(r,arguments),t.apply(e,r)}})}(window.WebComponents),function(e){"use strict";function t(){window.Polymer===o&&(window.Polymer=function(){throw new Error('You tried to use polymer without loading it first. To load polymer, ')})}if(!window.performance){var n=Date.now();window.performance={now:function(){return Date.now()-n}}}window.requestAnimationFrame||(window.requestAnimationFrame=function(){var e=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame;return e?function(t){return e(function(){t(performance.now())})}:function(e){return window.setTimeout(e,1e3/60)}}()),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(){return window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||function(e){clearTimeout(e)}}());var r=[],o=function(e,t){"string"!=typeof e&&1===arguments.length&&Array.prototype.push.call(arguments,document._currentScript),r.push(arguments)};window.Polymer=o,e.consumeDeclarations=function(t){e.consumeDeclarations=function(){throw"Possible attempt to load Polymer twice"},t&&t(r),r=null},HTMLImports.useNative?t():addEventListener("DOMContentLoaded",t)}(window.WebComponents),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents),function(e){window.Platform=e}(window.WebComponents); \ No newline at end of file diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 7dc1cb54282..20931f2b363 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -1,6 +1,6 @@ """ -homeassistant.components.groups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +homeassistant.components.group +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to group devices that can be turned on or off. """ diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index a288d5471d9..14be60fa97e 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -5,10 +5,11 @@ homeassistant.components.history Provide pre-made queries on top of the recorder component. """ import re -from datetime import datetime, timedelta +from datetime import timedelta from itertools import groupby from collections import defaultdict +import homeassistant.util.dt as date_util import homeassistant.components.recorder as recorder DOMAIN = 'history' @@ -22,7 +23,7 @@ def last_5_states(entity_id): query = """ SELECT * FROM states WHERE entity_id=? AND last_changed=last_updated - ORDER BY last_changed DESC LIMIT 0, 5 + ORDER BY state_id DESC LIMIT 0, 5 """ return recorder.query_states(query, (entity_id, )) @@ -30,7 +31,7 @@ def last_5_states(entity_id): def state_changes_during_period(start_time, end_time=None, entity_id=None): """ - Return states changes during period start_time - end_time. + Return states changes during UTC period start_time - end_time. """ where = "last_changed=last_updated AND last_changed > ? " data = [start_time] @@ -64,17 +65,17 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None): return result -def get_states(point_in_time, entity_ids=None, run=None): +def get_states(utc_point_in_time, entity_ids=None, run=None): """ Returns the states at a specific point in time. """ if run is None: - run = recorder.run_information(point_in_time) + run = recorder.run_information(utc_point_in_time) - # History did not run before point_in_time + # History did not run before utc_point_in_time if run is None: return [] where = run.where_after_start_run + "AND created < ? " - where_data = [point_in_time] + where_data = [utc_point_in_time] if entity_ids is not None: where += "AND entity_id IN ({}) ".format( @@ -93,9 +94,9 @@ def get_states(point_in_time, entity_ids=None, run=None): return recorder.query_states(query, where_data) -def get_state(point_in_time, entity_id, run=None): +def get_state(utc_point_in_time, entity_id, run=None): """ Return a state at a specific point in time. """ - states = get_states(point_in_time, (entity_id,), run) + states = get_states(utc_point_in_time, (entity_id,), run) return states[0] if states else None @@ -128,7 +129,7 @@ def _api_last_5_states(handler, path_match, data): def _api_history_period(handler, path_match, data): """ Return history over a period of time. """ # 1 day for now.. - start_time = datetime.now() - timedelta(seconds=86400) + start_time = date_util.utcnow() - timedelta(seconds=86400) entity_id = data.get('filter_entity_id') diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index fed43cb43de..e9ffea0d592 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -1,6 +1,6 @@ """ homeassistant.components.httpinterface -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This module provides an API and a HTTP interface for debug purposes. diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 24b45e6f058..2ff9b5caeaf 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -1,4 +1,7 @@ """ +homeassistant.components.isy994 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Connects to an ISY-994 controller and loads relevant components to control its devices. Also contains the base classes for ISY Sensors, Lights, and Switches. """ @@ -13,7 +16,8 @@ from homeassistant.helpers import validate_config from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED, - ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) + EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED, + ATTR_FRIENDLY_NAME) # homeassistant constants DOMAIN = "isy994" @@ -31,7 +35,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): """ - Setup isy994 component. + Setup ISY994 component. This will automatically import associated lights, switches, and sensors. """ try: @@ -77,6 +81,9 @@ def setup(hass, config): if not ISY.connected: return False + # listen for HA stop to disconnect + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop) + # Load components for the devices in the ISY controller that we support for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), ('light', DISCOVER_LIGHTS), @@ -91,8 +98,13 @@ def setup(hass, config): return True +def stop(event): + """ Cleanup the ISY subscription. """ + ISY.auto_update = False + + class ISYDeviceABC(ToggleEntity): - """ Abstract Class for an ISY device within home assistant. """ + """ Abstract Class for an ISY device. """ _attrs = {} _onattrs = [] @@ -133,7 +145,7 @@ class ISYDeviceABC(ToggleEntity): @property def value(self): - """ returns the unclean value from the controller """ + """ Returns the unclean value from the controller. """ # pylint: disable=protected-access return self.node.status._val @@ -147,7 +159,7 @@ class ISYDeviceABC(ToggleEntity): @property def unique_id(self): - """ Returns the id of this isy sensor """ + """ Returns the id of this ISY sensor. """ # pylint: disable=protected-access return self.node._id @@ -190,7 +202,7 @@ class ISYDeviceABC(ToggleEntity): return self.value def turn_on(self, **kwargs): - """ turns the device on """ + """ Turns the device on. """ if self.domain is not 'sensor': attrs = [kwargs.get(name) for name in self._onattrs] self.node.on(*attrs) @@ -198,7 +210,7 @@ class ISYDeviceABC(ToggleEntity): _LOGGER.error('ISY cannot turn on sensors.') def turn_off(self, **kwargs): - """ turns the device off """ + """ Turns the device off. """ if self.domain is not 'sensor': self.node.off() else: diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index 8e820856c34..b4959b48055 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -1,5 +1,5 @@ """ -homeassistant.keyboard +homeassistant.components.keyboard ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to emulate keyboard presses on host machine. diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index 73fb1580e60..3949c765023 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -1,4 +1,10 @@ -""" Provides demo lights. """ +""" +homeassistant.components.light.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Demo platform that implements lights. + +""" import random from homeassistant.helpers.entity import ToggleEntity diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index a8299fbd6ed..cad31d41cab 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -1,17 +1,16 @@ """ homeassistant.components.logbook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Parses events and generates a human log +Parses events and generates a human log. """ -from datetime import datetime from itertools import groupby from homeassistant import State, DOMAIN as HA_DOMAIN from homeassistant.const import ( EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -import homeassistant.util as util +import homeassistant.util.dt as dt_util import homeassistant.components.recorder as recorder import homeassistant.components.sun as sun @@ -38,10 +37,11 @@ def setup(hass, config): def _handle_get_logbook(handler, path_match, data): """ Return logbook entries. """ - start_today = datetime.now().date() + start_today = dt_util.now().replace(hour=0, minute=0, second=0) handler.write_json(humanify( - recorder.query_events(QUERY_EVENTS_AFTER, (start_today,)))) + recorder.query_events( + QUERY_EVENTS_AFTER, (dt_util.as_utc(start_today),)))) class Entry(object): @@ -60,7 +60,7 @@ class Entry(object): def as_dict(self): """ Convert Entry to a dict to be used within JSON. """ return { - 'when': util.datetime_to_str(self.when), + 'when': dt_util.datetime_to_str(self.when), 'name': self.name, 'message': self.message, 'domain': self.domain, diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 9ebdb85a92b..432e00545d8 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -1,10 +1,10 @@ """ -homeassistant.components.media_player.chromecast -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +homeassistant.components.media_player.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo implementation of the media player. -""" +""" from homeassistant.components.media_player import ( MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION, diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 97b0ec7405a..90a0929e6c0 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -1,9 +1,12 @@ """ -components.modbus -~~~~~~~~~~~~~~~~~~~~~~~~~ +homeassistant.components.modbus +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Modbus component, using pymodbus (python3 branch) -typical declaration in configuration.yaml +Configuration: + +To use the Modbus component you will need to add something like the following +to your config/configuration.yaml #Modbus TCP modbus: @@ -27,10 +30,8 @@ import logging from homeassistant.const import (EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -# The domain of your component. Should be equal to the name of your component DOMAIN = "modbus" -# List of component names (string) your component depends upon DEPENDENCIES = [] # Type of network @@ -86,11 +87,11 @@ def setup(hass, config): return False def stop_modbus(event): - """ Stop Modbus service""" + """ Stop Modbus service. """ NETWORK.close() def start_modbus(event): - """ Start Modbus service""" + """ Start Modbus service. """ NETWORK.connect() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 0728a979588..ecd15aeb8e2 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,6 +1,6 @@ """ homeassistant.components.notify -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to notify people. """ @@ -73,7 +73,7 @@ def setup(hass, config): # pylint: disable=too-few-public-methods class BaseNotificationService(object): - """ Provides an ABC for notifcation services. """ + """ Provides an ABC for notification services. """ def send_message(self, message, **kwargs): """ diff --git a/homeassistant/components/notify/instapush.py b/homeassistant/components/notify/instapush.py new file mode 100644 index 00000000000..531ef758e05 --- /dev/null +++ b/homeassistant/components/notify/instapush.py @@ -0,0 +1,161 @@ +""" +homeassistant.components.notify.instapush +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instapush notification service. + +Configuration: + +To use the Instapush notifier you will need to add something like the following +to your config/configuration.yaml + +notify: + platform: instapush + api_key: YOUR_APP_KEY + app_secret: YOUR_APP_SECRET + event: YOUR_EVENT + tracker: YOUR_TRACKER + +VARIABLES: + +api_key +*Required +To retrieve this value log into your account at https://instapush.im and go +to 'APPS', choose an app, and check 'Basic Info'. + +app_secret +*Required +To get this value log into your account at https://instapush.im and go to +'APPS'. The 'Application ID' can be found under 'Basic Info'. + +event +*Required +To retrieve a valid event log into your account at https://instapush.im and go +to 'APPS'. If you have no events to use with Home Assistant, create one event +for your app. + +tracker +*Required +To retrieve the tracker value log into your account at https://instapush.im and +go to 'APPS', choose the app, and check the event entries. + +Example usage of Instapush if you have an event 'notification' and a tracker +'home-assistant'. + +curl -X POST \ + -H "x-instapush-appid: YOUR_APP_KEY" \ + -H "x-instapush-appsecret: YOUR_APP_SECRET" \ + -H "Content-Type: application/json" \ + -d '{"event":"notification","trackers":{"home-assistant":"Switch 1"}}' \ + https://api.instapush.im/v1/post + +Details for the API : https://instapush.im/developer/rest + +""" +import logging +import json + +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, ATTR_TITLE, BaseNotificationService) +from homeassistant.const import CONF_API_KEY + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://api.instapush.im/v1/' + + +def get_service(hass, config): + """ Get the instapush notification service. """ + + if not validate_config(config, + {DOMAIN: [CONF_API_KEY, + 'app_secret', + 'event', + 'tracker']}, + _LOGGER): + return None + + try: + import requests + + except ImportError: + _LOGGER.exception( + "Unable to import requests. " + "Did you maybe not install the 'Requests' package?") + + return None + + # pylint: disable=unused-variable + try: + response = requests.get(_RESOURCE) + + except requests.ConnectionError: + _LOGGER.error( + "Connection error " + "Please check if https://instapush.im is available.") + + return None + + instapush = requests.Session() + headers = {'x-instapush-appid': config[DOMAIN][CONF_API_KEY], + 'x-instapush-appsecret': config[DOMAIN]['app_secret']} + response = instapush.get(_RESOURCE + 'events/list', + headers=headers) + + try: + if response.json()['error']: + _LOGGER.error(response.json()['msg']) + # pylint: disable=bare-except + except: + try: + next(events for events in response.json() + if events['title'] == config[DOMAIN]['event']) + except StopIteration: + _LOGGER.error( + "No event match your given value. " + "Please create an event at https://instapush.im") + else: + return InstapushNotificationService( + config[DOMAIN].get(CONF_API_KEY), + config[DOMAIN]['app_secret'], + config[DOMAIN]['event'], + config[DOMAIN]['tracker'] + ) + + +# pylint: disable=too-few-public-methods +class InstapushNotificationService(BaseNotificationService): + """ Implements notification service for Instapush. """ + + def __init__(self, api_key, app_secret, event, tracker): + # pylint: disable=no-name-in-module, unused-variable + from requests import Session + + self._api_key = api_key + self._app_secret = app_secret + self._event = event + self._tracker = tracker + self._headers = { + 'x-instapush-appid': self._api_key, + 'x-instapush-appsecret': self._app_secret, + 'Content-Type': 'application/json'} + + self.instapush = Session() + + def send_message(self, message="", **kwargs): + """ Send a message to a user. """ + + title = kwargs.get(ATTR_TITLE) + + data = {"event": self._event, + "trackers": {self._tracker: title + " : " + message}} + + response = self.instapush.post( + _RESOURCE + 'post', + data=json.dumps(data), + headers=self._headers) + + if response.json()['status'] == 401: + _LOGGER.error( + response.json()['msg'], + "Please check your details at https://instapush.im/") diff --git a/homeassistant/components/notify/nma.py b/homeassistant/components/notify/nma.py new file mode 100644 index 00000000000..db6c91d8fed --- /dev/null +++ b/homeassistant/components/notify/nma.py @@ -0,0 +1,97 @@ +""" +homeassistant.components.notify.nma +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +NMA (Notify My Android) notification service. + +Configuration: + +To use the NMA notifier you will need to add something like the following +to your config/configuration.yaml + +notify: + platform: nma + api_key: YOUR_API_KEY + +VARIABLES: + +api_key +*Required +Enter the API key for NMA. Go to https://www.notifymyandroid.com and create a +new API key to use with Home Assistant. + +Details for the API : https://www.notifymyandroid.com/api.jsp + +""" +import logging +import xml.etree.ElementTree as ET + +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, ATTR_TITLE, BaseNotificationService) +from homeassistant.const import CONF_API_KEY + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://www.notifymyandroid.com/publicapi/' + + +def get_service(hass, config): + """ Get the NMA notification service. """ + + if not validate_config(config, + {DOMAIN: [CONF_API_KEY]}, + _LOGGER): + return None + + try: + # pylint: disable=unused-variable + from requests import Session + + except ImportError: + _LOGGER.exception( + "Unable to import requests. " + "Did you maybe not install the 'Requests' package?") + + return None + + nma = Session() + response = nma.get(_RESOURCE + 'verify', + params={"apikey": config[DOMAIN][CONF_API_KEY]}) + tree = ET.fromstring(response.content) + + if tree[0].tag == 'error': + _LOGGER.error("Wrong API key supplied. %s", tree[0].text) + else: + return NmaNotificationService(config[DOMAIN][CONF_API_KEY]) + + +# pylint: disable=too-few-public-methods +class NmaNotificationService(BaseNotificationService): + """ Implements notification service for NMA. """ + + def __init__(self, api_key): + # pylint: disable=no-name-in-module, unused-variable + from requests import Session + + self._api_key = api_key + self._data = {"apikey": self._api_key} + + self.nma = Session() + + def send_message(self, message="", **kwargs): + """ Send a message to a user. """ + + title = kwargs.get(ATTR_TITLE) + + self._data['application'] = 'home-assistant' + self._data['event'] = title + self._data['description'] = message + self._data['priority'] = 0 + + response = self.nma.get(_RESOURCE + 'notify', + params=self._data) + tree = ET.fromstring(response.content) + + if tree[0].tag == 'error': + _LOGGER.exception( + "Unable to perform request. Error: %s", tree[0].text) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index cea1b216e8b..09bfd3244c8 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -1,5 +1,24 @@ """ +homeassistant.components.notify.pushbullet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PushBullet platform for notify component. + +Configuration: + +To use the PushBullet notifier you will need to add something like the +following to your config/configuration.yaml + +notify: + platform: pushbullet + api_key: YOUR_API_KEY + +Variables: + +api_key +*Required +Enter the API key for PushBullet. Go to https://www.pushbullet.com/ to retrieve +your API key. """ import logging diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index 466cfce8ea0..f8ec652a39c 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -1,4 +1,7 @@ """ +homeassistant.components.notify.pushover +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Pushover platform for notify component. Configuration: @@ -11,7 +14,7 @@ notify: api_key: ABCDEFGHJKLMNOPQRSTUVXYZ user_key: ABCDEFGHJKLMNOPQRSTUVXYZ -VARIABLES: +Variables: api_key *Required diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py new file mode 100644 index 00000000000..43bb5799458 --- /dev/null +++ b/homeassistant/components/notify/xmpp.py @@ -0,0 +1,127 @@ +""" +homeassistant.components.notify.xmpp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Jabber (XMPP) notification service. + +Configuration: + +To use the Jabber notifier you will need to add something like the following +to your config/configuration.yaml + +notify: + platform: xmpp + sender: YOUR_JID + password: YOUR_JABBER_ACCOUNT_PASSWORD + recipient: YOUR_RECIPIENT + +Variables: + +sender +*Required +The Jabber ID (JID) that will act as origin of the messages. Add your JID +including the domain, e.g. your_name@jabber.org. + +password +*Required +The password for your given Jabber account. + +recipient +*Required +The Jabber ID (JID) that will receive the messages. + +""" +import logging + +_LOGGER = logging.getLogger(__name__) + +try: + import sleekxmpp + +except ImportError: + _LOGGER.exception( + "Unable to import sleekxmpp. " + "Did you maybe not install the 'SleekXMPP' package?") + +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, ATTR_TITLE, BaseNotificationService) + + +def get_service(hass, config): + """ Get the Jabber (XMPP) notification service. """ + + if not validate_config(config, + {DOMAIN: ['sender', + 'password', + 'recipient']}, + _LOGGER): + return None + + try: + SendNotificationBot(config[DOMAIN]['sender'] + '/home-assistant', + config[DOMAIN]['password'], + config[DOMAIN]['recipient'], + '') + except ImportError: + _LOGGER.exception( + "Unable to contact jabber server." + "Please check your credentials.") + + return None + + return XmppNotificationService(config[DOMAIN]['sender'], + config[DOMAIN]['password'], + config[DOMAIN]['recipient']) + + +# pylint: disable=too-few-public-methods +class XmppNotificationService(BaseNotificationService): + """ Implements notification service for Jabber (XMPP). """ + + def __init__(self, sender, password, recipient): + self._sender = sender + self._password = password + self._recipient = recipient + + def send_message(self, message="", **kwargs): + """ Send a message to a user. """ + + title = kwargs.get(ATTR_TITLE) + data = title + ": " + message + + SendNotificationBot(self._sender + '/home-assistant', + self._password, + self._recipient, + data) + + +class SendNotificationBot(sleekxmpp.ClientXMPP): + """ Service for sending Jabber (XMPP) messages. """ + + def __init__(self, jid, password, recipient, msg): + + super(SendNotificationBot, self).__init__(jid, password) + + logging.basicConfig(level=logging.ERROR) + + self.recipient = recipient + self.msg = msg + + self.use_tls = True + self.use_ipv6 = False + self.add_event_handler('failed_auth', self.check_credentials) + self.add_event_handler('session_start', self.start) + self.connect() + self.process(block=False) + + def start(self, event): + """ Starts the communication and sends the message. """ + self.send_presence() + self.get_roster() + self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat') + self.disconnect(wait=True) + + def check_credentials(self, event): + """" Disconnect from the server if credentials are invalid. """ + self.disconnect() diff --git a/homeassistant/components/recorder.py b/homeassistant/components/recorder.py index 6856ce4d7b5..5dca3bddde4 100644 --- a/homeassistant/components/recorder.py +++ b/homeassistant/components/recorder.py @@ -10,11 +10,11 @@ import threading import queue import sqlite3 from datetime import datetime, date -import time import json import atexit from homeassistant import Event, EventOrigin, State +import homeassistant.util.dt as date_util from homeassistant.remote import JSONEncoder from homeassistant.const import ( MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, @@ -60,8 +60,9 @@ def row_to_state(row): """ Convert a databsae row to a state. """ try: return State( - row[1], row[2], json.loads(row[3]), datetime.fromtimestamp(row[4]), - datetime.fromtimestamp(row[5])) + row[1], row[2], json.loads(row[3]), + date_util.utc_from_timestamp(row[4]), + date_util.utc_from_timestamp(row[5])) except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to state: %s", row) @@ -72,7 +73,7 @@ def row_to_event(row): """ Convert a databse row to an event. """ try: return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()], - datetime.fromtimestamp(row[5])) + date_util.utc_from_timestamp(row[5])) except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to event: %s", row) @@ -113,10 +114,10 @@ class RecorderRun(object): self.start = _INSTANCE.recording_start self.closed_incorrect = False else: - self.start = datetime.fromtimestamp(row[1]) + self.start = date_util.utc_from_timestamp(row[1]) if row[2] is not None: - self.end = datetime.fromtimestamp(row[2]) + self.end = date_util.utc_from_timestamp(row[2]) self.closed_incorrect = bool(row[3]) @@ -166,7 +167,8 @@ class Recorder(threading.Thread): self.queue = queue.Queue() self.quit_object = object() self.lock = threading.Lock() - self.recording_start = datetime.now() + self.recording_start = date_util.utcnow() + self.utc_offset = date_util.now().utcoffset().total_seconds() def start_recording(event): """ Start recording. """ @@ -187,16 +189,21 @@ class Recorder(threading.Thread): if event == self.quit_object: self._close_run() self._close_connection() + self.queue.task_done() return elif event.event_type == EVENT_TIME_CHANGED: + self.queue.task_done() continue - elif event.event_type == EVENT_STATE_CHANGED: - self.record_state( - event.data['entity_id'], event.data.get('new_state')) + event_id = self.record_event(event) - self.record_event(event) + if event.event_type == EVENT_STATE_CHANGED: + self.record_state( + event.data['entity_id'], event.data.get('new_state'), + event_id) + + self.queue.task_done() def event_listener(self, event): """ Listens for new events on the EventBus and puts them @@ -207,33 +214,43 @@ class Recorder(threading.Thread): """ Tells the recorder to shut down. """ self.queue.put(self.quit_object) - def record_state(self, entity_id, state): + def record_state(self, entity_id, state, event_id): """ Save a state to the database. """ - now = datetime.now() + now = date_util.utcnow() + # State got deleted if state is None: - info = (entity_id, '', "{}", now, now, now) + state_state = '' + state_attr = '{}' + last_changed = last_updated = now else: - info = ( - entity_id.lower(), state.state, json.dumps(state.attributes), - state.last_changed, state.last_updated, now) + state_state = state.state + state_attr = json.dumps(state.attributes) + last_changed = state.last_changed + last_updated = state.last_updated + + info = ( + entity_id, state_state, state_attr, last_changed, last_updated, + now, self.utc_offset, event_id) self.query( "INSERT INTO states (" "entity_id, state, attributes, last_changed, last_updated," - "created) VALUES (?, ?, ?, ?, ?, ?)", info) + "created, utc_offset, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + info) def record_event(self, event): """ Save an event to the database. """ info = ( event.event_type, json.dumps(event.data, cls=JSONEncoder), - str(event.origin), datetime.now(), event.time_fired, + str(event.origin), date_util.utcnow(), event.time_fired, + self.utc_offset ) - self.query( + return self.query( "INSERT INTO events (" - "event_type, event_data, origin, created, time_fired" - ") VALUES (?, ?, ?, ?, ?)", info) + "event_type, event_data, origin, created, time_fired, utc_offset" + ") VALUES (?, ?, ?, ?, ?, ?)", info, RETURN_LASTROWID) def query(self, sql_query, data=None, return_value=None): """ Query the database. """ @@ -262,6 +279,10 @@ class Recorder(threading.Thread): "Error querying the database using: %s", sql_query) return [] + def block_till_done(self): + """ Blocks till all events processed. """ + self.queue.join() + def _setup_connection(self): """ Ensure database is ready to fly. """ db_path = self.hass.config.path(DB_FILE) @@ -282,7 +303,7 @@ class Recorder(threading.Thread): def save_migration(migration_id): """ Save and commit a migration to the database. """ cur.execute('INSERT INTO schema_version VALUES (?, ?)', - (migration_id, datetime.now())) + (migration_id, date_util.utcnow())) self.conn.commit() _LOGGER.info("Database migrated to version %d", migration_id) @@ -297,7 +318,7 @@ class Recorder(threading.Thread): migration_id = 0 if migration_id < 1: - cur.execute(""" + self.query(""" CREATE TABLE recorder_runs ( run_id integer primary key, start integer, @@ -306,7 +327,7 @@ class Recorder(threading.Thread): created integer) """) - cur.execute(""" + self.query(""" CREATE TABLE events ( event_id integer primary key, event_type text, @@ -314,10 +335,10 @@ class Recorder(threading.Thread): origin text, created integer) """) - cur.execute( + self.query( 'CREATE INDEX events__event_type ON events(event_type)') - cur.execute(""" + self.query(""" CREATE TABLE states ( state_id integer primary key, entity_id text, @@ -327,20 +348,57 @@ class Recorder(threading.Thread): last_updated integer, created integer) """) - cur.execute('CREATE INDEX states__entity_id ON states(entity_id)') + self.query('CREATE INDEX states__entity_id ON states(entity_id)') save_migration(1) if migration_id < 2: - cur.execute(""" + self.query(""" ALTER TABLE events ADD COLUMN time_fired integer """) - cur.execute('UPDATE events SET time_fired=created') + self.query('UPDATE events SET time_fired=created') save_migration(2) + if migration_id < 3: + utc_offset = self.utc_offset + + self.query(""" + ALTER TABLE recorder_runs + ADD COLUMN utc_offset integer + """) + + self.query(""" + ALTER TABLE events + ADD COLUMN utc_offset integer + """) + + self.query(""" + ALTER TABLE states + ADD COLUMN utc_offset integer + """) + + self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset]) + self.query("UPDATE events SET utc_offset=?", [utc_offset]) + self.query("UPDATE states SET utc_offset=?", [utc_offset]) + + save_migration(3) + + if migration_id < 4: + # We had a bug where we did not save utc offset for recorder runs + self.query( + """UPDATE recorder_runs SET utc_offset=? + WHERE utc_offset IS NULL""", [self.utc_offset]) + + self.query(""" + ALTER TABLE states + ADD COLUMN event_id integer + """) + + save_migration(4) + def _close_connection(self): """ Close connection to the database. """ _LOGGER.info("Closing database") @@ -356,19 +414,20 @@ class Recorder(threading.Thread): _LOGGER.warning("Found unfinished sessions") self.query( - "INSERT INTO recorder_runs (start, created) VALUES (?, ?)", - (self.recording_start, datetime.now())) + """INSERT INTO recorder_runs (start, created, utc_offset) + VALUES (?, ?, ?)""", + (self.recording_start, date_util.utcnow(), self.utc_offset)) def _close_run(self): """ Save end time for current run. """ self.query( "UPDATE recorder_runs SET end=? WHERE start=?", - (datetime.now(), self.recording_start)) + (date_util.utcnow(), self.recording_start)) def _adapt_datetime(datetimestamp): """ Turn a datetime into an integer for in the DB. """ - return time.mktime(datetimestamp.timetuple()) + return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp() def _verify_instance(): diff --git a/homeassistant/components/scheduler/__init__.py b/homeassistant/components/scheduler/__init__.py index f84dafd5ec3..e69ba6627a7 100644 --- a/homeassistant/components/scheduler/__init__.py +++ b/homeassistant/components/scheduler/__init__.py @@ -1,19 +1,19 @@ """ homeassistant.components.scheduler -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A component that will act as a scheduler and performe actions based +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A component that will act as a scheduler and perform actions based on the events in the schedule. It will read a json object from schedule.json in the config dir and create a schedule based on it. Each schedule is a JSON with the keys id, name, description, entity_ids, and events. -- days is an array with the weekday number (monday=0) that the schdule +- days is an array with the weekday number (monday=0) that the schedule is active - entity_ids an array with entity ids that the events in the schedule should effect (can also be groups) - events is an array of objects that describe the different events that is - supported. Read in the events descriptions for more information + supported. Read in the events descriptions for more information. """ import logging import json @@ -22,7 +22,6 @@ from homeassistant import bootstrap from homeassistant.loader import get_component from homeassistant.const import ATTR_ENTITY_ID -# The domain of your component. Should be equal to the name of your component DOMAIN = 'scheduler' DEPENDENCIES = [] @@ -33,10 +32,10 @@ _SCHEDULE_FILE = 'schedule.json' def setup(hass, config): - """ Create the schedules """ + """ Create the schedules. """ def setup_listener(schedule, event_data): - """ Creates the event listener based on event_data """ + """ Creates the event listener based on event_data. """ event_type = event_data['type'] component = event_type @@ -52,7 +51,7 @@ def setup(hass, config): event_data) def setup_schedule(schedule_data): - """ setup a schedule based on the description """ + """ Setup a schedule based on the description. """ schedule = Schedule(schedule_data['id'], name=schedule_data['name'], @@ -97,17 +96,17 @@ class Schedule(object): self.__event_listeners = [] def add_event_listener(self, event_listener): - """ Add a event to the schedule """ + """ Add a event to the schedule. """ self.__event_listeners.append(event_listener) def schedule(self, hass): - """ Schedule all the events in the schdule """ + """ Schedule all the events in the schedule. """ for event in self.__event_listeners: event.schedule(hass) class EventListener(object): - """ The base EventListner class that the schedule uses """ + """ The base EventListener class that the schedule uses. """ def __init__(self, schedule): self.my_schedule = schedule @@ -122,7 +121,7 @@ class EventListener(object): # pylint: disable=too-few-public-methods class ServiceEventListener(EventListener): - """ A EventListner that calls a service when executed """ + """ A EventListener that calls a service when executed. """ def __init__(self, schdule, service): EventListener.__init__(self, schdule) @@ -130,7 +129,7 @@ class ServiceEventListener(EventListener): (self.domain, self.service) = service.split('.') def execute(self, hass): - """ Call the service """ + """ Call the service. """ data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids} hass.call_service(self.domain, self.service, data) diff --git a/homeassistant/components/scheduler/time.py b/homeassistant/components/scheduler/time.py index 90fa495cee2..793f33d0502 100644 --- a/homeassistant/components/scheduler/time.py +++ b/homeassistant/components/scheduler/time.py @@ -1,4 +1,6 @@ """ +homeassistant.components.scheduler.time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An event in the scheduler component that will call the service every specified day at the time specified. A time event need to have the type 'time', which service to call and at @@ -11,17 +13,17 @@ which time. } """ - -from datetime import datetime, timedelta +from datetime import timedelta import logging +import homeassistant.util.dt as dt_util from homeassistant.components.scheduler import ServiceEventListener _LOGGER = logging.getLogger(__name__) def create_event_listener(schedule, event_listener_data): - """ Create a TimeEvent based on the description """ + """ Create a TimeEvent based on the description. """ service = event_listener_data['service'] (hour, minute, second) = [int(x) for x in @@ -32,7 +34,7 @@ def create_event_listener(schedule, event_listener_data): # pylint: disable=too-few-public-methods class TimeEventListener(ServiceEventListener): - """ The time event that the scheduler uses """ + """ The time event that the scheduler uses. """ # pylint: disable=too-many-arguments def __init__(self, schedule, service, hour, minute, second): @@ -43,16 +45,14 @@ class TimeEventListener(ServiceEventListener): self.second = second def schedule(self, hass): - """ Schedule this event so that it will be called """ + """ Schedule this event so that it will be called. """ - next_time = datetime.now().replace(hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=0) + next_time = dt_util.now().replace( + hour=self.hour, minute=self.minute, second=self.second) # Calculate the next time the event should be executed. # That is the next day that the schedule is configured to run - while next_time < datetime.now() or \ + while next_time < dt_util.now() or \ next_time.weekday() not in self.my_schedule.days: next_time = next_time + timedelta(days=1) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 265fdd8c1cc..97e12c47a46 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -6,7 +6,8 @@ Scripts are a sequence of actions that can be triggered manually by the user or automatically based upon automation events, etc. """ import logging -from datetime import datetime, timedelta +from datetime import timedelta +import homeassistant.util.dt as date_util import threading from homeassistant.util import split_entity_id @@ -109,7 +110,7 @@ class Script(object): self._call_service(action) elif CONF_DELAY in action: delay = timedelta(**action[CONF_DELAY]) - point_in_time = datetime.now() + delay + point_in_time = date_util.now() + delay self.listener = self.hass.track_point_in_time( self, point_in_time) return False diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index 71b6cf2b8fe..37e2555136a 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -1,4 +1,10 @@ -""" Support for Wink sensors. """ +""" +homeassistant.components.sensor.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Demo platform that has two fake sensors. + +""" from homeassistant.helpers.entity import Entity from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 739a058d24d..7a242c044f7 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -1,8 +1,11 @@ -""" Support for ISY994 sensors. """ -# system imports +""" +homeassistant.components.sensor.isy994 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for ISY994 sensors. +""" import logging -# homeassistant imports from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, HIDDEN_STRING) from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME, @@ -24,7 +27,7 @@ DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like', def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the isy994 platform. """ + """ Sets up the ISY994 platform. """ # pylint: disable=protected-access logger = logging.getLogger(__name__) devs = [] @@ -81,7 +84,7 @@ class WeatherPseudoNode(object): class ISYSensorDevice(ISYDeviceABC): - """ represents a isy sensor within home assistant. """ + """ Represents a ISY sensor. """ _domain = 'sensor' diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py new file mode 100644 index 00000000000..7fbcf65328d --- /dev/null +++ b/homeassistant/components/sensor/mysensors.py @@ -0,0 +1,158 @@ +""" +homeassistant.components.sensor.mysensors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for MySensors sensors. + +Configuration: + +To use the MySensors sensor you will need to add something like the +following to your config/configuration.yaml + +sensor: + platform: mysensors + port: '/dev/ttyACM0' + +Variables: + +port +*Required +Port of your connection to your MySensors device. +""" +import logging + +# pylint: disable=no-name-in-module, import-error +import homeassistant.external.pymysensors.mysensors.mysensors as mysensors +import homeassistant.external.pymysensors.mysensors.const as const +from homeassistant.helpers.entity import Entity + +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP, + TEMP_CELCIUS, TEMP_FAHRENHEIT, + STATE_ON, STATE_OFF) + +CONF_PORT = "port" +CONF_DEBUG = "debug" +CONF_PERSISTENCE = "persistence" + +ATTR_NODE_ID = "node_id" +ATTR_CHILD_ID = "child_id" + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Setup the mysensors platform. """ + + devices = {} # keep track of devices added to HA + # Just assume celcius means that the user wants metric for now. + # It may make more sense to make this a global config option in the future. + is_metric = (hass.config.temperature_unit == TEMP_CELCIUS) + + def sensor_update(update_type, nid): + """ Callback for sensor updates from the MySensors gateway. """ + _LOGGER.info("sensor_update %s: node %s", update_type, nid) + sensor = gateway.sensors[nid] + if sensor.sketch_name is None: + return + if nid not in devices: + devices[nid] = {} + + node = devices[nid] + new_devices = [] + for child_id, child in sensor.children.items(): + if child_id not in node: + node[child_id] = {} + for value_type, value in child.values.items(): + if value_type not in node[child_id]: + name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id) + node[child_id][value_type] = \ + MySensorsNodeValue( + nid, child_id, name, value_type, is_metric) + new_devices.append(node[child_id][value_type]) + else: + node[child_id][value_type].update_sensor( + value, sensor.battery_level) + + if new_devices: + _LOGGER.info("adding new devices: %s", new_devices) + add_devices(new_devices) + + port = config.get(CONF_PORT) + if port is None: + _LOGGER.error("Missing required key 'port'") + return False + + persistence = config.get(CONF_PERSISTENCE, True) + + gateway = mysensors.SerialGateway(port, sensor_update, + persistence=persistence) + gateway.metric = is_metric + gateway.debug = config.get(CONF_DEBUG, False) + gateway.start() + + if persistence: + for nid in gateway.sensors: + sensor_update('sensor_update', nid) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: gateway.stop()) + + +class MySensorsNodeValue(Entity): + """ Represents the value of a MySensors child node. """ + # pylint: disable=too-many-arguments + def __init__(self, node_id, child_id, name, value_type, metric): + self._name = name + self.node_id = node_id + self.child_id = child_id + self.battery_level = 0 + self.value_type = value_type + self.metric = metric + self._value = '' + + @property + def should_poll(self): + """ MySensor gateway pushes its state to HA. """ + return False + + @property + def name(self): + """ The name of this sensor. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._value + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity. """ + if self.value_type == const.SetReq.V_TEMP: + return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT + elif self.value_type == const.SetReq.V_HUM or \ + self.value_type == const.SetReq.V_DIMMER or \ + self.value_type == const.SetReq.V_LIGHT_LEVEL: + return '%' + return None + + @property + def state_attributes(self): + """ Returns the state attributes. """ + return { + ATTR_NODE_ID: self.node_id, + ATTR_CHILD_ID: self.child_id, + ATTR_BATTERY_LEVEL: self.battery_level, + } + + def update_sensor(self, value, battery_level): + """ Update a sensor with the latest value from the controller. """ + _LOGGER.info("%s value = %s", self._name, value) + if self.value_type == const.SetReq.V_TRIPPED or \ + self.value_type == const.SetReq.V_ARMED: + self._value = STATE_ON if int(value) == 1 else STATE_OFF + else: + self._value = value + self.battery_level = battery_level + self.update_ha_state() diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py new file mode 100644 index 00000000000..ed5520a3817 --- /dev/null +++ b/homeassistant/components/sensor/openweathermap.py @@ -0,0 +1,174 @@ +""" +homeassistant.components.sensor.openweathermap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +OpenWeatherMap (OWM) service. + +Configuration: + +To use the OpenWeatherMap sensor you will need to add something like the +following to your config/configuration.yaml + +sensor: + platform: openweathermap + api_key: YOUR_APP_KEY + monitored_variables: + - type: 'weather' + - type: 'temperature' + - type: 'wind_speed' + - type: 'humidity' + - type: 'pressure' + - type: 'clouds' + - type: 'rain' + - type: 'snow' + +VARIABLES: + +api_key +*Required +To retrieve this value log into your account at http://openweathermap.org/ + +monitored_variables +*Required +An array specifying the variables to monitor. + +These are the variables for the monitored_variables array: + +type +*Required +The variable you wish to monitor, see the configuration example above for a +list of all available variables + +Details for the API : http://bugs.openweathermap.org/projects/api/wiki + +Only metric measurements are supported at the moment. + +""" +import logging + +from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) +_THROTTLED_REFRESH = None +SENSOR_TYPES = { + 'weather': ['Condition', ''], + 'temperature': ['Temperature', ''], + 'wind_speed': ['Wind speed', 'm/s'], + 'humidity': ['Humidity', '%'], + 'pressure': ['Pressure', 'hPa'], + 'clouds': ['Cloud coverage', '%'], + 'rain': ['Rain', 'mm'], + 'snow': ['Snow', 'mm'] +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the OpenWeatherMap sensor. """ + + if None in (hass.config.latitude, hass.config.longitude): + _LOGGER.error("Latitude or longitude not set in Home Assistant config") + return False + + try: + from pyowm import OWM + + except ImportError: + _LOGGER.exception( + "Unable to import pyowm. " + "Did you maybe not install the 'PyOWM' package?") + + return None + + SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit + unit = hass.config.temperature_unit + owm = OWM(config.get(CONF_API_KEY, None)) + obs = owm.weather_at_coords(hass.config.latitude, hass.config.longitude) + + if not owm: + _LOGGER.error( + "Connection error " + "Please check your settings for OpenWeatherMap.") + return None + + dev = [] + for variable in config['monitored_variables']: + if variable['type'] not in SENSOR_TYPES: + _LOGGER.error('Sensor type: "%s" does not exist', variable['type']) + else: + dev.append(OpenWeatherMapSensor(variable['type'], obs, unit)) + + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class OpenWeatherMapSensor(Entity): + """ Implements an OpenWeatherMap sensor. """ + + def __init__(self, sensor_type, weather_data, unit): + self.client_name = 'Weather - ' + self._name = SENSOR_TYPES[sensor_type][0] + self.owa_client = weather_data + self._unit = unit + self.type = sensor_type + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self.update() + + @property + def name(self): + return self.client_name + ' ' + self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement + + # pylint: disable=too-many-branches + def update(self): + """ Gets the latest data from OWM and updates the states. """ + data = self.owa_client.get_weather() + + if self.type == 'weather': + self._state = data.get_detailed_status() + + if self.type == 'temperature': + if self._unit == TEMP_CELCIUS: + self._state = round(data.get_temperature('celsius')['temp'], + 1) + elif self._unit == TEMP_FAHRENHEIT: + self._state = round(data.get_temperature('fahrenheit')['temp'], + 1) + else: + self._state = round(data.get_temperature()['temp'], 1) + + elif self.type == 'wind_speed': + self._state = data.get_wind()['speed'] + + elif self.type == 'humidity': + self._state = data.get_humidity() + + elif self.type == 'pressure': + self._state = round(data.get_pressure()['press'], 0) + + elif self.type == 'clouds': + self._state = data.get_clouds() + + elif self.type == 'rain': + if data.get_rain(): + self._state = round(data.get_rain()['3h'], 0) + else: + self._state = 'not raining' + self._unit_of_measurement = '' + + elif self.type == 'snow': + if data.get_snow(): + self._state = round(data.get_snow(), 0) + else: + self._state = 'not snowing' + self._unit_of_measurement = '' diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index a1230def614..cc37bd96b6f 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -1,6 +1,6 @@ """ homeassistant.components.sensor.sabnzbd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Monitors SABnzbd NZB client API @@ -22,14 +22,12 @@ sensor: - type: 'disk_size' - type: 'disk_free' -VARIABLES: +Variables: base_url *Required This is the base URL of your SABnzbd instance including the port number if not -running on 80 -Example: http://192.168.1.32:8124/ - +running on 80. Example: http://192.168.1.32:8124/ name *Optional @@ -44,9 +42,7 @@ These are the variables for the monitored_variables array: type *Required The variable you wish to monitor, see the configuration example above for a -list of all available variables - - +list of all available variables. """ from homeassistant.util import Throttle @@ -75,7 +71,7 @@ _THROTTLED_REFRESH = None # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the sensors """ + """ Sets up the sensors. """ api_key = config.get("api_key") base_url = config.get("base_url") name = config.get("name", "SABnzbd") diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index e03e987802b..0f9d570f92a 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -1,9 +1,46 @@ """ homeassistant.components.sensor.systemmonitor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Shows system monitor values such as: disk, memory and processor use +Configuration: + +To use the System monitor sensor you will need to add something like the +following to your config/configuration.yaml + +sensor: + platform: systemmonitor + resources: + - type: 'disk_use_percent' + arg: '/' + - type: 'disk_use' + arg: '/home' + - type: 'disk_free' + arg: '/' + - type: 'memory_use_percent' + - type: 'memory_use' + - type: 'memory_free' + - type: 'processor_use' + - type: 'process' + arg: 'octave-cli' + +Variables: + +resources +*Required +An array specifying the variables to monitor. + +These are the variables for the resources array: + +type +*Required +The variable you wish to monitor, see the configuration example above for a +sample list of variables. + +arg +*Optional +Additional details for the type, eg. path, binary name, etc. """ from homeassistant.helpers.entity import Entity @@ -28,7 +65,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the sensors """ + """ Sets up the sensors. """ dev = [] for resource in config['resources']: @@ -43,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SystemMonitorSensor(Entity): - """ A system monitor sensor """ + """ A system monitor sensor. """ def __init__(self, sensor_type, argument=''): self._name = SENSOR_TYPES[sensor_type][0] + ' ' + argument diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py new file mode 100644 index 00000000000..cd35e8343ba --- /dev/null +++ b/homeassistant/components/sensor/time_date.py @@ -0,0 +1,112 @@ +""" +homeassistant.components.sensor.time_date +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Date and Time service. + +Configuration: + +To use the Date and Time sensor you will need to add something like the +following to your config/configuration.yaml + +sensor: + platform: time_date + display_options: + - type: 'time' + - type: 'date' + - type: 'date_time' + - type: 'time_date' + - type: 'time_utc' + - type: 'beat' + +Variables: + +display_options +*Required +An array specifying the variables to display. + +These are the variables for the display_options array.: + +type +*Required +The variable you wish to display, see the configuration example above for a +list of all available variables. +""" +import logging + +import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) +OPTION_TYPES = { + 'time': 'Time', + 'date': 'Date', + 'date_time': 'Date & Time', + 'time_date': 'Time & Date', + 'beat': 'Time (beat)', + 'time_utc': 'Time (UTC)', +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the Time and Date sensor. """ + + if hass.config.time_zone is None: + _LOGGER.error("Timezone is not set in Home Assistant config") + return False + + dev = [] + for variable in config['display_options']: + if variable['type'] not in OPTION_TYPES: + _LOGGER.error('Option type: "%s" does not exist', variable['type']) + else: + dev.append(TimeDateSensor(variable['type'])) + + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class TimeDateSensor(Entity): + """ Implements a Time and Date sensor. """ + + def __init__(self, option_type): + self._name = OPTION_TYPES[option_type] + self.type = option_type + self._state = None + self.update() + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + def update(self): + """ Gets the latest data and updates the states. """ + + time_date = dt_util.utcnow() + time = dt_util.datetime_to_short_time_str(dt_util.as_local(time_date)) + time_utc = dt_util.datetime_to_short_time_str(time_date) + date = dt_util.datetime_to_short_date_str(dt_util.as_local(time_date)) + + # Calculate the beat (Swatch Internet Time) time without date. + hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':') + beat = ((int(seconds) + (int(minutes) * 60) + ((int(hours) + 1) * + 3600)) / 86.4) + + if self.type == 'time': + self._state = time + elif self.type == 'date': + self._state = date + elif self.type == 'date_time': + self._state = date + ', ' + time + elif self.type == 'time_date': + self._state = time + ', ' + date + elif self.type == 'time_utc': + self._state = time_utc + elif self.type == 'beat': + self._state = '{0:.2f}'.format(beat) diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index 7b1a66315d1..8ce2b6951ca 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -1,6 +1,6 @@ """ homeassistant.components.sensor.transmission -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Monitors Transmission BitTorrent client API @@ -21,17 +21,15 @@ sensor: - type: 'download_speed' - type: 'upload_speed' -VARIABLES: +Variables: host *Required -This is the IP address of your Transmission Daemon -Example: 192.168.1.32 +This is the IP address of your Transmission daemon. Example: 192.168.1.32 port *Optional -The port your Transmission daemon uses, defaults to 9091 -Example: 8080 +The port your Transmission daemon uses, defaults to 9091. Example: 8080 username *Required @@ -43,7 +41,7 @@ Your Transmission password name *Optional -The name to use when displaying this Transmission instance +The name to use when displaying this Transmission instance. monitored_variables *Required @@ -54,9 +52,7 @@ These are the variables for the monitored_variables array: type *Required The variable you wish to monitor, see the configuration example above for a -list of all available variables - - +list of all available variables. """ from homeassistant.util import Throttle @@ -84,7 +80,7 @@ _THROTTLED_REFRESH = None # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the sensors """ + """ Sets up the sensors. """ host = config.get(CONF_HOST) username = config.get(CONF_USERNAME, None) password = config.get(CONF_PASSWORD, None) @@ -123,7 +119,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TransmissionSensor(Entity): - """ A Transmission sensor """ + """ A Transmission sensor. """ def __init__(self, sensor_type, transmission_client, client_name): self._name = SENSOR_TYPES[sensor_type][0] @@ -158,7 +154,7 @@ class TransmissionSensor(Entity): ) def update(self): - """ Gets the latest from Transmission and updates the state. """ + """ Gets the latest data from Transmission and updates the state. """ self.refresh_transmission_data() if self.type == 'current_status': if self.transmission_client.session: diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 4f6126e6991..1cbef3a899a 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -1,7 +1,11 @@ """ +homeassistant.components.sensor.vera +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Support for Vera sensors. Configuration: + To use the Vera sensors you will need to add something like the following to your config/configuration.yaml @@ -15,7 +19,7 @@ sensor: 13: name: Another sensor -VARIABLES: +Variables: vera_controller_url *Required @@ -96,12 +100,12 @@ def get_devices(hass, config): def setup_platform(hass, config, add_devices, discovery_info=None): - """ Performs setup for Vera controller devices """ + """ Performs setup for Vera controller devices. """ add_devices(get_devices(hass, config)) class VeraSensor(Entity): - """ Represents a Vera Sensor """ + """ Represents a Vera Sensor. """ def __init__(self, vera_device, extra_data=None): self.vera_device = vera_device diff --git a/homeassistant/components/simple_alarm.py b/homeassistant/components/simple_alarm.py index e4ae8e6e9e9..4f2dbd768d5 100644 --- a/homeassistant/components/simple_alarm.py +++ b/homeassistant/components/simple_alarm.py @@ -20,7 +20,7 @@ DEPENDENCIES = ['group', 'device_tracker', 'light'] CONF_KNOWN_LIGHT = "known_light" # Attribute to tell which light has to flash whem an unknown person comes home -# If ommitted will flash all. +# If omitted will flash all. CONF_UNKNOWN_LIGHT = "unknown_light" # Services to test the alarms diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 52baf430579..ae33f86f01e 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -22,10 +22,16 @@ which event (sunset or sunrise) and the offset. """ import logging -from datetime import datetime, timedelta +from datetime import timedelta -from homeassistant.util import str_to_datetime, datetime_to_str +try: + import ephem +except ImportError: + # Error will be raised during setup + ephem = None +import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity from homeassistant.components.scheduler import ServiceEventListener DEPENDENCIES = [] @@ -49,13 +55,21 @@ def is_on(hass, entity_id=None): def next_setting(hass, entity_id=None): - """ Returns the datetime object representing the next sun setting. """ + """ Returns the local datetime object of the next sun setting. """ + utc_next = next_setting_utc(hass, entity_id) + + return dt_util.as_local(utc_next) if utc_next else None + + +def next_setting_utc(hass, entity_id=None): + """ Returns the UTC datetime object of the next sun setting. """ entity_id = entity_id or ENTITY_ID state = hass.states.get(ENTITY_ID) try: - return str_to_datetime(state.attributes[STATE_ATTR_NEXT_SETTING]) + return dt_util.str_to_datetime( + state.attributes[STATE_ATTR_NEXT_SETTING]) except (AttributeError, KeyError): # AttributeError if state is None # KeyError if STATE_ATTR_NEXT_SETTING does not exist @@ -63,13 +77,21 @@ def next_setting(hass, entity_id=None): def next_rising(hass, entity_id=None): - """ Returns the datetime object representing the next sun rising. """ + """ Returns the local datetime object of the next sun rising. """ + utc_next = next_rising_utc(hass, entity_id) + + return dt_util.as_local(utc_next) if utc_next else None + + +def next_rising_utc(hass, entity_id=None): + """ Returns the UTC datetime object of the next sun rising. """ entity_id = entity_id or ENTITY_ID state = hass.states.get(ENTITY_ID) try: - return str_to_datetime(state.attributes[STATE_ATTR_NEXT_RISING]) + return dt_util.str_to_datetime( + state.attributes[STATE_ATTR_NEXT_RISING]) except (AttributeError, KeyError): # AttributeError if state is None # KeyError if STATE_ATTR_NEXT_RISING does not exist @@ -80,78 +102,94 @@ def setup(hass, config): """ Tracks the state of the sun. """ logger = logging.getLogger(__name__) - try: - import ephem - except ImportError: + if ephem is None: logger.exception("Error while importing dependency ephem.") return False - sun = ephem.Sun() # pylint: disable=no-member - - latitude = str(hass.config.latitude) - longitude = str(hass.config.longitude) - - # Validate latitude and longitude - observer = ephem.Observer() - - errors = [] - - try: - observer.lat = latitude # pylint: disable=assigning-non-slot - except ValueError: - errors.append("invalid value for latitude given: {}".format(latitude)) - - try: - observer.long = longitude # pylint: disable=assigning-non-slot - except ValueError: - errors.append("invalid value for latitude given: {}".format(latitude)) - - if errors: - logger.error("Error setting up: %s", ", ".join(errors)) + if None in (hass.config.latitude, hass.config.longitude): + logger.error("Latitude or longitude not set in Home Assistant config") return False - def update_sun_state(now): - """ Method to update the current state of the sun and - set time of next setting and rising. """ - utc_offset = datetime.utcnow() - datetime.now() - utc_now = now + utc_offset + try: + sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude)) + except ValueError: + # Raised when invalid latitude or longitude is given to Observer + logger.exception("Invalid value for latitude or longitude") + return False - observer = ephem.Observer() - observer.lat = latitude # pylint: disable=assigning-non-slot - observer.long = longitude # pylint: disable=assigning-non-slot - - next_rising_dt = ephem.localtime( - observer.next_rising(sun, start=utc_now)) - next_setting_dt = ephem.localtime( - observer.next_setting(sun, start=utc_now)) - - if next_rising_dt > next_setting_dt: - new_state = STATE_ABOVE_HORIZON - next_change = next_setting_dt - - else: - new_state = STATE_BELOW_HORIZON - next_change = next_rising_dt - - logger.info("%s. Next change: %s", - new_state, next_change.strftime("%H:%M")) - - state_attributes = { - STATE_ATTR_NEXT_RISING: datetime_to_str(next_rising_dt), - STATE_ATTR_NEXT_SETTING: datetime_to_str(next_setting_dt) - } - - hass.states.set(ENTITY_ID, new_state, state_attributes) - - # +1 second so Ephem will report it has set - hass.track_point_in_time(update_sun_state, - next_change + timedelta(seconds=1)) - - update_sun_state(datetime.now()) + sun.point_in_time_listener(dt_util.utcnow()) return True +class Sun(Entity): + """ Represents the Sun. """ + + entity_id = ENTITY_ID + + def __init__(self, hass, latitude, longitude): + self.hass = hass + self.observer = ephem.Observer() + # pylint: disable=assigning-non-slot + self.observer.lat = latitude + # pylint: disable=assigning-non-slot + self.observer.long = longitude + + self._state = self.next_rising = self.next_setting = None + + @property + def should_poll(self): + """ We trigger updates ourselves after sunset/sunrise """ + return False + + @property + def name(self): + return "Sun" + + @property + def state(self): + if self.next_rising > self.next_setting: + return STATE_ABOVE_HORIZON + + return STATE_BELOW_HORIZON + + @property + def state_attributes(self): + return { + STATE_ATTR_NEXT_RISING: dt_util.datetime_to_str(self.next_rising), + STATE_ATTR_NEXT_SETTING: dt_util.datetime_to_str(self.next_setting) + } + + @property + def next_change(self): + """ Returns the datetime when the next change to the state is. """ + return min(self.next_rising, self.next_setting) + + def update_as_of(self, utc_point_in_time): + """ Calculate sun state at a point in UTC time. """ + sun = ephem.Sun() # pylint: disable=no-member + + # pylint: disable=assigning-non-slot + self.observer.date = ephem.date(utc_point_in_time) + + self.next_rising = self.observer.next_rising( + sun, + start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC) + self.next_setting = self.observer.next_setting( + sun, + start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC) + + def point_in_time_listener(self, now): + """ Called when the state of the sun has changed. """ + self.update_as_of(now) + self.update_ha_state() + + # Schedule next update at next_change+1 second so sun state has changed + self.hass.track_point_in_utc_time( + self.point_in_time_listener, + self.next_change + timedelta(seconds=1)) + + def create_event_listener(schedule, event_listener_data): """ Create a sun event listener based on the description. """ @@ -195,7 +233,7 @@ class SunEventListener(ServiceEventListener): else: next_time = next_event + self.offset - while next_time < datetime.now() or \ + while next_time < dt_util.now() or \ next_time.weekday() not in self.my_schedule.days: next_time = next_time + timedelta(days=1) diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 96a48d01a9e..b54b48a1c9b 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -1,4 +1,10 @@ -""" Demo platform that has two fake switches. """ +""" +homeassistant.components.switch.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Demo platform that has two fake switches. + +""" from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index fe98bce69f9..75032d2954d 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -1,8 +1,11 @@ -""" Support for ISY994 switch. """ -# system imports +""" +homeassistant.components.switch.isy994 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for ISY994 switches. +""" import logging -# homeassistant imports from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, HIDDEN_STRING) from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED @@ -12,7 +15,7 @@ from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the isy994 platform. """ + """ Sets up the ISY994 platform. """ # pylint: disable=too-many-locals logger = logging.getLogger(__name__) devs = [] @@ -54,7 +57,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ISYSwitchDevice(ISYDeviceABC): - """ represents as isy light within home assistant. """ + """ Represents as ISY light. """ _domain = 'switch' _dtype = 'binary' @@ -62,7 +65,7 @@ class ISYSwitchDevice(ISYDeviceABC): class ISYProgramDevice(ISYSwitchDevice): - """ represents a door that can be manipulated within home assistant. """ + """ Represents a door that can be manipulated. """ _domain = 'switch' _dtype = 'binary' @@ -74,9 +77,9 @@ class ISYProgramDevice(ISYSwitchDevice): self.action_node = actions def turn_on(self, **kwargs): - """ turns the device on/closes the device """ + """ Turns the device on/closes the device. """ self.action_node.runThen() def turn_off(self, **kwargs): - """ turns the device off/opens the device """ + """ Turns the device off/opens the device. """ self.action_node.runElse() diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 7e5e039337f..6513ba71f4a 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -1,4 +1,7 @@ """ +homeassistant.components.switch.modbus +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Support for Modbus switches. Configuration: @@ -53,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ModbusSwitch(ToggleEntity): - """ Represents a Modbus Switch """ + """ Represents a Modbus switch. """ def __init__(self, name, slave, register, bit): self._name = name diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index fccd2ba5c08..84f5d2b31d5 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -1,4 +1,9 @@ -""" Support for Tellstick switches. """ +""" +homeassistant.components.switch.tellstick +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for Tellstick switches. +""" import logging @@ -9,7 +14,7 @@ import tellcore.constants as tellcore_constants # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return tellstick switches. """ + """ Find and return Tellstick switches. """ try: import tellcore.telldus as telldus except ImportError: @@ -30,7 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class TellstickSwitchDevice(ToggleEntity): - """ represents a Tellstick switch within home assistant. """ + """ Represents a Tellstick switch within Home Assistant. """ last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON | tellcore_constants.TELLSTICK_TURNOFF) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 2baf10f53d8..d8be9286413 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -1,4 +1,9 @@ -""" Support for WeMo switchces. """ +""" +homeassistant.components.switch.wemo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for WeMo switches. +""" import logging from homeassistant.helpers.entity import ToggleEntity @@ -8,7 +13,7 @@ from homeassistant.components.switch import ( # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return wemo switches. """ + """ Find and return WeMo switches. """ try: # pylint: disable=no-name-in-module, import-error import homeassistant.external.pywemo.pywemo as pywemo @@ -39,7 +44,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class WemoSwitch(ToggleEntity): - """ represents a WeMo switch within home assistant. """ + """ Represents a WeMo switch within Home Assistant. """ def __init__(self, wemo): self.wemo = wemo @@ -78,5 +83,5 @@ class WemoSwitch(ToggleEntity): self.wemo.off() def update(self): - """ Update Wemo state. """ + """ Update WeMo state. """ self.wemo.get_state(True) diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index f3d38d87c29..add56d222b1 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -1,4 +1,9 @@ -""" Support for WeMo switchces. """ +""" +homeassistant.components.switch.wink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for Wink switches. +""" import logging # pylint: disable=no-name-in-module, import-error diff --git a/homeassistant/components/thermostat/demo.py b/homeassistant/components/thermostat/demo.py index 5f0898c3086..0ecbd889edd 100644 --- a/homeassistant/components/thermostat/demo.py +++ b/homeassistant/components/thermostat/demo.py @@ -1,7 +1,10 @@ """ -Demo platform that offers a fake thermostat. -""" +homeassistant.components.thermostat.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Demo platform that offers a fake thermostat. + +""" from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index cfdbf9b1a92..5a99633041d 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -1,4 +1,7 @@ """ +homeassistant.components.wink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Connects to a Wink hub and loads relevant components to control its devices. """ import logging @@ -53,14 +56,14 @@ def setup(hass, config): class WinkToggleDevice(ToggleEntity): - """ represents a Wink switch within home assistant. """ + """ Represents a Wink switch within Home Assistant. """ def __init__(self, wink): self.wink = wink @property def unique_id(self): - """ Returns the id of this WeMo switch """ + """ Returns the id of this Wink switch. """ return "{}.{}".format(self.__class__, self.wink.deviceId()) @property diff --git a/homeassistant/config.py b/homeassistant/config.py new file mode 100644 index 00000000000..b4f70cd1952 --- /dev/null +++ b/homeassistant/config.py @@ -0,0 +1,140 @@ +""" +homeassistant.config +~~~~~~~~~~~~~~~~~~~~ + +Module to help with parsing and generating configuration files. +""" +import logging +import os + +from homeassistant import HomeAssistantError +from homeassistant.const import ( + CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, + CONF_TIME_ZONE) +import homeassistant.util as util + + +_LOGGER = logging.getLogger(__name__) + + +YAML_CONFIG_FILE = 'configuration.yaml' +CONF_CONFIG_FILE = 'home-assistant.conf' +DEFAULT_COMPONENTS = [ + 'discovery', 'frontend', 'conversation', 'history', 'logbook'] + + +def ensure_config_exists(config_dir, detect_location=True): + """ Ensures a config file exists in given config dir. + Creating a default one if needed. + Returns path to the config file. """ + config_path = find_config_file(config_dir) + + if config_path is None: + _LOGGER.info("Unable to find configuration. Creating default one") + config_path = create_default_config(config_dir, detect_location) + + return config_path + + +def create_default_config(config_dir, detect_location=True): + """ Creates a default configuration file in given config dir. + Returns path to new config file if success, None if failed. """ + config_path = os.path.join(config_dir, YAML_CONFIG_FILE) + + # Writing files with YAML does not create the most human readable results + # So we're hard coding a YAML template. + try: + with open(config_path, 'w') as config_file: + location_info = detect_location and util.detect_location_info() + + if location_info: + temp_unit = 'F' if location_info.use_fahrenheit else 'C' + + auto_config = { + CONF_NAME: 'Home', + CONF_LATITUDE: location_info.latitude, + CONF_LONGITUDE: location_info.longitude, + CONF_TEMPERATURE_UNIT: temp_unit, + CONF_TIME_ZONE: location_info.time_zone, + } + + config_file.write("homeassistant:\n") + + for key, value in auto_config.items(): + config_file.write(" {}: {}\n".format(key, value)) + + config_file.write("\n") + + for component in DEFAULT_COMPONENTS: + config_file.write("{}:\n\n".format(component)) + + return config_path + + except IOError: + _LOGGER.exception( + 'Unable to write default configuration file %s', config_path) + + return None + + +def find_config_file(config_dir): + """ Looks in given directory for supported config files. """ + for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE): + config_path = os.path.join(config_dir, filename) + + if os.path.isfile(config_path): + return config_path + + return None + + +def load_config_file(config_path): + """ Loads given config file. """ + config_ext = os.path.splitext(config_path)[1] + + if config_ext == '.yaml': + return load_yaml_config_file(config_path) + + elif config_ext == '.conf': + return load_conf_config_file(config_path) + + +def load_yaml_config_file(config_path): + """ Parse a YAML configuration file. """ + import yaml + + try: + with open(config_path) as conf_file: + # If configuration file is empty YAML returns None + # We convert that to an empty dict + conf_dict = yaml.load(conf_file) or {} + + except yaml.YAMLError: + _LOGGER.exception('Error reading YAML configuration file') + raise HomeAssistantError() + + if not isinstance(conf_dict, dict): + _LOGGER.error( + 'The configuration file %s does not contain a dictionary', + os.path.basename(config_path)) + raise HomeAssistantError() + + return conf_dict + + +def load_conf_config_file(config_path): + """ Parse the old style conf configuration. """ + import configparser + + config_dict = {} + + config = configparser.ConfigParser() + config.read(config_path) + + for section in config.sections(): + config_dict[section] = {} + + for key, val in config.items(section): + config_dict[section][key] = val + + return config_dict diff --git a/homeassistant/const.py b/homeassistant/const.py index 0e859eaf943..cfd37576ff1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -11,7 +11,7 @@ CONF_LONGITUDE = "longitude" CONF_TEMPERATURE_UNIT = "temperature_unit" CONF_NAME = "name" CONF_TIME_ZONE = "time_zone" -CONF_VISIBILITY = "visibility" +CONF_CUSTOMIZE = "customize" CONF_PLATFORM = "platform" CONF_HOST = "host" @@ -111,6 +111,7 @@ SERVER_PORT = 8123 URL_ROOT = "/" URL_API = "/api/" URL_API_STREAM = "/api/stream" +URL_API_CONFIG = "/api/config" URL_API_STATES = "/api/states" URL_API_STATES_ENTITY = "/api/states/{}" URL_API_EVENTS = "/api/events" @@ -119,6 +120,7 @@ URL_API_SERVICES = "/api/services" URL_API_SERVICES_SERVICE = "/api/services/{}/{}" URL_API_EVENT_FORWARD = "/api/event_forwarding" URL_API_COMPONENTS = "/api/components" +URL_API_BOOTSTRAP = "/api/bootstrap" HTTP_OK = 200 HTTP_CREATED = 201 diff --git a/homeassistant/external/netdisco b/homeassistant/external/netdisco index 6e712dd65e4..b2cad7c2b95 160000 --- a/homeassistant/external/netdisco +++ b/homeassistant/external/netdisco @@ -1 +1 @@ -Subproject commit 6e712dd65e474bf623b35c54f5290dbac192c7e4 +Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176 diff --git a/homeassistant/external/noop b/homeassistant/external/noop index 45fae73c1f4..4eaeb3367f9 160000 --- a/homeassistant/external/noop +++ b/homeassistant/external/noop @@ -1 +1 @@ -Subproject commit 45fae73c1f44342010fa07f3ed8909bf2819a508 +Subproject commit 4eaeb3367f9ada05dae3319cf24ab1da5de1aa89 diff --git a/homeassistant/external/nzbclients b/homeassistant/external/nzbclients index f9f9ba36934..f01997498fe 160000 --- a/homeassistant/external/nzbclients +++ b/homeassistant/external/nzbclients @@ -1 +1 @@ -Subproject commit f9f9ba36934f087b9c4241303b900794a7eb6c08 +Subproject commit f01997498fe190d6ac2a2c375a739024843bd44d diff --git a/homeassistant/external/pymysensors b/homeassistant/external/pymysensors new file mode 160000 index 00000000000..cd5ef892eee --- /dev/null +++ b/homeassistant/external/pymysensors @@ -0,0 +1 @@ +Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97 diff --git a/homeassistant/external/pywemo b/homeassistant/external/pywemo index 7f6c383ded7..ca94e41faa4 160000 --- a/homeassistant/external/pywemo +++ b/homeassistant/external/pywemo @@ -1 +1 @@ -Subproject commit 7f6c383ded75f1273cbca28e858b8a8c96da66d4 +Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index a39d46a29f6..f9751ffc14c 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -5,14 +5,17 @@ homeassistant.helpers.entity Provides ABC for entities in HA. """ +from collections import defaultdict + from homeassistant import NoEntitySpecifiedError from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON, - STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) + ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, + STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, + TEMP_FAHRENHEIT) # Dict mapping entity_id to a boolean that overwrites the hidden property -_OVERWRITE_HIDDEN = {} +_OVERWRITE = defaultdict(dict) class Entity(object): @@ -121,8 +124,15 @@ class Entity(object): if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement: attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement - if _OVERWRITE_HIDDEN.get(self.entity_id, self.hidden): - attr[ATTR_HIDDEN] = True + if self.hidden: + attr[ATTR_HIDDEN] = self.hidden + + # overwrite properties that have been set in the config file + attr.update(_OVERWRITE.get(self.entity_id, {})) + + # remove hidden property if false so it won't show up + if not attr.get(ATTR_HIDDEN, True): + attr.pop(ATTR_HIDDEN) # Convert temperature if we detect one if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS, @@ -143,15 +153,18 @@ class Entity(object): return "".format(self.name, self.state) @staticmethod - def overwrite_hidden(entity_id, hidden): + def overwrite_attribute(entity_id, attrs, vals): """ - Overwrite the hidden property of an entity. - Set hidden to None to remove any overwritten value in place. + Overwrite any attribute of an entity. + This function should receive a list of attributes and a + list of values. Set attribute to None to remove any overwritten + value in place. """ - if hidden is None: - _OVERWRITE_HIDDEN.pop(entity_id, None) - else: - _OVERWRITE_HIDDEN[entity_id.lower()] = hidden + for attr, val in zip(attrs, vals): + if val is None: + _OVERWRITE[entity_id.lower()].pop(attr, None) + else: + _OVERWRITE[entity_id.lower()][attr] = val class ToggleEntity(Entity): diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 1723091d5c2..381584fabb8 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -4,7 +4,7 @@ homeassistant.helpers.entity_component Provides helpers for components that manage entities. """ -from homeassistant.loader import get_component +from homeassistant.bootstrap import prepare_setup_platform from homeassistant.helpers import ( generate_entity_id, config_per_platform, extract_entity_ids) from homeassistant.components import group, discovery @@ -35,12 +35,16 @@ class EntityComponent(object): self.group = None self.is_polling = False + self.config = None + def setup(self, config): """ Sets up a full entity component: - Loads the platforms from the config - Will listen for supported discovered platforms """ + self.config = config + # Look in config for Domain, Domain 2, Domain 3 etc and load them for p_type, p_config in \ config_per_platform(config, self.domain, self.logger): @@ -115,18 +119,20 @@ class EntityComponent(object): self._update_entity_states, second=range(0, 60, self.scan_interval)) - def _setup_platform(self, platform_type, config, discovery_info=None): + def _setup_platform(self, platform_type, platform_config, + discovery_info=None): """ Tries to setup a platform for this component. """ - platform_name = '{}.{}'.format(self.domain, platform_type) - platform = get_component(platform_name) + platform = prepare_setup_platform( + self.hass, self.config, self.domain, platform_type) if platform is None: - self.logger.error('Unable to find platform %s', platform_type) return + platform_name = '{}.{}'.format(self.domain, platform_type) + try: platform.setup_platform( - self.hass, config, self.add_entities, discovery_info) + self.hass, platform_config, self.add_entities, discovery_info) self.hass.config.components.append(platform_name) @@ -135,15 +141,16 @@ class EntityComponent(object): # Support old deprecated method for now - 3/1/2015 if hasattr(platform, 'get_devices'): self.logger.warning( - "Please upgrade %s to return new entities using " - "setup_platform. See %s/demo.py for an example.", + 'Please upgrade %s to return new entities using ' + 'setup_platform. See %s/demo.py for an example.', platform_name, self.domain) - self.add_entities(platform.get_devices(self.hass, config)) + self.add_entities( + platform.get_devices(self.hass, platform_config)) else: self.logger.exception( - "Error while setting up platform %s", platform_type) + 'Error while setting up platform %s', platform_type) except Exception: # pylint: disable=broad-except self.logger.exception( - "Error while setting up platform %s", platform_type) + 'Error while setting up platform %s', platform_type) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 43d48898303..18c68808e94 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -5,9 +5,9 @@ homeassistant.helpers.state Helpers that help with state related things. """ import logging -from datetime import datetime from homeassistant import State +import homeassistant.util.dt as dt_util from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) @@ -26,7 +26,7 @@ class TrackStates(object): self.states = [] def __enter__(self): - self.now = datetime.now() + self.now = dt_util.utcnow() return self.states def __exit__(self, exc_type, exc_value, traceback): diff --git a/homeassistant/util.py b/homeassistant/util/__init__.py similarity index 91% rename from homeassistant/util.py rename to homeassistant/util/__init__.py index ae5fcea609d..5c69fd02243 100644 --- a/homeassistant/util.py +++ b/homeassistant/util/__init__.py @@ -8,7 +8,7 @@ import collections from itertools import chain import threading import queue -from datetime import datetime, timedelta +from datetime import datetime import re import enum import socket @@ -16,12 +16,19 @@ import random import string from functools import wraps +import requests + +# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package +# pylint: disable=unused-import +from .dt import ( # noqa + datetime_to_str, str_to_datetime, strip_microseconds, + datetime_to_local_str, utcnow) + + RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)') RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+') -DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y" - def sanitize_filename(filename): """ Sanitizes a filename by removing .. / and \\. """ @@ -40,33 +47,6 @@ def slugify(text): return RE_SLUGIFY.sub("", text) -def datetime_to_str(dattim): - """ Converts datetime to a string format. - - @rtype : str - """ - return dattim.strftime(DATE_STR_FORMAT) - - -def str_to_datetime(dt_str): - """ Converts a string to a datetime object. - - @rtype: datetime - """ - try: - return datetime.strptime(dt_str, DATE_STR_FORMAT) - except ValueError: # If dt_str did not match our format - return None - - -def strip_microseconds(dattim): - """ Returns a copy of dattime object but with microsecond set to 0. """ - if dattim.microsecond: - return dattim - timedelta(microseconds=dattim.microsecond) - else: - return dattim - - def split_entity_id(entity_id): """ Splits a state entity_id into domain, object_id. """ return entity_id.split(".", 1) @@ -79,7 +59,7 @@ def repr_helper(inp): repr_helper(key)+"="+repr_helper(item) for key, item in inp.items()) elif isinstance(inp, datetime): - return datetime_to_str(inp) + return datetime_to_local_str(inp) else: return str(inp) @@ -174,6 +154,32 @@ def get_random_string(length=10): return ''.join(generator.choice(source_chars) for _ in range(length)) +LocationInfo = collections.namedtuple( + "LocationInfo", + ['ip', 'country_code', 'country_name', 'region_code', 'region_name', + 'city', 'zip_code', 'time_zone', 'latitude', 'longitude', + 'use_fahrenheit']) + + +def detect_location_info(): + """ Detect location information. """ + try: + raw_info = requests.get( + 'https://freegeoip.net/json/', timeout=5).json() + except requests.RequestException: + return + + data = {key: raw_info.get(key) for key in LocationInfo._fields} + + # From Wikipedia: Fahrenheit is used in the Bahamas, Belize, + # the Cayman Islands, Palau, and the United States and associated + # territories of American Samoa and the U.S. Virgin Islands + data['use_fahrenheit'] = data['country_code'] in ( + 'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI') + + return LocationInfo(**data) + + class OrderedEnum(enum.Enum): """ Taken from Python 3.4.0 docs. """ # pylint: disable=no-init, too-few-public-methods @@ -436,7 +442,7 @@ class ThreadPool(object): return # Add to current running jobs - job_log = (datetime.now(), job) + job_log = (utcnow(), job) self.current_jobs.append(job_log) # Do the job diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py new file mode 100644 index 00000000000..fbe00c85527 --- /dev/null +++ b/homeassistant/util/dt.py @@ -0,0 +1,114 @@ +""" +homeassistant.util.dt +~~~~~~~~~~~~~~~~~~~~~ + +Provides helper methods to handle the time in HA. + +""" +import datetime as dt + +import pytz + +DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y" +DATE_SHORT_STR_FORMAT = "%Y-%m-%d" +TIME_SHORT_STR_FORMAT = "%H:%M" +UTC = DEFAULT_TIME_ZONE = pytz.utc + + +def set_default_time_zone(time_zone): + """ Sets a default time zone to be used when none is specified. """ + global DEFAULT_TIME_ZONE # pylint: disable=global-statement + + assert isinstance(time_zone, dt.tzinfo) + + DEFAULT_TIME_ZONE = time_zone + + +def get_time_zone(time_zone_str): + """ Get time zone from string. Return None if unable to determine. """ + try: + return pytz.timezone(time_zone_str) + except pytz.exceptions.UnknownTimeZoneError: + return None + + +def utcnow(): + """ Get now in UTC time. """ + return dt.datetime.now(pytz.utc) + + +def now(time_zone=None): + """ Get now in specified time zone. """ + return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE) + + +def as_utc(dattim): + """ Return a datetime as UTC time. + Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """ + if dattim.tzinfo == pytz.utc: + return dattim + elif dattim.tzinfo is None: + dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE) + + return dattim.astimezone(pytz.utc) + + +def as_local(dattim): + """ Converts a UTC datetime object to local time_zone. """ + if dattim.tzinfo == DEFAULT_TIME_ZONE: + return dattim + elif dattim.tzinfo is None: + dattim = dattim.replace(tzinfo=pytz.utc) + + return dattim.astimezone(DEFAULT_TIME_ZONE) + + +def utc_from_timestamp(timestamp): + """ Returns a UTC time from a timestamp. """ + return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc) + + +def datetime_to_local_str(dattim, time_zone=None): + """ Converts datetime to specified time_zone and returns a string. """ + return datetime_to_str(as_local(dattim)) + + +def datetime_to_str(dattim): + """ Converts datetime to a string format. + + @rtype : str + """ + return dattim.strftime(DATE_STR_FORMAT) + + +def datetime_to_short_time_str(dattim): + """ Converts datetime to a string format as short time. + + @rtype : str + """ + return dattim.strftime(TIME_SHORT_STR_FORMAT) + + +def datetime_to_short_date_str(dattim): + """ Converts datetime to a string format as short date. + + @rtype : str + """ + return dattim.strftime(DATE_SHORT_STR_FORMAT) + + +def str_to_datetime(dt_str): + """ Converts a string to a UTC datetime object. + + @rtype: datetime + """ + try: + return dt.datetime.strptime( + dt_str, DATE_STR_FORMAT).replace(tzinfo=pytz.utc) + except ValueError: # If dt_str did not match our format + return None + + +def strip_microseconds(dattim): + """ Returns a copy of dattime object but with microsecond set to 0. """ + return dattim.replace(microsecond=0) diff --git a/requirements.txt b/requirements.txt index e1b0a942f80..8bc2c4616ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # required for Home Assistant core requests>=2.0 pyyaml>=3.11 +pytz>=2015.2 # optional, needed for specific components @@ -40,8 +41,15 @@ PyISY>=1.0.2 # sensor.systemmonitor psutil>=2.2.1 -#pushover notifications +# pushover notifications python-pushover>=0.2 # Transmission Torrent Client transmissionrpc>=0.11 + +# OpenWeatherMap Web API +pyowm>=2.2.0 + +# XMPP Bindings (notify.xmpp) +sleekxmpp>=1.3.1 + diff --git a/scripts/run_tests b/scripts/run_tests index 8d1c6aed114..75b25ca805a 100755 --- a/scripts/run_tests +++ b/scripts/run_tests @@ -3,4 +3,8 @@ if [ ${PWD##*/} == "scripts" ]; then cd .. fi -python3 -m unittest discover tests +if [ "$1" = "coverage" ]; then + coverage run -m unittest discover tests +else + python3 -m unittest discover tests +fi diff --git a/tests/config/custom_components/device_tracker/test.py b/tests/config/custom_components/device_tracker/test.py index 481892a9a67..635d400316f 100644 --- a/tests/config/custom_components/device_tracker/test.py +++ b/tests/config/custom_components/device_tracker/test.py @@ -26,6 +26,10 @@ class MockScanner(object): """ Make a device leave the house. """ self.devices_home.remove(device) + def reset(self): + """ Resets which devices are home. """ + self.devices_home = [] + def scan_devices(self): """ Returns a list of fake devices. """ diff --git a/tests/helpers.py b/tests/helpers.py index d98c549346d..c6799defe21 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -5,16 +5,36 @@ tests.helper Helper method for writing tests. """ import os +from datetime import timedelta import homeassistant as ha +import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME +from homeassistant.const import ( + STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, + EVENT_STATE_CHANGED) +from homeassistant.components import sun -def get_test_home_assistant(): +def get_test_config_dir(): + """ Returns a path to a test config dir. """ + return os.path.join(os.path.dirname(__file__), "config") + + +def get_test_home_assistant(num_threads=None): """ Returns a Home Assistant object pointing at test config dir. """ + if num_threads: + orig_num_threads = ha.MIN_WORKER_THREAD + ha.MIN_WORKER_THREAD = num_threads + hass = ha.HomeAssistant() - hass.config.config_dir = os.path.join(os.path.dirname(__file__), "config") + + if num_threads: + ha.MIN_WORKER_THREAD = orig_num_threads + + hass.config.config_dir = get_test_config_dir() + hass.config.latitude = 32.87336 + hass.config.longitude = -117.22743 return hass @@ -32,6 +52,44 @@ def mock_service(hass, domain, service): return calls +def trigger_device_tracker_scan(hass): + """ Triggers the device tracker to scan. """ + hass.bus.fire( + EVENT_TIME_CHANGED, + {'now': + dt_util.utcnow().replace(second=0) + timedelta(hours=1)}) + + +def ensure_sun_risen(hass): + """ Trigger sun to rise if below horizon. """ + if not sun.is_on(hass): + hass.bus.fire( + EVENT_TIME_CHANGED, + {'now': + sun.next_rising_utc(hass) + timedelta(seconds=10)}) + + +def ensure_sun_set(hass): + """ Trigger sun to set if above horizon. """ + if sun.is_on(hass): + hass.bus.fire( + EVENT_TIME_CHANGED, + {'now': + sun.next_setting_utc(hass) + timedelta(seconds=10)}) + + +def mock_state_change_event(hass, new_state, old_state=None): + event_data = { + 'entity_id': new_state.entity_id, + 'new_state': new_state, + } + + if old_state: + event_data['old_state'] = old_state + + hass.bus.fire(EVENT_STATE_CHANGED, event_data) + + class MockModule(object): """ Provides a fake module. """ diff --git a/tests/test_component_demo.py b/tests/test_component_demo.py index 3107ba40833..3077b20a4da 100644 --- a/tests/test_component_demo.py +++ b/tests/test_component_demo.py @@ -4,13 +4,10 @@ tests.test_component_demo Tests demo component. """ -# pylint: disable=too-many-public-methods,protected-access import unittest import homeassistant as ha import homeassistant.components.demo as demo -from homeassistant.const import ( - SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON, STATE_OFF, ATTR_ENTITY_ID) class TestDemo(unittest.TestCase): @@ -23,46 +20,6 @@ class TestDemo(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_services(self): - """ Test the demo services. """ - # Test turning on and off different types - demo.setup(self.hass, {}) - - for domain in ('light', 'switch'): - # Focus on 1 entity - entity_id = self.hass.states.entity_ids(domain)[0] - - self.hass.services.call( - domain, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}) - - self.hass.pool.block_till_done() - - self.assertEqual(STATE_ON, self.hass.states.get(entity_id).state) - - self.hass.services.call( - domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}) - - self.hass.pool.block_till_done() - - self.assertEqual(STATE_OFF, self.hass.states.get(entity_id).state) - - # Act on all - self.hass.services.call(domain, SERVICE_TURN_ON) - - self.hass.pool.block_till_done() - - for entity_id in self.hass.states.entity_ids(domain): - self.assertEqual( - STATE_ON, self.hass.states.get(entity_id).state) - - self.hass.services.call(domain, SERVICE_TURN_OFF) - - self.hass.pool.block_till_done() - - for entity_id in self.hass.states.entity_ids(domain): - self.assertEqual( - STATE_OFF, self.hass.states.get(entity_id).state) - def test_if_demo_state_shows_by_default(self): """ Test if demo state shows if we give no configuration. """ demo.setup(self.hass, {demo.DOMAIN: {}}) diff --git a/tests/test_component_device_sun_light_trigger.py b/tests/test_component_device_sun_light_trigger.py new file mode 100644 index 00000000000..7a05f63099f --- /dev/null +++ b/tests/test_component_device_sun_light_trigger.py @@ -0,0 +1,127 @@ +""" +tests.test_component_device_sun_light_trigger +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests device sun light trigger component. +""" +# pylint: disable=too-many-public-methods,protected-access +import os +import unittest + +import homeassistant.loader as loader +from homeassistant.const import CONF_PLATFORM +from homeassistant.components import ( + device_tracker, light, sun, device_sun_light_trigger) + + +from helpers import ( + get_test_home_assistant, ensure_sun_risen, ensure_sun_set, + trigger_device_tracker_scan) + + +KNOWN_DEV_PATH = None + + +def setUpModule(): # pylint: disable=invalid-name + """ Initalizes a Home Assistant server. """ + global KNOWN_DEV_PATH + + hass = get_test_home_assistant() + + loader.prepare(hass) + KNOWN_DEV_PATH = hass.config.path( + device_tracker.KNOWN_DEVICES_FILE) + + hass.stop() + + with open(KNOWN_DEV_PATH, 'w') as fil: + fil.write('device,name,track,picture\n') + fil.write('DEV1,device 1,1,http://example.com/dev1.jpg\n') + fil.write('DEV2,device 2,1,http://example.com/dev2.jpg\n') + + +def tearDownModule(): # pylint: disable=invalid-name + """ Stops the Home Assistant server. """ + os.remove(KNOWN_DEV_PATH) + + +class TestDeviceSunLightTrigger(unittest.TestCase): + """ Test the device sun light trigger module. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = get_test_home_assistant() + + self.scanner = loader.get_component( + 'device_tracker.test').get_scanner(None, None) + + self.scanner.reset() + self.scanner.come_home('DEV1') + + loader.get_component('light.test').init() + + device_tracker.setup(self.hass, { + device_tracker.DOMAIN: {CONF_PLATFORM: 'test'} + }) + + light.setup(self.hass, { + light.DOMAIN: {CONF_PLATFORM: 'test'} + }) + + sun.setup(self.hass, {}) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_lights_on_when_sun_sets(self): + """ Test lights go on when there is someone home and the sun sets. """ + + device_sun_light_trigger.setup( + self.hass, {device_sun_light_trigger.DOMAIN: {}}) + + ensure_sun_risen(self.hass) + + light.turn_off(self.hass) + + self.hass.pool.block_till_done() + + ensure_sun_set(self.hass) + + self.hass.pool.block_till_done() + + self.assertTrue(light.is_on(self.hass)) + + def test_lights_turn_off_when_everyone_leaves(self): + """ Test lights turn off when everyone leaves the house. """ + light.turn_on(self.hass) + + self.hass.pool.block_till_done() + + device_sun_light_trigger.setup( + self.hass, {device_sun_light_trigger.DOMAIN: {}}) + + self.scanner.leave_home('DEV1') + + trigger_device_tracker_scan(self.hass) + + self.hass.pool.block_till_done() + + self.assertFalse(light.is_on(self.hass)) + + def test_lights_turn_on_when_coming_home_after_sun_set(self): + """ Test lights turn on when coming home after sun set. """ + light.turn_off(self.hass) + + ensure_sun_set(self.hass) + + self.hass.pool.block_till_done() + + device_sun_light_trigger.setup( + self.hass, {device_sun_light_trigger.DOMAIN: {}}) + + self.scanner.come_home('DEV2') + trigger_device_tracker_scan(self.hass) + + self.hass.pool.block_till_done() + + self.assertTrue(light.is_on(self.hass)) diff --git a/tests/test_component_device_scanner.py b/tests/test_component_device_tracker.py similarity index 96% rename from tests/test_component_device_scanner.py rename to tests/test_component_device_tracker.py index 2bd392c21d0..038b2363e7b 100644 --- a/tests/test_component_device_scanner.py +++ b/tests/test_component_device_tracker.py @@ -1,17 +1,18 @@ """ -tests.test_component_group -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.test_component_device_tracker +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests the group compoments. +Tests the device tracker compoments. """ # pylint: disable=protected-access,too-many-public-methods import unittest -from datetime import datetime, timedelta +from datetime import timedelta import logging import os import homeassistant as ha import homeassistant.loader as loader +import homeassistant.util.dt as dt_util from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM) import homeassistant.components.device_tracker as device_tracker @@ -80,6 +81,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): scanner = loader.get_component( 'device_tracker.test').get_scanner(None, None) + scanner.reset() + scanner.come_home('DEV1') scanner.come_home('DEV2') @@ -116,7 +119,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2') dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3') - now = datetime.now() + now = dt_util.utcnow() # Device scanner scans every 12 seconds. We need to sync our times to # be every 12 seconds or else the time_changed event will be ignored. diff --git a/tests/test_component_history.py b/tests/test_component_history.py new file mode 100644 index 00000000000..b6ae8dab33f --- /dev/null +++ b/tests/test_component_history.py @@ -0,0 +1,137 @@ +""" +tests.test_component_history +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the history component. +""" +# pylint: disable=protected-access,too-many-public-methods +import time +import os +import unittest + +import homeassistant as ha +import homeassistant.util.dt as dt_util +from homeassistant.components import history, recorder, http + +from helpers import get_test_home_assistant, mock_state_change_event + +SERVER_PORT = 8126 + + +class TestComponentHistory(unittest.TestCase): + """ Tests homeassistant.components.history module. """ + + def setUp(self): # pylint: disable=invalid-name + """ Init needed objects. """ + self.hass = get_test_home_assistant(1) + self.init_rec = False + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + if self.init_rec: + recorder._INSTANCE.block_till_done() + os.remove(self.hass.config.path(recorder.DB_FILE)) + + def init_recorder(self): + recorder.setup(self.hass, {}) + self.hass.start() + recorder._INSTANCE.block_till_done() + self.init_rec = True + + def test_setup(self): + """ Test setup method of history. """ + http.setup(self.hass, { + http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}}) + self.assertTrue(history.setup(self.hass, {})) + + def test_last_5_states(self): + """ Test retrieving the last 5 states. """ + self.init_recorder() + states = [] + + entity_id = 'test.last_5_states' + + for i in range(7): + self.hass.states.set(entity_id, "State {}".format(i)) + + if i > 1: + states.append(self.hass.states.get(entity_id)) + + self.hass.pool.block_till_done() + recorder._INSTANCE.block_till_done() + + self.assertEqual( + list(reversed(states)), history.last_5_states(entity_id)) + + def test_get_states(self): + """ Test getting states at a specific point in time. """ + self.init_recorder() + states = [] + + # Create 10 states for 5 different entities + # After the first 5, sleep a second and save the time + # history.get_states takes the latest states BEFORE point X + + for i in range(10): + state = ha.State( + 'test.point_in_time_{}'.format(i % 5), + "State {}".format(i), + {'attribute_test': i}) + + mock_state_change_event(self.hass, state) + self.hass.pool.block_till_done() + recorder._INSTANCE.block_till_done() + + if i < 5: + states.append(state) + + if i == 4: + time.sleep(1) + point = dt_util.utcnow() + + self.assertEqual( + states, + sorted( + history.get_states(point), key=lambda state: state.entity_id)) + + # Test get_state here because we have a DB setup + self.assertEqual( + states[0], history.get_state(point, states[0].entity_id)) + + def test_state_changes_during_period(self): + self.init_recorder() + entity_id = 'media_player.test' + + def set_state(state): + self.hass.states.set(entity_id, state) + self.hass.pool.block_till_done() + recorder._INSTANCE.block_till_done() + + return self.hass.states.get(entity_id) + + set_state('idle') + set_state('YouTube') + + start = dt_util.utcnow() + + time.sleep(1) + + states = [ + set_state('idle'), + set_state('Netflix'), + set_state('Plex'), + set_state('YouTube'), + ] + + time.sleep(1) + + end = dt_util.utcnow() + + set_state('Netflix') + set_state('Plex') + + self.assertEqual( + {entity_id: states}, + history.state_changes_during_period(start, end, entity_id)) diff --git a/tests/test_component_logbook.py b/tests/test_component_logbook.py new file mode 100644 index 00000000000..2f8f6b8c513 --- /dev/null +++ b/tests/test_component_logbook.py @@ -0,0 +1,98 @@ +""" +tests.test_component_logbook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the logbook component. +""" +# pylint: disable=protected-access,too-many-public-methods +import unittest +from datetime import timedelta + +import homeassistant as ha +from homeassistant.const import ( + EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +import homeassistant.util.dt as dt_util +from homeassistant.components import logbook, http + +from helpers import get_test_home_assistant + +SERVER_PORT = 8127 + + +class TestComponentHistory(unittest.TestCase): + """ Tests homeassistant.components.history module. """ + + def test_setup(self): + """ Test setup method. """ + try: + hass = get_test_home_assistant() + http.setup(hass, { + http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}}) + self.assertTrue(logbook.setup(hass, {})) + finally: + hass.stop() + + def test_humanify_filter_sensor(self): + """ Test humanify filter too frequent sensor values. """ + entity_id = 'sensor.bla' + + pointA = dt_util.strip_microseconds(dt_util.utcnow().replace(minute=2)) + pointB = pointA.replace(minute=5) + pointC = 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_id, 20) + eventC = self.create_state_changed_event(pointC, entity_id, 30) + + entries = list(logbook.humanify((eventA, eventB, eventC))) + + self.assertEqual(2, len(entries)) + self.assert_entry( + entries[0], pointB, 'bla', domain='sensor', entity_id=entity_id) + + self.assert_entry( + entries[1], pointC, 'bla', domain='sensor', entity_id=entity_id) + + def test_home_assistant_start_stop_grouped(self): + """ Tests if home assistant start and stop events are grouped if + occuring in the same minute. """ + entries = list(logbook.humanify(( + ha.Event(EVENT_HOMEASSISTANT_STOP), + ha.Event(EVENT_HOMEASSISTANT_START), + ))) + + self.assertEqual(1, len(entries)) + self.assert_entry( + entries[0], name='Home Assistant', message='restarted', + domain=ha.DOMAIN) + + def assert_entry(self, entry, when=None, name=None, message=None, + domain=None, entity_id=None): + """ Asserts an entry is what is expected """ + if when: + self.assertEqual(when, entry.when) + + if name: + self.assertEqual(name, entry.name) + + if message: + self.assertEqual(message, entry.message) + + if domain: + self.assertEqual(domain, entry.domain) + + if entity_id: + self.assertEqual(entity_id, entry.entity_id) + + def create_state_changed_event(self, event_time_fired, entity_id, state): + """ Create state changed event. """ + + # Logbook only cares about state change events that + # contain an old state but will not actually act on it. + state = ha.State(entity_id, state).as_dict() + + return ha.Event(EVENT_STATE_CHANGED, { + 'entity_id': entity_id, + 'old_state': state, + 'new_state': state, + }, time_fired=event_time_fired) diff --git a/tests/test_component_recorder.py b/tests/test_component_recorder.py new file mode 100644 index 00000000000..68c63b637d0 --- /dev/null +++ b/tests/test_component_recorder.py @@ -0,0 +1,70 @@ +""" +tests.test_component_recorder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests Recorder component. +""" +# pylint: disable=too-many-public-methods,protected-access +import unittest +import os + +from homeassistant.const import MATCH_ALL +from homeassistant.components import recorder + +from helpers import get_test_home_assistant + + +class TestRecorder(unittest.TestCase): + """ Test the chromecast module. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = get_test_home_assistant() + recorder.setup(self.hass, {}) + self.hass.start() + recorder._INSTANCE.block_till_done() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + recorder._INSTANCE.block_till_done() + os.remove(self.hass.config.path(recorder.DB_FILE)) + + def test_saving_state(self): + """ Tests saving and restoring a state. """ + entity_id = 'test.recorder' + state = 'restoring_from_db' + attributes = {'test_attr': 5, 'test_attr_10': 'nice'} + + self.hass.states.set(entity_id, state, attributes) + + self.hass.pool.block_till_done() + recorder._INSTANCE.block_till_done() + + states = recorder.query_states('SELECT * FROM states') + + self.assertEqual(1, len(states)) + self.assertEqual(self.hass.states.get(entity_id), states[0]) + + def test_saving_event(self): + """ Tests saving and restoring an event. """ + event_type = 'EVENT_TEST' + event_data = {'test_attr': 5, 'test_attr_10': 'nice'} + + events = [] + + def event_listener(event): + """ Records events from eventbus. """ + if event.event_type == event_type: + events.append(event) + + self.hass.bus.listen(MATCH_ALL, event_listener) + + self.hass.bus.fire(event_type, event_data) + + self.hass.pool.block_till_done() + recorder._INSTANCE.block_till_done() + + db_events = recorder.query_events( + 'SELECT * FROM events WHERE event_type = ?', (event_type, )) + + self.assertEqual(events, db_events) diff --git a/tests/test_component_sun.py b/tests/test_component_sun.py index a4ff19429f3..aec97ede6a8 100644 --- a/tests/test_component_sun.py +++ b/tests/test_component_sun.py @@ -6,11 +6,12 @@ Tests Sun component. """ # pylint: disable=too-many-public-methods,protected-access import unittest -import datetime as dt +from datetime import timedelta import ephem import homeassistant as ha +import homeassistant.util.dt as dt_util import homeassistant.components.sun as sun @@ -42,22 +43,20 @@ class TestSun(unittest.TestCase): observer.lat = '32.87336' # pylint: disable=assigning-non-slot observer.long = '117.22743' # pylint: disable=assigning-non-slot - utc_now = dt.datetime.utcnow() + utc_now = dt_util.utcnow() body_sun = ephem.Sun() # pylint: disable=no-member - next_rising_dt = ephem.localtime( - observer.next_rising(body_sun, start=utc_now)) - next_setting_dt = ephem.localtime( - observer.next_setting(body_sun, start=utc_now)) + next_rising_dt = observer.next_rising( + body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC) + next_setting_dt = observer.next_setting( + body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC) # Home Assistant strips out microseconds # strip it out of the datetime objects - next_rising_dt = next_rising_dt - dt.timedelta( - microseconds=next_rising_dt.microsecond) - next_setting_dt = next_setting_dt - dt.timedelta( - microseconds=next_setting_dt.microsecond) + next_rising_dt = dt_util.strip_microseconds(next_rising_dt) + next_setting_dt = dt_util.strip_microseconds(next_setting_dt) - self.assertEqual(next_rising_dt, sun.next_rising(self.hass)) - self.assertEqual(next_setting_dt, sun.next_setting(self.hass)) + self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass)) + self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass)) # Point it at a state without the proper attributes self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON) @@ -84,7 +83,7 @@ class TestSun(unittest.TestCase): self.assertIsNotNone(test_time) self.hass.bus.fire(ha.EVENT_TIME_CHANGED, - {ha.ATTR_NOW: test_time + dt.timedelta(seconds=5)}) + {ha.ATTR_NOW: test_time + timedelta(seconds=5)}) self.hass.pool.block_till_done() diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 00000000000..133f7d51f71 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,180 @@ +""" +tests.test_config +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests config utils. +""" +# pylint: disable=too-many-public-methods,protected-access +import unittest +import unittest.mock as mock +import os + +from homeassistant import DOMAIN, HomeAssistantError +import homeassistant.util as util +import homeassistant.config as config_util +from homeassistant.const import ( + CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, + CONF_TIME_ZONE) + +from helpers import get_test_config_dir + +CONFIG_DIR = get_test_config_dir() +YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) +CONF_PATH = os.path.join(CONFIG_DIR, config_util.CONF_CONFIG_FILE) + + +def create_file(path): + """ Creates an empty file. """ + with open(path, 'w'): + pass + + +def mock_detect_location_info(): + """ Mock implementation of util.detect_location_info. """ + return util.LocationInfo( + ip='1.1.1.1', + country_code='US', + country_name='United States', + region_code='CA', + region_name='California', + city='San Diego', + zip_code='92122', + time_zone='America/Los_Angeles', + latitude='2.0', + longitude='1.0', + use_fahrenheit=True, + ) + + +class TestConfig(unittest.TestCase): + """ Test the config utils. """ + + def tearDown(self): # pylint: disable=invalid-name + """ Clean up. """ + for path in (YAML_PATH, CONF_PATH): + if os.path.isfile(path): + os.remove(path) + + def test_create_default_config(self): + """ Test creationg of default config. """ + + config_util.create_default_config(CONFIG_DIR, False) + + self.assertTrue(os.path.isfile(YAML_PATH)) + + def test_find_config_file_yaml(self): + """ Test if it finds a YAML config file. """ + + create_file(YAML_PATH) + + self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) + + def test_find_config_file_conf(self): + """ Test if it finds the old CONF config file. """ + + create_file(CONF_PATH) + + self.assertEqual(CONF_PATH, config_util.find_config_file(CONFIG_DIR)) + + def test_find_config_file_prefers_yaml_over_conf(self): + """ Test if find config prefers YAML over CONF if both exist. """ + + create_file(YAML_PATH) + create_file(CONF_PATH) + + self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) + + def test_ensure_config_exists_creates_config(self): + """ Test that calling ensure_config_exists creates a new config file if + none exists. """ + + config_util.ensure_config_exists(CONFIG_DIR, False) + + self.assertTrue(os.path.isfile(YAML_PATH)) + + def test_ensure_config_exists_uses_existing_config(self): + """ Test that calling ensure_config_exists uses existing config. """ + + create_file(YAML_PATH) + config_util.ensure_config_exists(CONFIG_DIR, False) + + with open(YAML_PATH) as f: + content = f.read() + + # File created with create_file are empty + self.assertEqual('', content) + + def test_load_yaml_config_converts_empty_files_to_dict(self): + """ Test that loading an empty file returns an empty dict. """ + create_file(YAML_PATH) + + self.assertIsInstance( + config_util.load_yaml_config_file(YAML_PATH), dict) + + def test_load_yaml_config_raises_error_if_not_dict(self): + """ Test error raised when YAML file is not a dict. """ + with open(YAML_PATH, 'w') as f: + f.write('5') + + with self.assertRaises(HomeAssistantError): + config_util.load_yaml_config_file(YAML_PATH) + + def test_load_yaml_config_raises_error_if_malformed_yaml(self): + """ Test error raised if invalid YAML. """ + with open(YAML_PATH, 'w') as f: + f.write(':') + + with self.assertRaises(HomeAssistantError): + config_util.load_yaml_config_file(YAML_PATH) + + def test_load_config_loads_yaml_config(self): + """ Test correct YAML config loading. """ + with open(YAML_PATH, 'w') as f: + f.write('hello: world') + + self.assertEqual({'hello': 'world'}, + config_util.load_config_file(YAML_PATH)) + + def test_load_config_loads_conf_config(self): + """ Test correct YAML config loading. """ + create_file(CONF_PATH) + + self.assertEqual({}, config_util.load_config_file(CONF_PATH)) + + def test_conf_config_file(self): + """ Test correct CONF config loading. """ + with open(CONF_PATH, 'w') as f: + f.write('[ha]\ntime_zone=America/Los_Angeles') + + self.assertEqual({'ha': {'time_zone': 'America/Los_Angeles'}}, + config_util.load_conf_config_file(CONF_PATH)) + + def test_create_default_config_detect_location(self): + """ Test that detect location sets the correct config keys. """ + with mock.patch('homeassistant.util.detect_location_info', + mock_detect_location_info): + config_util.ensure_config_exists(CONFIG_DIR) + + config = config_util.load_config_file(YAML_PATH) + + self.assertIn(DOMAIN, config) + + ha_conf = config[DOMAIN] + + expected_values = { + CONF_LATITUDE: 2.0, + CONF_LONGITUDE: 1.0, + CONF_TEMPERATURE_UNIT: 'F', + CONF_NAME: 'Home', + CONF_TIME_ZONE: 'America/Los_Angeles' + } + + self.assertEqual(expected_values, ha_conf) + + def test_create_default_config_returns_none_if_write_error(self): + """ + Test that writing default config to non existing folder returns None. + """ + self.assertIsNone( + config_util.create_default_config( + os.path.join(CONFIG_DIR, 'non_existing_dir/'), False)) diff --git a/tests/test_core.py b/tests/test_core.py index a5c37f753b9..58052fe43f0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -30,7 +30,11 @@ class TestHomeAssistant(unittest.TestCase): def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ - self.hass.stop() + try: + self.hass.stop() + except ha.HomeAssistantError: + # Already stopped after the block till stopped test + pass def test_get_config_path(self): """ Test get_config_path method. """ @@ -72,7 +76,7 @@ class TestHomeAssistant(unittest.TestCase): runs = [] - self.hass.track_point_in_time( + self.hass.track_point_in_utc_time( lambda x: runs.append(1), birthday_paulus) self._send_time_changed(before_birthday) @@ -88,7 +92,7 @@ class TestHomeAssistant(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(runs)) - self.hass.track_point_in_time( + self.hass.track_point_in_utc_time( lambda x: runs.append(1), birthday_paulus) self._send_time_changed(after_birthday) diff --git a/tests/test_helper_entity.py b/tests/test_helper_entity.py index a36365afc3b..14559ded39a 100644 --- a/tests/test_helper_entity.py +++ b/tests/test_helper_entity.py @@ -25,7 +25,8 @@ class TestHelpersEntity(unittest.TestCase): def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ self.hass.stop() - entity.Entity.overwrite_hidden(self.entity.entity_id, None) + entity.Entity.overwrite_attribute(self.entity.entity_id, + [ATTR_HIDDEN], [None]) def test_default_hidden_not_in_attributes(self): """ Test that the default hidden property is set to False. """ @@ -43,7 +44,8 @@ class TestHelpersEntity(unittest.TestCase): def test_overwriting_hidden_property_to_true(self): """ Test we can overwrite hidden property to True. """ - entity.Entity.overwrite_hidden(self.entity.entity_id, True) + entity.Entity.overwrite_attribute(self.entity.entity_id, + [ATTR_HIDDEN], [True]) self.entity.update_ha_state() state = self.hass.states.get(self.entity.entity_id) @@ -51,7 +53,8 @@ class TestHelpersEntity(unittest.TestCase): def test_overwriting_hidden_property_to_false(self): """ Test we can overwrite hidden property to True. """ - entity.Entity.overwrite_hidden(self.entity.entity_id, False) + entity.Entity.overwrite_attribute(self.entity.entity_id, + [ATTR_HIDDEN], [False]) self.entity.hidden = True self.entity.update_ha_state() diff --git a/tests/test_util.py b/tests/test_util.py index 038db227e1a..f75b6db8aeb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -35,17 +35,6 @@ class TestUtil(unittest.TestCase): self.assertEqual("Test_More", util.slugify("Test More")) self.assertEqual("Test_More", util.slugify("Test_(More)")) - def test_datetime_to_str(self): - """ Test datetime_to_str. """ - self.assertEqual("12:00:00 09-07-1986", - util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0))) - - def test_str_to_datetime(self): - """ Test str_to_datetime. """ - self.assertEqual(datetime(1986, 7, 9, 12, 0, 0), - util.str_to_datetime("12:00:00 09-07-1986")) - self.assertIsNone(util.str_to_datetime("not a datetime string")) - def test_split_entity_id(self): """ Test split_entity_id. """ self.assertEqual(['domain', 'object_id'], diff --git a/tests/test_util_dt.py b/tests/test_util_dt.py new file mode 100644 index 00000000000..5deafb58040 --- /dev/null +++ b/tests/test_util_dt.py @@ -0,0 +1,137 @@ +""" +tests.test_util +~~~~~~~~~~~~~~~~~ + +Tests Home Assistant date util methods. +""" +# pylint: disable=too-many-public-methods +import unittest +from datetime import datetime, timedelta + +import homeassistant.util.dt as dt_util + +TEST_TIME_ZONE = 'America/Los_Angeles' + + +class TestDateUtil(unittest.TestCase): + """ Tests util date methods. """ + + def setUp(self): + self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE + + def tearDown(self): + dt_util.set_default_time_zone(self.orig_default_time_zone) + + def test_get_time_zone_retrieves_valid_time_zone(self): + """ Test getting a time zone. """ + time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) + + self.assertIsNotNone(time_zone) + self.assertEqual(TEST_TIME_ZONE, time_zone.zone) + + def test_get_time_zone_returns_none_for_garbage_time_zone(self): + """ Test getting a non existing time zone. """ + time_zone = dt_util.get_time_zone("Non existing time zone") + + self.assertIsNone(time_zone) + + def test_set_default_time_zone(self): + """ Test setting default time zone. """ + time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) + + dt_util.set_default_time_zone(time_zone) + + # We cannot compare the timezones directly because of DST + self.assertEqual(time_zone.zone, dt_util.now().tzinfo.zone) + + def test_utcnow(self): + """ Test the UTC now method. """ + self.assertAlmostEqual( + dt_util.utcnow().replace(tzinfo=None), + datetime.utcnow(), + delta=timedelta(seconds=1)) + + def test_now(self): + """ Test the now method. """ + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) + + self.assertAlmostEqual( + dt_util.as_utc(dt_util.now()).replace(tzinfo=None), + datetime.utcnow(), + delta=timedelta(seconds=1)) + + def test_as_utc_with_naive_object(self): + utcnow = datetime.utcnow() + + self.assertEqual(utcnow, + dt_util.as_utc(utcnow).replace(tzinfo=None)) + + def test_as_utc_with_utc_object(self): + utcnow = dt_util.utcnow() + + self.assertEqual(utcnow, dt_util.as_utc(utcnow)) + + def test_as_utc_with_local_object(self): + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) + + localnow = dt_util.now() + + utcnow = dt_util.as_utc(localnow) + + self.assertEqual(localnow, utcnow) + self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo) + + def test_as_local_with_naive_object(self): + now = dt_util.now() + + self.assertAlmostEqual( + now, dt_util.as_local(datetime.utcnow()), + delta=timedelta(seconds=1)) + + def test_as_local_with_local_object(self): + now = dt_util.now() + + self.assertEqual(now, now) + + def test_as_local_with_utc_object(self): + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) + + utcnow = dt_util.utcnow() + localnow = dt_util.as_local(utcnow) + + self.assertEqual(localnow, utcnow) + self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo) + + def test_utc_from_timestamp(self): + """ Test utc_from_timestamp method. """ + self.assertEqual( + datetime(1986, 7, 9, tzinfo=dt_util.UTC), + dt_util.utc_from_timestamp(521251200)) + + def test_datetime_to_str(self): + """ Test datetime_to_str. """ + self.assertEqual( + "12:00:00 09-07-1986", + dt_util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0))) + + def test_datetime_to_local_str(self): + """ Test datetime_to_local_str. """ + self.assertEqual( + dt_util.datetime_to_str(dt_util.now()), + dt_util.datetime_to_local_str(dt_util.utcnow())) + + def test_str_to_datetime_converts_correctly(self): + """ Test str_to_datetime converts strings. """ + self.assertEqual( + datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC), + dt_util.str_to_datetime("12:00:00 09-07-1986")) + + def test_str_to_datetime_returns_none_for_incorrect_format(self): + """ Test str_to_datetime returns None if incorrect format. """ + self.assertIsNone(dt_util.str_to_datetime("not a datetime string")) + + def test_strip_microseconds(self): + test_time = datetime(2015, 1, 1, microsecond=5000) + + self.assertNotEqual(0, test_time.microsecond) + self.assertEqual(0, dt_util.strip_microseconds(test_time).microsecond)