diff --git a/.coveragerc b/.coveragerc index ee40c987ea2..39a3dee22bf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,6 +7,9 @@ omit = homeassistant/external/* # omit pieces of code that rely on external devices being present + homeassistant/components/arduino.py + homeassistant/components/*/arduino.py + homeassistant/components/wink.py homeassistant/components/*/wink.py @@ -32,6 +35,7 @@ omit = homeassistant/components/light/hue.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/mpd.py + homeassistant/components/notify/file.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py homeassistant/components/notify/pushbullet.py @@ -40,6 +44,8 @@ omit = homeassistant/components/notify/syslog.py homeassistant/components/notify/xmpp.py homeassistant/components/sensor/bitcoin.py + homeassistant/components/sensor/efergy.py + homeassistant/components/sensor/forecast.py homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/sabnzbd.py @@ -47,6 +53,7 @@ omit = homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py + homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/wemo.py homeassistant/components/thermostat/nest.py diff --git a/.gitignore b/.gitignore index 8c4eec4d180..8dab1d873da 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,7 @@ nosetests.xml .pydevproject .python-version + +# venv stuff +pyvenv.cfg +pip-selfcheck.json diff --git a/.travis.yml b/.travis.yml index d8632860c2a..7af8ce86dcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: python python: - "3.4" diff --git a/README.md b/README.md index d35d89e7d53..18a01345741 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,45 @@ # Home Assistant [![Build Status](https://travis-ci.org/balloob/home-assistant.svg?branch=master)](https://travis-ci.org/balloob/home-assistant) [![Coverage Status](https://img.shields.io/coveralls/balloob/home-assistant.svg)](https://coveralls.io/r/balloob/home-assistant?branch=master) [![Join the chat at https://gitter.im/balloob/home-assistant](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -This is the source code for Home Assistant. For installation instructions, tutorials and the docs, please see [the website](https://home-assistant.io). For a functioning demo frontend of Home Assistant, [click here](https://home-assistant.io/demo/). +Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control. [Open a demo.](https://home-assistant.io/demo/) -Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control. - -It offers the following functionality through built-in components: - - * Track if devices are home by monitoring connected devices to a wireless router (supporting [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), and [DD-WRT](http://www.dd-wrt.com/site/index)) - * Track and control [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors - * Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast) and [Music Player Daemon](http://www.musicpd.org/) - * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/) - * Track running system services and monitoring your system stats (Memory, disk usage, and more) - * Control low-cost 433 MHz remote control wall-socket devices (https://github.com/r10r/rcswitch-pi) and other switches that can be turned on/off with shell commands - * Turn on the lights when people get home after sun set - * Turn on lights slowly during sun set to compensate for light loss - * Turn off all lights and devices when everybody leaves the house - * Offers web interface to monitor and control Home Assistant - * Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects - * [Ability to have multiple instances of Home Assistant work together](https://home-assistant.io/developers/architecture.html) - * Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), and [Jabber (XMPP)](http://xmpp.org) - * Allow to display details about a running [Transmission](http://www.transmissionbt.com/) client, the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), the time, the date, and the downloads from [SABnzbd](http://sabnzbd.org) - -Home Assistant also includes functionality for controlling HTPCs: - - * Simulate key presses for Play/Pause, Next track, Prev track, Volume up, Volume Down - * Download files - * Open URLs in the default browser +Check out [the website](https://home-assistant.io) for installation instructions, tutorials and documentation. [![screenshot-states](https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png)](https://home-assistant.io/demo/) +Examples of devices it can interface it: + + * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/) + * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors + * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) and [Kodi (XBMC)](http://kodi.tv/) + * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/) + * Integrate data from the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), [Transmission](http://www.transmissionbt.com/) or [SABnzbd](http://sabnzbd.org). + * [See full list of supported devices](https://home-assistant.io/components/) + +Built home automation on top of your devices: + + * Keep a precise history of every change to the state of your house + * Turn on the lights when people get home after sun set + * Turn on lights slowly during sun set to compensate for less light + * Turn off all lights and devices when everybody leaves the house + * Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects + * Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), and [Jabber (XMPP)](http://xmpp.org) + The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html). -If you run into issues while using Home Assistant or during development of a component, reach out to the [Home Assistant developer community](https://groups.google.com/forum/#!forum/home-assistant-dev). +If you run into issues while using Home Assistant or during development of a component, reach out to the [Home Assistant help section](https://home-assistant.io/help/) how to reach us. -## Installation instructions / Quick-start guide +## Quick-start guide -Running Home Assistant requires that [Python](https://www.python.org/) 3.4 and the package [requests](http://docs.python-requests.org/en/latest/) are installed. Run the following code to install and start Home Assistant: +Running Home Assistant requires [Python 3.4](https://www.python.org/). Run the following code to get up and running: -```python +``` git clone --recursive https://github.com/balloob/home-assistant.git +python3 -m venv home-assistant cd home-assistant -python3 -m pip install --user -r requirements.txt python3 -m homeassistant --open-ui ``` -The last command will start the Home Assistant server and launch its web interface. By default Home Assistant looks for the configuration file `config/home-assistant.conf`. A standard configuration file will be written if none exists. +The last command will start the Home Assistant server and launch its web interface. By default Home Assistant looks for the configuration file `config/configuration.yaml`. A standard configuration file will be written if none exists. If you are still exploring if you want to use Home Assistant in the first place, you can enable the demo mode by adding the `--demo-mode` argument to the last command. diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example index c99f760f21f..5acca361a30 100644 --- a/config/configuration.yaml.example +++ b/config/configuration.yaml.example @@ -159,5 +159,5 @@ scene: light.tv_back_light: on light.ceiling: state: on - color: [0.33, 0.66] + xy_color: [0.33, 0.66] brightness: 200 diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 316529ce74e..0a575afede0 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -4,15 +4,9 @@ from __future__ import print_function import sys import os import argparse -import importlib +import subprocess - -# Home Assistant dependencies, mapped module -> package name -DEPENDENCIES = { - 'requests': 'requests', - 'yaml': 'pyyaml', - 'pytz': 'pytz', -} +DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2'] def validate_python(): @@ -24,21 +18,29 @@ def validate_python(): sys.exit() +# Copy of homeassistant.util.package because we can't import yet +def install_package(package): + """Install a package on PyPi. Accepts pip compatible package strings. + Return boolean if install successfull.""" + args = ['python3', '-m', 'pip', 'install', '--quiet', package] + if sys.base_prefix == sys.prefix: + args.append('--user') + return not subprocess.call(args) + + def validate_dependencies(): """ Validate all dependencies that HA uses. """ + print("Validating dependencies...") import_fail = False - for module, name in DEPENDENCIES.items(): - try: - importlib.import_module(module) - except ImportError: + for requirement in DEPENDENCIES: + if not install_package(requirement): import_fail = True - print( - 'Fatal Error: Unable to find dependency {}'.format(name)) + print('Fatal Error: Unable to install dependency', requirement) if import_fail: print(("Install dependencies by running: " - "pip3 install -r requirements.txt")) + "python3 -m pip install -r requirements.txt")) sys.exit() diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index f8c595255d9..2da2f4fb7b5 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -14,8 +14,9 @@ import logging from collections import defaultdict import homeassistant -import homeassistant.util as util import homeassistant.util.dt as date_util +import homeassistant.util.package as pkg_util +import homeassistant.util.location as loc_util import homeassistant.config as config_util import homeassistant.loader as loader import homeassistant.components as core_components @@ -60,6 +61,17 @@ def setup_component(hass, domain, config=None): return True +def _handle_requirements(component, name): + """ Installs requirements for component. """ + if hasattr(component, 'REQUIREMENTS'): + for req in component.REQUIREMENTS: + if not pkg_util.install_package(req): + _LOGGER.error('Not initializing %s because could not install ' + 'dependency %s', name, req) + return False + return True + + def _setup_component(hass, domain, config): """ Setup a component for Home Assistant. """ component = loader.get_component(domain) @@ -74,6 +86,9 @@ def _setup_component(hass, domain, config): return False + if not _handle_requirements(component, domain): + return False + try: if component.setup(hass, config): hass.config.components.append(component.DOMAIN) @@ -109,18 +124,22 @@ def prepare_setup_platform(hass, config, domain, platform_name): if platform is None: return None - # Already loaded or no dependencies - elif (platform_path in hass.config.components or - not hasattr(platform, 'DEPENDENCIES')): + # Already loaded + elif platform_path in hass.config.components: 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 + if hasattr(platform, '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 + + if not _handle_requirements(platform, platform_path): + return None return platform @@ -276,7 +295,7 @@ def process_ha_core_config(hass, config): _LOGGER.info('Auto detecting location and temperature unit') - info = util.detect_location_info() + info = loc_util.detect_location_info() if info is None: _LOGGER.error('Could not detect location information') diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py new file mode 100644 index 00000000000..db91c5e0d9c --- /dev/null +++ b/homeassistant/components/arduino.py @@ -0,0 +1,143 @@ +""" +components.arduino +~~~~~~~~~~~~~~~~~~ +Arduino component that connects to a directly attached Arduino board which +runs with the Firmata firmware. + +Configuration: + +To use the Arduino board you will need to add something like the following +to your config/configuration.yaml + +arduino: + port: /dev/ttyACM0 + +Variables: + +port +*Required +The port where is your board connected to your Home Assistant system. +If you are using an original Arduino the port will be named ttyACM*. The exact +number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/ +'journalctl -f' output. Keep in mind that Arduino clones are often using a +different name for the port (e.g. '/dev/ttyUSB*'). + +A word of caution: The Arduino is not storing states. This means that with +every initialization the pins are set to off/low. +""" +import logging + +try: + from PyMata.pymata import PyMata +except ImportError: + PyMata = None + +from homeassistant.helpers import validate_config +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DOMAIN = "arduino" +DEPENDENCIES = [] +REQUIREMENTS = ['PyMata==2.07a'] +BOARD = None +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup the Arduino component. """ + + global PyMata # pylint: disable=invalid-name + if PyMata is None: + from PyMata.pymata import PyMata as PyMata_ + PyMata = PyMata_ + + import serial + + if not validate_config(config, + {DOMAIN: ['port']}, + _LOGGER): + return False + + global BOARD + try: + BOARD = ArduinoBoard(config[DOMAIN]['port']) + except (serial.serialutil.SerialException, FileNotFoundError): + _LOGGER.exception("Your port is not accessible.") + return False + + if BOARD.get_firmata()[1] <= 2: + _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer.") + return False + + def stop_arduino(event): + """ Stop the Arduino service. """ + BOARD.disconnect() + + def start_arduino(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) + + return True + + +class ArduinoBoard(object): + """ Represents an Arduino board. """ + + def __init__(self, port): + self._port = port + self._board = PyMata(self._port, verbose=False) + + def set_mode(self, pin, direction, mode): + """ Sets the mode and the direction of a given pin. """ + if mode == 'analog' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.INPUT, + self._board.ANALOG) + elif mode == 'analog' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.ANALOG) + elif mode == 'digital' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'digital' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'pwm': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.PWM) + + def get_analog_inputs(self): + """ Get the values from the pins. """ + self._board.capability_query() + return self._board.get_analog_response_table() + + def set_digital_out_high(self, pin): + """ Sets a given digital pin to high. """ + self._board.digital_write(pin, 1) + + def set_digital_out_low(self, pin): + """ Sets a given digital pin to low. """ + self._board.digital_write(pin, 0) + + def get_digital_in(self, pin): + """ Gets the value from a given digital pin. """ + self._board.digital_read(pin) + + def get_analog_in(self, pin): + """ Gets the value from a given analog pin. """ + self._board.analog_read(pin) + + def get_firmata(self): + """ Return the version of the Firmata firmware. """ + return self._board.get_firmata_version() + + def disconnect(self): + """ Disconnects the board and closes the serial connection. """ + self._board.reset() + self._board.close() diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py new file mode 100644 index 00000000000..38379e67e24 --- /dev/null +++ b/homeassistant/components/camera/__init__.py @@ -0,0 +1,227 @@ +# pylint: disable=too-many-lines +""" +homeassistant.components.camera +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Component to interface with various cameras. + +The following features are supported: + - Returning recorded camera images and streams + - Proxying image requests via HA for external access + - Converting a still image url into a live video stream + +Upcoming features + - Recording + - Snapshot + - Motion Detection Recording(for supported cameras) + - Automatic Configuration(for supported cameras) + - Creation of child entities for supported functions + - Collating motion event images passed via FTP into time based events + - A service for calling camera functions + - Camera movement(panning) + - Zoom + - Light/Nightvision toggling + - Support for more devices + - Expanded documentation +""" +import requests +import logging +import time +import re +from homeassistant.helpers.entity import Entity +from homeassistant.const import ( + ATTR_ENTITY_PICTURE, + HTTP_NOT_FOUND, + ATTR_ENTITY_ID, + ) + +from homeassistant.helpers.entity_component import EntityComponent + + +DOMAIN = 'camera' +DEPENDENCIES = ['http'] +GROUP_NAME_ALL_CAMERAS = 'all_cameras' +SCAN_INTERVAL = 30 +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +SWITCH_ACTION_RECORD = 'record' +SWITCH_ACTION_SNAPSHOT = 'snapshot' + +SERVICE_CAMERA = 'camera_service' + +STATE_RECORDING = 'recording' + +DEFAULT_RECORDING_SECONDS = 30 + +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = {} + +FILE_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S-%f' +DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S' + +REC_DIR_PREFIX = 'recording-' +REC_IMG_PREFIX = 'recording_image-' + +STATE_STREAMING = 'streaming' +STATE_IDLE = 'idle' + +CAMERA_PROXY_URL = '/api/camera_proxy_stream/{0}' +CAMERA_STILL_URL = '/api/camera_proxy/{0}' +ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?time={1}' + +MULTIPART_BOUNDARY = '--jpegboundary' +MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' + + +# pylint: disable=too-many-branches +def setup(hass, config): + """ Track states and offer events for sensors. """ + + component = EntityComponent( + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, + DISCOVERY_PLATFORMS) + + component.setup(config) + + # ------------------------------------------------------------------------- + # CAMERA COMPONENT ENDPOINTS + # ------------------------------------------------------------------------- + # The following defines the endpoints for serving images from the camera + # via the HA http server. This is means that you can access images from + # your camera outside of your LAN without the need for port forwards etc. + + # Because the authentication header can't be added in image requests these + # endpoints are secured with session based security. + + # pylint: disable=unused-argument + def _proxy_camera_image(handler, path_match, data): + """ Proxies the camera image via the HA server. """ + entity_id = path_match.group(ATTR_ENTITY_ID) + + camera = None + if entity_id in component.entities.keys(): + camera = component.entities[entity_id] + + if camera: + response = camera.camera_image() + handler.wfile.write(response) + else: + handler.send_response(HTTP_NOT_FOUND) + + hass.http.register_path( + 'GET', + re.compile(r'/api/camera_proxy/(?P[a-zA-Z\._0-9]+)'), + _proxy_camera_image, + require_auth=True) + + # pylint: disable=unused-argument + def _proxy_camera_mjpeg_stream(handler, path_match, data): + """ Proxies the camera image as an mjpeg stream via the HA server. + This function takes still images from the IP camera and turns them + into an MJPEG stream. This means that HA can return a live video + stream even with only a still image URL available. + """ + entity_id = path_match.group(ATTR_ENTITY_ID) + + camera = None + if entity_id in component.entities.keys(): + camera = component.entities[entity_id] + + if not camera: + handler.send_response(HTTP_NOT_FOUND) + handler.end_headers() + return + + try: + camera.is_streaming = True + camera.update_ha_state() + + handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8')) + handler.request.sendall(bytes( + 'Content-type: multipart/x-mixed-replace; \ + boundary=--jpgboundary\r\n\r\n', 'utf-8')) + handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8')) + + # MJPEG_START_HEADER.format() + + while True: + + img_bytes = camera.camera_image() + + headers_str = '\r\n'.join(( + 'Content-length: {}'.format(len(img_bytes)), + 'Content-type: image/jpeg', + )) + '\r\n\r\n' + + handler.request.sendall( + bytes(headers_str, 'utf-8') + + img_bytes + + bytes('\r\n', 'utf-8')) + + handler.request.sendall( + bytes('--jpgboundary\r\n', 'utf-8')) + + except (requests.RequestException, IOError): + camera.is_streaming = False + camera.update_ha_state() + + camera.is_streaming = False + + hass.http.register_path( + 'GET', + re.compile( + r'/api/camera_proxy_stream/(?P[a-zA-Z\._0-9]+)'), + _proxy_camera_mjpeg_stream, + require_auth=True) + + return True + + +class Camera(Entity): + """ The base class for camera components """ + + def __init__(self): + self.is_streaming = False + + @property + # pylint: disable=no-self-use + def is_recording(self): + """ Returns true if the device is recording """ + return False + + @property + # pylint: disable=no-self-use + def brand(self): + """ Should return a string of the camera brand """ + return None + + @property + # pylint: disable=no-self-use + def model(self): + """ Returns string of camera model """ + return None + + def camera_image(self): + """ Return bytes of camera image """ + raise NotImplementedError() + + @property + def state(self): + """ Returns the state of the entity. """ + if self.is_recording: + return STATE_RECORDING + elif self.is_streaming: + return STATE_STREAMING + else: + return STATE_IDLE + + @property + def state_attributes(self): + """ Returns optional state attributes. """ + return { + 'model_name': self.model, + 'brand': self.brand, + 'still_image_url': CAMERA_STILL_URL.format(self.entity_id), + ATTR_ENTITY_PICTURE: ENTITY_IMAGE_URL.format( + self.entity_id, str(time.time())), + 'stream_url': CAMERA_PROXY_URL.format(self.entity_id) + } diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py new file mode 100644 index 00000000000..f7cf5654f46 --- /dev/null +++ b/homeassistant/components/camera/generic.py @@ -0,0 +1,94 @@ +""" +Support for IP Cameras. + +This component provides basic support for IP cameras. For the basic support to +work you camera must support accessing a JPEG snapshot via a URL and you will +need to specify the "still_image_url" parameter which should be the location of +the JPEG image. + +As part of the basic support the following features will be provided: +-MJPEG video streaming +-Saving a snapshot +-Recording(JPEG frame capture) + +To use this component, add the following to your config/configuration.yaml: + +camera: + platform: generic + name: Door Camera + username: YOUR_USERNAME + password: YOUR_PASSWORD + still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg + + +VARIABLES: + +These are the variables for the device_data array: + +still_image_url +*Required +The URL your camera serves the image on. +Example: http://192.168.1.21:2112/ + +name +*Optional +This parameter allows you to override the name of your camera in homeassistant + +username +*Optional +THe username for acessing your camera + +password +*Optional +the password for accessing your camera + + +""" +import logging +from requests.auth import HTTPBasicAuth +from homeassistant.helpers import validate_config +from homeassistant.components.camera import DOMAIN +from homeassistant.components.camera import Camera +import requests + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Adds a generic IP Camera. """ + if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']}, + _LOGGER): + return None + + add_devices_callback([GenericCamera(config)]) + + +# pylint: disable=too-many-instance-attributes +class GenericCamera(Camera): + """ + A generic implementation of an IP camera that is reachable over a URL. + """ + + def __init__(self, device_info): + super().__init__() + self._name = device_info.get('name', 'Generic Camera') + self._username = device_info.get('username') + self._password = device_info.get('password') + self._still_image_url = device_info['still_image_url'] + + def camera_image(self): + """ Return a still image reponse from the camera """ + if self._username and self._password: + response = requests.get( + self._still_image_url, + auth=HTTPBasicAuth(self._username, self._password)) + else: + response = requests.get(self._still_image_url) + + return response.content + + @property + def name(self): + """ Return the name of this device """ + return self._name diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 2abae8095d6..d0b8b155b4a 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -51,6 +51,15 @@ def setup(hass, config): group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]]) group.setup_group(hass, 'bedroom', [lights[2], switches[1]]) + # Setup IP Camera + bootstrap.setup_component( + hass, 'camera', + {'camera': { + 'platform': 'generic', + 'name': 'IP Camera', + 'still_image_url': 'http://194.218.96.92/jpg/image.jpg', + }}) + # Setup scripts bootstrap.setup_component( hass, 'script', @@ -93,17 +102,17 @@ def setup(hass, config): # Setup fake device tracker hass.states.set("device_tracker.paulus", "home", {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/schoutsen/picture"}) + "http://graph.facebook.com/297400035/picture"}) hass.states.set("device_tracker.anne_therese", "not_home", {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/anne.t.frederiksen/picture"}) + "http://graph.facebook.com/621994601/picture"}) hass.states.set("group.all_devices", "home", { "auto": True, ATTR_ENTITY_ID: [ - "device_tracker.Paulus", - "device_tracker.Anne_Therese" + "device_tracker.paulus", + "device_tracker.anne_therese" ] }) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py new file mode 100755 index 00000000000..24d170a5de7 --- /dev/null +++ b/homeassistant/components/device_tracker/tplink.py @@ -0,0 +1,117 @@ +""" +homeassistant.components.device_tracker.tplink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a TP-Link router for device +presence. + +Configuration: + +To use the TP-Link tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: tplink + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. + +""" +import logging +from datetime import timedelta +import re +import threading +import requests + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + + +def get_scanner(hass, config): + """ Validates config and returns a TP-Link scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = TplinkDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class TplinkDeviceScanner(object): + """ This class queries a wireless router running TP-Link firmware + for connected devices. + """ + + def __init__(self, config): + host = config[CONF_HOST] + username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + + self.parse_macs = re.compile('[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-' + + '[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}') + + self.host = host + self.username = username + self.password = password + + self.last_results = {} + self.lock = threading.Lock() + self.success_init = self._update_info() + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + + return self.last_results + + # pylint: disable=no-self-use + def get_device_name(self, device): + """ The TP-Link firmware doesn't save the name of the wireless + device. """ + + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the TP-Link router is up to date. + Returns boolean if scanning successful. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host) + referer = 'http://{}'.format(self.host) + page = requests.get(url, auth=(self.username, self.password), + headers={'referer': referer}) + + result = self.parse_macs.findall(page.text) + + if result: + self.last_results = [mac.replace("-", ":") for mac in result] + return True + + return False diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 819d45b5b68..63c9a0af74f 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -22,6 +22,7 @@ from homeassistant.const import ( DOMAIN = "discovery" DEPENDENCIES = [] +REQUIREMENTS = ['zeroconf>=0.16.0'] SCAN_INTERVAL = 300 # seconds diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 8ff722e41b2..2892e278c5c 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -20,13 +20,21 @@ INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template') _LOGGER = logging.getLogger(__name__) +FRONTEND_URLS = [ + URL_ROOT, '/logbook', '/history', '/devService', '/devState', '/devEvent'] +STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)') + + def setup(hass, config): """ Setup serving the frontend. """ if 'http' not in hass.config.components: _LOGGER.error('Dependency http is not loaded') return False - hass.http.register_path('GET', URL_ROOT, _handle_get_root, False) + for url in FRONTEND_URLS: + hass.http.register_path('GET', url, _handle_get_root, False) + + hass.http.register_path('GET', STATES_URL, _handle_get_root, False) # Static files hass.http.register_path( diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 5623a8c5e7e..3860398c5f1 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 = "db1ec3e116565340804da0e590058d60" +VERSION = "301633b1e436a798afcbdb5776744588" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 9f5c3aefe40..07010bdb3ef 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -141,6 +141,12 @@ this.copyOwnProperty(n, api, prototype); } return prototype || api; }, +mixin: function (target, source) { +for (var i in source) { +target[i] = source[i]; +} +return target; +}, copyOwnProperty: function (name, source, target) { var pd = Object.getOwnPropertyDescriptor(source, name); if (pd) { @@ -516,7 +522,7 @@ debouncer.stop(); } } }); -Polymer.version = '1.0.3'; +Polymer.version = '1.0.6'; Polymer.Base._addFeature({ _registerFeatures: function () { this._prepIs(); @@ -540,13 +546,6 @@ this._marshalBehaviors(); - - +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Dc.apply(null,arguments)}function b(a){Dc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Fc)d=Fc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(+b._d),Gc===!1&&(Gc=!0,a.updateOffset(this),Gc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function q(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&p(a[d])!==p(b[d]))&&g++;return g+f}function r(){}function s(a){return a?a.toLowerCase().replace("_","-"):a}function t(a){for(var b,c,d,e,f=0;f0;){if(d=u(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&q(e,c,!0)>=b-1)break;b--}f++}return null}function u(a){var b=null;if(!Hc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ec._abbr,require("./locale/"+a),v(b)}catch(c){}return Hc[a]}function v(a,b){var c;return a&&(c="undefined"==typeof b?x(a):w(a,b),c&&(Ec=c)),Ec._abbr}function w(a,b){return null!==b?(b.abbr=a,Hc[a]||(Hc[a]=new r),Hc[a].set(b),v(a),Hc[a]):(delete Hc[a],null)}function x(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ec;if(!c(a)){if(b=u(a))return b;a=[a]}return t(a)}function y(a,b){var c=a.toLowerCase();Ic[c]=Ic[c+"s"]=Ic[b]=a}function z(a){return"string"==typeof a?Ic[a]||Ic[a.toLowerCase()]:void 0}function A(a){var b,c,d={};for(c in a)f(a,c)&&(b=z(c),b&&(d[b]=a[c]));return d}function B(b,c){return function(d){return null!=d?(D(this,b,d),a.updateOffset(this,c),this):C(this,b)}}function C(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function D(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function E(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=z(a),"function"==typeof this[a])return this[a](b);return this}function F(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthb;b++)Mc[d[b]]?d[b]=Mc[d[b]]:d[b]=H(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function J(a,b){return a.isValid()?(b=K(b,a.localeData()),Lc[b]||(Lc[b]=I(b)),Lc[b](a)):a.localeData().invalidDate()}function K(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Kc.lastIndex=0;d>=0&&Kc.test(a);)a=a.replace(Kc,c),Kc.lastIndex=0,d-=1;return a}function L(a,b,c){_c[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function M(a,b){return f(_c,a)?_c[a](b._strict,b._locale):new RegExp(N(a))}function N(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=p(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function V(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),R(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function W(b){return null!=b?(V(this,b),a.updateOffset(this,!0),this):C(this,"Month")}function X(){return R(this.year(),this.month())}function Y(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[cd]<0||c[cd]>11?cd:c[dd]<1||c[dd]>R(c[bd],c[cd])?dd:c[ed]<0||c[ed]>24||24===c[ed]&&(0!==c[fd]||0!==c[gd]||0!==c[hd])?ed:c[fd]<0||c[fd]>59?fd:c[gd]<0||c[gd]>59?gd:c[hd]<0||c[hd]>999?hd:-1,j(a)._overflowDayOfYear&&(bd>b||b>dd)&&(b=dd),j(a).overflow=b),a}function Z(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function $(a,b){var c=!0,d=a+"\n"+(new Error).stack;return g(function(){return c&&(Z(d),c=!1),b.apply(this,arguments)},b)}function _(a,b){kd[a]||(Z(b),kd[a]=!0)}function aa(a){var b,c,d=a._i,e=ld.exec(d);if(e){for(j(a).iso=!0,b=0,c=md.length;c>b;b++)if(md[b][1].exec(d)){a._f=md[b][0]+(e[6]||" ");break}for(b=0,c=nd.length;c>b;b++)if(nd[b][1].exec(d)){a._f+=nd[b][0];break}d.match(Yc)&&(a._f+="Z"),ta(a)}else a._isValid=!1}function ba(b){var c=od.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(aa(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ca(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function da(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ea(a){return fa(a)?366:365}function fa(a){return a%4===0&&a%100!==0||a%400===0}function ga(){return fa(this.year())}function ha(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Aa(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ia(a){return ha(a,this._week.dow,this._week.doy).week}function ja(){return this._week.dow}function ka(){return this._week.doy}function la(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function ma(a){var b=ha(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function na(a,b,c,d,e){var f,g,h=da(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:ea(a-1)+g}}function oa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function pa(a,b,c){return null!=a?a:null!=b?b:c}function qa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ra(a){var b,c,d,e,f=[];if(!a._d){for(d=qa(a),a._w&&null==a._a[dd]&&null==a._a[cd]&&sa(a),a._dayOfYear&&(e=pa(a._a[bd],d[bd]),a._dayOfYear>ea(e)&&(j(a)._overflowDayOfYear=!0),c=da(e,0,a._dayOfYear),a._a[cd]=c.getUTCMonth(),a._a[dd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[ed]&&0===a._a[fd]&&0===a._a[gd]&&0===a._a[hd]&&(a._nextDay=!0,a._a[ed]=0),a._d=(a._useUTC?da:ca).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[ed]=24)}}function sa(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=pa(b.GG,a._a[bd],ha(Aa(),1,4).year),d=pa(b.W,1),e=pa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=pa(b.gg,a._a[bd],ha(Aa(),f,g).year),d=pa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=na(c,d,e,g,f),a._a[bd]=h.year,a._dayOfYear=h.dayOfYear}function ta(b){if(b._f===a.ISO_8601)return void aa(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=K(b._f,b._locale).match(Jc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Mc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),Q(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[ed]<=12&&b._a[ed]>0&&(j(b).bigHour=void 0),b._a[ed]=ua(b._locale,b._a[ed],b._meridiem),ra(b),Y(b)}function ua(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function va(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(0/0));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function wa(a){if(!a._d){var b=A(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ra(a)}}function xa(a){var b,e=a._i,f=a._f;return a._locale=a._locale||x(a._l),null===e||void 0===f&&""===e?l({nullInput:!0}):("string"==typeof e&&(a._i=e=a._locale.preparse(e)),o(e)?new n(Y(e)):(c(f)?va(a):f?ta(a):d(e)?a._d=e:ya(a),b=new n(Y(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function ya(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?ba(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ra(b)):"object"==typeof f?wa(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function za(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,xa(f)}function Aa(a,b,c,d){return za(a,b,c,d,!1)}function Ba(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Aa();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+F(~~(a/60),2)+b+F(~~a%60,2)})}function Ha(a){var b=(a||"").match(Yc)||[],c=b[b.length-1]||[],d=(c+"").match(td)||["-",0,0],e=+(60*d[1])+p(d[2]);return"+"===d[0]?e:-e}function Ia(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Aa(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Aa(b).local();return c._isUTC?Aa(b).zone(c._offset||0):Aa(b).local()}function Ja(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ka(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ha(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ja(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?$a(this,Va(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ja(this)}function La(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Ma(a){return this.utcOffset(0,a)}function Na(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ja(this),"m")),this}function Oa(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ha(this._i)),this}function Pa(a){return a=a?Aa(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Qa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ra(){if(this._a){var a=this._isUTC?h(this._a):Aa(this._a);return this.isValid()&&q(this._a,a.toArray())>0}return!1}function Sa(){return!this._isUTC}function Ta(){return this._isUTC}function Ua(){return this._isUTC&&0===this._offset}function Va(a,b){var c,d,e,g=a,h=null;return Fa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=ud.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:p(h[dd])*c,h:p(h[ed])*c,m:p(h[fd])*c,s:p(h[gd])*c,ms:p(h[hd])*c}):(h=vd.exec(a))?(c="-"===h[1]?-1:1,g={y:Wa(h[2],c),M:Wa(h[3],c),d:Wa(h[4],c),h:Wa(h[5],c),m:Wa(h[6],c),s:Wa(h[7],c),w:Wa(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Ya(Aa(g.from),Aa(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ea(g),Fa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Wa(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Xa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Ya(a,b){var c;return b=Ia(b,a),a.isBefore(b)?c=Xa(a,b):(c=Xa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Za(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(_(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Va(c,d),$a(this,e,a),this}}function $a(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&D(b,"Date",C(b,"Date")+g*d),h&&V(b,C(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function _a(a){var b=a||Aa(),c=Ia(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,Aa(b)))}function ab(){return new n(this)}function bb(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Aa(a),+this>+a):(c=o(a)?+a:+Aa(a),c<+this.clone().startOf(b))}function cb(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Aa(a),+a>+this):(c=o(a)?+a:+Aa(a),+this.clone().endOf(b)a?Math.ceil(a):Math.floor(a)}function gb(a,b,c){var d,e,f=Ia(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=hb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:fb(e)}function hb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function ib(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function jb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Aa([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Mb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Hb(a,this.localeData()),this.add(a-b,"d")):b}function Nb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ob(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Pb(a,b){G(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Qb(a,b){return b._meridiemParse}function Rb(a){return"p"===(a+"").toLowerCase().charAt(0)}function Sb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Tb(a){G(0,[a,3],0,"millisecond")}function Ub(){return this._isUTC?"UTC":""}function Vb(){return this._isUTC?"Coordinated Universal Time":""}function Wb(a){return Aa(1e3*a)}function Xb(){return Aa.apply(null,arguments).parseZone()}function Yb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Zb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function $b(){return this._invalidDate}function _b(a){return this._ordinal.replace("%d",a)}function ac(a){return a}function bc(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function cc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function dc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function ec(a,b,c,d){var e=x(),f=h().set(d,b);return e[c](f,a)}function fc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return ec(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=ec(a,f,c,e);return g}function gc(a,b){return fc(a,b,"months",12,"month")}function hc(a,b){return fc(a,b,"monthsShort",12,"month")}function ic(a,b){return fc(a,b,"weekdays",7,"day")}function jc(a,b){return fc(a,b,"weekdaysShort",7,"day")}function kc(a,b){return fc(a,b,"weekdaysMin",7,"day")}function lc(){var a=this._data;return this._milliseconds=Rd(this._milliseconds),this._days=Rd(this._days),this._months=Rd(this._months),a.milliseconds=Rd(a.milliseconds),a.seconds=Rd(a.seconds),a.minutes=Rd(a.minutes),a.hours=Rd(a.hours),a.months=Rd(a.months),a.years=Rd(a.years),this}function mc(a,b,c,d){var e=Va(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function nc(a,b){return mc(this,a,b,1)}function oc(a,b){return mc(this,a,b,-1)}function pc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=fb(d/1e3),g.seconds=a%60,b=fb(a/60),g.minutes=b%60,c=fb(b/60),g.hours=c%24,e+=fb(c/24),h=fb(qc(e)),e-=fb(rc(h)),f+=fb(e/30),e%=30,h+=fb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function qc(a){return 400*a/146097}function rc(a){return 146097*a/400}function sc(a){var b,c,d=this._milliseconds;if(a=z(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*qc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(rc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function tc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*p(this._months/12)}function uc(a){return function(){return this.as(a)}}function vc(a){return a=z(a),this[a+"s"]()}function wc(a){return function(){return this._data[a]}}function xc(){return fb(this.days()/7)}function yc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function zc(a,b,c){var d=Va(a).abs(),e=fe(d.as("s")),f=fe(d.as("m")),g=fe(d.as("h")),h=fe(d.as("d")),i=fe(d.as("M")),j=fe(d.as("y")),k=e0,k[4]=c,yc.apply(null,k)}function Ac(a,b){return void 0===ge[a]?!1:void 0===b?ge[a]:(ge[a]=b,!0)}function Bc(a){var b=this.localeData(),c=zc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Cc(){var a=he(this.years()),b=he(this.months()),c=he(this.days()),d=he(this.hours()),e=he(this.minutes()),f=he(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Dc,Ec,Fc=a.momentProperties=[],Gc=!1,Hc={},Ic={},Jc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Kc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Lc={},Mc={},Nc=/\d/,Oc=/\d\d/,Pc=/\d{3}/,Qc=/\d{4}/,Rc=/[+-]?\d{6}/,Sc=/\d\d?/,Tc=/\d{1,3}/,Uc=/\d{1,4}/,Vc=/[+-]?\d{1,6}/,Wc=/\d+/,Xc=/[+-]?\d+/,Yc=/Z|[+-]\d\d:?\d\d/gi,Zc=/[+-]?\d+(\.\d{1,3})?/,$c=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,_c={},ad={},bd=0,cd=1,dd=2,ed=3,fd=4,gd=5,hd=6;G("M",["MM",2],"Mo",function(){return this.month()+1}),G("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),G("MMMM",0,0,function(a){return this.localeData().months(this,a)}),y("month","M"),L("M",Sc),L("MM",Sc,Oc),L("MMM",$c),L("MMMM",$c),O(["M","MM"],function(a,b){b[cd]=p(a)-1}),O(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[cd]=e:j(c).invalidMonth=a});var id="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),jd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),kd={};a.suppressDeprecationWarnings=!1;var ld=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,md=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],nd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],od=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=$("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),G(0,["YY",2],0,function(){return this.year()%100}),G(0,["YYYY",4],0,"year"),G(0,["YYYYY",5],0,"year"),G(0,["YYYYYY",6,!0],0,"year"),y("year","y"),L("Y",Xc),L("YY",Sc,Oc),L("YYYY",Uc,Qc),L("YYYYY",Vc,Rc),L("YYYYYY",Vc,Rc),O(["YYYY","YYYYY","YYYYYY"],bd),O("YY",function(b,c){c[bd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return p(a)+(p(a)>68?1900:2e3)};var pd=B("FullYear",!1);G("w",["ww",2],"wo","week"),G("W",["WW",2],"Wo","isoWeek"),y("week","w"),y("isoWeek","W"),L("w",Sc),L("ww",Sc,Oc),L("W",Sc),L("WW",Sc,Oc),P(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=p(a)});var qd={dow:0,doy:6};G("DDD",["DDDD",3],"DDDo","dayOfYear"),y("dayOfYear","DDD"),L("DDD",Tc),L("DDDD",Pc),O(["DDD","DDDD"],function(a,b,c){c._dayOfYear=p(a)}),a.ISO_8601=function(){};var rd=$("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return this>a?this:a}),sd=$("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return a>this?this:a});Ga("Z",":"),Ga("ZZ",""),L("Z",Yc),L("ZZ",Yc),O(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ha(a)});var td=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var ud=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,vd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Va.fn=Ea.prototype;var wd=Za(1,"add"),xd=Za(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var yd=$("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});G(0,["gg",2],0,function(){return this.weekYear()%100}),G(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ab("gggg","weekYear"),Ab("ggggg","weekYear"),Ab("GGGG","isoWeekYear"),Ab("GGGGG","isoWeekYear"),y("weekYear","gg"),y("isoWeekYear","GG"),L("G",Xc),L("g",Xc),L("GG",Sc,Oc),L("gg",Sc,Oc),L("GGGG",Uc,Qc),L("gggg",Uc,Qc),L("GGGGG",Vc,Rc),L("ggggg",Vc,Rc),P(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=p(a)}),P(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),G("Q",0,0,"quarter"),y("quarter","Q"),L("Q",Nc),O("Q",function(a,b){b[cd]=3*(p(a)-1)}),G("D",["DD",2],"Do","date"),y("date","D"),L("D",Sc),L("DD",Sc,Oc),L("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),O(["D","DD"],dd),O("Do",function(a,b){b[dd]=p(a.match(Sc)[0],10)});var zd=B("Date",!0);G("d",0,"do","day"),G("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),G("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),G("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),G("e",0,0,"weekday"),G("E",0,0,"isoWeekday"),y("day","d"),y("weekday","e"),y("isoWeekday","E"),L("d",Sc),L("e",Sc),L("E",Sc),L("dd",$c),L("ddd",$c),L("dddd",$c),P(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),P(["d","e","E"],function(a,b,c,d){b[d]=p(a)});var Ad="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Bd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Cd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");G("H",["HH",2],0,"hour"),G("h",["hh",2],0,function(){return this.hours()%12||12}),Pb("a",!0),Pb("A",!1),y("hour","h"),L("a",Qb),L("A",Qb),L("H",Sc),L("h",Sc),L("HH",Sc,Oc),L("hh",Sc,Oc),O(["H","HH"],ed),O(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),O(["h","hh"],function(a,b,c){b[ed]=p(a),j(c).bigHour=!0});var Dd=/[ap]\.?m?\.?/i,Ed=B("Hours",!0);G("m",["mm",2],0,"minute"),y("minute","m"),L("m",Sc),L("mm",Sc,Oc),O(["m","mm"],fd);var Fd=B("Minutes",!1);G("s",["ss",2],0,"second"),y("second","s"),L("s",Sc),L("ss",Sc,Oc),O(["s","ss"],gd);var Gd=B("Seconds",!1);G("S",0,0,function(){return~~(this.millisecond()/100)}),G(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Tb("SSS"),Tb("SSSS"),y("millisecond","ms"),L("S",Tc,Nc),L("SS",Tc,Oc),L("SSS",Tc,Pc),L("SSSS",Wc),O(["S","SS","SSS","SSSS"],function(a,b){b[hd]=p(1e3*("0."+a))});var Hd=B("Milliseconds",!1);G("z",0,0,"zoneAbbr"),G("zz",0,0,"zoneName");var Id=n.prototype;Id.add=wd,Id.calendar=_a,Id.clone=ab,Id.diff=gb,Id.endOf=sb,Id.format=kb,Id.from=lb,Id.fromNow=mb,Id.to=nb,Id.toNow=ob,Id.get=E,Id.invalidAt=zb,Id.isAfter=bb,Id.isBefore=cb,Id.isBetween=db,Id.isSame=eb,Id.isValid=xb,Id.lang=yd,Id.locale=pb,Id.localeData=qb,Id.max=sd,Id.min=rd,Id.parsingFlags=yb,Id.set=E,Id.startOf=rb,Id.subtract=xd,Id.toArray=wb,Id.toDate=vb,Id.toISOString=jb,Id.toJSON=jb,Id.toString=ib,Id.unix=ub,Id.valueOf=tb,Id.year=pd,Id.isLeapYear=ga,Id.weekYear=Cb,Id.isoWeekYear=Db,Id.quarter=Id.quarters=Gb,Id.month=W,Id.daysInMonth=X,Id.week=Id.weeks=la,Id.isoWeek=Id.isoWeeks=ma,Id.weeksInYear=Fb,Id.isoWeeksInYear=Eb,Id.date=zd,Id.day=Id.days=Mb,Id.weekday=Nb,Id.isoWeekday=Ob,Id.dayOfYear=oa,Id.hour=Id.hours=Ed,Id.minute=Id.minutes=Fd,Id.second=Id.seconds=Gd,Id.millisecond=Id.milliseconds=Hd,Id.utcOffset=Ka,Id.utc=Ma,Id.local=Na,Id.parseZone=Oa,Id.hasAlignedHourOffset=Pa,Id.isDST=Qa,Id.isDSTShifted=Ra,Id.isLocal=Sa,Id.isUtcOffset=Ta,Id.isUtc=Ua,Id.isUTC=Ua,Id.zoneAbbr=Ub,Id.zoneName=Vb,Id.dates=$("dates accessor is deprecated. Use date instead.",zd),Id.months=$("months accessor is deprecated. Use month instead",W),Id.years=$("years accessor is deprecated. Use year instead",pd),Id.zone=$("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",La);var Jd=Id,Kd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Ld={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Md="Invalid date",Nd="%d",Od=/\d{1,2}/,Pd={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour", +hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Qd=r.prototype;Qd._calendar=Kd,Qd.calendar=Yb,Qd._longDateFormat=Ld,Qd.longDateFormat=Zb,Qd._invalidDate=Md,Qd.invalidDate=$b,Qd._ordinal=Nd,Qd.ordinal=_b,Qd._ordinalParse=Od,Qd.preparse=ac,Qd.postformat=ac,Qd._relativeTime=Pd,Qd.relativeTime=bc,Qd.pastFuture=cc,Qd.set=dc,Qd.months=S,Qd._months=id,Qd.monthsShort=T,Qd._monthsShort=jd,Qd.monthsParse=U,Qd.week=ia,Qd._week=qd,Qd.firstDayOfYear=ka,Qd.firstDayOfWeek=ja,Qd.weekdays=Ib,Qd._weekdays=Ad,Qd.weekdaysMin=Kb,Qd._weekdaysMin=Cd,Qd.weekdaysShort=Jb,Qd._weekdaysShort=Bd,Qd.weekdaysParse=Lb,Qd.isPM=Rb,Qd._meridiemParse=Dd,Qd.meridiem=Sb,v("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===p(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=$("moment.lang is deprecated. Use moment.locale instead.",v),a.langData=$("moment.langData is deprecated. Use moment.localeData instead.",x);var Rd=Math.abs,Sd=uc("ms"),Td=uc("s"),Ud=uc("m"),Vd=uc("h"),Wd=uc("d"),Xd=uc("w"),Yd=uc("M"),Zd=uc("y"),$d=wc("milliseconds"),_d=wc("seconds"),ae=wc("minutes"),be=wc("hours"),ce=wc("days"),de=wc("months"),ee=wc("years"),fe=Math.round,ge={s:45,m:45,h:22,d:26,M:11},he=Math.abs,ie=Ea.prototype;ie.abs=lc,ie.add=nc,ie.subtract=oc,ie.as=sc,ie.asMilliseconds=Sd,ie.asSeconds=Td,ie.asMinutes=Ud,ie.asHours=Vd,ie.asDays=Wd,ie.asWeeks=Xd,ie.asMonths=Yd,ie.asYears=Zd,ie.valueOf=tc,ie._bubble=pc,ie.get=vc,ie.milliseconds=$d,ie.seconds=_d,ie.minutes=ae,ie.hours=be,ie.days=ce,ie.weeks=xc,ie.months=de,ie.years=ee,ie.humanize=Bc,ie.toISOString=Cc,ie.toString=Cc,ie.toJSON=Cc,ie.locale=pb,ie.localeData=qb,ie.toIsoString=$("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Cc),ie.lang=yd,G("X",0,0,"unix"),G("x",0,0,"valueOf"),L("x",Xc),L("X",Zc),O("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),O("x",function(a,b,c){c._d=new Date(p(a))}),a.version="2.10.3",b(Aa),a.fn=Jd,a.min=Ca,a.max=Da,a.utc=h,a.unix=Wb,a.months=gc,a.isDate=d,a.locale=v,a.invalid=l,a.duration=Va,a.isMoment=o,a.weekdays=ic,a.parseZone=Xb,a.localeData=x,a.isDuration=Fa,a.monthsShort=hc,a.weekdaysMin=kc,a.defineLocale=w,a.weekdaysShort=jc,a.normalizeUnits=z,a.relativeTimeThreshold=Ac;var je=a;return je}); @@ -10706,10 +8048,10 @@ window.hass.uiUtil.formatDate = function(dateObj) { * in a KeyboardEvent instance. */ var MODIFIER_KEYS = { - shift: 'shiftKey', - ctrl: 'ctrlKey', - alt: 'altKey', - meta: 'metaKey' + 'shift': 'shiftKey', + 'ctrl': 'ctrlKey', + 'alt': 'altKey', + 'meta': 'metaKey' }; /** @@ -11160,7 +8502,7 @@ window.hass.uiUtil.formatDate = function(dateObj) { /** * @demo demo/index.html - * @polymerBehavior + * @polymerBehavior Polymer.IronButtonState */ Polymer.IronButtonStateImpl = { @@ -11321,7 +8663,7 @@ window.hass.uiUtil.formatDate = function(dateObj) { }; - /** @polymerBehavior Polymer.IronButtonState */ + /** @polymerBehavior */ Polymer.IronButtonState = [ Polymer.IronA11yKeysBehavior, Polymer.IronButtonStateImpl @@ -11369,6 +8711,10 @@ window.hass.uiUtil.formatDate = function(dateObj) { transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); }; + --shadow-none: { + box-shadow: none; + }; + /* from http://codepen.io/shyndman/pen/c5394ddf2e8b2a5c9185904b57421cdb */ --shadow-elevation-2dp: { @@ -12729,10 +10075,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input /** * The Boolean return value of the media query. - * - * @attribute queryMatches - * @type Boolean - * @default false */ queryMatches: { type: Boolean, @@ -12743,9 +10085,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input /** * The CSS media query to evaluate. - * - * @attribute query - * @type String */ query: { type: String, @@ -13344,216 +10683,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input - - - - - - - - - - - @@ -13689,6 +10872,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input + + + + + + + + + + - + - - - - - - @@ -14916,9 +13626,10 @@ Fires `api-load` event when ready. /** * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. - * @polymerBehavior + * + * @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ - Polymer.NeonAnimationRunnerBehavior = [Polymer.NeonAnimatableBehavior, { + Polymer.NeonAnimationRunnerBehaviorImpl = { properties: { @@ -14929,6 +13640,7 @@ Fires `api-load` event when ready. } }, + /** @type {?Object} */ _player: { type: Object } @@ -14959,7 +13671,7 @@ Fires `api-load` event when ready. }, _runAnimationEffects: function(allEffects) { - return player = document.timeline.play(new GroupEffect(allEffects)); + return document.timeline.play(new GroupEffect(allEffects)); }, _completeAnimations: function(allAnimations) { @@ -14970,6 +13682,8 @@ Fires `api-load` event when ready. /** * Plays an animation with an optional `type`. + * @param {string=} type + * @param {!Object=} cookie */ playAnimation: function(type, cookie) { var allConfigs = this.getAnimationConfig(type); @@ -15007,8 +13721,13 @@ Fires `api-load` event when ready. this._player.cancel(); } } + }; - }]; + /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ + Polymer.NeonAnimationRunnerBehavior = [ + Polymer.NeonAnimatableBehavior, + Polymer.NeonAnimationRunnerBehaviorImpl + ]; - - + + + + + + - - @@ -20544,676 +19296,14 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { }()); - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -25854,7 +24423,7 @@ paper-ripple { this.brightnessSliderValue = newVal.attributes.brightness; } - this.debounce('more-info-light-animation-finish', function() { + this.async(function() { this.fire('iron-resize'); }.bind(this), 500); }, @@ -26007,7 +24576,7 @@ paper-ripple { }, - stateObjChanged: function(newVal, oldVal) { + stateObjChanged: function(newVal) { if (newVal) { this.isOff = newVal.state == 'off'; this.isPlaying = newVal.state == 'playing'; @@ -26022,9 +24591,7 @@ paper-ripple { this.supportsTurnOff = (newVal.attributes.supported_media_commands & 256) !== 0; } - this.debounce('more-info-volume-animation-finish', function() { - this.fire('iron-resize'); - }.bind(this), 500); + this.async(function() { this.fire('iron-resize'); }.bind(this), 500); }, computeClassNames: function(stateObj) { @@ -26090,6 +24657,80 @@ paper-ripple { }); })(); + + + + + + + + +