diff --git a/.coveragerc b/.coveragerc index d57aa96b40d..3d32256e9fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -147,11 +147,12 @@ omit = homeassistant/components/tado.py homeassistant/components/*/tado.py - + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/concord232.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/simplisafe.py + homeassistant/components/alarm_control_panel/totalconnect.py homeassistant/components/apiai.py homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/concord232.py @@ -225,6 +226,7 @@ omit = homeassistant/components/light/hue.py homeassistant/components/light/hyperion.py homeassistant/components/light/lifx.py + homeassistant/components/light/lifx_legacy.py homeassistant/components/light/limitlessled.py homeassistant/components/light/osramlightify.py homeassistant/components/light/tikteck.py @@ -235,6 +237,7 @@ omit = homeassistant/components/light/zengge.py homeassistant/components/lirc.py homeassistant/components/lock/nuki.py + homeassistant/components/lock/lockitron.py homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/apple_tv.py homeassistant/components/media_player/aquostv.py @@ -323,6 +326,7 @@ omit = homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/comed_hourly_pricing.py homeassistant/components/sensor/cpuspeed.py + homeassistant/components/sensor/crimereports.py homeassistant/components/sensor/cups.py homeassistant/components/sensor/currencylayer.py homeassistant/components/sensor/darksky.py @@ -332,6 +336,7 @@ omit = homeassistant/components/sensor/dovado.py homeassistant/components/sensor/dte_energy_bridge.py homeassistant/components/sensor/ebox.py + homeassistant/components/sensor/eddystone_temperature.py homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/emoncms.py homeassistant/components/sensor/fastdotcom.py @@ -357,9 +362,11 @@ omit = homeassistant/components/sensor/linux_battery.py homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/lyft.py + homeassistant/components/sensor/metoffice.py homeassistant/components/sensor/miflora.py homeassistant/components/sensor/modem_callerid.py homeassistant/components/sensor/mqtt_room.py + homeassistant/components/sensor/mvglive.py homeassistant/components/sensor/netdata.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/nut.py @@ -431,12 +438,12 @@ omit = homeassistant/components/tts/picotts.py homeassistant/components/upnp.py homeassistant/components/weather/bom.py + homeassistant/components/weather/metoffice.py homeassistant/components/weather/openweathermap.py homeassistant/components/weather/zamg.py homeassistant/components/zeroconf.py homeassistant/components/zwave/__init__.py homeassistant/components/zwave/util.py - homeassistant/components/zwave/workaround.py [report] diff --git a/.gitignore b/.gitignore index aa27aa435bd..d5c29180e09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,4 @@ config/* -!config/home-assistant.conf.default - -# There is not a better solution afaik.. -!config/custom_components -config/custom_components/* -!config/custom_components/example.py -!config/custom_components/hello_world.py -!config/custom_components/mqtt_example.py -!config/panels -config/panels/* -!config/panels/react.html tests/testing_config/deps tests/testing_config/home-assistant.log diff --git a/README.rst b/README.rst index 2b166cd9a13..66c9cedea74 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,7 @@ Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs| ============================================================================================================================================================================================== -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. +Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control. To get started: @@ -12,83 +10,22 @@ To get started: python3 -m pip install homeassistant hass --open-ui -Check out `the website `__ for `a -demo `__, installation instructions, -tutorials and documentation. +Check out `home-assistant.io `__ for `a +demo `__, `installation instructions `__, +`tutorials `__ and `documentation `__. |screenshot-states| -Examples of devices Home Assistant can interface with: +Featured integrations +--------------------- -- Monitoring connected devices to a wireless router: - `OpenWrt `__, - `Tomato `__, - `Netgear `__, - `DD-WRT `__, - `TPLink `__, - `ASUSWRT `__, - `Xiaomi `__ and any SNMP - capable Linksys WAP/WRT -- `Philips Hue `__ lights, - `WeMo `__ - switches, `Edimax `__ switches, - `Efergy `__ energy monitoring, and - `Tellstick `__ devices and - sensors -- `Google - Chromecasts `__, - `Music Player Daemon `__, `Logitech - Squeezebox `__, - `Plex `__, `Kodi (XBMC) `__, - iTunes (by way of - `itunes-api `__), and Amazon - Fire TV (by way of - `python-firetv `__) -- Support for - `ISY994 `__ - (Insteon and X10 devices), `Z-Wave `__, `Nest - Thermostats `__, - `RFXtrx `__, - `Arduino `__, `Raspberry - Pi `__, and - `Modbus `__ -- Interaction with `IFTTT `__ -- Integrate data from the `Bitcoin `__ network, - meteorological data from - `OpenWeatherMap `__ and - `Forecast.io `__, - `Transmission `__, or - `SABnzbd `__. -- `See full list of supported - devices `__ +|screenshot-components| -Build 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 sunset -- Turn on lights slowly during sunset to compensate for less light -- Turn off all lights and devices when everybody leaves the house -- Offers a `REST API `__ - and can interface with MQTT for easy integration with other projects - like `OwnTracks `__ -- Allow sending notifications using - `Instapush `__, `Notify My Android - (NMA) `__, - `PushBullet `__, - `PushOver `__, - `Slack `__, - `Telegram `__, `Join `__, and `Jabber - (XMPP) `__ - -The system is built using a modular approach so support for other devices or actions can -be implemented easily. See also the `section on -architecture `__ -and the `section on creating your own +The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture `__ and the `section on creating your own components `__. If you run into issues while using Home Assistant or during development -of a component, check the `Home Assistant help -section `__ of our website for further help and information. +of a component, check the `Home Assistant help section `__ of our website for further help and information. .. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master :target: https://travis-ci.org/home-assistant/home-assistant @@ -100,3 +37,5 @@ section `__ of our website for further help and :target: https://gitter.im/home-assistant/home-assistant/devs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png :target: https://home-assistant.io/demo/ +.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png + :target: https://home-assistant.io/components/ diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example deleted file mode 100644 index 08b0324371f..00000000000 --- a/config/configuration.yaml.example +++ /dev/null @@ -1,158 +0,0 @@ -homeassistant: - # Omitted values in this section will be auto detected using freegeoip.io - - # Location required to calculate the time the sun rises and sets. - # Coordinates are also used for location for weather related components. - # Google Maps can be used to determine more precise GPS coordinates. - latitude: 32.87336 - longitude: 117.22743 - - # Impacts weather/sunrise data - elevation: 665 - - # 'metric' for Metric System, 'imperial' for imperial system - unit_system: metric - - # Pick yours from here: - # http://en.wikipedia.org/wiki/List_of_tz_database_time_zones - time_zone: America/Los_Angeles - - # Name of the location where Home Assistant is running - name: Home - -http: - api_password: mypass - # Set to 1 to enable development mode - # development: 1 - -# Enable the frontend -frontend: - -light: -# platform: hue - -wink: - # Get your token at https://winkbearertoken.appspot.com - access_token: 'YOUR_TOKEN' - -device_tracker: - # The following tracker are available: - # https://home-assistant.io/components/#presence-detection - platform: netgear - host: 192.168.1.1 - username: admin - password: PASSWORD - -switch: - platform: wemo - -climate: - platform: nest - # Required: username and password that are used to login to the Nest thermostat. - username: myemail@mydomain.com - password: mypassword - -downloader: - download_dir: downloads - -notify: - platform: pushbullet - api_key: ABCDEFGHJKLMNOPQRSTUVXYZ - -device_sun_light_trigger: - # Optional: specify a specific light/group of lights that has to be turned on - light_group: group.living_room - # Optional: specify which light profile to use when turning lights on - light_profile: relax - # Optional: disable lights being turned off when everybody leaves the house - # disable_turn_off: 1 - -# 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) -# You can also have groups within groups. -# https://home-assistant.io/components/group/ -group: - default_view: - view: yes - entities: - - group.awesome_people - - group.climate - kitchen: - name: Kitchen - entities: - - switch.kitchen_pin_3 - upstairs: - name: Kids - icon: mdi:account-multiple - view: yes - entities: - - input_boolean.notify_home - - camera.demo_camera - -browser: -keyboard: - -# https://home-assistant.io/getting-started/automation/ -automation: - - alias: Turn on light when sun sets - trigger: - platform: sun - event: sunset - offset: "-01:00:00" - condition: - condition: state - entity_id: group.all_devices - state: 'home' - action: - service: light.turn_on - -# Another way to do is to collect all entries under one "sensor:" -# sensor: -# - platform: mqtt -# name: "MQTT Sensor 1" -# - platform: mqtt -# name: "MQTT Sensor 2" -# -# Details: https://home-assistant.io/getting-started/devices/ - -sensor: - platform: systemmonitor - resources: - - type: 'disk_use_percent' - arg: '/' - - type: 'disk_use_percent' - arg: '/home' - -sensor 2: - platform: cpuspeed - -script: - wakeup: - alias: Wake Up - sequence: - - event: LOGBOOK_ENTRY - event_data: - name: Paulus - message: is waking up - entity_id: device_tracker.paulus - domain: light - - alias: Bedroom lights on - service: light.turn_on - data: - entity_id: group.bedroom - brightness: 100 - - delay: - minutes: 1 - - alias: Living room lights on - service: light.turn_on - data: - entity_id: group.living_room - -scene: - - name: Romantic - entities: - light.tv_back_light: on - light.ceiling: - state: on - xy_color: [0.33, 0.66] - brightness: 200 diff --git a/config/custom_components/example.py b/config/custom_components/example.py deleted file mode 100644 index 4d3df9328d8..00000000000 --- a/config/custom_components/example.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Example of a custom component. - -Example component to target an entity_id to: - - turn it on at 7AM in the morning - - turn it on if anyone comes home and it is off - - turn it off if all lights are turned off - - turn it off if all people leave the house - - offer a service to turn it on for 10 seconds - -Configuration: - -To use the Example custom component you will need to add the following to -your configuration.yaml file. - -example: - target: TARGET_ENTITY - -Variable: - -target -*Required -TARGET_ENTITY should be one of your devices that can be turned on and off, -ie a light or a switch. Example value could be light.Ceiling or switch.AC -(if you have these devices with those names). -""" -import time -import logging - -from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF -from homeassistant.helpers import validate_config -from homeassistant.helpers.event_decorators import \ - track_state_change, track_time_change -from homeassistant.helpers.service import service -import homeassistant.components as core -from homeassistant.components import device_tracker -from homeassistant.components import light - -# The domain of your component. Should be equal to the name of your component. -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 -# initialize devices have been setup. -DEPENDENCIES = ['group', 'device_tracker', 'light'] - -# Configuration key for the entity id we are targeting. -CONF_TARGET = 'target' - -# Variable for storing configuration parameters. -TARGET_ID = None - -# Name of the service that we expose. -SERVICE_FLASH = 'flash' - -# Shortcut for the logger -_LOGGER = logging.getLogger(__name__) - - -def setup(hass, config): - """Setup example component.""" - global TARGET_ID - - # Validate that all required config options are given. - if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER): - return False - - TARGET_ID = config[DOMAIN][CONF_TARGET] - - # Validate that the target entity id exists. - if hass.states.get(TARGET_ID) is None: - _LOGGER.error("Target entity id %s does not exist", - TARGET_ID) - - # Tell the bootstrapper that we failed to initialize and clear the - # stored target id so our functions don't run. - TARGET_ID = None - return False - - # Tell the bootstrapper that we initialized successfully. - return True - - -@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES) -def track_devices(hass, entity_id, old_state, new_state): - """Called when the group.all devices change state.""" - # If the target id is not set, return - if not TARGET_ID: - return - - # If anyone comes home and the entity is not on, turn it on. - if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID): - - core.turn_on(hass, TARGET_ID) - - # If all people leave the house and the entity is on, turn it off. - elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID): - - core.turn_off(hass, TARGET_ID) - - -@track_time_change(hour=7, minute=0, second=0) -def wake_up(hass, now): - """Turn light on in the morning. - - Turn the light on at 7 AM if there are people home and it is not already - on. - """ - if not TARGET_ID: - return - - if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID): - _LOGGER.info('People home at 7AM, turning it on') - core.turn_on(hass, TARGET_ID) - - -@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF) -def all_lights_off(hass, entity_id, old_state, new_state): - """If all lights turn off, turn off.""" - if not TARGET_ID: - return - - if core.is_on(hass, TARGET_ID): - _LOGGER.info('All lights have been turned off, turning it off') - core.turn_off(hass, TARGET_ID) - - -@service(DOMAIN, SERVICE_FLASH) -def flash_service(hass, call): - """Service that will toggle the target. - - Set the light to off for 10 seconds if on and vice versa. - """ - if not TARGET_ID: - return - - if core.is_on(hass, TARGET_ID): - core.turn_off(hass, TARGET_ID) - - time.sleep(10) - - core.turn_on(hass, TARGET_ID) - - else: - core.turn_on(hass, TARGET_ID) - - time.sleep(10) - - core.turn_off(hass, TARGET_ID) diff --git a/config/custom_components/hello_world.py b/config/custom_components/hello_world.py deleted file mode 100644 index b35e9f6c0ed..00000000000 --- a/config/custom_components/hello_world.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -The "hello world" custom component. - -This component implements the bare minimum that a component should implement. - -Configuration: - -To use the hello_word component you will need to add the following to your -configuration.yaml file. - -hello_world: -""" - -# The domain of your component. Should be equal to the name of your component. -DOMAIN = "hello_world" - -# List of component names (string) your component depends upon. -DEPENDENCIES = [] - - -def setup(hass, config): - """Setup our skeleton component.""" - # States are in the format DOMAIN.OBJECT_ID. - hass.states.set('hello_world.Hello_World', 'Works!') - - # Return boolean to indicate that initialization was successfully. - return True diff --git a/config/custom_components/mqtt_example.py b/config/custom_components/mqtt_example.py deleted file mode 100644 index 451a60deef4..00000000000 --- a/config/custom_components/mqtt_example.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Example of a custom MQTT component. - -Shows how to communicate with MQTT. Follows a topic on MQTT and updates the -state of an entity to the last message received on that topic. - -Also offers a service 'set_state' that will publish a message on the topic that -will be passed via MQTT to our message received listener. Call the service with -example payload {"new_state": "some new state"}. - -Configuration: - -To use the mqtt_example component you will need to add the following to your -configuration.yaml file. - -mqtt_example: - topic: "home-assistant/mqtt_example" -""" -import homeassistant.loader as loader - -# The domain of your component. Should be equal to the name of your component. -DOMAIN = "mqtt_example" - -# List of component names (string) your component depends upon. -DEPENDENCIES = ['mqtt'] - -CONF_TOPIC = 'topic' -DEFAULT_TOPIC = 'home-assistant/mqtt_example' - - -def setup(hass, config): - """Setup the MQTT example component.""" - mqtt = loader.get_component('mqtt') - topic = config[DOMAIN].get('topic', DEFAULT_TOPIC) - entity_id = 'mqtt_example.last_message' - - # Listen to a message on MQTT. - def message_received(topic, payload, qos): - """A new MQTT message has been received.""" - hass.states.set(entity_id, payload) - - mqtt.subscribe(hass, topic, message_received) - - hass.states.set(entity_id, 'No messages') - - # Service to publish a message on MQTT. - def set_state_service(call): - """Service to send a message.""" - mqtt.publish(hass, topic, call.data.get('new_state')) - - # Register our service with Home Assistant. - hass.services.register(DOMAIN, 'set_state', set_state_service) - - # Return boolean to indicate that initialization was successfully. - return True diff --git a/config/panels/react.html b/config/panels/react.html deleted file mode 100644 index dc2735cf759..00000000000 --- a/config/panels/react.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/docs/screenshot-components.png b/docs/screenshot-components.png new file mode 100755 index 00000000000..247f3073a5e Binary files /dev/null and b/docs/screenshot-components.png differ diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 1d5da4e798f..7035b26f670 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -20,6 +20,17 @@ from homeassistant.const import ( from homeassistant.util.async import run_callback_threadsafe +def attempt_use_uvloop(): + """Attempt to use uvloop.""" + import asyncio + + try: + import uvloop + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + except ImportError: + pass + + def monkey_patch_asyncio(): """Replace weakref.WeakSet to address Python 3 bug. @@ -311,8 +322,7 @@ def setup_and_run_hass(config_dir: str, EVENT_HOMEASSISTANT_START, open_browser ) - hass.start() - return hass.exit_code + return hass.start() def try_to_restart() -> None: @@ -359,11 +369,13 @@ def try_to_restart() -> None: def main() -> int: """Start Home Assistant.""" + validate_python() + + attempt_use_uvloop() + if sys.version_info[:3] < (3, 5, 3): monkey_patch_asyncio() - validate_python() - args = get_arguments() if args.script is not None: diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e05fff98865..4c586d12ccd 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -74,8 +74,6 @@ def async_from_config_dict(config: Dict[str, Any], This method is a coroutine. """ start = time() - hass.async_track_tasks() - core_config = config.get(core.DOMAIN, {}) try: @@ -140,10 +138,10 @@ def async_from_config_dict(config: Dict[str, Any], continue hass.async_add_job(async_setup_component(hass, component, config)) - yield from hass.async_stop_track_tasks() + yield from hass.async_block_till_done() stop = time() - _LOGGER.info('Home Assistant initialized in %ss', round(stop-start, 2)) + _LOGGER.info('Home Assistant initialized in %.2fs', stop-start) async_register_signal_handling(hass) return hass diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index 07f90cf4476..22cbdefd403 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -1,13 +1,13 @@ """ + Interfaces with Alarm.com alarm control panels. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.alarmdotcom/ """ import logging - +import asyncio import voluptuous as vol - import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( @@ -15,10 +15,9 @@ from homeassistant.const import ( STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE, CONF_NAME) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession -REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom' - '/archive/0.1.1.zip' - '#pyalarmdotcom==0.1.1'] +REQUIREMENTS = ['pyalarmdotcom==0.2.9'] _LOGGER = logging.getLogger(__name__) @@ -32,14 +31,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup an Alarm.com control panel.""" +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Setup a Alarm.com control panel.""" name = config.get(CONF_NAME) code = config.get(CONF_CODE) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - add_devices([AlarmDotCom(hass, name, code, username, password)], True) + alarmdotcom = AlarmDotCom(hass, name, code, username, password) + yield from alarmdotcom.async_login() + async_add_devices([alarmdotcom]) class AlarmDotCom(alarm.AlarmControlPanel): @@ -47,18 +49,30 @@ class AlarmDotCom(alarm.AlarmControlPanel): def __init__(self, hass, name, code, username, password): """Initialize the Alarm.com status.""" - from pyalarmdotcom.pyalarmdotcom import Alarmdotcom - self._alarm = Alarmdotcom(username, password, timeout=10) + from pyalarmdotcom import Alarmdotcom + _LOGGER.debug('Setting up Alarm.com...') self._hass = hass self._name = name self._code = str(code) if code else None self._username = username self._password = password + self._websession = async_get_clientsession(self._hass) self._state = STATE_UNKNOWN + self._alarm = Alarmdotcom(username, + password, + self._websession, + hass.loop) - def update(self): + @asyncio.coroutine + def async_login(self): + """Login to Alarm.com.""" + yield from self._alarm.async_login() + + @asyncio.coroutine + def async_update(self): """Fetch the latest state.""" - self._state = self._alarm.state + yield from self._alarm.async_update() + return self._alarm.state @property def name(self): @@ -73,45 +87,36 @@ class AlarmDotCom(alarm.AlarmControlPanel): @property def state(self): """Return the state of the device.""" - if self._state == 'Disarmed': + if self._alarm.state.lower() == 'disarmed': return STATE_ALARM_DISARMED - elif self._state == 'Armed Stay': + elif self._alarm.state.lower() == 'armed stay': return STATE_ALARM_ARMED_HOME - elif self._state == 'Armed Away': + elif self._alarm.state.lower() == 'armed away': return STATE_ALARM_ARMED_AWAY else: return STATE_UNKNOWN - def alarm_disarm(self, code=None): + @asyncio.coroutine + def async_alarm_disarm(self, code=None): """Send disarm command.""" - if not self._validate_code(code, 'disarming home'): - return - from pyalarmdotcom.pyalarmdotcom import Alarmdotcom - # Open another session to alarm.com to fire off the command - _alarm = Alarmdotcom(self._username, self._password, timeout=10) - _alarm.disarm() + if self._validate_code(code): + yield from self._alarm.async_alarm_disarm() - def alarm_arm_home(self, code=None): - """Send arm home command.""" - if not self._validate_code(code, 'arming home'): - return - from pyalarmdotcom.pyalarmdotcom import Alarmdotcom - # Open another session to alarm.com to fire off the command - _alarm = Alarmdotcom(self._username, self._password, timeout=10) - _alarm.arm_stay() + @asyncio.coroutine + def async_alarm_arm_home(self, code=None): + """Send arm hom command.""" + if self._validate_code(code): + yield from self._alarm.async_alarm_arm_home() - def alarm_arm_away(self, code=None): + @asyncio.coroutine + def async_alarm_arm_away(self, code=None): """Send arm away command.""" - if not self._validate_code(code, 'arming home'): - return - from pyalarmdotcom.pyalarmdotcom import Alarmdotcom - # Open another session to alarm.com to fire off the command - _alarm = Alarmdotcom(self._username, self._password, timeout=10) - _alarm.arm_away() + if self._validate_code(code): + yield from self._alarm.async_alarm_arm_away() - def _validate_code(self, code, state): + def _validate_code(self, code): """Validate given code.""" check = self._code is None or code == self._code if not check: - _LOGGER.warning('Wrong code entered for %s', state) + _LOGGER.warning('Wrong code entered.') return check diff --git a/homeassistant/components/alarm_control_panel/totalconnect.py b/homeassistant/components/alarm_control_panel/totalconnect.py new file mode 100644 index 00000000000..a69e260c053 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/totalconnect.py @@ -0,0 +1,87 @@ +"""Interfaces with TotalConnect alarm control panels.""" +import logging + +import voluptuous as vol + +import homeassistant.components.alarm_control_panel as alarm +from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, + CONF_NAME) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['total_connect_client==0.7'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'Total Connect' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup a TotalConnect control panel.""" + name = config.get(CONF_NAME) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + + total_connect = TotalConnect(name, username, password) + add_devices([total_connect], True) + + +class TotalConnect(alarm.AlarmControlPanel): + """Represent an TotalConnect status.""" + + def __init__(self, name, username, password): + """Initialize the TotalConnect status.""" + from total_connect_client import TotalConnectClient + + _LOGGER.debug('Setting up TotalConnect...') + self._name = name + self._username = username + self._password = password + self._state = STATE_UNKNOWN + self._client = TotalConnectClient.TotalConnectClient(username, + password) + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + def update(self): + """Return the state of the device.""" + status = self._client.get_armed_status() + + if status == self._client.DISARMED: + state = STATE_ALARM_DISARMED + elif status == self._client.ARMED_STAY: + state = STATE_ALARM_ARMED_HOME + elif status == self._client.ARMED_AWAY: + state = STATE_ALARM_ARMED_AWAY + else: + state = STATE_UNKNOWN + + self._state = state + + def alarm_disarm(self, code=None): + """Send disarm command.""" + self._client.disarm() + + def alarm_arm_home(self, code=None): + """Send arm home command.""" + self._client.arm_stay() + + def alarm_arm_away(self, code=None): + """Send arm away command.""" + self._client.arm_away() diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py index e2929d159da..2fb039f0ab3 100644 --- a/homeassistant/components/android_ip_webcam.py +++ b/homeassistant/components/android_ip_webcam.py @@ -26,17 +26,24 @@ from homeassistant.util.dt import utcnow from homeassistant.components.camera.mjpeg import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL) -DOMAIN = 'android_ip_webcam' -REQUIREMENTS = ["pydroid-ipcam==0.6"] +REQUIREMENTS = ['pydroid-ipcam==0.8'] _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(seconds=10) - -DATA_IP_WEBCAM = 'android_ip_webcam' +ATTR_AUD_CONNS = 'Audio Connections' ATTR_HOST = 'host' ATTR_VID_CONNS = 'Video Connections' -ATTR_AUD_CONNS = 'Audio Connections' + +CONF_MOTION_SENSOR = 'motion_sensor' + +DATA_IP_WEBCAM = 'android_ip_webcam' +DEFAULT_NAME = 'IP Webcam' +DEFAULT_PORT = 8080 +DEFAULT_TIMEOUT = 10 +DOMAIN = 'android_ip_webcam' + +SCAN_INTERVAL = timedelta(seconds=10) +SIGNAL_UPDATE_DATA = 'android_ip_webcam_update' KEY_MAP = { 'audio_connections': 'Audio Connections', @@ -123,15 +130,6 @@ SENSORS = ['audio_connections', 'battery_level', 'battery_temp', 'battery_voltage', 'light', 'motion', 'pressure', 'proximity', 'sound', 'video_connections'] -SIGNAL_UPDATE_DATA = 'android_ip_webcam_update' - -CONF_MOTION_SENSOR = 'motion_sensor' - -DEFAULT_NAME = 'IP Webcam' -DEFAULT_PORT = 8080 -DEFAULT_TIMEOUT = 10 - - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -153,7 +151,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Setup the IP Webcam component.""" + """Set up the IP Webcam component.""" from pydroid_ipcam import PyDroidIPCam webcams = hass.data[DATA_IP_WEBCAM] = {} @@ -161,7 +159,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_setup_ipcamera(cam_config): - """Setup a ip camera.""" + """Set up an IP camera.""" host = cam_config[CONF_HOST] username = cam_config.get(CONF_USERNAME) password = cam_config.get(CONF_PASSWORD) @@ -171,7 +169,7 @@ def async_setup(hass, config): sensors = cam_config[CONF_SENSORS] motion = cam_config[CONF_MOTION_SENSOR] - # init ip webcam + # Init ip webcam cam = PyDroidIPCam( hass.loop, websession, host, cam_config[CONF_PORT], username=username, password=password, @@ -192,7 +190,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_update_data(now): - """Update data from ipcam in SCAN_INTERVAL.""" + """Update data from IP camera in SCAN_INTERVAL.""" yield from cam.update() async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host) @@ -201,7 +199,7 @@ def async_setup(hass, config): yield from async_update_data(None) - # load platforms + # Load platforms webcams[host] = cam mjpeg_camera = { diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 1af85605874..b22bd851190 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -50,9 +50,11 @@ def setup(hass, config): hass.http.register_view(APIDomainServicesView) hass.http.register_view(APIEventForwardingView) hass.http.register_view(APIComponentsView) - hass.http.register_view(APIErrorLogView) hass.http.register_view(APITemplateView) + hass.http.register_static_path( + URL_API_ERROR_LOG, hass.config.path(ERROR_LOG_FILENAME), False) + return True @@ -402,20 +404,6 @@ class APIComponentsView(HomeAssistantView): return self.json(request.app['hass'].config.components) -class APIErrorLogView(HomeAssistantView): - """View to handle ErrorLog requests.""" - - url = URL_API_ERROR_LOG - name = "api:error-log" - - @asyncio.coroutine - def get(self, request): - """Serve error log.""" - resp = yield from self.file( - request, request.app['hass'].config.path(ERROR_LOG_FILENAME)) - return resp - - class APITemplateView(HomeAssistantView): """View to handle requests.""" diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 96d5b0499d2..66c7c763cc9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -12,10 +12,11 @@ import os import voluptuous as vol from homeassistant.setup import async_prepare_setup_platform +from homeassistant.core import CoreState from homeassistant import config as conf_util from homeassistant.const import ( ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_TOGGLE, SERVICE_RELOAD) + SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START) from homeassistant.components import logbook from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import extract_domain_configs, script, condition @@ -81,8 +82,7 @@ _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) PLATFORM_SCHEMA = vol.Schema({ CONF_ALIAS: cv.string, - vol.Optional(CONF_INITIAL_STATE, - default=DEFAULT_INITIAL_STATE): cv.boolean, + vol.Optional(CONF_INITIAL_STATE): cv.boolean, vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, @@ -101,15 +101,13 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({ RELOAD_SERVICE_SCHEMA = vol.Schema({}) -def is_on(hass, entity_id=None): +def is_on(hass, entity_id): """ Return true if specified automation entity_id is on. - Check all automation if no entity_id specified. + Async friendly. """ - entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) - return any(hass.states.is_state(entity_id, STATE_ON) - for entity_id in entity_ids) + return hass.states.is_state(entity_id, STATE_ON) def turn_on(hass, entity_id=None): @@ -231,7 +229,6 @@ class AutomationEntity(ToggleEntity): self._async_detach_triggers = None self._cond_func = cond_func self._async_action = async_action - self._enabled = False self._last_triggered = None self._hidden = hidden self._initial_state = initial_state @@ -261,38 +258,54 @@ class AutomationEntity(ToggleEntity): @property def is_on(self) -> bool: """Return True if entity is on.""" - return self._enabled + return self._async_detach_triggers is not None @asyncio.coroutine def async_added_to_hass(self) -> None: """Startup with initial state or previous state.""" - state = yield from async_get_last_state(self.hass, self.entity_id) - if state is None: - if self._initial_state: - yield from self.async_enable() + enable_automation = DEFAULT_INITIAL_STATE + + if self._initial_state is not None: + enable_automation = self._initial_state else: - self._last_triggered = state.attributes.get('last_triggered') - if state.state == STATE_ON: + state = yield from async_get_last_state(self.hass, self.entity_id) + if state: + enable_automation = state.state == STATE_ON + self._last_triggered = state.attributes.get('last_triggered') + + if not enable_automation: + return + + # HomeAssistant is starting up + elif self.hass.state == CoreState.not_running: + @asyncio.coroutine + def async_enable_automation(event): + """Start automation on startup.""" yield from self.async_enable() + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, async_enable_automation) + + # HomeAssistant is running + else: + yield from self.async_enable() + @asyncio.coroutine def async_turn_on(self, **kwargs) -> None: """Turn the entity on and update the state.""" - if self._enabled: + if self.is_on: return yield from self.async_enable() - yield from self.async_update_ha_state() @asyncio.coroutine def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" - if not self._enabled: + if not self.is_on: return self._async_detach_triggers() self._async_detach_triggers = None - self._enabled = False yield from self.async_update_ha_state() @asyncio.coroutine @@ -318,12 +331,12 @@ class AutomationEntity(ToggleEntity): This method is a coroutine. """ - if self._enabled: + if self.is_on: return self._async_detach_triggers = yield from self._async_attach_triggers( self.async_trigger) - self._enabled = True + yield from self.async_update_ha_state() @asyncio.coroutine @@ -342,7 +355,7 @@ def _async_process_config(hass, config, component): list_no) hidden = config_block[CONF_HIDE_ENTITY] - initial_state = config_block[CONF_INITIAL_STATE] + initial_state = config_block.get(CONF_INITIAL_STATE) action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 21bf243e34f..0ff10665eb3 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -2,15 +2,15 @@ Offer event listening automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#event-trigger +at https://home-assistant.io/docs/automation/trigger/#event-trigger """ import asyncio import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.const import CONF_PLATFORM +from homeassistant.core import callback, CoreState +from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START from homeassistant.helpers import config_validation as cv CONF_EVENT_TYPE = "event_type" @@ -31,6 +31,19 @@ def async_trigger(hass, config, action): event_type = config.get(CONF_EVENT_TYPE) event_data = config.get(CONF_EVENT_DATA) + if (event_type == EVENT_HOMEASSISTANT_START and + hass.state == CoreState.starting): + _LOGGER.warning('Deprecation: Automations should not listen to event ' + "'homeassistant_start'. Use platform 'homeassistant' " + 'instead. Feature will be removed in 0.45') + hass.async_run_job(action, { + 'trigger': { + 'platform': 'event', + 'event': None, + }, + }) + return lambda: None + @callback def handle_event(event): """Listen for events and calls the action when data matches.""" diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py new file mode 100644 index 00000000000..0222ef02c26 --- /dev/null +++ b/homeassistant/components/automation/homeassistant.py @@ -0,0 +1,55 @@ +""" +Offer Home Assistant core automation rules. + +For more details about this automation rule, please refer to the documentation +at https://home-assistant.io/components/automation/#homeassistant-trigger +""" +import asyncio +import logging + +import voluptuous as vol + +from homeassistant.core import callback, CoreState +from homeassistant.const import ( + CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP) + +EVENT_START = 'start' +EVENT_SHUTDOWN = 'shutdown' +_LOGGER = logging.getLogger(__name__) + +TRIGGER_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'homeassistant', + vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), +}) + + +@asyncio.coroutine +def async_trigger(hass, config, action): + """Listen for events based on configuration.""" + event = config.get(CONF_EVENT) + + if event == EVENT_SHUTDOWN: + @callback + def hass_shutdown(event): + """Called when Home Assistant is shutting down.""" + hass.async_run_job(action, { + 'trigger': { + 'platform': 'homeassistant', + 'event': event, + }, + }) + + return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, + hass_shutdown) + + # Automation are enabled while hass is starting up, fire right away + # Check state because a config reload shouldn't trigger it. + elif hass.state == CoreState.starting: + hass.async_run_job(action, { + 'trigger': { + 'platform': 'homeassistant', + 'event': event, + }, + }) + + return lambda: None diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 56109e27f1b..c827fe8f7a4 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -70,7 +70,7 @@ def async_trigger(hass, config, action): nonlocal held_less_than, held_more_than pressed_time = dt_util.utcnow() if held_more_than is None and held_less_than is None: - call_action() + hass.add_job(call_action) if held_more_than is not None and held_less_than is None: cancel_pressed_more_than = track_point_in_utc_time( hass, @@ -88,7 +88,7 @@ def async_trigger(hass, config, action): held_time = dt_util.utcnow() - pressed_time if held_less_than is not None and held_time < held_less_than: if held_more_than is None or held_time > held_more_than: - call_action() + hass.add_job(call_action) hass.data['litejet_system'].on_switch_pressed(number, pressed) hass.data['litejet_system'].on_switch_released(number, released) diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index fbea2cede38..172a368225d 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -2,7 +2,7 @@ Offer MQTT listening automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#mqtt-trigger +at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger """ import asyncio import json diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 8b3c3e57670..3657724f679 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -2,7 +2,7 @@ Offer numeric state listening automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#numeric-state-trigger +at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger """ import asyncio import logging diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index fdc46079263..1f55ef67f25 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -2,7 +2,7 @@ Offer state listening automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#state-trigger +at https://home-assistant.io/docs/automation/trigger/#state-trigger """ import asyncio import voluptuous as vol diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 4529b5a8b60..3ce84d60a91 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -2,7 +2,7 @@ Offer sun based automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#sun-trigger +at https://home-assistant.io/docs/automation/trigger/#sun-trigger """ import asyncio from datetime import timedelta diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index a83671d5fa8..0fcdeaae5e0 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -2,7 +2,7 @@ Offer template automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#template-trigger +at https://home-assistant.io/docs/automation/trigger/#template-trigger """ import asyncio import logging diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index e33fd0f6ba9..0adcd5f8272 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -2,7 +2,7 @@ Offer time listening automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#time-trigger +at https://home-assistant.io/docs/automation/trigger/#time-trigger """ import asyncio import logging diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 8ffc0498317..c2a0e4d094d 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -2,7 +2,7 @@ Offer zone automation rules. For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#zone-trigger +at https://home-assistant.io/docs/automation/trigger/#zone-trigger """ import asyncio import voluptuous as vol diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/binary_sensor/blink.py index 8d84ffb9c90..1e95d4d466b 100644 --- a/homeassistant/components/binary_sensor/blink.py +++ b/homeassistant/components/binary_sensor/blink.py @@ -11,7 +11,7 @@ DEPENDENCIES = ['blink'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the blink binary sensors.""" + """Set up the blink binary sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py index 135d9a1e028..9a7a1dcf546 100644 --- a/homeassistant/components/binary_sensor/hikvision.py +++ b/homeassistant/components/binary_sensor/hikvision.py @@ -18,7 +18,7 @@ from homeassistant.const import ( CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE) -REQUIREMENTS = ['pyhik==0.1.0'] +REQUIREMENTS = ['pyhik==0.1.2'] _LOGGER = logging.getLogger(__name__) CONF_IGNORED = 'ignored' @@ -33,7 +33,6 @@ ATTR_DELAY = 'delay' DEVICE_CLASS_MAP = { 'Motion': 'motion', 'Line Crossing': 'motion', - 'IO Trigger': None, 'Field Detection': 'motion', 'Video Loss': None, 'Tamper Detection': 'motion', @@ -47,6 +46,7 @@ DEVICE_CLASS_MAP = { 'Bad Video': None, 'PIR Alarm': 'motion', 'Face Detection': 'motion', + 'Scene Change Detection': 'motion', } CUSTOMIZE_SCHEMA = vol.Schema({ @@ -91,24 +91,30 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities = [] - for sensor in data.sensors: - # Build sensor name, then parse customize config. - sensor_name = sensor.replace(' ', '_') + for sensor, channel_list in data.sensors.items(): + for channel in channel_list: + # Build sensor name, then parse customize config. + if data.type == 'NVR': + sensor_name = '{}_{}'.format( + sensor.replace(' ', '_'), channel[1]) + else: + sensor_name = sensor.replace(' ', '_') - custom = customize.get(sensor_name.lower(), {}) - ignore = custom.get(CONF_IGNORED) - delay = custom.get(CONF_DELAY) + custom = customize.get(sensor_name.lower(), {}) + ignore = custom.get(CONF_IGNORED) + delay = custom.get(CONF_DELAY) - _LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s', - data.name, sensor_name, ignore, delay) - if not ignore: - entities.append(HikvisionBinarySensor(hass, sensor, data, delay)) + _LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s', + data.name, sensor_name, ignore, delay) + if not ignore: + entities.append(HikvisionBinarySensor( + hass, sensor, channel[1], data, delay)) add_entities(entities) class HikvisionData(object): - """Hikvision camera event stream object.""" + """Hikvision device event stream object.""" def __init__(self, hass, url, port, name, username, password): """Initialize the data oject.""" @@ -144,25 +150,40 @@ class HikvisionData(object): @property def cam_id(self): - """Return camera id.""" + """Return device id.""" return self.camdata.get_id @property def name(self): - """Return camera name.""" + """Return device name.""" return self._name + @property + def type(self): + """Return device type.""" + return self.camdata.get_type + + def get_attributes(self, sensor, channel): + """Return attribute list for sensor/channel.""" + return self.camdata.fetch_attributes(sensor, channel) + class HikvisionBinarySensor(BinarySensorDevice): """Representation of a Hikvision binary sensor.""" - def __init__(self, hass, sensor, cam, delay): + def __init__(self, hass, sensor, channel, cam, delay): """Initialize the binary_sensor.""" self._hass = hass self._cam = cam - self._name = self._cam.name + ' ' + sensor - self._id = self._cam.cam_id + '.' + sensor self._sensor = sensor + self._channel = channel + + if self._cam.type == 'NVR': + self._name = '{} {} {}'.format(self._cam.name, sensor, channel) + else: + self._name = '{} {}'.format(self._cam.name, sensor) + + self._id = '{}.{}.{}'.format(self._cam.cam_id, sensor, channel) if delay is None: self._delay = 0 @@ -176,11 +197,11 @@ class HikvisionBinarySensor(BinarySensorDevice): def _sensor_state(self): """Extract sensor state.""" - return self._cam.sensors[self._sensor][0] + return self._cam.get_attributes(self._sensor, self._channel)[0] def _sensor_last_update(self): """Extract sensor last update time.""" - return self._cam.sensors[self._sensor][3] + return self._cam.get_attributes(self._sensor, self._channel)[3] @property def name(self): diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py index f6c8d9edbd1..03cc7e6bd9b 100644 --- a/homeassistant/components/binary_sensor/insteon_plm.py +++ b/homeassistant/components/binary_sensor/insteon_plm.py @@ -32,7 +32,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): InsteonPLMBinarySensorDevice(hass, plm, address, name) ) - hass.async_add_job(async_add_devices(device_list)) + async_add_devices(device_list) class InsteonPLMBinarySensorDevice(BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/ring.py b/homeassistant/components/binary_sensor/ring.py new file mode 100644 index 00000000000..429e92afa7f --- /dev/null +++ b/homeassistant/components/binary_sensor/ring.py @@ -0,0 +1,109 @@ +""" +This component provides HA sensor support for Ring Door Bell/Chimes. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.ring/ +""" +import logging +from datetime import timedelta + +import voluptuous as vol +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.ring import ( + CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE) + +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) + +DEPENDENCIES = ['ring'] + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=5) + +# Sensor types: Name, category, device_class +SENSOR_TYPES = { + 'ding': ['Ding', ['doorbell'], 'occupancy'], + 'motion': ['Motion', ['doorbell'], 'motion'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE): + cv.string, + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a sensor for a Ring device.""" + ring = hass.data.get('ring') + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + for device in ring.doorbells: + if 'doorbell' in SENSOR_TYPES[sensor_type][1]: + sensors.append(RingBinarySensor(hass, + device, + sensor_type)) + add_devices(sensors, True) + return True + + +class RingBinarySensor(BinarySensorDevice): + """A binary sensor implementation for Ring device.""" + + def __init__(self, hass, data, sensor_type): + """Initialize a sensor for Ring device.""" + super(RingBinarySensor, self).__init__() + self._sensor_type = sensor_type + self._data = data + self._name = "{0} {1}".format(self._data.name, + SENSOR_TYPES.get(self._sensor_type)[0]) + self._device_class = SENSOR_TYPES.get(self._sensor_type)[2] + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self._state + + @property + def device_class(self): + """Return the class of the binary sensor.""" + return self._device_class + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attrs = {} + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + + attrs['device_id'] = self._data.id + attrs['firmware'] = self._data.firmware + attrs['timezone'] = self._data.timezone + + if self._data.alert and self._data.alert_expires_at: + attrs['expires_at'] = self._data.alert_expires_at + attrs['state'] = self._data.alert.get('state') + + return attrs + + def update(self): + """Get the latest data and updates the state.""" + self._data.check_alerts() + + if self._data.alert: + self._state = (self._sensor_type == + self._data.alert.get('kind')) + else: + self._state = False diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py index 1b53738a25f..c2590925df7 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/binary_sensor/workday.py @@ -1,4 +1,9 @@ -"""Sensor to indicate whether the current day is a workday.""" +""" +Sensor to indicate whether the current day is a workday. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.workday/ +""" import asyncio import logging import datetime @@ -6,8 +11,8 @@ import datetime import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN, - CONF_NAME, WEEKDAYS) +from homeassistant.const import ( + STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, WEEKDAYS) import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv @@ -48,29 +53,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Workday sensor.""" + """Set up the Workday sensor.""" import holidays - # Get the Sensor name from the config sensor_name = config.get(CONF_NAME) - - # Get the country code from the config country = config.get(CONF_COUNTRY) - - # Get the province from the config province = config.get(CONF_PROVINCE) - - # Get the list of workdays from the config workdays = config.get(CONF_WORKDAYS) - - # Get the list of excludes from the config excludes = config.get(CONF_EXCLUDES) - # Instantiate the holidays module for the current year year = datetime.datetime.now().year obj_holidays = getattr(holidays, country)(years=year) - # Also apply the provience, if available for the configured country if province: if province not in obj_holidays.PROVINCES: _LOGGER.error('There is no province/state %s in country %s', @@ -81,14 +75,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): obj_holidays = getattr(holidays, country)(prov=province, years=year) - # Output found public holidays via the debug channel _LOGGER.debug("Found the following holidays for your configuration:") for date, name in sorted(obj_holidays.items()): _LOGGER.debug("%s %s", date, name) - # Add ourselves as device - add_devices([IsWorkdaySensor(obj_holidays, workdays, - excludes, sensor_name)], True) + add_devices([IsWorkdaySensor( + obj_holidays, workdays, excludes, sensor_name)], True) def day_to_string(day): @@ -122,7 +114,6 @@ class IsWorkdaySensor(Entity): def is_include(self, day, now): """Check if given day is in the includes list.""" - # Check includes if day in self._workdays: return True elif 'holiday' in self._workdays and now in self._obj_holidays: @@ -132,7 +123,6 @@ class IsWorkdaySensor(Entity): def is_exclude(self, day, now): """Check if given day is in the excludes list.""" - # Check excludes if day in self._excludes: return True elif 'holiday' in self._excludes and now in self._obj_holidays: diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py index 2a079716b68..4ae5007d665 100644 --- a/homeassistant/components/blink.py +++ b/homeassistant/components/blink.py @@ -5,17 +5,19 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/blink/ """ import logging + import voluptuous as vol + import homeassistant.helpers.config_validation as cv -from homeassistant.const import (CONF_USERNAME, - CONF_PASSWORD, - ATTR_FRIENDLY_NAME, - ATTR_ARMED) +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, ATTR_FRIENDLY_NAME, ATTR_ARMED) from homeassistant.helpers import discovery + +REQUIREMENTS = ['blinkpy==0.5.2'] + _LOGGER = logging.getLogger(__name__) DOMAIN = 'blink' -REQUIREMENTS = ['blinkpy==0.5.2'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -50,7 +52,7 @@ class BlinkSystem(object): def setup(hass, config): - """Setup Blink System.""" + """Set up Blink System.""" hass.data[DOMAIN] = BlinkSystem(config) discovery.load_platform(hass, 'camera', DOMAIN, {}, config) discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) @@ -77,11 +79,11 @@ def setup(hass, config): hass.data[DOMAIN].blink.arm = value hass.data[DOMAIN].blink.refresh() - hass.services.register(DOMAIN, 'snap_picture', snap_picture, - schema=SNAP_PICTURE_SCHEMA) - hass.services.register(DOMAIN, 'arm_camera', arm_camera, - schema=ARM_CAMERA_SCHEMA) - hass.services.register(DOMAIN, 'arm_system', arm_system, - schema=ARM_SYSTEM_SCHEMA) + hass.services.register( + DOMAIN, 'snap_picture', snap_picture, schema=SNAP_PICTURE_SCHEMA) + hass.services.register( + DOMAIN, 'arm_camera', arm_camera, schema=ARM_CAMERA_SCHEMA) + hass.services.register( + DOMAIN, 'arm_system', arm_system, schema=ARM_SYSTEM_SCHEMA) return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index b531a931a7a..388a9fce39b 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -7,6 +7,7 @@ https://home-assistant.io/components/camera/ """ import asyncio import collections +from contextlib import suppress from datetime import timedelta import logging import hashlib @@ -58,7 +59,6 @@ def async_get_image(hass, entity_id, timeout=10): state.attributes.get(ATTR_ENTITY_PICTURE) ) - response = None try: with async_timeout.timeout(timeout, loop=hass.loop): response = yield from websession.get(url) @@ -70,13 +70,9 @@ def async_get_image(hass, entity_id, timeout=10): image = yield from response.read() return image - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): raise HomeAssistantError("Can't connect to {0}".format(url)) - finally: - if response is not None: - yield from response.release() - @asyncio.coroutine def async_setup(hass, config): @@ -172,7 +168,7 @@ class Camera(Entity): if not img_bytes: break - if img_bytes is not None and img_bytes != last_image: + if img_bytes and img_bytes != last_image: write(img_bytes) # Chrome seems to always ignore first picture, @@ -185,8 +181,8 @@ class Camera(Entity): yield from asyncio.sleep(.5) - except (asyncio.CancelledError, ConnectionResetError): - _LOGGER.debug("Close stream by frontend.") + except asyncio.CancelledError: + _LOGGER.debug("Stream closed by frontend.") response = None finally: @@ -268,16 +264,14 @@ class CameraImageView(CameraView): @asyncio.coroutine def handle(self, request, camera): """Serve camera image.""" - try: - image = yield from camera.async_camera_image() + with suppress(asyncio.CancelledError, asyncio.TimeoutError): + with async_timeout.timeout(10, loop=request.app['hass'].loop): + image = yield from camera.async_camera_image() - if image is None: - return web.Response(status=500) + if image: + return web.Response(body=image) - return web.Response(body=image) - - except asyncio.CancelledError: - _LOGGER.debug("Close stream by frontend.") + return web.Response(status=500) class CameraMjpegStream(CameraView): diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py index f272dda4bb3..a50cdc859a7 100644 --- a/homeassistant/components/camera/amcrest.py +++ b/homeassistant/components/camera/amcrest.py @@ -16,9 +16,9 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_stream) + async_get_clientsession, async_aiohttp_proxy_web) -REQUIREMENTS = ['amcrest==1.1.4'] +REQUIREMENTS = ['amcrest==1.1.8'] _LOGGER = logging.getLogger(__name__) @@ -125,7 +125,7 @@ class AmcrestCam(Camera): stream_coro = websession.get( streaming_url, auth=self._token, timeout=TIMEOUT) - yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) + yield from async_aiohttp_proxy_web(self.hass, request, stream_coro) @property def name(self): diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index ed8c84f90df..d4c7b54fc7f 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -8,14 +8,14 @@ import asyncio import logging import voluptuous as vol -from aiohttp import web +from homeassistant.const import CONF_NAME from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS) import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_NAME - +from homeassistant.helpers.aiohttp_client import ( + async_aiohttp_proxy_stream) DEPENDENCIES = ['ffmpeg'] _LOGGER = logging.getLogger(__name__) @@ -69,26 +69,10 @@ class FFmpegCamera(Camera): yield from stream.open_camera( self._input, extra_cmd=self._extra_arguments) - response = web.StreamResponse() - response.content_type = 'multipart/x-mixed-replace;boundary=ffserver' - - yield from response.prepare(request) - - try: - while True: - data = yield from stream.read(102400) - if not data: - break - response.write(data) - - except asyncio.CancelledError: - _LOGGER.debug("Close stream by frontend.") - response = None - - finally: - yield from stream.close() - if response is not None: - yield from response.write_eof() + yield from async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + yield from stream.close() @property def name(self): diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index a374d19f4d1..c1f9513d2c6 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -66,9 +66,13 @@ class FoscamCamera(Camera): def camera_image(self): """Return a still image reponse from the camera.""" # Send the request to snap a picture and return raw jpg data - response = requests.get(self._snap_picture_url, timeout=10) - - return response.content + # Handle exception if host is not reachable or url failed + try: + response = requests.get(self._snap_picture_url, timeout=10) + except requests.exceptions.ConnectionError: + return None + else: + return response.content @property def name(self): diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index 3f50bc799c4..a330836f32b 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -107,7 +107,6 @@ class GenericCamera(Camera): None, fetch) # async else: - response = None try: websession = async_get_clientsession(self.hass) with async_timeout.timeout(10, loop=self.hass.loop): @@ -117,14 +116,9 @@ class GenericCamera(Camera): except asyncio.TimeoutError: _LOGGER.error('Timeout getting camera image') return self._last_image - except (aiohttp.errors.ClientError, - aiohttp.errors.DisconnectedError, - aiohttp.errors.HttpProcessingError) as err: + except aiohttp.ClientError as err: _LOGGER.error('Error getting new camera image: %s', err) return self._last_image - finally: - if response is not None: - yield from response.release() self._last_url = url return self._last_image diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 532b91e7442..a158c36152e 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -19,7 +19,7 @@ from homeassistant.const import ( HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_stream) + async_get_clientsession, async_aiohttp_proxy_web) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -93,7 +93,6 @@ class MjpegCamera(Camera): return image websession = async_get_clientsession(self.hass) - response = None try: with async_timeout.timeout(10, loop=self.hass.loop): response = yield from websession.get( @@ -105,14 +104,9 @@ class MjpegCamera(Camera): except asyncio.TimeoutError: _LOGGER.error('Timeout getting camera image') - except (aiohttp.errors.ClientError, - aiohttp.errors.ClientDisconnectedError) as err: + except aiohttp.ClientError as err: _LOGGER.error('Error getting new camera image: %s', err) - finally: - if response is not None: - yield from response.release() - def camera_image(self): """Return a still image response from the camera.""" if self._username and self._password: @@ -140,7 +134,7 @@ class MjpegCamera(Camera): websession = async_get_clientsession(self.hass) stream_coro = websession.get(self._mjpeg_url, auth=self._auth) - yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) + yield from async_aiohttp_proxy_web(self.hass, request, stream_coro) @property def name(self): diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 2b4d72f92f8..e986d81887b 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -19,7 +19,7 @@ from homeassistant.components.camera import ( Camera, PLATFORM_SCHEMA) from homeassistant.helpers.aiohttp_client import ( async_get_clientsession, async_create_clientsession, - async_aiohttp_proxy_stream) + async_aiohttp_proxy_web) import homeassistant.helpers.config_validation as cv from homeassistant.util.async import run_coroutine_threadsafe @@ -74,7 +74,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): 'version': '1', 'query': 'SYNO.' } - query_req = None try: with async_timeout.timeout(timeout, loop=hass.loop): query_req = yield from websession_init.get( @@ -88,14 +87,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera_path = query_resp['data'][CAMERA_API]['path'] streaming_path = query_resp['data'][STREAMING_API]['path'] - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.exception("Error on %s", syno_api_url) return False - finally: - if query_req is not None: - yield from query_req.release() - # Authticate to NAS to get a session id syno_auth_url = SYNO_API_URL.format( config.get(CONF_URL), WEBAPI_PATH, auth_path) @@ -128,13 +123,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): syno_camera_url, params=camera_payload ) - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.exception("Error on %s", syno_camera_url) return False camera_resp = yield from camera_req.json() cameras = camera_resp['data']['cameras'] - yield from camera_req.release() # add cameras devices = [] @@ -172,7 +166,6 @@ def get_session_id(hass, websession, username, password, login_url, timeout): 'session': 'SurveillanceStation', 'format': 'sid' } - auth_req = None try: with async_timeout.timeout(timeout, loop=hass.loop): auth_req = yield from websession.get( @@ -182,14 +175,10 @@ def get_session_id(hass, websession, username, password, login_url, timeout): auth_resp = yield from auth_req.json() return auth_resp['data']['sid'] - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.exception("Error on %s", login_url) return False - finally: - if auth_req is not None: - yield from auth_req.release() - class SynologyCamera(Camera): """An implementation of a Synology NAS based IP camera.""" @@ -235,12 +224,11 @@ class SynologyCamera(Camera): image_url, params=image_payload ) - except (asyncio.TimeoutError, aiohttp.errors.ClientError): - _LOGGER.exception("Error on %s", image_url) + except (asyncio.TimeoutError, aiohttp.ClientError): + _LOGGER.error("Error fetching %s", image_url) return None image = yield from response.read() - yield from response.release() return image @@ -260,7 +248,7 @@ class SynologyCamera(Camera): stream_coro = self._websession.get( streaming_url, params=streaming_payload) - yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro) + yield from async_aiohttp_proxy_web(self.hass, request, stream_coro) @property def name(self): diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index bd2e38433d6..2a0bd2eb5c1 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -692,18 +692,16 @@ class ClimateDevice(Entity): def _convert_for_display(self, temp): """Convert temperature into preferred units for display purposes.""" - if (temp is None or not isinstance(temp, Number) or - self.temperature_unit == self.unit_of_measurement): + if temp is None or not isinstance(temp, Number): return temp - - value = convert_temperature(temp, self.temperature_unit, - self.unit_of_measurement) - + if self.temperature_unit != self.unit_of_measurement: + temp = convert_temperature(temp, self.temperature_unit, + self.unit_of_measurement) # Round in the units appropriate if self.precision == PRECISION_HALVES: - return round(value * 2) / 2.0 + return round(temp * 2) / 2.0 elif self.precision == PRECISION_TENTHS: - return round(value, 1) + return round(temp, 1) else: # PRECISION_WHOLE as a fall back - return round(value) + return round(temp) diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index 66b50cd507d..7385eeac98a 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -16,11 +16,15 @@ _LOGGER = logging.getLogger(__name__) STATE_MANUAL = "manual" STATE_BOOST = "boost" +STATE_COMFORT = "comfort" +STATE_LOWERING = "lowering" HM_STATE_MAP = { "AUTO_MODE": STATE_AUTO, "MANU_MODE": STATE_MANUAL, "BOOST_MODE": STATE_BOOST, + "COMFORT_MODE": STATE_COMFORT, + "LOWERING_MODE": STATE_LOWERING } HM_TEMP_MAP = [ diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/climate/tado.py index e5242f88162..734b13dc7e7 100644 --- a/homeassistant/components/climate/tado.py +++ b/homeassistant/components/climate/tado.py @@ -1,40 +1,40 @@ -ļ»æ"""tado component to create a climate device for each zone.""" +ļ»æ""" +Tado component to create a climate device for each zone. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.tado/ +""" import logging from homeassistant.const import TEMP_CELSIUS - -from homeassistant.components.climate import ( - ClimateDevice) -from homeassistant.const import ( - ATTR_TEMPERATURE) -from homeassistant.components.tado import ( - DATA_TADO) - -CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode -CONST_MODE_OFF = "OFF" # Switch off heating in a zone - -# When we change the temperature setting, we need an overlay mode -# wait until tado changes the mode automatic -CONST_OVERLAY_TADO_MODE = "TADO_MODE" -# the user has change the temperature or mode manually -CONST_OVERLAY_MANUAL = "MANUAL" -# the temperature will be reset after a timespan -CONST_OVERLAY_TIMER = "TIMER" - -OPERATION_LIST = { - CONST_OVERLAY_MANUAL: "Manual", - CONST_OVERLAY_TIMER: "Timer", - CONST_OVERLAY_TADO_MODE: "Tado mode", - CONST_MODE_SMART_SCHEDULE: "Smart schedule", - CONST_MODE_OFF: "Off"} +from homeassistant.components.climate import ClimateDevice +from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.components.tado import DATA_TADO _LOGGER = logging.getLogger(__name__) +CONST_MODE_SMART_SCHEDULE = 'SMART_SCHEDULE' # Default mytado mode +CONST_MODE_OFF = 'OFF' # Switch off heating in a zone + +# When we change the temperature setting, we need an overlay mode +# wait until tado changes the mode automatic +CONST_OVERLAY_TADO_MODE = 'TADO_MODE' +# the user has change the temperature or mode manually +CONST_OVERLAY_MANUAL = 'MANUAL' +# the temperature will be reset after a timespan +CONST_OVERLAY_TIMER = 'TIMER' + +OPERATION_LIST = { + CONST_OVERLAY_MANUAL: 'Manual', + CONST_OVERLAY_TIMER: 'Timer', + CONST_OVERLAY_TADO_MODE: 'Tado mode', + CONST_MODE_SMART_SCHEDULE: 'Smart schedule', + CONST_MODE_OFF: 'Off', +} + def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the climate platform.""" - # get the PyTado object from the hub component + """Set up the Tado climate platform.""" tado = hass.data[DATA_TADO] try: @@ -45,10 +45,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): climate_devices = [] for zone in zones: - climate_devices.append(create_climate_device(tado, hass, - zone, - zone['name'], - zone['id'])) + climate_devices.append(create_climate_device( + tado, hass, zone, zone['name'], zone['id'])) if len(climate_devices) > 0: add_devices(climate_devices, True) @@ -58,13 +56,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def create_climate_device(tado, hass, zone, name, zone_id): - """Create a climate device.""" + """Create a Tado climate device.""" capabilities = tado.get_capabilities(zone_id) unit = TEMP_CELSIUS - min_temp = float(capabilities["temperatures"]["celsius"]["min"]) - max_temp = float(capabilities["temperatures"]["celsius"]["max"]) - ac_mode = capabilities["type"] != "HEATING" + min_temp = float(capabilities['temperatures']['celsius']['min']) + max_temp = float(capabilities['temperatures']['celsius']['max']) + ac_mode = capabilities['type'] != 'HEATING' data_id = 'zone {} {}'.format(name, zone_id) device = TadoClimate(tado, @@ -74,10 +72,10 @@ def create_climate_device(tado, hass, zone, name, zone_id): ac_mode) tado.add_sensor(data_id, { - "id": zone_id, - "zone": zone, - "name": name, - "climate": device + 'id': zone_id, + 'zone': zone, + 'name': name, + 'climate': device }) return device @@ -89,7 +87,7 @@ class TadoClimate(ClimateDevice): def __init__(self, store, zone_name, zone_id, data_id, min_temp, max_temp, ac_mode, tolerance=0.3): - """Initialization of TadoClimate device.""" + """Initialization of Tado climate device.""" self._store = store self._data_id = data_id @@ -202,8 +200,7 @@ class TadoClimate(ClimateDevice): data = self._store.get_data(self._data_id) if data is None: - _LOGGER.debug('Recieved no data for zone %s', - self.zone_name) + _LOGGER.debug("Recieved no data for zone %s", self.zone_name) return if 'sensorDataPoints' in data: @@ -232,11 +229,11 @@ class TadoClimate(ClimateDevice): if 'tadoMode' in data: mode = data['tadoMode'] - self._is_away = mode == "AWAY" + self._is_away = mode == 'AWAY' if 'setting' in data: power = data['setting']['power'] - if power == "OFF": + if power == 'OFF': self._current_operation = CONST_MODE_OFF self._device_is_active = False else: @@ -249,48 +246,47 @@ class TadoClimate(ClimateDevice): overlay = False termination = "" - # if you set mode manualy to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" + # If you set mode manualy to off, there will be an overlay + # and a termination, but we want to see the mode "OFF" if overlay and self._device_is_active: - # there is an overlay the device is on + # There is an overlay the device is on self._overlay_mode = termination self._current_operation = termination else: - # there is no overlay, the mode will always be - # "SMART_SCHEDULE" + # There is no overlay, the mode will always be + # "SMART_SCHEDULE" self._overlay_mode = CONST_MODE_SMART_SCHEDULE self._current_operation = CONST_MODE_SMART_SCHEDULE def _control_heating(self): """Send new target temperature to mytado.""" - if not self._active and None not in (self._cur_temp, - self._target_temp): + if not self._active and None not in ( + self._cur_temp, self._target_temp): self._active = True - _LOGGER.info('Obtained current and target temperature. ' - 'tado thermostat active.') + _LOGGER.info("Obtained current and target temperature. " + "Tado thermostat active") if not self._active or self._current_operation == self._overlay_mode: return if self._current_operation == CONST_MODE_SMART_SCHEDULE: - _LOGGER.info('Switching mytado.com to SCHEDULE (default) ' - 'for zone %s', self.zone_name) + _LOGGER.info("Switching mytado.com to SCHEDULE (default) " + "for zone %s", self.zone_name) self._store.reset_zone_overlay(self.zone_id) self._overlay_mode = self._current_operation return if self._current_operation == CONST_MODE_OFF: - _LOGGER.info('Switching mytado.com to OFF for zone %s', + _LOGGER.info("Switching mytado.com to OFF for zone %s", self.zone_name) self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL) self._overlay_mode = self._current_operation return - _LOGGER.info('Switching mytado.com to %s mode for zone %s', + _LOGGER.info("Switching mytado.com to %s mode for zone %s", self._current_operation, self.zone_name) - self._store.set_zone_overlay(self.zone_id, - self._current_operation, - self._target_temp) + self._store.set_zone_overlay( + self.zone_id, self._current_operation, self._target_temp) self._overlay_mode = self._current_operation diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py index f2c2551d371..df29768b3ff 100644 --- a/homeassistant/components/climate/wink.py +++ b/homeassistant/components/climate/wink.py @@ -19,7 +19,6 @@ DEPENDENCIES = ['wink'] STATE_AUX = 'aux' STATE_ECO = 'eco' STATE_FAN = 'fan' -SPEED_LOWEST = 'lowest' SPEED_LOW = 'low' SPEED_MEDIUM = 'medium' SPEED_HIGH = 'high' @@ -400,7 +399,7 @@ class WinkAC(WinkDevice, ClimateDevice): op_list.append(STATE_COOL) if 'auto_eco' in modes: op_list.append(STATE_ECO) - if 'fan_eco' in modes: + if 'fan_only' in modes: op_list.append(STATE_FAN) return op_list @@ -439,9 +438,7 @@ class WinkAC(WinkDevice, ClimateDevice): def current_fan_mode(self): """Return the current fan mode.""" speed = self.wink.current_fan_speed() - if speed <= 0.3 and speed >= 0.0: - return SPEED_LOWEST - elif speed <= 0.5 and speed > 0.3: + if speed <= 0.4 and speed > 0.3: return SPEED_LOW elif speed <= 0.8 and speed > 0.5: return SPEED_MEDIUM @@ -453,14 +450,12 @@ class WinkAC(WinkDevice, ClimateDevice): @property def fan_list(self): """List of available fan modes.""" - return [SPEED_LOWEST, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] def set_fan_mode(self, mode): """Set fan speed.""" - if mode == SPEED_LOWEST: - speed = 0.3 - elif mode == SPEED_LOW: - speed = 0.5 + if mode == SPEED_LOW: + speed = 0.4 elif mode == SPEED_MEDIUM: speed = 0.8 elif mode == SPEED_HIGH: diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 4aaea884816..74fadb8c5fd 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -112,8 +112,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): _LOGGER.debug("Setpoint is 0, setting default to " "current_temperature=%s", self._current_temperature) - self._target_temperature = ( - round((float(self._current_temperature)), 1)) + if self._current_temperature is not None: + self._target_temperature = ( + round((float(self._current_temperature)), 1)) else: self._target_temperature = round( (float(self.values.primary.data)), 1) diff --git a/homeassistant/components/cover/vera.py b/homeassistant/components/cover/vera.py index 48abe373eac..033cea4a828 100644 --- a/homeassistant/components/cover/vera.py +++ b/homeassistant/components/cover/vera.py @@ -47,6 +47,7 @@ class VeraCover(VeraDevice, CoverDevice): def set_cover_position(self, position, **kwargs): """Move the cover to a specific position.""" self.vera_device.set_level(position) + self.schedule_update_ha_state() @property def is_closed(self): @@ -60,11 +61,14 @@ class VeraCover(VeraDevice, CoverDevice): def open_cover(self, **kwargs): """Open the cover.""" self.vera_device.open() + self.schedule_update_ha_state() def close_cover(self, **kwargs): """Close the cover.""" self.vera_device.close() + self.schedule_update_ha_state() def stop_cover(self, **kwargs): """Stop the cover.""" self.vera_device.stop() + self.schedule_update_ha_state() diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index 129dbd32ffe..3f26da183b5 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -20,12 +20,13 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -def get_device(values, **kwargs): +def get_device(values, node_config, **kwargs): """Create zwave entity device.""" + invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS) if (values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and values.primary.index == 0): - return ZwaveRollershutter(values) + return ZwaveRollershutter(values, invert_buttons) elif (values.primary.command_class in [ zwave.const.COMMAND_CLASS_SWITCH_BINARY, zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]): @@ -36,13 +37,14 @@ def get_device(values, **kwargs): class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): """Representation of an Zwave roller shutter.""" - def __init__(self, values): + def __init__(self, values, invert_buttons): """Initialize the zwave rollershutter.""" ZWaveDeviceEntity.__init__(self, values, DOMAIN) # pylint: disable=no-member self._open_id = None self._close_id = None self._current_position = None + self._invert_buttons = invert_buttons self._workaround = workaround.get_device_mapping(values.primary) if self._workaround: @@ -56,10 +58,9 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): if self.values.open and self.values.close and \ self._open_id is None and self._close_id is None: - if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE: + if self._invert_buttons: self._open_id = self.values.close.value_id self._close_id = self.values.open.value_id - self._workaround = None else: self._open_id = self.values.open.value_id self._close_id = self.values.close.value_id diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 3e04f46cb50..f2a538507a0 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -553,7 +553,6 @@ class Device(Entity): # bytes like 00 get truncates to 0, API needs full bytes oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes]) url = 'http://api.macvendors.com/' + oui - resp = None try: websession = async_get_clientsession(self.hass) @@ -570,13 +569,9 @@ class Device(Entity): # in the 'known_devices.yaml' file which only happens # the first time the device is seen. return 'unknown' - except (asyncio.TimeoutError, aiohttp.errors.ClientError, - aiohttp.errors.ClientDisconnectedError): + except (asyncio.TimeoutError, aiohttp.ClientError): # same as above return 'unknown' - finally: - if resp is not None: - yield from resp.release() @asyncio.coroutine def async_added_to_hass(self): diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 75cebbd95e7..255440a18e1 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -18,6 +18,7 @@ from homeassistant.components.device_tracker import ( # NOQA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['http'] +URL = '/api/locative' def setup_scanner(hass, config, see, discovery_info=None): @@ -30,7 +31,7 @@ def setup_scanner(hass, config, see, discovery_info=None): class LocativeView(HomeAssistantView): """View to handle locative requests.""" - url = '/api/locative' + url = URL name = 'api:locative' def __init__(self, see): diff --git a/homeassistant/components/device_tracker/mysensors.py b/homeassistant/components/device_tracker/mysensors.py index 5bbbee89be3..e1a16a017e4 100644 --- a/homeassistant/components/device_tracker/mysensors.py +++ b/homeassistant/components/device_tracker/mysensors.py @@ -16,11 +16,11 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see, discovery_info=None): """Setup the MySensors tracker.""" - def mysensors_callback(gateway, node_id): + def mysensors_callback(gateway, msg): """Callback for mysensors platform.""" - node = gateway.sensors[node_id] + node = gateway.sensors[msg.node_id] if node.sketch_name is None: - _LOGGER.info('No sketch_name: node %s', node_id) + _LOGGER.info('No sketch_name: node %s', msg.node_id) return pres = gateway.const.Presentation @@ -37,12 +37,12 @@ def setup_scanner(hass, config, see, discovery_info=None): 'latitude,longitude,altitude', position) continue name = '{} {} {}'.format( - node.sketch_name, node_id, child.id) + node.sketch_name, msg.node_id, child.id) attr = { mysensors.ATTR_CHILD_ID: child.id, mysensors.ATTR_DESCRIPTION: child.description, mysensors.ATTR_DEVICE: gateway.device, - mysensors.ATTR_NODE_ID: node_id, + mysensors.ATTR_NODE_ID: msg.node_id, } see( dev_id=slugify(name), diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/device_tracker/snmp.py index 6e8b07e6bab..1449ae6dbef 100644 --- a/homeassistant/components/device_tracker/snmp.py +++ b/homeassistant/components/device_tracker/snmp.py @@ -19,7 +19,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pysnmp==4.3.4'] +REQUIREMENTS = ['pysnmp==4.3.5'] CONF_COMMUNITY = 'community' CONF_AUTHKEY = 'authkey' @@ -125,7 +125,10 @@ class SnmpScanner(DeviceScanner): for resrow in restable: for _, val in resrow: - mac = binascii.hexlify(val.asOctets()).decode('utf-8') + try: + mac = binascii.hexlify(val.asOctets()).decode('utf-8') + except AttributeError: + continue _LOGGER.debug("Found MAC %s", mac) mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)]) devices.append({'mac': mac}) diff --git a/homeassistant/components/device_tracker/tado.py b/homeassistant/components/device_tracker/tado.py index ca0bec29706..3c21037d028 100644 --- a/homeassistant/components/device_tracker/tado.py +++ b/homeassistant/components/device_tracker/tado.py @@ -105,8 +105,6 @@ class TadoDeviceScanner(DeviceScanner): _LOGGER.debug("Requesting Tado") last_results = [] - response = None - tado_json = None try: with async_timeout.timeout(10, loop=self.hass.loop): @@ -127,14 +125,10 @@ class TadoDeviceScanner(DeviceScanner): tado_json = yield from response.json() - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Cannot load Tado data") return False - finally: - if response is not None: - yield from response.release() - # Without a home_id, we fetched an URL where the mobile devices can be # found under the mobileDevices key. if 'mobileDevices' in tado_json: diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/device_tracker/upc_connect.py index 879a645fe30..cec00221aaf 100644 --- a/homeassistant/components/device_tracker/upc_connect.py +++ b/homeassistant/components/device_tracker/upc_connect.py @@ -101,7 +101,6 @@ class UPCDeviceScanner(DeviceScanner): @asyncio.coroutine def async_login(self): """Login into firmware and get first token.""" - response = None try: # get first token with async_timeout.timeout(10, loop=self.hass.loop): @@ -109,7 +108,8 @@ class UPCDeviceScanner(DeviceScanner): "http://{}/common_page/login.html".format(self.host) ) - yield from response.text() + yield from response.text() + self.token = response.cookies['sessionToken'].value # login @@ -119,18 +119,12 @@ class UPCDeviceScanner(DeviceScanner): }) # successfull? - if data is not None: - return True - return False + return data is not None - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Can not load login page from %s", self.host) return False - finally: - if response is not None: - yield from response.release() - @asyncio.coroutine def _async_ws_function(self, function, additional_form=None): """Execute a command on UPC firmware webservice.""" @@ -142,8 +136,7 @@ class UPCDeviceScanner(DeviceScanner): if additional_form: form_data.update(additional_form) - redirects = True if function != CMD_DEVICES else False - response = None + redirects = function != CMD_DEVICES try: with async_timeout.timeout(10, loop=self.hass.loop): response = yield from self.websession.post( @@ -163,10 +156,6 @@ class UPCDeviceScanner(DeviceScanner): self.token = response.cookies['sessionToken'].value return (yield from response.text()) - except (asyncio.TimeoutError, aiohttp.errors.ClientError): + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Error on %s", function) self.token = None - - finally: - if response is not None: - yield from response.release() diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 7d95cfb20fe..b6f65d9953a 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -153,7 +153,7 @@ def setup(hass, config): sw_path = "service_worker.js" hass.http.register_static_path("/service_worker.js", - os.path.join(STATIC_PATH, sw_path), 0) + os.path.join(STATIC_PATH, sw_path), False) hass.http.register_static_path("/robots.txt", os.path.join(STATIC_PATH, "robots.txt")) hass.http.register_static_path("/static", STATIC_PATH) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index cae324af9ba..44563480cdb 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -3,8 +3,8 @@ FINGERPRINTS = { "compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0", "core.js": "5d08475f03adb5969bd31855d5ca0cfd", - "frontend.html": "53c45b837a3bcae7cfb9ef4a5919844f", - "mdi.html": "4921d26f29dc148c3e8bd5bcd8ce5822", + "frontend.html": "feaf3e9453eca239f29eb10e7645a84f", + "mdi.html": "989f02c51eba561dc32b9ecc628a84b3", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", "panels/ha-panel-config.html": "6dcb246cd356307a638f81c4f89bf9b3", "panels/ha-panel-dev-event.html": "1f169700c2345785855b1d7919d12326", @@ -12,9 +12,9 @@ FINGERPRINTS = { "panels/ha-panel-dev-service.html": "0fe8e6acdccf2dc3d1ae657b2c7f2df0", "panels/ha-panel-dev-state.html": "48d37db4a1d6708314ded1d624d0f4d4", "panels/ha-panel-dev-template.html": "6f353392d68574fbc5af188bca44d0ae", - "panels/ha-panel-history.html": "bfd5f929d5aa9cefdd799ec37624efa1", + "panels/ha-panel-history.html": "6945cebe5d8075aae560d2c4246b063f", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", "panels/ha-panel-logbook.html": "a1fc2b5d739bedb9d87e4da4cd929a71", - "panels/ha-panel-map.html": "9aa065b1908089f3bb5af7fdf9485be5", + "panels/ha-panel-map.html": "e3c7a54f90dd4269d7e53cdcd96514c6", "websocket_test.html": "575de64b431fe11c3785bf96d7813450" } diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index ead35a04662..ce38aab6821 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -391,7 +391,7 @@ window.hassUtil.computeDomain = function (stateObj) { }; window.hassUtil.computeStateName = function (stateObj) { - if (!stateObj._entityDisplay) { + if (stateObj._entityDisplay === undefined) { stateObj._entityDisplay = ( stateObj.attributes.friendly_name || window.HAWS.extractObjectId(stateObj.entity_id) @@ -419,7 +419,7 @@ window.hassUtil.isComponentLoaded = function (hass, component) { window.hassUtil.computeLocationName = function (hass) { return hass.config.core.location_name; -}; \ No newline at end of file +}()); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 09e5808da65..f41ff60ac76 100644 Binary files a/homeassistant/components/frontend/www_static/frontend.html.gz and b/homeassistant/components/frontend/www_static/frontend.html.gz differ diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index f4c59e1eff3..abbdc6f0555 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit f4c59e1eff3223262c198a29cf70c62572de019b +Subproject commit abbdc6f055524c5d3ed0bb50e35400fed40d573f diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index 0354cac0c44..afb3f013acf 100644 --- a/homeassistant/components/frontend/www_static/mdi.html +++ b/homeassistant/components/frontend/www_static/mdi.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/mdi.html.gz b/homeassistant/components/frontend/www_static/mdi.html.gz index e58a965a79c..638f13140e4 100644 Binary files a/homeassistant/components/frontend/www_static/mdi.html.gz and b/homeassistant/components/frontend/www_static/mdi.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html index b665faaf2fd..5d1f6163383 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz index e8d34afeb38..4d7d1ec9bad 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html index f04929a0b2e..013f27cf9b9 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html @@ -2,7 +2,7 @@ n.DomUtil.addClass(t,"leaflet-container"+(n.Browser.touch?" leaflet-touch":"")+(n.Browser.retina?" leaflet-retina":"")+(n.Browser.ielt9?" leaflet-oldie":"")+(n.Browser.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var e=n.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),n.DomUtil.setPosition(this._mapPane,new n.Point(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(n.DomUtil.addClass(t.markerPane,"leaflet-zoom-hide"),n.DomUtil.addClass(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e){n.DomUtil.setPosition(this._mapPane,new n.Point(0,0));var i=!this._loaded;this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset");var o=this._zoom!==e;this._moveStart(o)._move(t,e)._moveEnd(o),this.fire("viewreset"),i&&this.fire("load")},_moveStart:function(t){return t&&this.fire("zoomstart"),this.fire("movestart")},_move:function(t,e,n){e===i&&(e=this._zoom);var o=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(o||n&&n.pinch)&&this.fire("zoom",n),this.fire("move",n)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return n.Util.cancelAnimFrame(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){n.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(n.DomEvent){this._targets={},this._targets[n.stamp(this._container)]=this;var i=e?"off":"on";n.DomEvent[i](this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress",this._handleDOMEvent,this),this.options.trackResize&&n.DomEvent[i](t,"resize",this._onResize,this),n.Browser.any3d&&this.options.transform3DLimit&&this[i]("moveend",this._onMoveEnd)}},_onResize:function(){n.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=n.Util.requestAnimFrame(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,o=[],s="mouseout"===e||"mouseover"===e,r=t.target||t.srcElement,a=!1;r;){if((i=this._targets[n.stamp(r)])&&("click"===e||"preclick"===e)&&!t._simulated&&this._draggableMoved(i)){a=!0;break}if(i&&i.listens(e,!0)){if(s&&!n.DomEvent._isExternalTarget(r,t))break;if(o.push(i),s)break}if(r===this._container)break;r=r.parentNode}return o.length||a||s||!n.DomEvent._isExternalTarget(r,t)||(o=[this]),o},_handleDOMEvent:function(t){if(this._loaded&&!n.DomEvent._skipped(t)){var e="keypress"===t.type&&13===t.keyCode?"click":t.type;"mousedown"===e&&n.DomUtil.preventOutline(t.target||t.srcElement),this._fireDOMEvent(t,e)}},_fireDOMEvent:function(t,e,i){if("click"===t.type){var o=n.Util.extend({},t);o.type="preclick",this._fireDOMEvent(o,o.type,i)}if(!t._stopped&&(i=(i||[]).concat(this._findEventTargets(t,e)),i.length)){var s=i[0];"contextmenu"===e&&s.listens(e,!0)&&n.DomEvent.preventDefault(t);var r={originalEvent:t};if("keypress"!==t.type){var a=s instanceof n.Marker;r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom(),o=n.Browser.any3d?this.options.zoomSnap:1;return o&&(t=Math.round(t/o)*o),Math.max(e,Math.min(i,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){n.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return!((e&&e.animate)!==!0&&!this.getSize().contains(i)||(this.panBy(i,e),0))},_createAnimProxy:function(){var t=this._proxy=n.DomUtil.create("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(e){var i=n.DomUtil.TRANSFORM,o=t.style[i];n.DomUtil.setTransform(t,this.project(e.center,e.zoom),this.getZoomScale(e.zoom,1)),o===t.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var e=this.getCenter(),i=this.getZoom();n.DomUtil.setTransform(t,this.project(e,i),this.getZoomScale(i,1))},this)},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var o=this.getZoomScale(e),s=this._getCenterOffset(t)._divideBy(1-1/o);return!(i.animate!==!0&&!this.getSize().contains(s)||(n.Util.requestAnimFrame(function(){this._moveStart(!0)._animateZoom(t,e,!0)},this),0))},_animateZoom:function(t,e,i,o){i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,n.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:o}),setTimeout(n.bind(this._onZoomTransitionEnd,this),250)},_onZoomTransitionEnd:function(){this._animatingZoom&&(n.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),n.Util.requestAnimFrame(function(){this._moveEnd(!0)},this))}}),n.map=function(t,e){return new n.Map(t,e)},n.Layer=n.Evented.extend({options:{pane:"overlayPane",nonBubblingEvents:[],attribution:null},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n.stamp(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n.stamp(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var e=t.target;if(e.hasLayer(this)){if(this._map=e,this._zoomAnimated=e._zoomAnimated,this.getEvents){var i=this.getEvents();e.on(i,this),this.once("remove",function(){e.off(i,this)},this)}this.onAdd(e),this.getAttribution&&e.attributionControl&&e.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),e.fire("layeradd",{layer:this})}}}),n.Map.include({addLayer:function(t){var e=n.stamp(t);return this._layers[e]?this:(this._layers[e]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var e=n.stamp(t);return this._layers[e]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[e],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n.stamp(t)in this._layers},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},_addLayers:function(t){t=t?n.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;ethis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),this.options.minZoom===i&&this._layersMinZoom&&this.getZoom()100&&o<500||t.target._simulatedClick&&!t._simulated?void n.DomEvent.stop(t):(n.DomEvent._lastClick=i,void e(t))}},n.DomEvent.addListener=n.DomEvent.on,n.DomEvent.removeListener=n.DomEvent.off,n.PosAnimation=n.Evented.extend({run:function(t,e,i,o){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(o||.5,.2),this._startPos=n.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=n.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(t){var e=+new Date-this._startTime,i=1e3*this._duration;e1e-7;l++)e=r*Math.sin(h),e=Math.pow((1-e)/(1+e),r/2),u=Math.PI/2-2*Math.atan(a*e)-h,h+=u;return new n.LatLng(h*i,t.x*i/o)}},n.CRS.EPSG3395=n.extend({},n.CRS.Earth,{code:"EPSG:3395",projection:n.Projection.Mercator,transformation:function(){var t=.5/(Math.PI*n.Projection.Mercator.R);return new n.Transformation(t,.5,-t,.5)}()}),n.GridLayer=n.Layer.extend({options:{tileSize:256,opacity:1,updateWhenIdle:n.Browser.mobile,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:i,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){n.setOptions(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView(),this._update()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),n.DomUtil.remove(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=null},bringToFront:function(){return this._map&&(n.DomUtil.toFront(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(n.DomUtil.toBack(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){return this._map&&(this._removeAllTiles(),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=n.Util.throttle(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return e.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof n.Point?t:new n.Point(t,t)},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var e,i=this.getPane().children,n=-t(-(1/0),1/0),o=0,s=i.length;othis.options.maxZoom||io&&this._retainParent(s,r,a,o))},_retainChildren:function(t,e,i,o){for(var s=2*t;s<2*t+2;s++)for(var r=2*e;r<2*e+2;r++){var a=new n.Point(s,r);a.z=i+1;var h=this._tileCoordsToKey(a),l=this._tiles[h];l&&l.active?l.retain=!0:(l&&l.loaded&&(l.retain=!0),i+1this.options.maxZoom||this.options.minZoom!==i&&s1)return void this._setView(t,s);for(var m=a.min.y;m<=a.max.y;m++)for(var p=a.min.x;p<=a.max.x;p++){var f=new n.Point(p,m);if(f.z=this._tileZoom,this._isValidTile(f)){var g=this._tiles[this._tileCoordsToKey(f)];g?g.current=!0:l.push(f)}}if(l.sort(function(t,e){return t.distanceTo(h)-e.distanceTo(h)}),0!==l.length){this._loading||(this._loading=!0,this.fire("loading"));var v=e.createDocumentFragment();for(p=0;pi.max.x)||!e.wrapLat&&(t.yi.max.y))return!1}if(!this.options.bounds)return!0;var o=this._tileCoordsToBounds(t);return n.latLngBounds(this.options.bounds).overlaps(o)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToBounds:function(t){var e=this._map,i=this.getTileSize(),o=t.scaleBy(i),s=o.add(i),r=e.unproject(o,t.z),a=e.unproject(s,t.z),h=new n.LatLngBounds(r,a);return this.options.noWrap||e.wrapLatLngBounds(h),h},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var e=t.split(":"),i=new n.Point(+e[0],+e[1]);return i.z=+e[2],i},_removeTile:function(t){var e=this._tiles[t];e&&(n.DomUtil.remove(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){n.DomUtil.addClass(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=n.Util.falseFn,t.onmousemove=n.Util.falseFn,n.Browser.ielt9&&this.options.opacity<1&&n.DomUtil.setOpacity(t,this.options.opacity),n.Browser.android&&!n.Browser.android23&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,e){var i=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),n.bind(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&n.Util.requestAnimFrame(n.bind(this._tileReady,this,t,null,s)),n.DomUtil.setPosition(s,i),this._tiles[o]={el:s,coords:t,current:!0},e.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,e,i){if(this._map){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var o=this._tileCoordsToKey(t);(i=this._tiles[o])&&(i.loaded=+new Date,this._map._fadeAnimated?(n.DomUtil.setOpacity(i.el,0),n.Util.cancelAnimFrame(this._fadeFrame),this._fadeFrame=n.Util.requestAnimFrame(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(n.DomUtil.addClass(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),n.Browser.ielt9||!this._map._fadeAnimated?n.Util.requestAnimFrame(this._pruneTiles,this):setTimeout(n.bind(this._pruneTiles,this),250)))}},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new n.Point(this._wrapX?n.Util.wrapNum(t.x,this._wrapX):t.x,this._wrapY?n.Util.wrapNum(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new n.Bounds(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),n.gridLayer=function(t){return new n.GridLayer(t)},n.TileLayer=n.GridLayer.extend({options:{minZoom:0,maxZoom:18,maxNativeZoom:null,minNativeZoom:null,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,e){this._url=t,e=n.setOptions(this,e),e.detectRetina&&n.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomReverse?(e.zoomOffset--,e.minZoom++):(e.zoomOffset++,e.maxZoom--),e.minZoom=Math.max(0,e.minZoom)),"string"==typeof e.subdomains&&(e.subdomains=e.subdomains.split("")),n.Browser.android||this.on("tileunload",this._onTileRemove)},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},createTile:function(t,i){var o=e.createElement("img");return n.DomEvent.on(o,"load",n.bind(this._tileOnLoad,this,i,o)),n.DomEvent.on(o,"error",n.bind(this._tileOnError,this,i,o)),this.options.crossOrigin&&(o.crossOrigin=""),o.alt="",o.setAttribute("role","presentation"),o.src=this.getTileUrl(t),o},getTileUrl:function(t){var e={r:n.Browser.retina?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var i=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=i),e["-y"]=i}return n.Util.template(this._url,n.extend(e,this.options))},_tileOnLoad:function(t,e){n.Browser.ielt9?setTimeout(n.bind(t,this,null,e),0):t(null,e)},_tileOnError:function(t,e,i){var n=this.options.errorTileUrl;n&&e.src!==n&&(e.src=n),t(i,e)},getTileSize:function(){var t=this._map,e=n.GridLayer.prototype.getTileSize.call(this),i=this._tileZoom+this.options.zoomOffset,o=this.options.minNativeZoom,s=this.options.maxNativeZoom;return null!==o&&is?e.divideBy(t.getZoomScale(s,i)).round():e},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,e=this.options.maxZoom,i=this.options.zoomReverse,n=this.options.zoomOffset,o=this.options.minNativeZoom,s=this.options.maxNativeZoom;return i&&(t=e-t),t+=n,null!==o&&ts?s:t},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_abortLoading:function(){var t,e;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&(e=this._tiles[t].el,e.onload=n.Util.falseFn,e.onerror=n.Util.falseFn,e.complete||(e.src=n.Util.emptyImageUrl,n.DomUtil.remove(e)))}}),n.tileLayer=function(t,e){return new n.TileLayer(t,e)},n.TileLayer.WMS=n.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var i=n.extend({},this.defaultWmsParams);for(var o in e)o in this.options||(i[o]=e[o]);e=n.setOptions(this,e),i.width=i.height=e.tileSize*(e.detectRetina&&n.Browser.retina?2:1),this.wmsParams=i},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,n.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._tileCoordsToBounds(t),i=this._crs.project(e.getNorthWest()),o=this._crs.project(e.getSouthEast()),s=(this._wmsVersion>=1.3&&this._crs===n.CRS.EPSG4326?[o.y,i.x,i.y,o.x]:[i.x,o.y,o.x,i.y]).join(","),r=n.TileLayer.prototype.getTileUrl.call(this,t);return r+n.Util.getParamString(this.wmsParams,r,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+s},setParams:function(t,e){return n.extend(this.wmsParams,t),e||this.redraw(),this}}),n.tileLayer.wms=function(t,e){return new n.TileLayer.WMS(t,e)},n.ImageOverlay=n.Layer.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1},initialize:function(t,e,i){this._url=t,this._bounds=n.latLngBounds(e),n.setOptions(this,i)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(n.DomUtil.addClass(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){n.DomUtil.remove(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&n.DomUtil.toFront(this._image),this},bringToBack:function(){return this._map&&n.DomUtil.toBack(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=t,this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t=this._image=n.DomUtil.create("img","leaflet-image-layer "+(this._zoomAnimated?"leaflet-zoom-animated":""));t.onselectstart=n.Util.falseFn,t.onmousemove=n.Util.falseFn,t.onload=n.bind(this.fire,this,"load"),this.options.crossOrigin&&(t.crossOrigin=""),t.src=this._url,t.alt=this.options.alt},_animateZoom:function(t){ var e=this._map.getZoomScale(t.zoom),i=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;n.DomUtil.setTransform(this._image,i,e)},_reset:function(){var t=this._image,e=new n.Bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),i=e.getSize();n.DomUtil.setPosition(t,e.min),t.style.width=i.x+"px",t.style.height=i.y+"px"},_updateOpacity:function(){n.DomUtil.setOpacity(this._image,this.options.opacity)}}),n.imageOverlay=function(t,e,i){return new n.ImageOverlay(t,e,i)},n.Icon=n.Class.extend({initialize:function(t){n.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(i,e&&"IMG"===e.tagName?e:null);return this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i=this.options,o=i[e+"Size"];"number"==typeof o&&(o=[o,o]);var s=n.point(o),r=n.point("shadow"===e&&i.shadowAnchor||i.iconAnchor||s&&s.divideBy(2,!0));t.className="leaflet-marker-"+e+" "+(i.className||""),r&&(t.style.marginLeft=-r.x+"px",t.style.marginTop=-r.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return i=i||e.createElement("img"),i.src=t,i},_getIconUrl:function(t){return n.Browser.retina&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}}),n.icon=function(t){return new n.Icon(t)},n.Icon.Default=n.Icon.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return n.Icon.Default.imagePath||(n.Icon.Default.imagePath=this._detectIconPath()),(this.options.imagePath||n.Icon.Default.imagePath)+n.Icon.prototype._getIconUrl.call(this,t)},_detectIconPath:function(){var t=n.DomUtil.create("div","leaflet-default-icon-path",e.body),i=n.DomUtil.getStyle(t,"background-image")||n.DomUtil.getStyle(t,"backgroundImage");return e.body.removeChild(t),0===i.indexOf("url")?i.replace(/^url\([\"\']?/,"").replace(/marker-icon\.png[\"\']?\)$/,""):""}}),n.Marker=n.Layer.extend({options:{icon:new n.Icon.Default,interactive:!0,draggable:!1,keyboard:!0,title:"",alt:"",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",nonBubblingEvents:["click","dblclick","mouseover","mouseout","contextmenu"]},initialize:function(t,e){n.setOptions(this,e),this._latlng=n.latLng(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var e=this._latlng;return this._latlng=n.latLng(t),this.update(),this.fire("move",{oldLatLng:e,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),i=t.icon.createIcon(this._icon),o=!1;i!==this._icon&&(this._icon&&this._removeIcon(),o=!0,t.title&&(i.title=t.title),t.alt&&(i.alt=t.alt)),n.DomUtil.addClass(i,e),t.keyboard&&(i.tabIndex="0"),this._icon=i,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex});var s=t.icon.createShadow(this._shadow),r=!1;s!==this._shadow&&(this._removeShadow(),r=!0),s&&(n.DomUtil.addClass(s,e),s.alt=""),this._shadow=s,t.opacity<1&&this._updateOpacity(),o&&this.getPane().appendChild(this._icon),this._initInteraction(),s&&r&&this.getPane("shadowPane").appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),n.DomUtil.remove(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&n.DomUtil.remove(this._shadow),this._shadow=null},_setPos:function(t){n.DomUtil.setPosition(this._icon,t),this._shadow&&n.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(e)},_initInteraction:function(){if(this.options.interactive&&(n.DomUtil.addClass(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),n.Handler.MarkerDrag)){var t=this.options.draggable;this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new n.Handler.MarkerDrag(this),t&&this.dragging.enable()}},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;n.DomUtil.setOpacity(this._icon,t),this._shadow&&n.DomUtil.setOpacity(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor||[0,0]},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor||[0,0]}}),n.marker=function(t,e){return new n.Marker(t,e)},n.DivIcon=n.Icon.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:e.createElement("div"),o=this.options;if(i.innerHTML=o.html!==!1?o.html:"",o.bgPos){var s=n.point(o.bgPos);i.style.backgroundPosition=-s.x+"px "+-s.y+"px"}return this._setIconStyles(i,"icon"),i},createShadow:function(){return null}}),n.divIcon=function(t){return new n.DivIcon(t)},n.DivOverlay=n.Layer.extend({options:{offset:[0,7],className:"",pane:"popupPane"},initialize:function(t,e){n.setOptions(this,t),this._source=e},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&n.DomUtil.setOpacity(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&n.DomUtil.setOpacity(this._container,1),this.bringToFront()},onRemove:function(t){t._fadeAnimated?(n.DomUtil.setOpacity(this._container,0),this._removeTimeout=setTimeout(n.bind(n.DomUtil.remove,n.DomUtil,this._container),200)):n.DomUtil.remove(this._container)},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=n.latLng(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&n.DomUtil.toFront(this._container),this},bringToBack:function(){return this._map&&n.DomUtil.toBack(this._container),this},_updateContent:function(){if(this._content){var t=this._contentNode,e="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof e)t.innerHTML=e;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(e)}this.fire("contentupdate")}},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=n.point(this.options.offset),i=this._getAnchor();this._zoomAnimated?n.DomUtil.setPosition(this._container,t.add(i)):e=e.add(t).add(i);var o=this._containerBottom=-e.y,s=this._containerLeft=-Math.round(this._containerWidth/2)+e.x;this._container.style.bottom=o+"px",this._container.style.left=s+"px"}},_getAnchor:function(){return[0,0]}}),n.Popup=n.DivOverlay.extend({options:{maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,className:""},openOn:function(t){return t.openPopup(this),this},onAdd:function(t){n.DivOverlay.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof n.Path||this._source.on("preclick",n.DomEvent.stopPropagation))},onRemove:function(t){n.DivOverlay.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof n.Path||this._source.off("preclick",n.DomEvent.stopPropagation))},getEvents:function(){var t=n.DivOverlay.prototype.getEvents.call(this);return("closeOnClick"in this.options?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t="leaflet-popup",e=this._container=n.DomUtil.create("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated");if(this.options.closeButton){var i=this._closeButton=n.DomUtil.create("a",t+"-close-button",e);i.href="#close",i.innerHTML="×",n.DomEvent.on(i,"click",this._onCloseButtonClick,this)}var o=this._wrapper=n.DomUtil.create("div",t+"-content-wrapper",e);this._contentNode=n.DomUtil.create("div",t+"-content",o),n.DomEvent.disableClickPropagation(o).disableScrollPropagation(this._contentNode).on(o,"contextmenu",n.DomEvent.stopPropagation),this._tipContainer=n.DomUtil.create("div",t+"-tip-container",e),this._tip=n.DomUtil.create("div",t+"-tip",this._tipContainer)},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var o=t.offsetHeight,s=this.options.maxHeight,r="leaflet-popup-scrolled";s&&o>s?(e.height=s+"px",n.DomUtil.addClass(t,r)):n.DomUtil.removeClass(t,r),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),i=this._getAnchor();n.DomUtil.setPosition(this._container,e.add(i))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,e=parseInt(n.DomUtil.getStyle(this._container,"marginBottom"),10)||0,i=this._container.offsetHeight+e,o=this._containerWidth,s=new n.Point(this._containerLeft,-i-this._containerBottom);s._add(n.DomUtil.getPosition(this._container));var r=t.layerPointToContainerPoint(s),a=n.point(this.options.autoPanPadding),h=n.point(this.options.autoPanPaddingTopLeft||a),l=n.point(this.options.autoPanPaddingBottomRight||a),u=t.getSize(),c=0,d=0;r.x+o+l.x>u.x&&(c=r.x+o-u.x+l.x),r.x-c-h.x<0&&(c=r.x-h.x),r.y+i+l.y>u.y&&(d=r.y+i-u.y+l.y),r.y-d-h.y<0&&(d=r.y-h.y),(c||d)&&t.fire("autopanstart").panBy([c,d])}},_onCloseButtonClick:function(t){this._close(),n.DomEvent.stop(t)},_getAnchor:function(){return n.point(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}}),n.popup=function(t,e){return new n.Popup(t,e)},n.Map.mergeOptions({closePopupOnClick:!0}),n.Map.include({openPopup:function(t,e,i){return t instanceof n.Popup||(t=new n.Popup(i).setContent(t)),e&&t.setLatLng(e),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),n.Layer.include({bindPopup:function(t,e){return t instanceof n.Popup?(n.setOptions(t,e),this._popup=t,t._source=this):(this._popup&&!e||(this._popup=new n.Popup(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,e){if(t instanceof n.Layer||(e=t,t=this),t instanceof n.FeatureGroup)for(var i in this._layers){t=this._layers[i];break}return e||(e=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,e)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e=t.layer||t.target;if(this._popup&&this._map)return n.DomEvent.stop(t),e instanceof n.Path?void this.openPopup(t.layer||t.target,t.latlng):void(this._map.hasLayer(this._popup)&&this._popup._source===e?this.closePopup():this.openPopup(e,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),n.Tooltip=n.DivOverlay.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){n.DivOverlay.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){n.DivOverlay.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=n.DivOverlay.prototype.getEvents.call(this);return n.Browser.touch&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=n.DomUtil.create("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e=this._map,i=this._container,o=e.latLngToContainerPoint(e.getCenter()),s=e.layerPointToContainerPoint(t),r=this.options.direction,a=i.offsetWidth,h=i.offsetHeight,l=n.point(this.options.offset),u=this._getAnchor();"top"===r?t=t.add(n.point(-a/2+l.x,-h+l.y+u.y,!0)):"bottom"===r?t=t.subtract(n.point(a/2-l.x,-l.y,!0)):"center"===r?t=t.subtract(n.point(a/2+l.x,h/2-u.y+l.y,!0)):"right"===r||"auto"===r&&s.xh&&(s=r,h=a);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;ne&&(i.push(t[n]),o=n);return oe.max.x&&(i|=2),t.ye.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,o){var s,r=e.x,a=e.y,h=i.x-r,l=i.y-a,u=h*h+l*l;return u>0&&(s=((t.x-r)*h+(t.y-a)*l)/u,s>1?(r=i.x,a=i.y):s>0&&(r+=h*s,a+=l*s)),h=t.x-r,l=t.y-a,o?h*h+l*l:new n.Point(r,a)}},n.Polyline=n.Path.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,e){n.setOptions(this,e),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var e,i,o=1/0,s=null,r=n.LineUtil._sqClosestPointOnSegment,a=0,h=this._parts.length;ae)return r=(n-e)/i,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,e){return e=e||this._defaultShape(),t=n.latLng(t),e.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new n.LatLngBounds,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return n.Polyline._flat(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var e=[],i=n.Polyline._flat(t),o=0,s=t.length;o=2&&e[0]instanceof n.LatLng&&e[0].equals(e[i-1])&&e.pop(),e},_setLatLngs:function(t){n.Polyline.prototype._setLatLngs.call(this,t),n.Polyline._flat(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return n.Polyline._flat(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,e=this.options.weight,i=new n.Point(e,e);if(t=new n.Bounds(t.min.subtract(i),t.max.add(i)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t)){if(this.options.noClip)return void(this._parts=this._rings);for(var o,s=0,r=this._rings.length;s';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),n.SVG.include(n.Browser.vml?{_initContainer:function(){this._container=n.DomUtil.create("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(n.Renderer.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=n.SVG.create("shape");n.DomUtil.addClass(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=n.SVG.create("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[n.stamp(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;n.DomUtil.remove(e),t.removeInteractiveTarget(e),delete this._layers[n.stamp(t)]},_updateStyle:function(t){var e=t._stroke,i=t._fill,o=t.options,s=t._container;s.stroked=!!o.stroke,s.filled=!!o.fill,o.stroke?(e||(e=t._stroke=n.SVG.create("stroke")),s.appendChild(e),e.weight=o.weight+"px",e.color=o.color,e.opacity=o.opacity,o.dashArray?e.dashStyle=n.Util.isArray(o.dashArray)?o.dashArray.join(" "):o.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=o.lineCap.replace("butt","flat"),e.joinstyle=o.lineJoin):e&&(s.removeChild(e),t._stroke=null),o.fill?(i||(i=t._fill=n.SVG.create("fill")),s.appendChild(i),i.color=o.fillColor||o.color,i.opacity=o.fillOpacity):i&&(s.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){n.DomUtil.toFront(t._container)},_bringToBack:function(t){n.DomUtil.toBack(t._container)}}:{}),n.Browser.vml&&(n.SVG.create=function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}()),n.Canvas=n.Renderer.extend({getEvents:function(){var t=n.Renderer.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){n.Renderer.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=e.createElement("canvas");n.DomEvent.on(t,"mousemove",n.Util.throttle(this._onMouseMove,32,this),this).on(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this).on(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_updatePaths:function(){if(!this._postponeUpdatePaths){var t;this._redrawBounds=null;for(var e in this._layers)t=this._layers[e],t._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},n.Renderer.prototype._update.call(this);var t=this._bounds,e=this._container,i=t.getSize(),o=n.Browser.retina?2:1;n.DomUtil.setPosition(e,t.min),e.width=o*i.x,e.height=o*i.y,e.style.width=i.x+"px",e.style.height=i.y+"px",n.Browser.retina&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){n.Renderer.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n.stamp(t)]=t;var e=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=e),this._drawLast=e,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var e=t._order,i=e.next,o=e.prev;i?i.prev=o:this._drawLast=o,o?o.next=i:this._drawFirst=i,delete t._order,delete this._layers[n.stamp(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if(t.options.dashArray){var e,i=t.options.dashArray.split(","),n=[];for(e=0;et.y!=o.y>t.y&&t.x<(o.x-i.x)*(t.y-i.y)/(o.y-i.y)+i.x&&(u=!u);return u||n.Polyline.prototype._containsPoint.call(this,t,!0)},n.CircleMarker.prototype._containsPoint=function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()},n.GeoJSON=n.FeatureGroup.extend({initialize:function(t,e){n.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,o,s=n.Util.isArray(t)?t:t.features;if(s){for(e=0,i=s.length;e1)return void(this._moved=!0);var o=i.touches&&1===i.touches.length?i.touches[0]:i,s=new n.Point(o.clientX,o.clientY),r=s.subtract(this._startPoint);(r.x||r.y)&&(Math.abs(r.x)+Math.abs(r.y)50&&(this._positions.shift(),this._times.shift())}this._map.fire("move",t).fire("drag",t)},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,e){return t-(t-e)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),e=this._offsetLimit;t.xe.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,r=Math.abs(o+i)0?s:-s))-e;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(e+r):t.setZoomAround(this._lastMousePos,e+r))}}),n.Map.addInitHook("addHandler","scrollWheelZoom",n.Map.ScrollWheelZoom),n.extend(n.DomEvent,{_touchstart:n.Browser.msPointer?"MSPointerDown":n.Browser.pointer?"pointerdown":"touchstart",_touchend:n.Browser.msPointer?"MSPointerUp":n.Browser.pointer?"pointerup":"touchend",addDoubleTapListener:function(t,e,i){function o(t){var e;if(n.Browser.pointer){if(!n.Browser.edge||"mouse"===t.pointerType)return;e=n.DomEvent._pointersCount}else e=t.touches.length;if(!(e>1)){var i=Date.now(),o=i-(r||i);a=t.touches?t.touches[0]:t,h=o>0&&o<=l,r=i}}function s(t){if(h&&!a.cancelBubble){if(n.Browser.pointer){if(!n.Browser.edge||"mouse"===t.pointerType)return;var i,o,s={};for(o in a)i=a[o],s[o]=i&&i.bind?i.bind(a):i;a=s}a.type="dblclick",e(a),r=null}}var r,a,h=!1,l=250,u="_leaflet_",c=this._touchstart,d=this._touchend;return t[u+c+i]=o,t[u+d+i]=s,t[u+"dblclick"+i]=e,t.addEventListener(c,o,!1),t.addEventListener(d,s,!1),t.addEventListener("dblclick",e,!1),this},removeDoubleTapListener:function(t,e){var i="_leaflet_",o=t[i+this._touchstart+e],s=t[i+this._touchend+e],r=t[i+"dblclick"+e];return t.removeEventListener(this._touchstart,o,!1),t.removeEventListener(this._touchend,s,!1),n.Browser.edge||t.removeEventListener("dblclick",r,!1),this}}),n.extend(n.DomEvent,{POINTER_DOWN:n.Browser.msPointer?"MSPointerDown":"pointerdown",POINTER_MOVE:n.Browser.msPointer?"MSPointerMove":"pointermove",POINTER_UP:n.Browser.msPointer?"MSPointerUp":"pointerup",POINTER_CANCEL:n.Browser.msPointer?"MSPointerCancel":"pointercancel",TAG_WHITE_LIST:["INPUT","SELECT","OPTION"],_pointers:{},_pointersCount:0,addPointerListener:function(t,e,i,n){return"touchstart"===e?this._addPointerStart(t,i,n):"touchmove"===e?this._addPointerMove(t,i,n):"touchend"===e&&this._addPointerEnd(t,i,n),this},removePointerListener:function(t,e,i){var n=t["_leaflet_"+e+i];return"touchstart"===e?t.removeEventListener(this.POINTER_DOWN,n,!1):"touchmove"===e?t.removeEventListener(this.POINTER_MOVE,n,!1):"touchend"===e&&(t.removeEventListener(this.POINTER_UP,n,!1),t.removeEventListener(this.POINTER_CANCEL,n,!1)),this},_addPointerStart:function(t,i,o){var s=n.bind(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(this.TAG_WHITE_LIST.indexOf(t.target.tagName)<0))return;n.DomEvent.preventDefault(t)}this._handlePointer(t,i)},this);if(t["_leaflet_touchstart"+o]=s,t.addEventListener(this.POINTER_DOWN,s,!1),!this._pointerDocListener){var r=n.bind(this._globalPointerUp,this);e.documentElement.addEventListener(this.POINTER_DOWN,n.bind(this._globalPointerDown,this),!0),e.documentElement.addEventListener(this.POINTER_MOVE,n.bind(this._globalPointerMove,this),!0),e.documentElement.addEventListener(this.POINTER_UP,r,!0),e.documentElement.addEventListener(this.POINTER_CANCEL,r,!0),this._pointerDocListener=!0}},_globalPointerDown:function(t){this._pointers[t.pointerId]=t,this._pointersCount++},_globalPointerMove:function(t){this._pointers[t.pointerId]&&(this._pointers[t.pointerId]=t)},_globalPointerUp:function(t){delete this._pointers[t.pointerId],this._pointersCount--},_handlePointer:function(t,e){t.touches=[];for(var i in this._pointers)t.touches.push(this._pointers[i]);t.changedTouches=[t],e(t)},_addPointerMove:function(t,e,i){var o=n.bind(function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&this._handlePointer(t,e)},this);t["_leaflet_touchmove"+i]=o,t.addEventListener(this.POINTER_MOVE,o,!1)},_addPointerEnd:function(t,e,i){var o=n.bind(function(t){this._handlePointer(t,e)},this);t["_leaflet_touchend"+i]=o,t.addEventListener(this.POINTER_UP,o,!1),t.addEventListener(this.POINTER_CANCEL,o,!1)}}),n.Map.mergeOptions({touchZoom:n.Browser.touch&&!n.Browser.android23,bounceAtZoomLimits:!0}),n.Map.TouchZoom=n.Handler.extend({addHooks:function(){n.DomUtil.addClass(this._map._container,"leaflet-touch-zoom"),n.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){n.DomUtil.removeClass(this._map._container,"leaflet-touch-zoom"),n.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var o=i.mouseEventToContainerPoint(t.touches[0]),s=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(o.add(s)._divideBy(2))),this._startDist=o.distanceTo(s),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),n.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),n.DomEvent.preventDefault(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var e=this._map,i=e.mouseEventToContainerPoint(t.touches[0]),o=e.mouseEventToContainerPoint(t.touches[1]),s=i.distanceTo(o)/this._startDist;if(this._zoom=e.getScaleZoom(s,this._startZoom),!e.options.bounceAtZoomLimits&&(this._zoome.getMaxZoom()&&s>1)&&(this._zoom=e._limitZoom(this._zoom)),"center"===e.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=i._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=e.unproject(e.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(e._moveStart(!0),this._moved=!0),n.Util.cancelAnimFrame(this._animRequest);var a=n.bind(e._move,e,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=n.Util.requestAnimFrame(a,this,!0),n.DomEvent.preventDefault(t)}},_onTouchEnd:function(){return this._moved&&this._zooming?(this._zooming=!1,n.Util.cancelAnimFrame(this._animRequest),n.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd),void(this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom)))):void(this._zooming=!1)}}),n.Map.addInitHook("addHandler","touchZoom",n.Map.TouchZoom),n.Map.mergeOptions({tap:!0,tapTolerance:15}),n.Map.Tap=n.Handler.extend({addHooks:function(){n.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){n.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(n.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],o=i.target;this._startPos=this._newPos=new n.Point(i.clientX,i.clientY),o.tagName&&"a"===o.tagName.toLowerCase()&&n.DomUtil.addClass(o,"leaflet-active"),this._holdTimeout=setTimeout(n.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),n.DomEvent.on(e,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),n.DomEvent.off(e,{touchmove:this._onMove,touchend:this._onUp},this), -this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],o=i.target;o&&o.tagName&&"a"===o.tagName.toLowerCase()&&n.DomUtil.removeClass(o,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new n.Point(e.clientX,e.clientY),this._simulateEvent("mousemove",e)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),n.Browser.touch&&!n.Browser.pointer&&n.Map.addInitHook("addHandler","tap",n.Map.Tap),n.Map.mergeOptions({boxZoom:!0}),n.Map.BoxZoom=n.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){n.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){n.DomEvent.off(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_resetState:function(){this._moved=!1},_onMouseDown:function(t){return!(!t.shiftKey||1!==t.which&&1!==t.button)&&(this._resetState(),n.DomUtil.disableTextSelection(),n.DomUtil.disableImageDrag(),this._startPoint=this._map.mouseEventToContainerPoint(t),void n.DomEvent.on(e,{contextmenu:n.DomEvent.stop,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this))},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=n.DomUtil.create("div","leaflet-zoom-box",this._container),n.DomUtil.addClass(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var e=new n.Bounds(this._point,this._startPoint),i=e.getSize();n.DomUtil.setPosition(this._box,e.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(n.DomUtil.remove(this._box),n.DomUtil.removeClass(this._container,"leaflet-crosshair")),n.DomUtil.enableTextSelection(),n.DomUtil.enableImageDrag(),n.DomEvent.off(e,{contextmenu:n.DomEvent.stop,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){setTimeout(n.bind(this._resetState,this),0);var e=new n.LatLngBounds(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(e).fire("boxzoomend",{boxZoomBounds:e})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),n.Map.addInitHook("addHandler","boxZoom",n.Map.BoxZoom),n.Map.mergeOptions({keyboard:!0,keyboardPanDelta:80}),n.Map.Keyboard=n.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),n.DomEvent.on(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),n.DomEvent.off(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;e0&&t.screenY>0&&this._map.getContainer().focus()}}),n.control=function(t){return new n.Control(t)},n.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,s){var r=i+t+" "+i+s;e[t+s]=n.DomUtil.create("div",r,o)}var e=this._controlCorners={},i="leaflet-",o=this._controlContainer=n.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){n.DomUtil.remove(this._controlContainer)}}),n.Control.Zoom=n.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=n.DomUtil.create("div",e+" leaflet-bar"),o=this.options;return this._zoomInButton=this._createButton(o.zoomInText,o.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(o.zoomOutText,o.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,o,s){var r=n.DomUtil.create("a",i,o);return r.innerHTML=t,r.href="#",r.title=e,r.setAttribute("role","button"),r.setAttribute("aria-label",e),n.DomEvent.on(r,"mousedown dblclick",n.DomEvent.stopPropagation).on(r,"click",n.DomEvent.stop).on(r,"click",s,this).on(r,"click",this._refocusOnMap,this),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";n.DomUtil.removeClass(this._zoomInButton,e),n.DomUtil.removeClass(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMinZoom())&&n.DomUtil.addClass(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMaxZoom())&&n.DomUtil.addClass(this._zoomInButton,e)}}),n.Map.mergeOptions({zoomControl:!0}),n.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new n.Control.Zoom,this.addControl(this.zoomControl))}),n.control.zoom=function(t){return new n.Control.Zoom(t)},n.Control.Attribution=n.Control.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){n.setOptions(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=n.DomUtil.create("div","leaflet-control-attribution"),n.DomEvent&&n.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}}}),n.Map.mergeOptions({attributionControl:!0}),n.Map.addInitHook(function(){this.options.attributionControl&&(new n.Control.Attribution).addTo(this)}),n.control.attribution=function(t){return new n.Control.Attribution(t)},n.Control.Scale=n.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=n.DomUtil.create("div",e),o=this.options;return this._addScales(o,e+"-line",i),t.on(o.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=n.DomUtil.create("div",e,i)),t.imperial&&(this._iScale=n.DomUtil.create("div",e,i))},_update:function(){var t=this._map,e=t.getSize().y/2,i=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(i)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t),i=e<1e3?e+" m":e/1e3+" km";this._updateScale(this._mScale,i,e/t)},_updateImperial:function(t){var e,i,n,o=3.2808399*t;o>5280?(e=o/5280,i=this._getRoundNum(e),this._updateScale(this._iScale,i+" mi",i/e)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,e,i){t.style.width=Math.round(this.options.maxWidth*i)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),n.control.scale=function(t){return new n.Control.Scale(t)},n.Control.Layers=n.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,e,i,n){return i1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=e&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var e=this._getLayer(n.stamp(t.target)),i=e.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)},_createRadioElement:function(t,i){var n='",o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,o=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=n.stamp(t.layer),n.DomEvent.on(i,"click",this._onInputClick,this);var r=e.createElement("span");r.innerHTML=" "+t.name;var a=e.createElement("div");return o.appendChild(a),a.appendChild(i),a.appendChild(r),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(o),this._checkDisabledLayers(),o},_onInputClick:function(){var t,e,i,n=this._form.getElementsByTagName("input"),o=[],s=[];this._handlingClick=!0;for(var r=n.length-1;r>=0;r--)t=n[r],e=this._getLayer(t.layerId).layer,i=this._map.hasLayer(e),t.checked&&!i?o.push(e):!t.checked&&i&&s.push(e);for(r=0;r=0;s--)t=n[s],e=this._getLayer(t.layerId).layer,t.disabled=e.options.minZoom!==i&&oe.options.maxZoom},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),n.control.layers=function(t,e,i){return new n.Control.Layers(t,e,i)}}(window,document)