diff --git a/.coveragerc b/.coveragerc index 1a62d7e60f3..506e51a63d8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -13,6 +13,9 @@ omit = homeassistant/components/arduino.py homeassistant/components/*/arduino.py + homeassistant/components/bbb_gpio.py + homeassistant/components/*/bbb_gpio.py + homeassistant/components/bloomsky.py homeassistant/components/*/bloomsky.py @@ -34,6 +37,9 @@ omit = homeassistant/components/insteon_hub.py homeassistant/components/*/insteon_hub.py + homeassistant/components/insteon_local.py + homeassistant/components/*/insteon_local.py + homeassistant/components/ios.py homeassistant/components/*/ios.py @@ -157,13 +163,16 @@ omit = homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py + homeassistant/components/device_tracker/ping.py homeassistant/components/device_tracker/snmp.py homeassistant/components/device_tracker/swisscom.py homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/tplink.py + homeassistant/components/device_tracker/trackr.py homeassistant/components/device_tracker/ubus.py homeassistant/components/device_tracker/volvooncall.py + homeassistant/components/device_tracker/xiaomi.py homeassistant/components/discovery.py homeassistant/components/downloader.py homeassistant/components/emoncms_history.py @@ -183,8 +192,10 @@ omit = homeassistant/components/light/lifx.py homeassistant/components/light/limitlessled.py homeassistant/components/light/osramlightify.py + homeassistant/components/light/tikteck.py homeassistant/components/light/x10.py homeassistant/components/light/yeelight.py + homeassistant/components/light/zengge.py homeassistant/components/lirc.py homeassistant/components/media_player/aquostv.py homeassistant/components/media_player/braviatv.py @@ -202,6 +213,7 @@ omit = homeassistant/components/media_player/lg_netcast.py homeassistant/components/media_player/mpchc.py homeassistant/components/media_player/mpd.py + homeassistant/components/media_player/nad.py homeassistant/components/media_player/onkyo.py homeassistant/components/media_player/panasonic_viera.py homeassistant/components/media_player/pandora.py @@ -219,12 +231,14 @@ omit = homeassistant/components/notify/aws_lambda.py homeassistant/components/notify/aws_sns.py homeassistant/components/notify/aws_sqs.py + homeassistant/components/notify/facebook.py homeassistant/components/notify/free_mobile.py homeassistant/components/notify/gntp.py homeassistant/components/notify/group.py homeassistant/components/notify/instapush.py homeassistant/components/notify/joaoapps_join.py homeassistant/components/notify/kodi.py + homeassistant/components/notify/lannouncer.py homeassistant/components/notify/llamalab_automate.py homeassistant/components/notify/matrix.py homeassistant/components/notify/message_bird.py @@ -254,6 +268,7 @@ omit = homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bom.py homeassistant/components/sensor/broadlink.py + homeassistant/components/sensor/dublin_bus_transport.py homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/cups.py @@ -277,6 +292,8 @@ omit = homeassistant/components/sensor/haveibeenpwned.py homeassistant/components/sensor/hddtemp.py homeassistant/components/sensor/hp_ilo.py + homeassistant/components/sensor/hydroquebec.py + homeassistant/components/sensor/iss.py homeassistant/components/sensor/imap.py homeassistant/components/sensor/imap_email_content.py homeassistant/components/sensor/influxdb.py @@ -301,6 +318,7 @@ omit = homeassistant/components/sensor/scrape.py homeassistant/components/sensor/sensehat.py homeassistant/components/sensor/serial_pm.py + homeassistant/components/sensor/sma.py homeassistant/components/sensor/snmp.py homeassistant/components/sensor/sonarr.py homeassistant/components/sensor/speedtest.py @@ -317,6 +335,7 @@ omit = homeassistant/components/sensor/transmission.py homeassistant/components/sensor/twitch.py homeassistant/components/sensor/uber.py + homeassistant/components/sensor/usps.py homeassistant/components/sensor/vasttrafik.py homeassistant/components/sensor/waqi.py homeassistant/components/sensor/xbox_live.py @@ -331,6 +350,7 @@ omit = homeassistant/components/switch/edimax.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/hook.py + homeassistant/components/switch/kankun.py homeassistant/components/switch/mystrom.py homeassistant/components/switch/netio.py homeassistant/components/switch/orvibo.py @@ -342,7 +362,9 @@ omit = homeassistant/components/switch/transmission.py homeassistant/components/switch/wake_on_lan.py homeassistant/components/thingspeak.py + homeassistant/components/tts/picotts.py homeassistant/components/upnp.py + homeassistant/components/weather/bom.py homeassistant/components/weather/openweathermap.py homeassistant/components/zeroconf.py diff --git a/.travis.yml b/.travis.yml index 9cf13f2c831..f4c696a2236 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ matrix: env: TOXENV=typing - python: "3.5" env: TOXENV=py35 + - python: "3.6" + env: TOXENV=py36 allow_failures: - python: "3.5" env: TOXENV=typing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a63c1400723..9a3238e665d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot The process is straight-forward. - - Read [How to get faster PR reviews](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/faster_reviews.md) by Kubernetes (but skip step 0) + - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0) - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant). - Write the code for your device, notification service, sensor, or IoT thing. - Ensure tests work. diff --git a/Dockerfile b/Dockerfile index 634edb8af59..7522ca9cb64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,24 +6,14 @@ VOLUME /config RUN mkdir -p /usr/src/app WORKDIR /usr/src/app -RUN pip3 install --no-cache-dir colorlog cython - -# For the nmap tracker, bluetooth tracker, Z-Wave, tellstick -RUN echo "deb http://download.telldus.com/debian/ stable main" >> /etc/apt/sources.list.d/telldus.list && \ - wget -qO - http://download.telldus.se/debian/telldus-public.key | apt-key add - && \ - apt-get update && \ - apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev \ - libtelldus-core2 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -COPY script/build_python_openzwave script/build_python_openzwave -RUN script/build_python_openzwave && \ - mkdir -p /usr/local/share/python-openzwave && \ - ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config +# Copy build scripts +COPY script/setup_docker_prereqs script/build_python_openzwave script/build_libcec script/ +RUN script/setup_docker_prereqs +# Install hass component dependencies COPY requirements_all.txt requirements_all.txt RUN pip3 install --no-cache-dir -r requirements_all.txt && \ - pip3 install mysqlclient psycopg2 uvloop + pip3 install --no-cache-dir mysqlclient psycopg2 uvloop # Copy source COPY . . diff --git a/README.rst b/README.rst index 43517760ed7..1e25b6dcc90 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,8 @@ Examples of devices Home Assistant can interface with: `Netgear `__, `DD-WRT `__, `TPLink `__, - `ASUSWRT `__ and any SNMP + `ASUSWRT `__, + `Xiaomi `__ and any SNMP capable Linksys WAP/WRT - `Philips Hue `__ lights, `WeMo `__ diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 510b98b6bdd..d9ab410b745 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -356,7 +356,8 @@ def try_to_restart() -> None: def main() -> int: """Start Home Assistant.""" - monkey_patch_asyncio() + if sys.version_info[:3] < (3, 5, 3): + monkey_patch_asyncio() validate_python() diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3d03336c0fe..da7886ad1e8 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -395,6 +395,10 @@ def async_from_config_dict(config: Dict[str, Any], if not loader.PREPARED: yield from hass.loop.run_in_executor(None, loader.prepare, hass) + # Merge packages + conf_util.merge_packages_config( + config, core_config.get(conf_util.CONF_PACKAGES, {})) + # Make a copy because we are mutating it. # Use OrderedDict in case original one was one. # Convert values to dictionaries if they are None @@ -606,7 +610,7 @@ def async_log_exception(ex, domain, config, hass): message += '{}.'.format(humanize_error(config, ex)) domain_config = config.get(domain, config) - message += " (See {}:{}). ".format( + message += " (See {}, line {}). ".format( getattr(domain_config, '__config_file__', '?'), getattr(domain_config, '__line__', '?')) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 54be6aa4d0b..f6fbc933467 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel/ """ import asyncio +from datetime import timedelta import logging import os @@ -20,7 +21,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent DOMAIN = 'alarm_control_panel' -SCAN_INTERVAL = 30 +SCAN_INTERVAL = timedelta(seconds=30) ATTR_CHANGED_BY = 'changed_by' ENTITY_ID_FORMAT = DOMAIN + '.{}' @@ -152,40 +153,48 @@ class AlarmControlPanel(Entity): """Send disarm command.""" raise NotImplementedError() - @asyncio.coroutine def async_alarm_disarm(self, code=None): - """Send disarm command.""" - yield from self.hass.loop.run_in_executor( + """Send disarm command. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( None, self.alarm_disarm, code) def alarm_arm_home(self, code=None): """Send arm home command.""" raise NotImplementedError() - @asyncio.coroutine def async_alarm_arm_home(self, code=None): - """Send arm home command.""" - yield from self.hass.loop.run_in_executor( + """Send arm home command. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( None, self.alarm_arm_home, code) def alarm_arm_away(self, code=None): """Send arm away command.""" raise NotImplementedError() - @asyncio.coroutine def async_alarm_arm_away(self, code=None): - """Send arm away command.""" - yield from self.hass.loop.run_in_executor( + """Send arm away command. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( None, self.alarm_arm_away, code) def alarm_trigger(self, code=None): """Send alarm trigger command.""" raise NotImplementedError() - @asyncio.coroutine def async_alarm_trigger(self, code=None): - """Send alarm trigger command.""" - yield from self.hass.loop.run_in_executor( + """Send alarm trigger command. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( None, self.alarm_trigger, code) @property diff --git a/homeassistant/components/alarm_control_panel/concord232.py b/homeassistant/components/alarm_control_panel/concord232.py index de153a9e0a5..18a492d6c12 100755 --- a/homeassistant/components/alarm_control_panel/concord232.py +++ b/homeassistant/components/alarm_control_panel/concord232.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.concord232/ """ import datetime +from datetime import timedelta import logging import requests @@ -25,7 +26,7 @@ DEFAULT_HOST = 'localhost' DEFAULT_NAME = 'CONCORD232' DEFAULT_PORT = 5007 -SCAN_INTERVAL = 1 +SCAN_INTERVAL = timedelta(seconds=1) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index 58ec8d915ab..b7b3beec72d 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -16,7 +16,7 @@ from homeassistant.const import ( STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pynx584==0.2'] +REQUIREMENTS = ['pynx584==0.4'] _LOGGER = logging.getLogger(__name__) @@ -86,9 +86,11 @@ class NX584Alarm(alarm.AlarmControlPanel): _LOGGER.error('Unable to connect to %(host)s: %(reason)s', dict(host=self._url, reason=ex)) self._state = STATE_UNKNOWN + zones = [] except IndexError: _LOGGER.error('nx584 reports no partitions') self._state = STATE_UNKNOWN + zones = [] bypassed = False for zone in zones: diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 9bd0d783fee..59d2ec8852a 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -203,11 +203,12 @@ class AlexaResponse(object): self.reprompt = None self.session_attributes = {} self.should_end_session = True + self.variables = {} if intent is not None and 'slots' in intent: - self.variables = {key: value['value'] for key, value - in intent['slots'].items() if 'value' in value} - else: - self.variables = {} + for key, value in intent['slots'].items(): + if 'value' in value: + underscored_key = key.replace('.', '_') + self.variables[underscored_key] = value['value'] def add_card(self, card_type, title, content): """Add a card to the response.""" diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index da8ad9f88ba..8e03133e032 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -133,6 +133,9 @@ class APIEventStream(HomeAssistantView): except asyncio.TimeoutError: yield from to_write.put(STREAM_PING_PAYLOAD) + except asyncio.CancelledError: + _LOGGER.debug('STREAM %s ABORT', id(stop_obj)) + finally: _LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj)) unsub_stream() diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py new file mode 100644 index 00000000000..d8acaaa184c --- /dev/null +++ b/homeassistant/components/bbb_gpio.py @@ -0,0 +1,74 @@ +""" +Support for controlling GPIO pins of a Beaglebone Black. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/bbb_gpio/ +""" +import logging + +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + +REQUIREMENTS = ['Adafruit_BBIO==1.0.0'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'bbb_gpio' + + +# pylint: disable=no-member +def setup(hass, config): + """Set up the BeagleBone Black GPIO component.""" + # pylint: disable=import-error + import Adafruit_BBIO.GPIO as GPIO + + def cleanup_gpio(event): + """Stuff to do before stopping.""" + GPIO.cleanup() + + def prepare_gpio(event): + """Stuff to do when home assistant starts.""" + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) + return True + + +# noqa: F821 + +def setup_output(pin): + """Setup a GPIO as output.""" + # pylint: disable=import-error,undefined-variable + import Adafruit_BBIO.GPIO as GPIO + GPIO.setup(pin, GPIO.OUT) + + +def setup_input(pin, pull_mode): + """Setup a GPIO as input.""" + # pylint: disable=import-error,undefined-variable + import Adafruit_BBIO.GPIO as GPIO + GPIO.setup(pin, GPIO.IN, # noqa: F821 + GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821 + else GPIO.PUD_UP) # noqa: F821 + + +def write_output(pin, value): + """Write a value to a GPIO.""" + # pylint: disable=import-error,undefined-variable + import Adafruit_BBIO.GPIO as GPIO + GPIO.output(pin, value) + + +def read_input(pin): + """Read a value from a GPIO.""" + # pylint: disable=import-error,undefined-variable + import Adafruit_BBIO.GPIO as GPIO + return GPIO.input(pin) + + +def edge_detect(pin, event_callback, bounce): + """Add detection for RISING and FALLING events.""" + # pylint: disable=import-error,undefined-variable + import Adafruit_BBIO.GPIO as GPIO + GPIO.add_event_detect( + pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 38b08fd32b4..26a19ce3f59 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/binary_sensor/ """ import asyncio +from datetime import timedelta import logging import voluptuous as vol @@ -15,7 +16,7 @@ from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa DOMAIN = 'binary_sensor' -SCAN_INTERVAL = 30 +SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_FORMAT = DOMAIN + '.{}' SENSOR_CLASSES = [ diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index 72d0a240809..f051120d680 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -4,6 +4,7 @@ Support for custom shell commands to retrieve values. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.command_line/ """ +from datetime import timedelta import logging import voluptuous as vol @@ -22,7 +23,7 @@ DEFAULT_NAME = 'Binary Command Sensor' DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_OFF = 'OFF' -SCAN_INTERVAL = 60 +SCAN_INTERVAL = timedelta(seconds=60) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COMMAND): cv.string, diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index d9cb11ba6a7..109eed1fdc2 100755 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -27,7 +27,7 @@ DEFAULT_NAME = 'Alarm' DEFAULT_PORT = '5007' DEFAULT_SSL = False -SCAN_INTERVAL = 1 +SCAN_INTERVAL = datetime.timedelta(seconds=1) ZONE_TYPES_SCHEMA = vol.Schema({ cv.positive_int: vol.In(SENSOR_CLASSES), diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/binary_sensor/flic.py index 980af069f38..94a75fcda4b 100644 --- a/homeassistant/components/binary_sensor/flic.py +++ b/homeassistant/components/binary_sensor/flic.py @@ -1,6 +1,6 @@ """Contains functionality to use flic buttons as a binary sensor.""" -import asyncio import logging +import threading import voluptuous as vol @@ -10,7 +10,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP) from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.util.async import run_callback_threadsafe REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4'] @@ -43,9 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -@asyncio.coroutine -def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +def setup_platform(hass, config, add_entities, discovery_info=None): """Setup the flic platform.""" import pyflic @@ -63,26 +60,29 @@ def async_setup_platform(hass, config, async_add_entities, def new_button_callback(address): """Setup newly verified button as device in home assistant.""" - hass.add_job(async_setup_button(hass, config, async_add_entities, - client, address)) + setup_button(hass, config, add_entities, client, address) client.on_new_verified_button = new_button_callback if discovery: - start_scanning(hass, config, async_add_entities, client) + start_scanning(config, add_entities, client) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: client.close()) - hass.loop.run_in_executor(None, client.handle_events) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: client.close()) + + # Start the pyflic event handling thread + threading.Thread(target=client.handle_events).start() + + def get_info_callback(items): + """Add entities for already verified buttons.""" + addresses = items["bd_addr_of_verified_buttons"] or [] + for address in addresses: + setup_button(hass, config, add_entities, client, address) # Get addresses of already verified buttons - addresses = yield from async_get_verified_addresses(client) - if addresses: - for address in addresses: - yield from async_setup_button(hass, config, async_add_entities, - client, address) + client.get_info(get_info_callback) -def start_scanning(hass, config, async_add_entities, client): +def start_scanning(config, add_entities, client): """Start a new flic client for scanning & connceting to new buttons.""" import pyflic @@ -97,36 +97,20 @@ def start_scanning(hass, config, async_add_entities, client): address, result) # Restart scan wizard - start_scanning(hass, config, async_add_entities, client) + start_scanning(config, add_entities, client) scan_wizard.on_completed = scan_completed_callback client.add_scan_wizard(scan_wizard) -@asyncio.coroutine -def async_setup_button(hass, config, async_add_entities, client, address): +def setup_button(hass, config, add_entities, client, address): """Setup single button device.""" timeout = config.get(CONF_TIMEOUT) ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES) button = FlicButton(hass, client, address, timeout, ignored_click_types) _LOGGER.info("Connected to button (%s)", address) - yield from async_add_entities([button]) - - -@asyncio.coroutine -def async_get_verified_addresses(client): - """Retrieve addresses of verified buttons.""" - future = asyncio.Future() - loop = asyncio.get_event_loop() - - def get_info_callback(items): - """Set the addressed of connected buttons as result of the future.""" - addresses = items["bd_addr_of_verified_buttons"] - run_callback_threadsafe(loop, future.set_result, addresses) - client.get_info(get_info_callback) - - return future + add_entities([button]) class FlicButton(BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py index 90d61cbf3b7..1cc98372cee 100644 --- a/homeassistant/components/binary_sensor/hikvision.py +++ b/homeassistant/components/binary_sensor/hikvision.py @@ -17,7 +17,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, EVENT_HOMEASSISTANT_STOP, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE) -REQUIREMENTS = ['pyhik==0.0.6', 'pydispatcher==2.0.5'] +REQUIREMENTS = ['pyhik==0.0.7', 'pydispatcher==2.0.5'] _LOGGER = logging.getLogger(__name__) CONF_IGNORED = 'ignored' diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index e938f946457..6406dabd26f 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( - map_sv_types, devices, add_devices, MySensorsBinarySensor)) + map_sv_types, devices, MySensorsBinarySensor, add_devices)) class MySensorsBinarySensor( diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index b21e40dc5dd..ad7612d11a6 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -16,7 +16,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.const import (CONF_HOST, CONF_PORT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pynx584==0.2'] +REQUIREMENTS = ['pynx584==0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index e99a2625ea2..32de0f653a7 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -20,6 +20,8 @@ DEPENDENCIES = [] PHILIO = 0x013c PHILIO_SLIM_SENSOR = 0x0002 PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0) +PHILIO_3_IN_1_SENSOR_GEN_4 = 0x000d +PHILIO_3_IN_1_SENSOR_GEN_4_MOTION = (PHILIO, PHILIO_3_IN_1_SENSOR_GEN_4, 0) WENZHOU = 0x0118 WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0) @@ -27,6 +29,7 @@ WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event' DEVICE_MAPPINGS = { PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, + PHILIO_3_IN_1_SENSOR_GEN_4_MOTION: WORKAROUND_NO_OFF_EVENT, WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, } @@ -96,6 +99,7 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity, Entity): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id or \ self._value.node == value.node: + _LOGGER.debug('Value changed for label %s', self._value.label) self.schedule_update_ha_state() diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 503b97a2b13..e4de69c3ce8 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -6,6 +6,8 @@ https://home-assistant.io/components/calendar/ """ import logging +from datetime import timedelta + import re from homeassistant.components.google import (CONF_OFFSET, @@ -20,6 +22,7 @@ from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=60) DOMAIN = 'calendar' ENTITY_ID_FORMAT = DOMAIN + '.{}' @@ -27,7 +30,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' def setup(hass, config): """Track states and offer events for calendars.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, 60, DOMAIN) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DOMAIN) component.setup(config) @@ -144,10 +147,10 @@ class CalendarEventDevice(Entity): def _get_date(date): """Get the dateTime from date or dateTime as a local.""" if 'date' in date: - return dt.as_utc(dt.dt.datetime.combine( - dt.parse_date(date['date']), dt.dt.time())) + return dt.start_of_local_day(dt.dt.datetime.combine( + dt.parse_date(date['date']), dt.dt.time.min)) else: - return dt.parse_datetime(date['dateTime']) + return dt.as_local(dt.parse_datetime(date['dateTime'])) start = _get_date(self.data.event['start']) end = _get_date(self.data.event['end']) diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 741b3238b49..022d39b602b 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -66,7 +66,7 @@ class GoogleCalendarData(object): """Get the latest data.""" service = self.calendar_service.get() params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) - params['timeMin'] = dt.utcnow().isoformat('T') + params['timeMin'] = dt.start_of_local_day().isoformat('T') params['calendarId'] = self.calendar_id if self.search: params['q'] = self.search diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 427d4535ef6..89a2f6c5e46 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -6,18 +6,27 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/camera/ """ import asyncio +from datetime import timedelta import logging +import hashlib +import aiohttp from aiohttp import web +import async_timeout +from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'camera' DEPENDENCIES = ['http'] -SCAN_INTERVAL = 30 +SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_FORMAT = DOMAIN + '.{}' STATE_RECORDING = 'recording' @@ -27,11 +36,45 @@ STATE_IDLE = 'idle' ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' +@asyncio.coroutine +def async_get_image(hass, entity_id, timeout=10): + """Fetch a image from a camera entity.""" + websession = async_get_clientsession(hass) + state = hass.states.get(entity_id) + + if state is None: + raise HomeAssistantError( + "No entity '{0}' for grab a image".format(entity_id)) + + url = "{0}{1}".format( + hass.config.api.base_url, + state.attributes.get(ATTR_ENTITY_PICTURE) + ) + + response = None + try: + with async_timeout.timeout(timeout, loop=hass.loop): + response = yield from websession.get(url) + + if response.status != 200: + raise HomeAssistantError("Error {0} on {1}".format( + response.status, url)) + + image = yield from response.read() + return image + + except (asyncio.TimeoutError, aiohttp.errors.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): """Setup the camera component.""" - component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) hass.http.register_view(CameraImageView(component.entities)) hass.http.register_view(CameraMjpegStream(component.entities)) @@ -46,11 +89,13 @@ class Camera(Entity): def __init__(self): """Initialize a camera.""" self.is_streaming = False + self._access_token = hashlib.sha256( + str.encode(str(id(self)))).hexdigest() @property def access_token(self): """Access token for this camera.""" - return str(id(self)) + return self._access_token @property def should_poll(self): @@ -81,15 +126,12 @@ class Camera(Entity): """Return bytes of camera image.""" raise NotImplementedError() - @asyncio.coroutine def async_camera_image(self): """Return bytes of camera image. - This method must be run in the event loop. + This method must be run in the event loop and returns a coroutine. """ - image = yield from self.hass.loop.run_in_executor( - None, self.camera_image) - return image + return self.hass.loop.run_in_executor(None, self.camera_image) @asyncio.coroutine def handle_async_mjpeg_stream(self, request): @@ -131,8 +173,14 @@ class Camera(Entity): yield from response.drain() yield from asyncio.sleep(.5) + + except asyncio.CancelledError: + _LOGGER.debug("Close stream by frontend.") + response = None + finally: - yield from response.write_eof() + if response is not None: + yield from response.write_eof() @property def state(self): @@ -201,12 +249,16 @@ class CameraImageView(CameraView): @asyncio.coroutine def handle(self, request, camera): """Serve camera image.""" - image = yield from camera.async_camera_image() + try: + image = yield from camera.async_camera_image() - if image is None: - return web.Response(status=500) + if image is None: + return web.Response(status=500) - return web.Response(body=image) + return web.Response(body=image) + + except asyncio.CancelledError: + _LOGGER.debug("Close stream by frontend.") class CameraMjpegStream(CameraView): diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index bb7c7ac6cdc..0b8d60ab7f5 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -84,9 +84,15 @@ class FFmpegCamera(Camera): if not data: break response.write(data) + + except asyncio.CancelledError: + _LOGGER.debug("Close stream by frontend.") + response = None + finally: - self.hass.async_add_job(stream.close()) - yield from response.write_eof() + yield from stream.close() + if response is not None: + yield from response.write_eof() @property def name(self): diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 981ed9dbf49..dd030099a45 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -124,9 +124,13 @@ class MjpegCamera(Camera): except asyncio.TimeoutError: raise HTTPGatewayTimeout() + except asyncio.CancelledError: + _LOGGER.debug("Close stream by frontend.") + response = None + finally: if stream is not None: - self.hass.async_add_job(stream.release()) + stream.close() if response is not None: yield from response.write_eof() diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 6d5b4546933..424e269c555 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -276,9 +276,13 @@ class SynologyCamera(Camera): _LOGGER.exception("Error on %s", streaming_url) raise HTTPGatewayTimeout() + except asyncio.CancelledError: + _LOGGER.debug("Close stream by frontend.") + response = None + finally: if stream is not None: - self.hass.async_add_job(stream.release()) + stream.close() if response is not None: yield from response.write_eof() diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index c29100ecaad..c5252209996 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_PORT from homeassistant.components.camera import Camera, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['uvcclient==0.9.0'] +REQUIREMENTS = ['uvcclient==0.10.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 80ef97622d5..3058258c75a 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -4,15 +4,18 @@ Provides functionality to interact with climate devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/climate/ """ +import asyncio +from datetime import timedelta import logging import os +import functools as ft from numbers import Number -import voluptuous as vol -from homeassistant.helpers.entity_component import EntityComponent +import voluptuous as vol from homeassistant.config import load_yaml_config_file from homeassistant.util.temperature import convert as convert_temperature +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa import homeassistant.helpers.config_validation as cv @@ -23,7 +26,7 @@ from homeassistant.const import ( DOMAIN = "climate" ENTITY_ID_FORMAT = DOMAIN + ".{}" -SCAN_INTERVAL = 60 +SCAN_INTERVAL = timedelta(seconds=60) SERVICE_SET_AWAY_MODE = "set_away_mode" SERVICE_SET_AUX_HEAT = "set_aux_heat" @@ -185,17 +188,38 @@ def set_swing_mode(hass, swing_mode, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data) -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Setup climate devices.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) - component.setup(config) + yield from component.async_setup(config) - descriptions = load_yaml_config_file( + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, os.path.join(os.path.dirname(__file__), 'services.yaml')) - def away_mode_set_service(service): + @asyncio.coroutine + def _async_update_climate(target_climate): + """Update climate entity after service stuff.""" + update_tasks = [] + for climate in target_climate: + if not climate.should_poll: + continue + + update_coro = hass.loop.create_task( + climate.async_update_ha_state(True)) + if hasattr(climate, 'async_update'): + update_tasks.append(update_coro) + else: + yield from update_coro + + if update_tasks: + yield from asyncio.wait(update_tasks, loop=hass.loop) + + @asyncio.coroutine + def async_away_mode_set_service(service): """Set away mode on target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) away_mode = service.data.get(ATTR_AWAY_MODE) @@ -207,21 +231,21 @@ def setup(hass, config): for climate in target_climate: if away_mode: - climate.turn_away_mode_on() + yield from climate.async_turn_away_mode_on() else: - climate.turn_away_mode_off() + yield from climate.async_turn_away_mode_off() - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service, descriptions.get(SERVICE_SET_AWAY_MODE), schema=SET_AWAY_MODE_SCHEMA) - def aux_heat_set_service(service): + @asyncio.coroutine + def async_aux_heat_set_service(service): """Set auxillary heater on target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) aux_heat = service.data.get(ATTR_AUX_HEAT) @@ -233,21 +257,21 @@ def setup(hass, config): for climate in target_climate: if aux_heat: - climate.turn_aux_heat_on() + yield from climate.async_turn_aux_heat_on() else: - climate.turn_aux_heat_off() + yield from climate.async_turn_aux_heat_off() - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_AUX_HEAT, aux_heat_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service, descriptions.get(SERVICE_SET_AUX_HEAT), schema=SET_AUX_HEAT_SCHEMA) - def temperature_set_service(service): + @asyncio.coroutine + def async_temperature_set_service(service): """Set temperature on the target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) for climate in target_climate: kwargs = {} @@ -261,18 +285,19 @@ def setup(hass, config): else: kwargs[value] = temp - climate.set_temperature(**kwargs) - if climate.should_poll: - climate.update_ha_state(True) + yield from climate.async_set_temperature(**kwargs) - hass.services.register( - DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service, + yield from _async_update_climate(target_climate) + + hass.services.async_register( + DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service, descriptions.get(SERVICE_SET_TEMPERATURE), schema=SET_TEMPERATURE_SCHEMA) - def humidity_set_service(service): + @asyncio.coroutine + def async_humidity_set_service(service): """Set humidity on the target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) humidity = service.data.get(ATTR_HUMIDITY) @@ -283,19 +308,19 @@ def setup(hass, config): return for climate in target_climate: - climate.set_humidity(humidity) + yield from climate.async_set_humidity(humidity) - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_HUMIDITY, humidity_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service, descriptions.get(SERVICE_SET_HUMIDITY), schema=SET_HUMIDITY_SCHEMA) - def fan_mode_set_service(service): + @asyncio.coroutine + def async_fan_mode_set_service(service): """Set fan mode on target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) fan = service.data.get(ATTR_FAN_MODE) @@ -306,19 +331,19 @@ def setup(hass, config): return for climate in target_climate: - climate.set_fan_mode(fan) + yield from climate.async_set_fan_mode(fan) - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service, descriptions.get(SERVICE_SET_FAN_MODE), schema=SET_FAN_MODE_SCHEMA) - def operation_set_service(service): + @asyncio.coroutine + def async_operation_set_service(service): """Set operating mode on the target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) operation_mode = service.data.get(ATTR_OPERATION_MODE) @@ -329,19 +354,19 @@ def setup(hass, config): return for climate in target_climate: - climate.set_operation_mode(operation_mode) + yield from climate.async_set_operation_mode(operation_mode) - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_OPERATION_MODE, operation_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service, descriptions.get(SERVICE_SET_OPERATION_MODE), schema=SET_OPERATION_MODE_SCHEMA) - def swing_set_service(service): + @asyncio.coroutine + def async_swing_set_service(service): """Set swing mode on the target climate devices.""" - target_climate = component.extract_from_service(service) + target_climate = component.async_extract_from_service(service) swing_mode = service.data.get(ATTR_SWING_MODE) @@ -352,15 +377,15 @@ def setup(hass, config): return for climate in target_climate: - climate.set_swing_mode(swing_mode) + yield from climate.async_set_swing_mode(swing_mode) - if climate.should_poll: - climate.update_ha_state(True) + yield from _async_update_climate(target_climate) - hass.services.register( - DOMAIN, SERVICE_SET_SWING_MODE, swing_set_service, + hass.services.async_register( + DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service, descriptions.get(SERVICE_SET_SWING_MODE), schema=SET_SWING_MODE_SCHEMA) + return True @@ -521,38 +546,110 @@ class ClimateDevice(Entity): """Set new target temperature.""" raise NotImplementedError() + def async_set_temperature(self, **kwargs): + """Set new target temperature. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, ft.partial(self.set_temperature, **kwargs)) + def set_humidity(self, humidity): """Set new target humidity.""" raise NotImplementedError() + def async_set_humidity(self, humidity): + """Set new target humidity. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.set_humidity, humidity) + def set_fan_mode(self, fan): """Set new target fan mode.""" raise NotImplementedError() + def async_set_fan_mode(self, fan): + """Set new target fan mode. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.set_fan_mode, fan) + def set_operation_mode(self, operation_mode): """Set new target operation mode.""" raise NotImplementedError() + def async_set_operation_mode(self, operation_mode): + """Set new target operation mode. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.set_operation_mode, operation_mode) + def set_swing_mode(self, swing_mode): """Set new target swing operation.""" raise NotImplementedError() + def async_set_swing_mode(self, swing_mode): + """Set new target swing operation. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.set_swing_mode, swing_mode) + def turn_away_mode_on(self): """Turn away mode on.""" raise NotImplementedError() + def async_turn_away_mode_on(self): + """Turn away mode on. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.turn_away_mode_on) + def turn_away_mode_off(self): """Turn away mode off.""" raise NotImplementedError() + def async_turn_away_mode_off(self): + """Turn away mode off. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.turn_away_mode_off) + def turn_aux_heat_on(self): """Turn auxillary heater on.""" raise NotImplementedError() + def async_turn_aux_heat_on(self): + """Turn auxillary heater on. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.turn_aux_heat_on) + def turn_aux_heat_off(self): """Turn auxillary heater off.""" raise NotImplementedError() + def async_turn_aux_heat_off(self): + """Turn auxillary heater off. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.turn_aux_heat_off) + @property def min_temp(self): """Return the minimum temperature.""" diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index 84d8c32f9ff..bfb11f703d1 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -22,16 +22,25 @@ _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time' +ATTR_RESUME_ALL = 'resume_all' + +DEFAULT_RESUME_ALL = False DEPENDENCIES = ['ecobee'] SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time' +SERVICE_RESUME_PROGRAM = 'ecobee_resume_program' SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), }) +RESUME_PROGRAM_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Ecobee Thermostat Platform.""" @@ -48,21 +57,36 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def fan_min_on_time_set_service(service): """Set the minimum fan on time on the target thermostats.""" - entity_id = service.data.get('entity_id') + entity_id = service.data.get(ATTR_ENTITY_ID) + fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME] if entity_id: target_thermostats = [device for device in devices - if device.entity_id == entity_id] + if device.entity_id in entity_id] else: target_thermostats = devices - fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME] - for thermostat in target_thermostats: thermostat.set_fan_min_on_time(str(fan_min_on_time)) thermostat.update_ha_state(True) + def resume_program_set_service(service): + """Resume the program on the target thermostats.""" + entity_id = service.data.get(ATTR_ENTITY_ID) + resume_all = service.data.get(ATTR_RESUME_ALL) + + if entity_id: + target_thermostats = [device for device in devices + if device.entity_id in entity_id] + else: + target_thermostats = devices + + for thermostat in target_thermostats: + thermostat.resume_program(resume_all) + + thermostat.update_ha_state(True) + descriptions = load_yaml_config_file( path.join(path.dirname(__file__), 'services.yaml')) @@ -71,6 +95,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): descriptions.get(SERVICE_SET_FAN_MIN_ON_TIME), schema=SET_FAN_MIN_ON_TIME_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service, + descriptions.get(SERVICE_RESUME_PROGRAM), + schema=RESUME_PROGRAM_SCHEMA) + class Thermostat(ClimateDevice): """A thermostat class for Ecobee.""" @@ -249,6 +278,12 @@ class Thermostat(ClimateDevice): fan_min_on_time) self.update_without_throttle = True + def resume_program(self, resume_all): + """Resume the thermostat schedule program.""" + self.data.ecobee.resume_program(self.thermostat_index, + str(resume_all).lower()) + self.update_without_throttle = True + # Home and Sleep mode aren't used in UI yet: # def turn_home_mode_on(self): diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 72bd0b22522..41697f7b31d 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -8,18 +8,27 @@ import logging import voluptuous as vol -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import ( + ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES, + STATE_UNKNOWN, STATE_AUTO, STATE_ON, STATE_OFF, +) from homeassistant.const import ( CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE) -from homeassistant.util.temperature import convert + import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['bluepy_devices==0.2.0'] +REQUIREMENTS = ['python-eq3bt==0.1.2'] _LOGGER = logging.getLogger(__name__) -ATTR_MODE = 'mode' -ATTR_MODE_READABLE = 'mode_readable' +STATE_BOOST = "boost" +STATE_AWAY = "away" +STATE_MANUAL = "manual" + +ATTR_STATE_WINDOW_OPEN = "window_open" +ATTR_STATE_VALVE = "valve" +ATTR_STATE_LOCKED = "is_locked" +ATTR_STATE_LOW_BAT = "low_battery" DEVICE_SCHEMA = vol.Schema({ vol.Required(CONF_MAC): cv.string, @@ -48,10 +57,23 @@ class EQ3BTSmartThermostat(ClimateDevice): def __init__(self, _mac, _name): """Initialize the thermostat.""" - from bluepy_devices.devices import eq3btsmart + # we want to avoid name clash with this module.. + import eq3bt as eq3 + + self.modes = {None: STATE_UNKNOWN, # When not yet connected. + eq3.Mode.Unknown: STATE_UNKNOWN, + eq3.Mode.Auto: STATE_AUTO, + # away handled separately, here just for reverse mapping + eq3.Mode.Away: STATE_AWAY, + eq3.Mode.Closed: STATE_OFF, + eq3.Mode.Open: STATE_ON, + eq3.Mode.Manual: STATE_MANUAL, + eq3.Mode.Boost: STATE_BOOST} + + self.reverse_modes = {v: k for k, v in self.modes.items()} self._name = _name - self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac) + self._thermostat = eq3.Thermostat(_mac) @property def name(self): @@ -63,6 +85,11 @@ class EQ3BTSmartThermostat(ClimateDevice): """Return the unit of measurement that is used.""" return TEMP_CELSIUS + @property + def precision(self): + """Return eq3bt's precision 0.5.""" + return PRECISION_HALVES + @property def current_temperature(self): """Can not report temperature, so return target_temperature.""" @@ -81,24 +108,53 @@ class EQ3BTSmartThermostat(ClimateDevice): self._thermostat.target_temperature = temperature @property - def device_state_attributes(self): - """Return the device specific state attributes.""" - return { - ATTR_MODE: self._thermostat.mode, - ATTR_MODE_READABLE: self._thermostat.mode_readable, - } + def current_operation(self): + """Current mode.""" + return self.modes[self._thermostat.mode] + + @property + def operation_list(self): + """List of available operation modes.""" + return [x for x in self.modes.values()] + + def set_operation_mode(self, operation_mode): + """Set operation mode.""" + self._thermostat.mode = self.reverse_modes[operation_mode] + + def turn_away_mode_off(self): + """Away mode off turns to AUTO mode.""" + self.set_operation_mode(STATE_AUTO) + + def turn_away_mode_on(self): + """Set away mode on.""" + self.set_operation_mode(STATE_AWAY) + + @property + def is_away_mode_on(self): + """Return if we are away.""" + return self.current_operation == STATE_AWAY @property def min_temp(self): """Return the minimum temperature.""" - return convert(self._thermostat.min_temp, TEMP_CELSIUS, - self.unit_of_measurement) + return self._thermostat.min_temp @property def max_temp(self): """Return the maximum temperature.""" - return convert(self._thermostat.max_temp, TEMP_CELSIUS, - self.unit_of_measurement) + return self._thermostat.max_temp + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + dev_specific = { + ATTR_STATE_LOCKED: self._thermostat.locked, + ATTR_STATE_LOW_BAT: self._thermostat.low_battery, + ATTR_STATE_VALVE: self._thermostat.valve_state, + ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open, + } + + return dev_specific def update(self): """Update the data from the thermostat.""" diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/climate/mysensors.py index 13a062a335e..6c55b3b4451 100755 --- a/homeassistant/components/climate/mysensors.py +++ b/homeassistant/components/climate/mysensors.py @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): } devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( - map_sv_types, devices, add_devices, MySensorsHVAC)) + map_sv_types, devices, MySensorsHVAC, add_devices)) class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index d28e8c4dd88..801052b31ff 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -76,7 +76,7 @@ set_operation_mode: fields: entity_id: description: Name(s) of entities to change - example: 'climet.nest' + example: 'climate.nest' operation_mode: description: New value of operation mode @@ -94,3 +94,27 @@ set_swing_mode: swing_mode: description: New value of swing mode example: 1 + +ecobee_set_fan_min_on_time: + description: Set the minimum fan on time + + fields: + entity_id: + description: Name(s) of entities to change + example: 'climate.kitchen' + + fan_min_on_time: + description: New value of fan min on time + example: 5 + +ecobee_resume_program: + description: Resume the programmed schedule + + fields: + entity_id: + description: Name(s) of entities to change + example: 'climate.kitchen' + + resume_all: + description: Resume all events and return to the scheduled program. This default to false which removes only the top event. + example: true diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index eb3aca7be5b..f7bbe341cf7 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -89,9 +89,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id or \ self._value.node == value.node: + _LOGGER.debug('Value changed for label %s', self._value.label) self.update_properties() self.schedule_update_ha_state() - _LOGGER.debug("Value changed on network %s", value) def update_properties(self): """Callback on data change for the registered node/value pair.""" diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 6c268e49be6..da473df111e 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover/ """ import os +from datetime import timedelta import logging import voluptuous as vol @@ -23,7 +24,7 @@ from homeassistant.const import ( DOMAIN = 'cover' -SCAN_INTERVAL = 15 +SCAN_INTERVAL = timedelta(seconds=15) GROUP_NAME_ALL_COVERS = 'all covers' ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers') diff --git a/homeassistant/components/cover/mysensors.py b/homeassistant/components/cover/mysensors.py index 7dd63a8c745..a75ad36354b 100644 --- a/homeassistant/components/cover/mysensors.py +++ b/homeassistant/components/cover/mysensors.py @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): }) devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( - map_sv_types, devices, add_devices, MySensorsCover)) + map_sv_types, devices, MySensorsCover, add_devices)) class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice): diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index a190e69bf53..89947e3e4fc 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -78,9 +78,9 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id or \ self._value.node == value.node: + _LOGGER.debug('Value changed for label %s', self._value.label) self.update_properties() - self.update_ha_state() - _LOGGER.debug("Value changed on network %s", value) + self.schedule_update_ha_state() def update_properties(self): """Callback on data change for the registered node/value pair.""" @@ -170,9 +170,9 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): def value_changed(self, value): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: + _LOGGER.debug('Value changed for label %s', self._value.label) self._state = value.data - self.update_ha_state() - _LOGGER.debug("Value changed on network %s", value) + self.schedule_update_ha_state() @property def is_closed(self): diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 80f89d7c134..170159e1d25 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -23,12 +23,14 @@ COMPONENTS_WITH_DEMO_PLATFORM = [ 'cover', 'device_tracker', 'fan', + 'image_processing', 'light', 'lock', 'media_player', 'notify', 'sensor', 'switch', + 'tts', ] diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index d497ea4c314..21e7c7b0da1 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -8,7 +8,7 @@ import asyncio from datetime import timedelta import logging import os -from typing import Any, Sequence, Callable +from typing import Any, List, Sequence, Callable import aiohttp import async_timeout @@ -24,6 +24,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType import homeassistant.helpers.config_validation as cv import homeassistant.util as util @@ -50,10 +51,10 @@ CONF_TRACK_NEW = 'track_new_devices' DEFAULT_TRACK_NEW = True CONF_CONSIDER_HOME = 'consider_home' -DEFAULT_CONSIDER_HOME = 180 +DEFAULT_CONSIDER_HOME = timedelta(seconds=180) CONF_SCAN_INTERVAL = 'interval_seconds' -DEFAULT_SCAN_INTERVAL = 12 +DEFAULT_SCAN_INTERVAL = timedelta(seconds=12) CONF_AWAY_HIDE = 'hide_if_away' DEFAULT_AWAY_HIDE = False @@ -69,12 +70,16 @@ ATTR_LOCATION_NAME = 'location_name' ATTR_GPS = 'gps' ATTR_BATTERY = 'battery' ATTR_ATTRIBUTES = 'attributes' +ATTR_SOURCE_TYPE = 'source_type' + +SOURCE_TYPE_GPS = 'gps' +SOURCE_TYPE_ROUTER = 'router' PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds + vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, vol.Optional(CONF_CONSIDER_HOME, - default=timedelta(seconds=DEFAULT_CONSIDER_HOME)): vol.All( + default=DEFAULT_CONSIDER_HOME): vol.All( cv.time_period, cv.positive_timedelta) }) @@ -121,8 +126,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): return False else: conf = conf[0] if len(conf) > 0 else {} - consider_home = conf.get(CONF_CONSIDER_HOME, - timedelta(seconds=DEFAULT_CONSIDER_HOME)) + consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME) track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) devices = yield from async_load_config(yaml_path, hass, consider_home) @@ -142,23 +146,34 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): if platform is None: return + _LOGGER.info("Setting up %s.%s", DOMAIN, p_type) try: - if hasattr(platform, 'get_scanner'): + scanner = None + setup = None + if hasattr(platform, 'async_get_scanner'): + scanner = yield from platform.async_get_scanner( + hass, {DOMAIN: p_config}) + elif hasattr(platform, 'get_scanner'): scanner = yield from hass.loop.run_in_executor( None, platform.get_scanner, hass, {DOMAIN: p_config}) + elif hasattr(platform, 'async_setup_scanner'): + setup = yield from platform.async_setup_scanner( + hass, p_config, tracker.async_see) + elif hasattr(platform, 'setup_scanner'): + setup = yield from hass.loop.run_in_executor( + None, platform.setup_scanner, hass, p_config, tracker.see) + else: + raise HomeAssistantError("Invalid device_tracker platform.") - if scanner is None: - _LOGGER.error('Error setting up platform %s', p_type) - return - + if scanner: yield from async_setup_scanner_platform( hass, p_config, scanner, tracker.async_see) return - ret = yield from hass.loop.run_in_executor( - None, platform.setup_scanner, hass, p_config, tracker.see) - if not ret: + if not setup: _LOGGER.error('Error setting up platform %s', p_type) + return + except Exception: # pylint: disable=broad-except _LOGGER.exception('Error setting up platform %s', p_type) @@ -223,17 +238,19 @@ class DeviceTracker(object): def see(self, mac: str=None, dev_id: str=None, host_name: str=None, location_name: str=None, gps: GPSType=None, gps_accuracy=None, - battery: str=None, attributes: dict=None): + battery: str=None, attributes: dict=None, + source_type: str=SOURCE_TYPE_GPS): """Notify the device tracker that you see a device.""" self.hass.add_job( self.async_see(mac, dev_id, host_name, location_name, gps, - gps_accuracy, battery, attributes) + gps_accuracy, battery, attributes, source_type) ) @asyncio.coroutine def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None, location_name: str=None, gps: GPSType=None, - gps_accuracy=None, battery: str=None, attributes: dict=None): + gps_accuracy=None, battery: str=None, attributes: dict=None, + source_type: str=SOURCE_TYPE_GPS): """Notify the device tracker that you see a device. This method is a coroutine. @@ -251,7 +268,8 @@ class DeviceTracker(object): if device: yield from device.async_seen(host_name, location_name, gps, - gps_accuracy, battery, attributes) + gps_accuracy, battery, attributes, + source_type) if device.track: yield from device.async_update_ha_state() return @@ -266,7 +284,8 @@ class DeviceTracker(object): self.mac_to_dev[mac] = device yield from device.async_seen(host_name, location_name, gps, - gps_accuracy, battery, attributes) + gps_accuracy, battery, attributes, + source_type) if device.track: yield from device.async_update_ha_state() @@ -370,6 +389,9 @@ class Device(Entity): self.away_hide = hide_if_away self.vendor = vendor + + self.source_type = None + self._attributes = {} @property @@ -390,7 +412,9 @@ class Device(Entity): @property def state_attributes(self): """Return the device state attributes.""" - attr = {} + attr = { + ATTR_SOURCE_TYPE: self.source_type + } if self.gps: attr[ATTR_LATITUDE] = self.gps[0] @@ -415,12 +439,13 @@ class Device(Entity): @asyncio.coroutine def async_seen(self, host_name: str=None, location_name: str=None, gps: GPSType=None, gps_accuracy=0, battery: str=None, - attributes: dict=None): + attributes: dict=None, source_type: str=SOURCE_TYPE_GPS): """Mark the device as seen.""" + self.source_type = source_type self.last_seen = dt_util.utcnow() self.host_name = host_name self.location_name = location_name - self.gps_accuracy = gps_accuracy or 0 + if battery: self.battery = battery if attributes: @@ -431,7 +456,10 @@ class Device(Entity): if gps is not None: try: self.gps = float(gps[0]), float(gps[1]) + self.gps_accuracy = gps_accuracy or 0 except (ValueError, TypeError, IndexError): + self.gps = None + self.gps_accuracy = 0 _LOGGER.warning('Could not parse gps value for %s: %s', self.dev_id, gps) @@ -456,7 +484,7 @@ class Device(Entity): return elif self.location_name: self._state = self.location_name - elif self.gps is not None: + elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS: zone_state = zone.async_active_zone( self.hass, self.gps[0], self.gps[1], self.gps_accuracy) if zone_state is None: @@ -465,9 +493,9 @@ class Device(Entity): self._state = STATE_HOME else: self._state = zone_state.name - elif self.stale(): self._state = STATE_NOT_HOME + self.gps = None self.last_update_home = False else: self._state = STATE_HOME @@ -485,13 +513,18 @@ class Device(Entity): if not self.mac: return None + if '_' in self.mac: + _, mac = self.mac.split('_', 1) + else: + mac = self.mac + # prevent lookup of invalid macs - if not len(self.mac.split(':')) == 6: + if not len(mac.split(':')) == 6: return 'unknown' # we only need the first 3 bytes of the mac for a lookup # this improves somewhat on privacy - oui_bytes = self.mac.split(':')[0:3] + oui_bytes = mac.split(':')[0:3] # 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 @@ -521,6 +554,34 @@ class Device(Entity): yield from resp.release() +class DeviceScanner(object): + """Device scanner object.""" + + hass = None # type: HomeAssistantType + + def scan_devices(self) -> List[str]: + """Scan for devices.""" + raise NotImplementedError() + + def async_scan_devices(self) -> Any: + """Scan for devices. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor(None, self.scan_devices) + + def get_device_name(self, mac: str) -> str: + """Get device name from mac.""" + raise NotImplementedError() + + def async_get_device_name(self, mac: str) -> Any: + """Get device name from mac. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor(None, self.get_device_name, mac) + + def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): """Load devices from YAML configuration file.""" return run_coroutine_threadsafe( @@ -577,26 +638,39 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, This method is a coroutine. """ interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + scanner.hass = hass # Initial scan of each mac we also tell about host name for config seen = set() # type: Any - def device_tracker_scan(now: dt_util.dt.datetime): + @asyncio.coroutine + def async_device_tracker_scan(now: dt_util.dt.datetime): """Called when interval matches.""" - found_devices = scanner.scan_devices() + found_devices = yield from scanner.async_scan_devices() for mac in found_devices: if mac in seen: host_name = None else: - host_name = scanner.get_device_name(mac) + host_name = yield from scanner.async_get_device_name(mac) seen.add(mac) - hass.add_job(async_see_device(mac=mac, host_name=host_name)) - async_track_utc_time_change( - hass, device_tracker_scan, second=range(0, 60, interval)) + kwargs = { + 'mac': mac, + 'host_name': host_name, + 'source_type': SOURCE_TYPE_ROUTER + } - hass.async_add_job(device_tracker_scan, None) + zone_home = hass.states.get(zone.ENTITY_ID_HOME) + if zone_home: + kwargs['gps'] = [zone_home.attributes[ATTR_LATITUDE], + zone_home.attributes[ATTR_LONGITUDE]] + kwargs['gps_accuracy'] = 0 + + hass.async_add_job(async_see_device(**kwargs)) + + async_track_time_interval(hass, async_device_tracker_scan, interval) + hass.async_add_job(async_device_tracker_scan, None) def update_config(path: str, dev_id: str, device: Device): diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 9583237a912..95286800b3c 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -14,7 +14,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA) +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -46,7 +47,7 @@ def get_scanner(hass, config): Device = namedtuple("Device", ["mac", "ip", "last_update"]) -class ActiontecDeviceScanner(object): +class ActiontecDeviceScanner(DeviceScanner): """This class queries a an actiontec router for connected devices.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 6383bc962a4..42e8a5b2d5c 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -12,7 +12,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -42,7 +43,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class ArubaDeviceScanner(object): +class ArubaDeviceScanner(DeviceScanner): """This class queries a Aruba Access Point for connected devices.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 4e860846f8e..be530abc9e2 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -14,7 +14,8 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -97,7 +98,7 @@ def get_scanner(hass, config): AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp nvram') -class AsusWrtDeviceScanner(object): +class AsusWrtDeviceScanner(DeviceScanner): """This class queries a router running ASUSWRT firmware.""" # Eighth attribute needed for mode (AP mode vs router mode) diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index a07db8ec404..d47aa818673 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -11,8 +11,8 @@ import requests import voluptuous as vol -from homeassistant.components.device_tracker import (PLATFORM_SCHEMA, - ATTR_ATTRIBUTES) +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, ATTR_ATTRIBUTES) from homeassistant.const import CONF_USERNAME, CONF_PASSWORD import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_utc_time_change diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/device_tracker/bbox.py index 50864f47be1..60b9738cc71 100644 --- a/homeassistant/components/device_tracker/bbox.py +++ b/homeassistant/components/device_tracker/bbox.py @@ -9,7 +9,7 @@ import logging from datetime import timedelta import homeassistant.util.dt as dt_util -from homeassistant.components.device_tracker import DOMAIN +from homeassistant.components.device_tracker import DOMAIN, DeviceScanner from homeassistant.util import Throttle REQUIREMENTS = ['pybbox==0.0.5-alpha'] @@ -29,7 +29,7 @@ def get_scanner(hass, config): Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update']) -class BboxDeviceScanner(object): +class BboxDeviceScanner(DeviceScanner): """This class scans for devices connected to the bbox.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 5b5b9ce2411..8c29bc94be5 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -5,13 +5,8 @@ from datetime import timedelta import voluptuous as vol from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.components.device_tracker import ( - YAML_DEVICES, - CONF_TRACK_NEW, - CONF_SCAN_INTERVAL, - DEFAULT_SCAN_INTERVAL, - PLATFORM_SCHEMA, - load_config, - DEFAULT_TRACK_NEW + YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, + PLATFORM_SCHEMA, load_config, DEFAULT_TRACK_NEW ) import homeassistant.util as util import homeassistant.util.dt as dt_util @@ -24,9 +19,11 @@ REQUIREMENTS = ['gattlib==0.20150805'] BLE_PREFIX = 'BLE_' MIN_SEEN_NEW = 5 CONF_SCAN_DURATION = "scan_duration" +CONF_BLUETOOTH_DEVICE = "device_id" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int + vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int, + vol.Optional(CONF_BLUETOOTH_DEVICE, default="hci0"): cv.string }) @@ -60,7 +57,7 @@ def setup_scanner(hass, config, see): """Discover Bluetooth LE devices.""" _LOGGER.debug("Discovering Bluetooth LE devices") try: - service = DiscoveryService() + service = DiscoveryService(ble_dev_id) devices = service.discover(duration) _LOGGER.debug("Bluetooth LE devices discovered = %s", devices) except RuntimeError as error: @@ -70,6 +67,7 @@ def setup_scanner(hass, config, see): yaml_path = hass.config.path(YAML_DEVICES) duration = config.get(CONF_SCAN_DURATION) + ble_dev_id = config.get(CONF_BLUETOOTH_DEVICE) devs_to_track = [] devs_donot_track = [] diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/device_tracker/bt_home_hub_5.py index 3b4115ff355..301ec61abc2 100644 --- a/homeassistant/components/device_tracker/bt_home_hub_5.py +++ b/homeassistant/components/device_tracker/bt_home_hub_5.py @@ -16,7 +16,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST from homeassistant.util import Throttle @@ -40,7 +41,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class BTHomeHub5DeviceScanner(object): +class BTHomeHub5DeviceScanner(DeviceScanner): """This class queries a BT Home Hub 5.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/cisco_ios.py b/homeassistant/components/device_tracker/cisco_ios.py index 0d42282b17c..95319a872ae 100644 --- a/homeassistant/components/device_tracker/cisco_ios.py +++ b/homeassistant/components/device_tracker/cisco_ios.py @@ -10,7 +10,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \ CONF_PORT from homeassistant.util import Throttle @@ -39,7 +40,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class CiscoDeviceScanner(object): +class CiscoDeviceScanner(DeviceScanner): """This class queries a wireless router running Cisco IOS firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index a67ee3d1d39..2c8a2ec4907 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -13,7 +13,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -41,7 +42,7 @@ def get_scanner(hass, config): return None -class DdWrtDeviceScanner(object): +class DdWrtDeviceScanner(DeviceScanner): """This class queries a wireless router running DD-WRT firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index fd30946c919..055c3bc85c0 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -10,7 +10,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -38,7 +39,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class FritzBoxScanner(object): +class FritzBoxScanner(DeviceScanner): """This class queries a FRITZ!Box router.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index b5ae5ded01a..0878f8b005b 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, DOMAIN, ATTR_ATTRIBUTES, ENTITY_ID_FORMAT) + PLATFORM_SCHEMA, DOMAIN, ATTR_ATTRIBUTES, ENTITY_ID_FORMAT, DeviceScanner) from homeassistant.components.zone import active_zone from homeassistant.helpers.event import track_utc_time_change import homeassistant.helpers.config_validation as cv @@ -131,7 +131,7 @@ def setup_scanner(hass, config: dict, see): return True -class Icloud(object): +class Icloud(DeviceScanner): """Represent an icloud account in Home Assistant.""" def __init__(self, hass, username, password, name, see): diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 10641b3a921..32eb033a284 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -8,9 +8,8 @@ import asyncio from functools import partial import logging -from homeassistant.const import (ATTR_LATITUDE, ATTR_LONGITUDE, - STATE_NOT_HOME, - HTTP_UNPROCESSABLE_ENTITY) +from homeassistant.const import ( + ATTR_LATITUDE, ATTR_LONGITUDE, STATE_NOT_HOME, HTTP_UNPROCESSABLE_ENTITY) from homeassistant.components.http import HomeAssistantView # pylint: disable=unused-import from homeassistant.components.device_tracker import ( # NOQA @@ -64,18 +63,18 @@ class LocativeView(HomeAssistantView): return ('Device id not specified.', HTTP_UNPROCESSABLE_ENTITY) - if 'id' not in data: - _LOGGER.error('Location id not specified.') - return ('Location id not specified.', - HTTP_UNPROCESSABLE_ENTITY) - if 'trigger' not in data: _LOGGER.error('Trigger is not specified.') return ('Trigger is not specified.', HTTP_UNPROCESSABLE_ENTITY) + if 'id' not in data and data['trigger'] != 'test': + _LOGGER.error('Location id not specified.') + return ('Location id not specified.', + HTTP_UNPROCESSABLE_ENTITY) + device = data['device'].replace('-', '') - location_name = data['id'].lower() + location_name = data.get('id', data['trigger']).lower() direction = data['trigger'] gps_location = (data[ATTR_LATITUDE], data[ATTR_LONGITUDE]) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index f9e90fee6c7..9dd9a11c426 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -14,7 +14,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -37,7 +38,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class LuciDeviceScanner(object): +class LuciDeviceScanner(DeviceScanner): """This class queries a wireless router running OpenWrt firmware. Adapted from Tomato scanner. diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index ff6fe2f1e41..d6716dfb9b1 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -11,7 +11,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT) from homeassistant.util import Throttle @@ -47,7 +48,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class NetgearDeviceScanner(object): +class NetgearDeviceScanner(DeviceScanner): """Queries a Netgear wireless router using the SOAP-API.""" def __init__(self, host, username, password, port): diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index 404492e35cf..182826a1f62 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -14,7 +14,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOSTS from homeassistant.util import Throttle @@ -63,7 +64,7 @@ def _arp(ip_address): return None -class NmapDeviceScanner(object): +class NmapDeviceScanner(DeviceScanner): """This class scans for devices using nmap.""" exclude = [] diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/device_tracker/ping.py new file mode 100644 index 00000000000..9c64d37f820 --- /dev/null +++ b/homeassistant/components/device_tracker/ping.py @@ -0,0 +1,92 @@ +""" +Tracks devices by sending a ICMP ping. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.ping/ + +device_tracker: + - platform: ping + count: 2 + hosts: + host_one: pc.local + host_two: 192.168.2.25 +""" +import logging +import subprocess +import sys +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL) +from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant import util +from homeassistant import const +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = [] + +_LOGGER = logging.getLogger(__name__) + +CONF_PING_COUNT = 'count' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(const.CONF_HOSTS): {cv.string: cv.string}, + vol.Optional(CONF_PING_COUNT, default=1): cv.positive_int, +}) + + +class Host: + """Host object with ping detection.""" + + def __init__(self, ip_address, dev_id, hass, config): + """Initialize the Host pinger.""" + self.hass = hass + self.ip_address = ip_address + self.dev_id = dev_id + self._count = config[CONF_PING_COUNT] + if sys.platform == "win32": + self._ping_cmd = ['ping', '-n 1', '-w 1000', self.ip_address] + else: + self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1', + self.ip_address] + + def ping(self): + """Send ICMP ping and return True if success.""" + pinger = subprocess.Popen(self._ping_cmd, stdout=subprocess.PIPE) + try: + pinger.communicate() + return pinger.returncode == 0 + except subprocess.CalledProcessError: + return False + + def update(self, see): + """Update device state by sending one or more ping messages.""" + failed = 0 + while failed < self._count: # check more times if host in unreachable + if self.ping(): + see(dev_id=self.dev_id) + return True + failed += 1 + + _LOGGER.debug("ping KO on ip=%s failed=%d", self.ip_address, failed) + + +def setup_scanner(hass, config, see): + """Setup the Host objects and return the update function.""" + hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in + config[const.CONF_HOSTS].items()] + interval = timedelta(seconds=len(hosts) * config[CONF_PING_COUNT]) + \ + DEFAULT_SCAN_INTERVAL + _LOGGER.info("Started ping tracker with interval=%s on hosts: %s", + interval, ",".join([host.ip_address for host in hosts])) + + def update(now): + """Update all the hosts on every interval time.""" + for host in hosts: + host.update(see) + track_point_in_utc_time(hass, update, now + interval) + return True + + return update(util.dt.utcnow()) diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/device_tracker/snmp.py index 39315ebfd7a..43c0662e568 100644 --- a/homeassistant/components/device_tracker/snmp.py +++ b/homeassistant/components/device_tracker/snmp.py @@ -12,7 +12,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST from homeassistant.util import Throttle @@ -46,7 +47,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class SnmpScanner(object): +class SnmpScanner(DeviceScanner): """Queries any SNMP capable Access Point for connected devices.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/swisscom.py b/homeassistant/components/device_tracker/swisscom.py index 7966fe3ea6a..530e39d3e57 100644 --- a/homeassistant/components/device_tracker/swisscom.py +++ b/homeassistant/components/device_tracker/swisscom.py @@ -12,7 +12,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST from homeassistant.util import Throttle @@ -35,7 +36,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class SwisscomDeviceScanner(object): +class SwisscomDeviceScanner(DeviceScanner): """This class queries a router running Swisscom Internet-Box firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index cf8f808f82a..e9ab4e347eb 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -13,7 +13,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -46,7 +47,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class ThomsonDeviceScanner(object): +class ThomsonDeviceScanner(DeviceScanner): """This class queries a router running THOMSON firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index f463c5a809d..3f01600259b 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -14,7 +14,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -38,7 +39,7 @@ def get_scanner(hass, config): return TomatoDeviceScanner(config[DOMAIN]) -class TomatoDeviceScanner(object): +class TomatoDeviceScanner(DeviceScanner): """This class queries a wireless router running Tomato firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 3e691a7149d..8d476136d23 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -15,7 +15,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -42,7 +43,7 @@ def get_scanner(hass, config): return None -class TplinkDeviceScanner(object): +class TplinkDeviceScanner(DeviceScanner): """This class queries a wireless router running TP-Link firmware.""" def __init__(self, config): diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/device_tracker/trackr.py new file mode 100644 index 00000000000..2eb0def278f --- /dev/null +++ b/homeassistant/components/device_tracker/trackr.py @@ -0,0 +1,79 @@ +""" +Support for the TrackR platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.trackr/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.device_tracker import PLATFORM_SCHEMA +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_utc_time_change + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['pytrackr==0.0.5'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +def setup_scanner(hass, config: dict, see): + """Validate the configuration and return a TrackR scanner.""" + TrackRDeviceScanner(hass, config, see) + return True + + +class TrackRDeviceScanner(object): + """A class representing a TrackR device.""" + + def __init__(self, hass, config: dict, see) -> None: + """Initialize the TrackR device scanner.""" + from pytrackr.api import trackrApiInterface + self.hass = hass + self.api = trackrApiInterface(config.get(CONF_USERNAME), + config.get(CONF_PASSWORD)) + self.see = see + self.devices = self.api.get_trackrs() + self._update_info() + + track_utc_time_change(self.hass, self._update_info, + second=range(0, 60, 30)) + + def _update_info(self, now=None) -> None: + """Update the device info.""" + _LOGGER.debug('Updating devices %s', now) + + # Update self.devices to collect new devices added + # to the users account. + self.devices = self.api.get_trackrs() + + for trackr in self.devices: + trackr.update_state() + trackr_id = trackr.tracker_id() + trackr_device_id = trackr.id() + lost = trackr.lost() + dev_id = trackr.name().replace(" ", "_") + if dev_id is None: + dev_id = trackr_id + location = trackr.last_known_location() + lat = location['latitude'] + lon = location['longitude'] + + attrs = { + 'last_updated': trackr.last_updated(), + 'last_seen': trackr.last_time_seen(), + 'trackr_id': trackr_id, + 'id': trackr_device_id, + 'lost': lost, + 'battery_level': trackr.battery_level() + } + + self.see( + dev_id=dev_id, gps=(lat, lon), attributes=attrs + ) diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index 9d9b8e718d6..083e1599d11 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -14,7 +14,8 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle @@ -37,7 +38,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -class UbusDeviceScanner(object): +class UbusDeviceScanner(DeviceScanner): """ This class queries a wireless router running OpenWrt firmware. diff --git a/homeassistant/components/device_tracker/unifi.py b/homeassistant/components/device_tracker/unifi.py index ab84eb22e04..fa6668638f5 100644 --- a/homeassistant/components/device_tracker/unifi.py +++ b/homeassistant/components/device_tracker/unifi.py @@ -10,7 +10,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.loader as loader -from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD # Unifi package doesn't list urllib3 as a requirement @@ -59,7 +60,7 @@ def get_scanner(hass, config): return UnifiScanner(ctrl) -class UnifiScanner(object): +class UnifiScanner(DeviceScanner): """Provide device_tracker support from Unifi WAP client data.""" def __init__(self, controller): diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/device_tracker/upc_connect.py new file mode 100644 index 00000000000..aafa9824a4e --- /dev/null +++ b/homeassistant/components/device_tracker/upc_connect.py @@ -0,0 +1,164 @@ +""" +Support for UPC ConnectBox router. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.upc_connect/ +""" +import asyncio +import logging +import xml.etree.ElementTree as ET + +import aiohttp +import async_timeout +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.helpers.aiohttp_client import async_create_clientsession + + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_IP = '192.168.0.1' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_IP): cv.string, +}) + +CMD_LOGIN = 15 +CMD_DEVICES = 123 + + +@asyncio.coroutine +def async_get_scanner(hass, config): + """Return the UPC device scanner.""" + scanner = UPCDeviceScanner(hass, config[DOMAIN]) + success_init = yield from scanner.async_login() + + return scanner if success_init else None + + +class UPCDeviceScanner(DeviceScanner): + """This class queries a router running UPC ConnectBox firmware.""" + + def __init__(self, hass, config): + """Initialize the scanner.""" + self.hass = hass + self.host = config[CONF_HOST] + self.password = config[CONF_PASSWORD] + + self.data = {} + self.token = None + + self.headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'Referer': "http://{}/index.html".format(self.host), + 'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/47.0.2526.106 Safari/537.36") + } + + self.websession = async_create_clientsession( + hass, cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop)) + + @asyncio.coroutine + def async_scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + if self.token is None: + reconnect = yield from self.async_login() + if not reconnect: + _LOGGER.error("Not connected to %s", self.host) + return [] + + raw = yield from self._async_ws_function(CMD_DEVICES) + xml_root = ET.fromstring(raw) + + return [mac.text for mac in xml_root.iter('MACAddr')] + + @asyncio.coroutine + def async_get_device_name(self, device): + """The firmware doesn't save the name of the wireless device.""" + return None + + @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): + response = yield from self.websession.get( + "http://{}/common_page/login.html".format(self.host) + ) + + self.token = self._async_get_token() + + # login + data = yield from self._async_ws_function(CMD_LOGIN, { + 'Username': 'NULL', + 'Password': self.password, + }) + + # successfull? + if data.find("successful") != -1: + return True + return False + + except (asyncio.TimeoutError, aiohttp.errors.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.""" + form_data = { + 'token': self.token, + 'fun': function + } + + if additional_form: + form_data.update(additional_form) + + response = None + try: + with async_timeout.timeout(10, loop=self.hass.loop): + response = yield from self.websession.post( + "http://{}/xml/getter.xml".format(self.host), + data=form_data, + headers=self.headers + ) + + # error on UPC webservice + if response.status != 200: + _LOGGER.warning( + "Error %d on %s.", response.status, function) + self.token = None + return + + # load data, store token for next request + raw = yield from response.text() + self.token = self._async_get_token() + + return raw + + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.error("Error on %s", function) + self.token = None + + finally: + if response is not None: + yield from response.release() + + def _async_get_token(self): + """Extract token from cookies.""" + cookie_manager = self.websession.cookie_jar.filter_cookies( + "http://{}".format(self.host)) + + return cookie_manager.get('sessionToken') diff --git a/homeassistant/components/device_tracker/volvooncall.py b/homeassistant/components/device_tracker/volvooncall.py index 36e0d96cdf1..a7dba230831 100644 --- a/homeassistant/components/device_tracker/volvooncall.py +++ b/homeassistant/components/device_tracker/volvooncall.py @@ -14,12 +14,9 @@ from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow from homeassistant.util import slugify from homeassistant.const import ( - CONF_PASSWORD, - CONF_SCAN_INTERVAL, - CONF_USERNAME) + CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME) from homeassistant.components.device_tracker import ( - DEFAULT_SCAN_INTERVAL, - PLATFORM_SCHEMA) + DEFAULT_SCAN_INTERVAL, PLATFORM_SCHEMA) MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1) diff --git a/homeassistant/components/device_tracker/xiaomi.py b/homeassistant/components/device_tracker/xiaomi.py new file mode 100644 index 00000000000..ff53d1fe99f --- /dev/null +++ b/homeassistant/components/device_tracker/xiaomi.py @@ -0,0 +1,145 @@ +""" +Support for Xiaomi Mi routers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.xiaomi/ +""" +import logging +import threading +from datetime import timedelta + +import requests +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.util import Throttle + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME, default='admin'): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +def get_scanner(hass, config): + """Validate the configuration and return a Xiaomi Device Scanner.""" + scanner = XioamiDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class XioamiDeviceScanner(DeviceScanner): + """This class queries a Xiaomi Mi router. + + Adapted from Luci scanner. + """ + + def __init__(self, config): + """Initialize the scanner.""" + host = config[CONF_HOST] + username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.last_results = {} + self.token = _get_token(host, username, password) + + self.host = host + + self.mac2name = None + self.success_init = self.token is not None + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return self.last_results + + def get_device_name(self, device): + """Return the name of the given device or None if we don't know.""" + with self.lock: + if self.mac2name is None: + url = "http://{}/cgi-bin/luci/;stok={}/api/misystem/devicelist" + url = url.format(self.host, self.token) + result = _get_device_list(url) + if result: + hosts = [x for x in result + if 'mac' in x and 'name' in x] + mac2name_list = [ + (x['mac'].upper(), x['name']) for x in hosts] + self.mac2name = dict(mac2name_list) + else: + # Error, handled in the _req_json_rpc + return + return self.mac2name.get(device.upper(), None) + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """Ensure the informations from the router are up to date. + + Returns true if scanning successful. + """ + if not self.success_init: + return False + + with self.lock: + _LOGGER.info('Refreshing device list') + url = "http://{}/cgi-bin/luci/;stok={}/api/misystem/devicelist" + url = url.format(self.host, self.token) + result = _get_device_list(url) + if result: + self.last_results = [] + for device_entry in result: + # Check if the device is marked as connected + if int(device_entry['online']) == 1: + self.last_results.append(device_entry['mac']) + + return True + + return False + + +def _get_device_list(url, **kwargs): + try: + res = requests.get(url, timeout=5, **kwargs) + except requests.exceptions.Timeout: + _LOGGER.exception('Connection to the router timed out') + return + return _extract_result(res, 'list') + + +def _get_token(host, username, password): + """Get authentication token for the given host+username+password.""" + url = 'http://{}/cgi-bin/luci/api/xqsystem/login'.format(host) + data = {'username': username, 'password': password} + try: + res = requests.post(url, data=data, timeout=5) + except requests.exceptions.Timeout: + _LOGGER.exception('Connection to the router timed out') + return + return _extract_result(res, 'token') + + +def _extract_result(res, key_name): + if res.status_code == 200: + try: + result = res.json() + except ValueError: + # If json decoder could not parse the response + _LOGGER.exception('Failed to parse response from mi router') + return + try: + return result[key_name] + except KeyError: + _LOGGER.exception('No %s in response from mi router. %s', + key_name, result) + return + else: + _LOGGER.error('Invalid response from mi router: %s', res) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 32cb205fa0f..32d57b4bf85 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -38,6 +38,7 @@ SERVICE_HANDLERS = { 'directv': ('media_player', 'directv'), 'denonavr': ('media_player', 'denonavr'), 'samsung_tv': ('media_player', 'samsungtv'), + 'yeelight': ('light', 'yeelight'), } CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 57a4f18825a..9b0a2828394 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -7,7 +7,8 @@ from aiohttp import web from homeassistant import core from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET, - STATE_ON, STATE_OFF, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, + SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON, STATE_OFF, + HTTP_BAD_REQUEST, HTTP_NOT_FOUND, ) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS @@ -90,7 +91,7 @@ class HueOneLightStateView(HomeAssistantView): self.config = config @core.callback - def get(self, request, username, entity_id=None): + def get(self, request, username, entity_id): """Process a request to get the state of an individual light.""" hass = request.app['hass'] entity_id = self.config.number_to_entity_id(entity_id) @@ -161,6 +162,9 @@ class HueOneLightChangeView(HomeAssistantView): # Choose general HA domain domain = core.DOMAIN + # Entity needs separate call to turn on + turn_on_needed = False + # Convert the resulting "on" status into the service we need to call service = SERVICE_TURN_ON if result else SERVICE_TURN_OFF @@ -189,11 +193,20 @@ class HueOneLightChangeView(HomeAssistantView): ATTR_SUPPORTED_MEDIA_COMMANDS, 0) if media_commands & SUPPORT_VOLUME_SET == SUPPORT_VOLUME_SET: if brightness is not None: + turn_on_needed = True domain = entity.domain service = SERVICE_VOLUME_SET # Convert 0-100 to 0.0-1.0 data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0 + # If the requested entity is a cover, convert to open_cover/close_cover + elif entity.domain == "cover": + domain = entity.domain + if service == SERVICE_TURN_ON: + service = SERVICE_OPEN_COVER + else: + service = SERVICE_CLOSE_COVER + if entity.domain in config.off_maps_to_on_domains: # Map the off command to on service = SERVICE_TURN_ON @@ -206,7 +219,7 @@ class HueOneLightChangeView(HomeAssistantView): config.cached_states[entity_id] = (result, brightness) # Separate call to turn on needed - if domain != core.DOMAIN: + if turn_on_needed: hass.async_add_job(hass.services.async_call( core.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True)) @@ -288,6 +301,9 @@ def get_entity_state(config, entity): final_brightness = round(min(1.0, level) * 255) else: final_state, final_brightness = cached_state + # Make sure brightness is valid + if final_brightness is None: + final_brightness = 255 if final_state else 0 return (final_state, final_brightness) diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py index 29ce08b2f0a..2c101a227cf 100644 --- a/homeassistant/components/envisalink.py +++ b/homeassistant/components/envisalink.py @@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity from homeassistant.components.discovery import load_platform -REQUIREMENTS = ['pyenvisalink==1.9', 'pydispatcher==2.0.5'] +REQUIREMENTS = ['pyenvisalink==2.0', 'pydispatcher==2.0.5'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'envisalink' diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 79793435625..efb7e0b1496 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -4,6 +4,7 @@ Provides functionality to interact with fans. For more details about this component, please refer to the documentation at https://home-assistant.io/components/fan/ """ +from datetime import timedelta import logging import os @@ -21,7 +22,7 @@ import homeassistant.helpers.config_validation as cv DOMAIN = 'fan' -SCAN_INTERVAL = 30 +SCAN_INTERVAL = timedelta(seconds=30) GROUP_NAME_ALL_FANS = 'all fans' ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS) @@ -32,9 +33,11 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' ATTR_SUPPORTED_FEATURES = 'supported_features' SUPPORT_SET_SPEED = 1 SUPPORT_OSCILLATE = 2 +SUPPORT_DIRECTION = 4 SERVICE_SET_SPEED = 'set_speed' SERVICE_OSCILLATE = 'oscillate' +SERVICE_SET_DIRECTION = 'set_direction' SPEED_OFF = 'off' SPEED_LOW = 'low' @@ -42,15 +45,20 @@ SPEED_MED = 'med' SPEED_MEDIUM = 'medium' SPEED_HIGH = 'high' +DIRECTION_FORWARD = 'forward' +DIRECTION_REVERSE = 'reverse' + ATTR_SPEED = 'speed' ATTR_SPEED_LIST = 'speed_list' ATTR_OSCILLATING = 'oscillating' +ATTR_DIRECTION = 'direction' PROP_TO_ATTR = { 'speed': ATTR_SPEED, 'speed_list': ATTR_SPEED_LIST, 'oscillating': ATTR_OSCILLATING, 'supported_features': ATTR_SUPPORTED_FEATURES, + 'direction': ATTR_DIRECTION, } # type: dict FAN_SET_SPEED_SCHEMA = vol.Schema({ @@ -76,6 +84,11 @@ FAN_TOGGLE_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): cv.entity_ids }) +FAN_SET_DIRECTION_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_DIRECTION): cv.string +}) # type: dict + _LOGGER = logging.getLogger(__name__) @@ -140,6 +153,18 @@ def set_speed(hass, entity_id: str=None, speed: str=None) -> None: hass.services.call(DOMAIN, SERVICE_SET_SPEED, data) +def set_direction(hass, entity_id: str=None, direction: str=None) -> None: + """Set direction for all or specified fan.""" + data = { + key: value for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_DIRECTION, direction), + ] if value is not None + } + + hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data) + + def setup(hass, config: dict) -> None: """Expose fan control via statemachine and services.""" component = EntityComponent( @@ -157,7 +182,8 @@ def setup(hass, config: dict) -> None: service_fun = None for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_SET_SPEED, SERVICE_OSCILLATE]: + SERVICE_SET_SPEED, SERVICE_OSCILLATE, + SERVICE_SET_DIRECTION]: if service_def == service.service: service_fun = service_def break @@ -190,6 +216,10 @@ def setup(hass, config: dict) -> None: descriptions.get(SERVICE_OSCILLATE), schema=FAN_OSCILLATE_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service, + descriptions.get(SERVICE_SET_DIRECTION), + schema=FAN_SET_DIRECTION_SCHEMA) + return True @@ -200,7 +230,11 @@ class FanEntity(ToggleEntity): def set_speed(self: ToggleEntity, speed: str) -> None: """Set the speed of the fan.""" - pass + raise NotImplementedError() + + def set_direction(self: ToggleEntity, direction: str) -> None: + """Set the direction of the fan.""" + raise NotImplementedError() def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: """Turn on the fan.""" @@ -217,14 +251,23 @@ class FanEntity(ToggleEntity): @property def is_on(self): """Return true if the entity is on.""" - return self.state_attributes.get(ATTR_SPEED, STATE_UNKNOWN) \ - not in [SPEED_OFF, STATE_UNKNOWN] + return self.speed not in [SPEED_OFF, STATE_UNKNOWN] + + @property + def speed(self) -> str: + """Return the current speed.""" + return None @property def speed_list(self: ToggleEntity) -> list: """Get the list of available speeds.""" return [] + @property + def current_direction(self) -> str: + """Return the current direction of the fan.""" + return None + @property def state_attributes(self: ToggleEntity) -> dict: """Return optional state attributes.""" diff --git a/homeassistant/components/fan/demo.py b/homeassistant/components/fan/demo.py index ba2deb83125..7ba6b4d67fb 100644 --- a/homeassistant/components/fan/demo.py +++ b/homeassistant/components/fan/demo.py @@ -7,14 +7,14 @@ https://home-assistant.io/components/demo/ from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH, FanEntity, SUPPORT_SET_SPEED, - SUPPORT_OSCILLATE) + SUPPORT_OSCILLATE, SUPPORT_DIRECTION) from homeassistant.const import STATE_OFF FAN_NAME = 'Living Room Fan' FAN_ENTITY_ID = 'fan.living_room_fan' -DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE +DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION # pylint: disable=unused-argument @@ -31,8 +31,9 @@ class DemoFan(FanEntity): def __init__(self, hass, name: str, initial_state: str) -> None: """Initialize the entity.""" self.hass = hass - self.speed = initial_state + self._speed = initial_state self.oscillating = False + self.direction = "forward" self._name = name @property @@ -45,6 +46,11 @@ class DemoFan(FanEntity): """No polling needed for a demo fan.""" return False + @property + def speed(self) -> str: + """Return the current speed.""" + return self._speed + @property def speed_list(self) -> list: """Get the list of available speeds.""" @@ -61,7 +67,12 @@ class DemoFan(FanEntity): def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - self.speed = speed + self._speed = speed + self.update_ha_state() + + def set_direction(self, direction: str) -> None: + """Set the direction of the fan.""" + self.direction = direction self.update_ha_state() def oscillate(self, oscillating: bool) -> None: @@ -69,6 +80,11 @@ class DemoFan(FanEntity): self.oscillating = oscillating self.update_ha_state() + @property + def current_direction(self) -> str: + """Fan direction.""" + return self.direction + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/fan/isy994.py b/homeassistant/components/fan/isy994.py index 2deb938d337..fd0690f4253 100644 --- a/homeassistant/components/fan/isy994.py +++ b/homeassistant/components/fan/isy994.py @@ -64,7 +64,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity): def __init__(self, node) -> None: """Initialize the ISY994 fan device.""" isy.ISYDevice.__init__(self, node) - self.speed = self.state + + @property + def speed(self) -> str: + """Return the current speed.""" + return self.state @property def state(self) -> str: diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py new file mode 100644 index 00000000000..066dbfcb561 --- /dev/null +++ b/homeassistant/components/fan/wink.py @@ -0,0 +1,92 @@ +""" +Support for Wink fans. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fan.wink/ +""" +import logging + +from homeassistant.components.fan import (FanEntity, SPEED_HIGH, + SPEED_LOW, SPEED_MEDIUM, + STATE_UNKNOWN) +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.wink import WinkDevice + +_LOGGER = logging.getLogger(__name__) + +SPEED_LOWEST = "lowest" +SPEED_AUTO = "auto" + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Wink platform.""" + import pywink + + add_devices(WinkFanDevice(fan, hass) for fan in pywink.get_fans()) + + +class WinkFanDevice(WinkDevice, FanEntity): + """Representation of a Wink fan.""" + + def __init__(self, wink, hass): + """Initialize the fan.""" + WinkDevice.__init__(self, wink, hass) + + def set_drection(self: ToggleEntity, direction: str) -> None: + """Set the direction of the fan.""" + self.wink.set_fan_direction(direction) + + def set_speed(self: ToggleEntity, speed: str) -> None: + """Set the speed of the fan.""" + self.wink.set_fan_speed(speed) + + def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: + """Turn on the fan.""" + self.wink.set_state(True) + + def turn_off(self: ToggleEntity, **kwargs) -> None: + """Turn off the fan.""" + self.wink.set_state(False) + + @property + def is_on(self): + """Return true if the entity is on.""" + return self.wink.state() + + @property + def speed(self) -> str: + """Return the current speed.""" + current_wink_speed = self.wink.current_fan_speed() + if SPEED_AUTO == current_wink_speed: + return SPEED_AUTO + if SPEED_LOWEST == current_wink_speed: + return SPEED_LOWEST + if SPEED_LOW == current_wink_speed: + return SPEED_LOW + if SPEED_MEDIUM == current_wink_speed: + return SPEED_MEDIUM + if SPEED_HIGH == current_wink_speed: + return SPEED_HIGH + return STATE_UNKNOWN + + @property + def current_direction(self): + """Return direction of the fan [forward, reverse].""" + return self.wink.current_fan_direction() + + @property + def speed_list(self: ToggleEntity) -> list: + """Get the list of available speeds.""" + wink_supported_speeds = self.wink.fan_speeds() + supported_speeds = [] + if SPEED_AUTO in wink_supported_speeds: + supported_speeds.append(SPEED_AUTO) + if SPEED_LOWEST in wink_supported_speeds: + supported_speeds.append(SPEED_LOWEST) + if SPEED_LOW in wink_supported_speeds: + supported_speeds.append(SPEED_LOW) + if SPEED_MEDIUM in wink_supported_speeds: + supported_speeds.append(SPEED_MEDIUM) + if SPEED_HIGH in wink_supported_speeds: + supported_speeds.append(SPEED_HIGH) + return supported_speeds diff --git a/homeassistant/components/frontend/templates/index.html b/homeassistant/components/frontend/templates/index.html index afa9ca68af9..a13d640a579 100644 --- a/homeassistant/components/frontend/templates/index.html +++ b/homeassistant/components/frontend/templates/index.html @@ -8,6 +8,7 @@ + {% for panel in panels.values() -%} {% endfor -%} diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 6c480254cb8..3af14628008 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,18 +1,18 @@ """DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.""" FINGERPRINTS = { - "core.js": "ad1ebcd0614c98a390d982087a7ca75c", - "frontend.html": "826ee6a4b39c939e31aa468b1ef618f9", - "mdi.html": "46a76f877ac9848899b8ed382427c16f", + "core.js": "22d39af274e1d824ca1302e10971f2d8", + "frontend.html": "61e57194179b27563a05282b58dd4f47", + "mdi.html": "5bb2f1717206bad0d187c2633062c575", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", - "panels/ha-panel-dev-event.html": "c2d5ec676be98d4474d19f94d0262c1e", - "panels/ha-panel-dev-info.html": "a9c07bf281fe9791fb15827ec1286825", - "panels/ha-panel-dev-service.html": "ac74f7ce66fd7136d25c914ea12f4351", - "panels/ha-panel-dev-state.html": "65e5f791cc467561719bf591f1386054", - "panels/ha-panel-dev-template.html": "7d744ab7f7c08b6d6ad42069989de400", - "panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295", + "panels/ha-panel-dev-event.html": "f19840b9a6a46f57cb064b384e1353f5", + "panels/ha-panel-dev-info.html": "3765a371478cc66d677cf6dcc35267c6", + "panels/ha-panel-dev-service.html": "e32bcd3afdf485417a3e20b4fc760776", + "panels/ha-panel-dev-state.html": "8257d99a38358a150eafdb23fa6727e0", + "panels/ha-panel-dev-template.html": "cbb251acabd5e7431058ed507b70522b", + "panels/ha-panel-history.html": "7baeb4bd7d9ce0def4f95eab6f10812e", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", - "panels/ha-panel-logbook.html": "4bc5c8370a85a4215413fbae8f85addb", + "panels/ha-panel-logbook.html": "93de4cee3a2352a6813b5c218421d534", "panels/ha-panel-map.html": "3b0ca63286cbe80f27bd36dbc2434e89", "websocket_test.html": "575de64b431fe11c3785bf96d7813450" } diff --git a/homeassistant/components/frontend/www_static/core.js b/homeassistant/components/frontend/www_static/core.js index 6cab0b713f3..e3134a1ea79 100644 --- a/homeassistant/components/frontend/www_static/core.js +++ b/homeassistant/components/frontend/www_static/core.js @@ -1,4 +1,4 @@ -!(function(){"use strict";function t(t){return t&&t.__esModule?t.default:t}function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}function n(t,e){var n=e.authToken,r=e.host;return xe({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function r(){return Ve.getInitialState()}function i(t,e){var n=e.errorMessage;return t.withMutations((function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)}))}function o(t,e){var n=e.authToken,r=e.host;return Fe({authToken:n,host:r})}function u(){return Ge.getInitialState()}function a(t,e){var n=e.rememberAuth;return n}function s(t){return t.withMutations((function(t){t.set("isStreaming",!0).set("hasError",!1)}))}function c(t){return t.withMutations((function(t){t.set("isStreaming",!1).set("hasError",!0)}))}function f(){return Xe.getInitialState()}function h(t){return{type:"auth",api_password:t}}function l(){return{type:"get_states"}}function p(){return{type:"get_config"}}function _(){return{type:"get_services"}}function d(){return{type:"get_panels"}}function v(t,e,n){var r={type:"call_service",domain:t,service:e};return n&&(r.service_data=n),r}function y(t){var e={type:"subscribe_events"};return t&&(e.event_type=t),e}function m(t){return{type:"unsubscribe_events",subscription:t}}function g(){return{type:"ping"}}function S(t,e){return{type:"result",success:!1,error:{code:t,message:e}}}function b(t){return t.result}function E(t,e){var n=new tn(t,e);return n.connect()}function I(t,e,n,r){void 0===r&&(r=null);var i=t.evaluate(Mo.authInfo),o=i.host+"/api/"+n;return new Promise(function(t,n){var u=new XMLHttpRequest;u.open(e,o,!0),u.setRequestHeader("X-HA-access",i.authToken),u.onload=function(){var e;try{e="application/json"===u.getResponseHeader("content-type")?JSON.parse(u.responseText):u.responseText}catch(t){e=u.responseText}u.status>199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function O(t,e){var n=e.model,r=e.result,i=e.params,o=n.entity;if(!r)return t;var u=i.replace?sn({}):t.get(o),a=Array.isArray(r)?r:[r],s=n.fromJSON||sn;return t.set(o,u.withMutations((function(t){for(var e=0;e6e4}function mt(t,e){var n=e.date;return n.toISOString()}function gt(){return Qr.getInitialState()}function St(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,$r({})):t.withMutations((function(t){r.forEach((function(e){return t.setIn([n,e[0].entity_id],$r(e.map(In.fromJSON)))}))}))}function bt(){return ti.getInitialState()}function Et(t,e){var n=e.stateHistory;return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,ii(e.map(In.fromJSON)))}))}))}function It(){return oi.getInitialState()}function Ot(t,e){var n=e.stateHistory,r=(new Date).getTime();return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,r)})),history.length>1&&t.set(si,r)}))}function wt(){return ci.getInitialState()}function Tt(t,e){t.dispatch(Wr.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function At(t,e){void 0===e&&(e=null),t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),on(t,"GET",n).then((function(e){return t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})}),(function(){return t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})}))}function Dt(t,e){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_START,{date:e}),on(t,"GET","history/period/"+e).then((function(n){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})}),(function(){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_ERROR,{})}))}function Ct(t){var e=t.evaluate(li);return Dt(t,e)}function zt(t){t.registerStores({currentEntityHistoryDate:Qr,entityHistory:ti,isLoadingEntityHistory:ni,recentEntityHistory:oi,recentEntityHistoryUpdated:ci})}function Rt(t){t.registerStores({moreInfoEntityId:Yr})}function Mt(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;o0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function Yt(t){var e=fo[t.hassId];e&&(e.scheduleHealthCheck.clear(),e.conn.close(),fo[t.hassId]=!1)}function Jt(t,e){void 0===e&&(e={});var n=e.syncOnInitialConnect;void 0===n&&(n=!0),Yt(t);var r=t.evaluate(Mo.authToken),i="https:"===document.location.protocol?"wss://":"ws://";i+=document.location.hostname,document.location.port&&(i+=":"+document.location.port),i+="/api/websocket",E(i,{authToken:r}).then((function(e){var r=Bt((function(){return e.ping()}),so);r(),e.socket.addEventListener("message",r),fo[t.hassId]={conn:e,scheduleHealthCheck:r},co.forEach((function(n){return e.subscribeEvents(ao.bind(null,t),n)})),t.batch((function(){t.dispatch(Ye.STREAM_START),n&&io.fetchAll(t)})),e.addEventListener("disconnected",(function(){t.dispatch(Ye.STREAM_ERROR)})),e.addEventListener("ready",(function(){t.batch((function(){t.dispatch(Ye.STREAM_START),io.fetchAll(t)}))}))}))}function Wt(t){t.registerStores({streamStatus:Xe})}function Xt(t,e,n){void 0===n&&(n={});var r=n.rememberAuth;void 0===r&&(r=!1);var i=n.host;void 0===i&&(i=""),t.dispatch(Ue.VALIDATING_AUTH_TOKEN,{authToken:e,host:i}),io.fetchAll(t).then((function(){t.dispatch(Ue.VALID_AUTH_TOKEN,{authToken:e,host:i,rememberAuth:r}),vo.start(t,{syncOnInitialConnect:!1})}),(function(e){void 0===e&&(e={});var n=e.message;void 0===n&&(n=go),t.dispatch(Ue.INVALID_AUTH_TOKEN,{errorMessage:n})}))}function Qt(t){t.dispatch(Ue.LOG_OUT,{})}function Zt(t){t.registerStores({authAttempt:Ve,authCurrent:Ge,rememberAuth:Be})}function $t(){if(!("localStorage"in window))return{};var t=window.localStorage,e="___test";try{return t.setItem(e,e),t.removeItem(e),t}catch(t){return{}}}function te(){var t=new Uo({debug:!1});return t.hassId=Ho++,t}function ee(t,e,n){Object.keys(n).forEach((function(r){var i=n[r];if("register"in i&&i.register(e),"getters"in i&&Object.defineProperty(t,r+"Getters",{value:i.getters,enumerable:!0}),"actions"in i){var o={};Object.getOwnPropertyNames(i.actions).forEach((function(t){"function"==typeof i.actions[t]&&Object.defineProperty(o,t,{value:i.actions[t].bind(null,e),enumerable:!0})})),Object.defineProperty(t,r+"Actions",{value:o,enumerable:!0})}}))}function ne(t,e){return xo(t.attributes.entity_id.map((function(t){return e.get(t)})).filter((function(t){return!!t})))}function re(t){return on(t,"GET","error_log")}function ie(t,e){var n=e.date;return n.toISOString()}function oe(){return Jo.getInitialState()}function ue(t,e){var n=e.date,r=e.entries;return t.set(n,eu(r.map($o.fromJSON)))}function ae(){return nu.getInitialState()}function se(t,e){var n=e.date;return t.set(n,(new Date).getTime())}function ce(){return ou.getInitialState()}function fe(t,e){t.dispatch(Bo.LOGBOOK_DATE_SELECTED,{date:e})}function he(t,e){t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_START,{date:e}),on(t,"GET","logbook/"+e).then((function(n){return t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_SUCCESS,{date:e,entries:n})}),(function(){return t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_ERROR,{})}))}function le(t){return!t||(new Date).getTime()-t>su}function pe(t){t.registerStores({currentLogbookDate:Jo,isLoadingLogbookEntries:Xo,logbookEntries:nu,logbookEntriesUpdated:ou})}function _e(t){return t.set("active",!0)}function de(t){return t.set("active",!1)}function ve(){return Su.getInitialState()}function ye(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",on(t,"POST","notify.html5",{subscription:e,browser:n}).then((function(){return t.dispatch(yu.PUSH_NOTIFICATIONS_SUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Vn.createNotification(t,n),!1}))}function me(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){return on(t,"DELETE","notify.html5",{subscription:e}).then((function(){return e.unsubscribe()})).then((function(){return t.dispatch(yu.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Vn.createNotification(t,n),!1}))}function ge(t){t.registerStores({pushNotifications:Su})}function Se(t,e){return on(t,"POST","template",{template:e})}function be(t){return t.set("isListening",!0)}function Ee(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)}))}function Ie(t,e){var n=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)}))}function Oe(){return ku.getInitialState()}function we(){return ku.getInitialState()}function Te(){return ku.getInitialState()}function Ae(t){return Pu[t.hassId]}function De(t){var e=Ae(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(Lu.VOICE_TRANSMITTING,{finalTranscript:n}),tr.callService(t,"conversation","process",{text:n}).then((function(){t.dispatch(Lu.VOICE_DONE)}),(function(){t.dispatch(Lu.VOICE_ERROR)}))}}function Ce(t){var e=Ae(t);e&&(e.recognition.stop(),Pu[t.hassId]=!1)}function ze(t){De(t),Ce(t)}function Re(t){var e=ze.bind(null,t);e();var n=new webkitSpeechRecognition;Pu[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(Lu.VOICE_START)},n.onerror=function(){return t.dispatch(Lu.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ae(t);if(n){for(var r="",i="",o=e.resultIndex;o=n)}function c(t,e){return h(t,e,0)}function f(t,e){return h(t,e,e)}function h(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function l(t){return v(t)?t:C(t)}function p(t){return y(t)?t:z(t)}function _(t){return m(t)?t:R(t)}function d(t){return v(t)&&!g(t)?t:M(t)}function v(t){return!(!t||!t[dn])}function y(t){return!(!t||!t[vn])}function m(t){return!(!t||!t[yn])}function g(t){return y(t)||m(t)}function S(t){return!(!t||!t[mn])}function b(t){this.next=t}function E(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function I(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(En&&t[En]||t[In]);if("function"==typeof e)return e}function D(t){return t&&"number"==typeof t.length}function C(t){return null===t||void 0===t?U():v(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?U().toKeyedSeq():v(t)?y(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?U():v(t)?y(t)?t.entrySeq():t.toIndexedSeq():x(t)}function M(t){return(null===t||void 0===t?U():v(t)?y(t)?t.entrySeq():t:x(t)).toSetSeq()}function L(t){this._array=t,this.size=t.length}function j(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function P(t){return!(!t||!t[wn])}function U(){return Tn||(Tn=new L([]))}function H(t){var e=Array.isArray(t)?new L(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new j(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=q(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=q(t)||"object"==typeof t&&new j(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function q(t){return D(t)?new L(t):w(t)?new k(t):O(t)?new N(t):void 0}function F(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new b(function(){var t=i[n?o-u:u];return u++>o?I():E(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(){throw TypeError("Abstract")}function B(){}function Y(){}function J(){}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){return e?Q(e,t,"",{"":t}):Z(t)}function Q(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map((function(n,r){return Q(t,n,r,e)}))):$(e)?t.call(r,n,z(e).map((function(n,r){return Q(t,n,r,e)}))):e}function Z(t){return Array.isArray(t)?R(t).map(Z).toList():$(t)?z(t).map(Z).toMap():t}function $(t){return t&&(t.constructor===Object||void 0===t.constructor)}function tt(t){return t>>>1&1073741824|3221225471&t}function et(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return tt(n)}return"string"===e?t.length>jn?nt(t):rt(t):"function"==typeof t.hashCode?t.hashCode():it(t)}function nt(t){var e=Pn[t];return void 0===e&&(e=rt(t),kn===Nn&&(kn=0,Pn={}),kn++,Pn[t]=e),e}function rt(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ut(t,e){if(!t)throw new Error(e)}function at(t){ut(t!==1/0,"Cannot perform this action with an infinite size.")}function st(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ct(t){this._iter=t,this.size=t.size}function ft(t){this._iter=t,this.size=t.size}function ht(t){this._iter=t,this.size=t.size}function lt(t){var e=Lt(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=jt,e.__iterateUncached=function(e,n){var r=this;return t.__iterate((function(t,n){return e(n,t,r)!==!1}),n)},e.__iteratorUncached=function(e,n){if(e===bn){var r=t.__iterator(e,n);return new b(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===Sn?gn:Sn,n)},e}function pt(t,e,n){var r=Lt(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,ln);return o===ln?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate((function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1}),i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(bn,i);return new b(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return E(r,a,e.call(n,u[1],a,t),i)})},r}function _t(t,e){var n=Lt(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=lt(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=jt,n.__iterate=function(e,n){var r=this;return t.__iterate((function(t,n){return e(t,n,r)}),!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function dt(t,e,n,r){var i=Lt(t);return r&&(i.has=function(r){var i=t.get(r,ln);return i!==ln&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,ln);return o!==ln&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate((function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)}),o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(bn,o),a=0;return new b(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return E(i,r?c:a++,f,o)}})},i}function vt(t,e,n){var r=Pt().asMutable();return t.__iterate((function(i,o){r.update(e.call(n,i,o,t),0,(function(t){return t+1}))})),r.asImmutable()}function yt(t,e,n){var r=y(t),i=(S(t)?Ie():Pt()).asMutable();t.__iterate((function(o,u){i.update(e.call(n,o,u,t),(function(t){return t=t||[],t.push(r?[u,o]:o),t}))}));var o=Mt(t);return i.map((function(e){return Ct(t,o(e))}))}function mt(t,e,n,r){var i=t.size;if(void 0!==e&&(e|=0),void 0!==n&&(n|=0),s(e,n,i))return t;var o=c(e,i),a=f(n,i);if(o!==o||a!==a)return mt(t.toSeq().cacheResult(),e,n,r);var h,l=a-o;l===l&&(h=l<0?0:l);var p=Lt(t);return p.size=0===h?h:t.size&&h||void 0,!r&&P(t)&&h>=0&&(p.get=function(e,n){return e=u(this,e),e>=0&&eh)return I();var t=i.next();return r||e===Sn?t:e===gn?E(e,a-1,void 0,t):E(e,a-1,t.value[1],t)})},p}function gt(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate((function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)})),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(bn,i),a=!0;return new b(function(){if(!a)return I();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===bn?t:E(r,s,c,t):(a=!1,I())})},r}function St(t,e,n,r){var i=Lt(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate((function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)})),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(bn,o),s=!0,c=0;return new b(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===Sn?t:i===gn?E(i,c++,void 0,t):E(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===bn?t:E(i,o,f,t)})},i}function bt(t,e){var n=y(t),r=[t].concat(e).map((function(t){return v(t)?n&&(t=p(t)):t=n?H(t):x(Array.isArray(t)?t:[t]),t})).filter((function(t){return 0!==t.size}));if(0===r.length)return t;if(1===r.length){var i=r[0];if(i===t||n&&y(i)||m(t)&&m(i))return i}var o=new L(r);return n?o=o.toKeyedSeq():m(t)||(o=o.toSetSeq()),o=o.flatten(!0),o.size=r.reduce((function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}}),0),o}function Et(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){function o(t,s){var c=this;t.__iterate((function(t,i){return(!e||s0}function Dt(t,e,n){var r=Lt(t);return r.size=new L(n).map((function(t){return t.size})).min(),r.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(Sn,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},r.__iteratorUncached=function(t,r){var i=n.map((function(t){return t=l(t),T(r?t.reverse():t)})),o=0,u=!1; -return new b(function(){var n;return u||(n=i.map((function(t){return t.next()})),u=n.some((function(t){return t.done}))),u?I():E(t,o++,e.apply(null,n.map((function(t){return t.value}))))})},r}function Ct(t,e){return P(t)?e:t.constructor(e)}function zt(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Rt(t){return at(t.size),o(t)}function Mt(t){return y(t)?p:m(t)?_:d}function Lt(t){return Object.create((y(t)?z:m(t)?R:M).prototype)}function jt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):C.prototype.cacheResult.call(this)}function Nt(t,e){return t>e?1:t>>n)&hn,a=(0===n?r:r>>>n)&hn,s=u===a?[Zt(t,e,n+cn,r,i)]:(o=new Ft(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new Vt(t,o+1,u)}function ne(t,e,n){for(var r=[],i=0;i>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function ae(t,e,n,r){var o=r?t:i(t);return o[e]=n,o}function se(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&ro?0:o-n,c=u-n;return c>fn&&(c=fn),function(){if(i===c)return Yn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>fn&&(f=fn),function(){for(;;){if(a){var t=a();if(t!==Yn)return t;a=null}if(c===f)return Yn;var o=e?--f:c++;a=n(s&&s[o],r-cn,i+(o<=t.size||n<0)return t.withMutations((function(t){n<0?Se(t,n).set(0,r):Se(t,0,n+1).set(n,r)}));n+=t._origin;var i=t._tail,o=t._root,a=e(_n);return n>=Ee(t._capacity)?i=ye(i,t.__ownerID,0,n,r,a):o=ye(o,t.__ownerID,t._level,n,r,a),a.value?t.__ownerID?(t._root=o,t._tail=i,t.__hash=void 0,t.__altered=!0,t):_e(t._origin,t._capacity,t._level,o,i):t}function ye(t,e,r,i,o,u){var a=i>>>r&hn,s=t&&a0){var f=t&&t.array[a],h=ye(f,e,r-cn,i,o,u);return h===f?t:(c=me(t,e),c.array[a]=h,c)}return s&&t.array[a]===o?t:(n(u),c=me(t,e),void 0===o&&a===c.array.length-1?c.array.pop():c.array[a]=o,c)}function me(t,e){return e&&t&&e===t.ownerID?t:new le(t?t.array.slice():[],e)}function ge(t,e){if(e>=Ee(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&hn],r-=cn;return n}}function Se(t,e,n){void 0!==e&&(e|=0),void 0!==n&&(n|=0);var i=t.__ownerID||new r,o=t._origin,u=t._capacity,a=o+e,s=void 0===n?u:n<0?u+n:o+n;if(a===o&&s===u)return t;if(a>=s)return t.clear();for(var c=t._level,f=t._root,h=0;a+h<0;)f=new le(f&&f.array.length?[void 0,f]:[],i),c+=cn,h+=1<=1<l?new le([],i):_;if(_&&p>l&&acn;y-=cn){var m=l>>>y&hn;v=v.array[m]=me(v.array[m],i)}v.array[l>>>cn&hn]=_}if(s=p)a-=p,s-=p,c=cn,f=null,d=d&&d.removeBefore(i,0,a);else if(a>o||p>>c&hn;if(g!==p>>>c&hn)break;g&&(h+=(1<o&&(f=f.removeBefore(i,c,a-h)),f&&pi&&(i=a.size),v(u)||(a=a.map((function(t){return X(t)}))),r.push(a)}return i>t.size&&(t=t.setSize(i)),ie(t,e,r)}function Ee(t){return t>>cn<=fn&&u.size>=2*o.size?(i=u.filter((function(t,e){return void 0!==t&&a!==e})),r=i.toKeyedSeq().map((function(t){return t[0]})).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):we(r,i)}function De(t){return null===t||void 0===t?Re():Ce(t)?t:Re().unshiftAll(t)}function Ce(t){return!(!t||!t[Wn])}function ze(t,e,n,r){var i=Object.create(Xn);return i.size=t,i._head=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Re(){return Qn||(Qn=ze(0))}function Me(t){return null===t||void 0===t?ke():Le(t)&&!S(t)?t:ke().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function Le(t){return!(!t||!t[Zn])}function je(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function Ne(t,e){var n=Object.create($n);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function ke(){return tr||(tr=Ne(Jt()))}function Pe(t){return null===t||void 0===t?xe():Ue(t)?t:xe().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function Ue(t){return Le(t)&&S(t)}function He(t,e){var n=Object.create(er);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function xe(){return nr||(nr=He(Te()))}function Ve(t,e){var n,r=function(o){if(o instanceof r)return o;if(!(this instanceof r))return new r(o);if(!n){n=!0;var u=Object.keys(t);Ge(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=Pt(o)},i=r.prototype=Object.create(rr);return i.constructor=r,r}function qe(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return r._map=e,r.__ownerID=n,r}function Fe(t){return t._name||t.constructor.name||"Record"}function Ge(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(t){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){ut(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Be(t,e){if(t===e)return!0;if(!v(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||y(t)!==y(e)||m(t)!==m(e)||S(t)!==S(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!g(t);if(S(t)){var r=t.entries();return e.every((function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))}))&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var o=t;t=e,e=o}var u=!0,a=e.__iterate((function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,ln)):!W(t.get(r,ln),e))return u=!1,!1}));return u&&t.size===a}function Ye(t,e,n){if(!(this instanceof Ye))return new Ye(t,e,n);if(ut(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),ee?-1:0}function rn(t){if(t.size===1/0)return 0;var e=S(t),n=y(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+un(et(t),et(e))|0}:function(t,e){r=r+un(et(t),et(e))|0}:e?function(t){r=31*r+et(t)|0}:function(t){r=r+et(t)|0});return on(i,r)}function on(t,e){return e=Dn(e,3432918353),e=Dn(e<<15|e>>>-15,461845907),e=Dn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Dn(e^e>>>16,2246822507),e=Dn(e^e>>>13,3266489909),e=tt(e^e>>>16)}function un(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var an=Array.prototype.slice,sn="delete",cn=5,fn=1<r?I():E(t,i,n[e?r-i++:i++])})},t(j,z),j.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},j.prototype.has=function(t){return this._object.hasOwnProperty(t)},j.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},j.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new b(function(){var u=r[e?i-o:o];return o++>i?I():E(t,u,n[u])})},j.prototype[mn]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new b(I);var i=0;return new b(function(){var e=r.next();return e.done?e:E(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return E(t,i,r[i++])})};var Tn;t(K,l),t(B,K),t(Y,K),t(J,K),K.Keyed=B,K.Indexed=Y,K.Set=J;var An,Dn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t|=0,e|=0;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Cn=Object.isExtensible,zn=(function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}})(),Rn="function"==typeof WeakMap;Rn&&(An=new WeakMap);var Mn=0,Ln="__immutablehash__";"function"==typeof Symbol&&(Ln=Symbol(Ln));var jn=16,Nn=255,kn=0,Pn={};t(st,z),st.prototype.get=function(t,e){return this._iter.get(t,e)},st.prototype.has=function(t){return this._iter.has(t)},st.prototype.valueSeq=function(){return this._iter.valueSeq()},st.prototype.reverse=function(){var t=this,e=_t(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=pt(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},st.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Rt(this):0,function(i){return t(i,e?--n:n++,r)}),e)},st.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(Sn,e),r=e?Rt(this):0;return new b(function(){var i=n.next();return i.done?i:E(t,e?--r:r++,i.value,i)})},st.prototype[mn]=!0,t(ct,R),ct.prototype.includes=function(t){return this._iter.includes(t)},ct.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate((function(e){return t(e,r++,n)}),e)},ct.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e),r=0;return new b(function(){var e=n.next();return e.done?e:E(t,r++,e.value,e)})},t(ft,M),ft.prototype.has=function(t){return this._iter.includes(t)},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){return t(e,e,n)}),e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e);return new b(function(){var e=n.next();return e.done?e:E(t,e.value,e.value,e)})},t(ht,z),ht.prototype.entrySeq=function(){return this._iter.toSeq()},ht.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){if(e){zt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}}),e)},ht.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e);return new b(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){zt(r);var i=v(r);return E(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=ft.prototype.cacheResult=ht.prototype.cacheResult=jt,t(Pt,B),Pt.prototype.toString=function(){return this.__toString("Map {","}")},Pt.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},Pt.prototype.set=function(t,e){return Wt(this,t,e)},Pt.prototype.setIn=function(t,e){return this.updateIn(t,ln,(function(){return e}))},Pt.prototype.remove=function(t){return Wt(this,t,ln)},Pt.prototype.deleteIn=function(t){return this.updateIn(t,(function(){return ln}))},Pt.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},Pt.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,kt(t),e,n);return r===ln?void 0:r},Pt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Jt()},Pt.prototype.merge=function(){return ne(this,void 0,arguments)},Pt.prototype.mergeWith=function(t){var e=an.call(arguments,1);return ne(this,t,e)},Pt.prototype.mergeIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]}))},Pt.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},Pt.prototype.mergeDeepWith=function(t){var e=an.call(arguments,1);return ne(this,re(t),e)},Pt.prototype.mergeDeepIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]}))},Pt.prototype.sort=function(t){return Ie(wt(this,t))},Pt.prototype.sortBy=function(t,e){return Ie(wt(this,e,t))},Pt.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},Pt.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},Pt.prototype.asImmutable=function(){return this.__ensureOwner()},Pt.prototype.wasAltered=function(){return this.__altered},Pt.prototype.__iterator=function(t,e){return new Gt(this,t,e)},Pt.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate((function(e){return r++,t(e[1],e[0],n)}),e),r},Pt.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Yt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Pt.isMap=Ut;var Un="@@__IMMUTABLE_MAP__@@",Hn=Pt.prototype;Hn[Un]=!0,Hn[sn]=Hn.remove,Hn.removeIn=Hn.deleteIn,Ht.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Vn)return $t(t,f,o,u);var _=t&&t===this.ownerID,d=_?f:i(f);return p?c?h===l-1?d.pop():d[h]=d.pop():d[h]=[o,u]:d.push([o,u]),_?(this.entries=d,this):new Ht(t,d)}},xt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&hn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ue(o&i-1)].get(t+cn,e,n,r)},xt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=1<=qn)return ee(t,l,c,a,_);if(f&&!_&&2===l.length&&Qt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&Qt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?ae(l,h,_,d):ce(l,h,d):se(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new xt(t,v,y)},Vt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&hn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Vt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=i===ln,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Xt(f,t,e+cn,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&hn;if(r>=this.array.length)return new le([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-cn,n),i===u&&o)return this}if(o&&!i)return this;var a=me(this,t);if(!o)for(var s=0;s>>e&hn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-cn,n),i===o&&r===this.array.length-1)return this}var u=me(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Bn,Yn={};t(Ie,Pt),Ie.of=function(){return this(arguments)},Ie.prototype.toString=function(){return this.__toString("OrderedMap {","}")},Ie.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},Ie.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Te()},Ie.prototype.set=function(t,e){return Ae(this,t,e)},Ie.prototype.remove=function(t){return Ae(this,t,ln)},Ie.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Ie.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate((function(e){return e&&t(e[1],e[0],n)}),e)},Ie.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},Ie.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?we(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},Ie.isOrderedMap=Oe,Ie.prototype[mn]=!0,Ie.prototype[sn]=Ie.prototype.remove;var Jn;t(De,Y),De.of=function(){return this(arguments)},De.prototype.toString=function(){return this.__toString("Stack [","]")},De.prototype.get=function(t,e){var n=this._head;for(t=u(this,t);n&&t--;)n=n.next;return n?n.value:e},De.prototype.peek=function(){return this._head&&this._head.value},De.prototype.push=function(){var t=arguments;if(0===arguments.length)return this;for(var e=this.size+arguments.length,n=this._head,r=arguments.length-1;r>=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pushAll=function(t){if(t=_(t),0===t.size)return this;at(t.size);var e=this.size,n=this._head;return t.reverse().forEach((function(t){e++,n={value:t,next:n}})),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pop=function(){return this.slice(1)},De.prototype.unshift=function(){return this.push.apply(this,arguments)},De.prototype.unshiftAll=function(t){return this.pushAll(t)},De.prototype.shift=function(){return this.pop.apply(this,arguments)},De.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Re()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=f(e,this.size);if(r!==this.size)return Y.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):ze(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?ze(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new b(function(){if(r){var e=r.value;return r=r.next,E(t,n++,e)}return I()})},De.isStack=Ce;var Wn="@@__IMMUTABLE_STACK__@@",Xn=De.prototype;Xn[Wn]=!0,Xn.withMutations=Hn.withMutations,Xn.asMutable=Hn.asMutable,Xn.asImmutable=Hn.asImmutable,Xn.wasAltered=Hn.wasAltered;var Qn;t(Me,J),Me.of=function(){return this(arguments)},Me.fromKeys=function(t){return this(p(t).keySeq())},Me.prototype.toString=function(){return this.__toString("Set {","}")},Me.prototype.has=function(t){return this._map.has(t)},Me.prototype.add=function(t){return je(this,this._map.set(t,!0))},Me.prototype.remove=function(t){return je(this,this._map.remove(t))},Me.prototype.clear=function(){return je(this,this._map.clear())},Me.prototype.union=function(){var t=an.call(arguments,0);return t=t.filter((function(t){return 0!==t.size})),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations((function(e){for(var n=0;n1?" by "+this._step:"")+" ]"},Ye.prototype.get=function(t,e){return this.has(t)?this._start+u(this,t)*this._step:e},Ye.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=0&&nn?I():E(t,o++,u)})},Ye.prototype.equals=function(t){return t instanceof Ye?this._start===t._start&&this._end===t._end&&this._step===t._step:Be(this,t)};var ir;t(Je,R),Je.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Je.prototype.get=function(t,e){return this.has(t)?this._value:e},Je.prototype.includes=function(t){return W(this._value,t)},Je.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Je(this._value,f(e,n)-c(t,n))},Je.prototype.reverse=function(){return this},Je.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Je.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Je.prototype.__iterate=function(t,e){for(var n=this,r=0;rthis.size?e:this.find((function(e,n){return n===t}),void 0,e)},has:function(t){return t=u(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){var e=this.reactorState.get("dirtyStores");if(0!==e.size){var n=c.default.Set().withMutations((function(n){n.union(t.observerState.get("any")),e.forEach((function(e){var r=t.observerState.getIn(["stores",e]);r&&n.union(r)}))}));n.forEach((function(e){var n=t.observerState.getIn(["observersMap",e]);if(n){var r=n.get("getter"),i=n.get("handler"),o=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c.default.is(a,s)||i.call(null,s)}}));var r=p.resetDirtyStores(this.reactorState);this.prevReactorState=r,this.reactorState=r}}}},{key:"batchStart",value:function(){this.__batchDepth++}},{key:"batchEnd",value:function(){if(this.__batchDepth--,this.__batchDepth<=0){this.__isDispatching=!0;try{this.__notify()}catch(t){throw this.__isDispatching=!1,t}this.__isDispatching=!1}}}]),t})();e.default=(0,y.toFactory)(g),t.exports=e.default},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n={};return(0,o.each)(e,(function(e,r){n[r]=t.evaluate(e)})),n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(4);e.default=function(t){return{getInitialState:function(){return i(t,this.getDataBindings())},componentDidMount:function(){var e=this;this.__unwatchFns=[],(0,o.each)(this.getDataBindings(),(function(n,i){var o=t.observe(n,(function(t){e.setState(r({},i,t))}));e.__unwatchFns.push(o)}))},componentWillUnmount:function(){for(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){return new M({result:t,reactorState:e})}function o(t,e){return t.withMutations((function(t){(0,R.each)(e,(function(e,n){t.getIn(["stores",n])&&console.warn("Store already defined for id = "+n);var r=e.getInitialState();if(void 0===r&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(r))throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable");t.update("stores",(function(t){return t.set(n,e)})).update("state",(function(t){return t.set(n,r)})).update("dirtyStores",(function(t){return t.add(n)})).update("storeStates",(function(t){return I(t,[n])}))})),E(t)}))}function u(t,e){return t.withMutations((function(t){(0,R.each)(e,(function(e,n){t.update("stores",(function(t){return t.set(n,e)}))}))}))}function a(t,e,n){if(void 0===e&&f(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var r=t.get("state"),i=t.get("dirtyStores"),o=r.withMutations((function(r){A.default.dispatchStart(t,e,n),t.get("stores").forEach((function(o,u){var a=r.get(u),s=void 0;try{s=o.handle(a,e,n)}catch(e){throw A.default.dispatchError(t,e.message),e}if(void 0===s&&f(t,"throwOnUndefinedStoreReturnValue")){var c="Store handler must return a value, did you forget a return statement";throw A.default.dispatchError(t,c),new Error(c)}r.set(u,s),a!==s&&(i=i.add(u))})),A.default.dispatchEnd(t,r,i)})),u=t.set("state",o).set("dirtyStores",i).update("storeStates",(function(t){return I(t,i)}));return E(u)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations((function(r){(0,R.each)(e,(function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),n.push(i))}}))})),i=w.default.Set(n);return t.update("state",(function(t){return t.merge(r)})).update("dirtyStores",(function(t){return t.union(i)})).update("storeStates",(function(t){return I(t,n)}))}function c(t,e,n){var r=e;(0,z.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),u=w.default.Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=0===o.size?t.update("any",(function(t){return t.add(i)})):t.withMutations((function(t){o.forEach((function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,w.default.Set()),t.updateIn(["stores",e],(function(t){return t.add(i)}))}))})),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(t,e,n){var r=t.get("observersMap").filter((function(t){var r=t.get("getterKey"),i=!n||t.get("handler")===n;return!!i&&((0,z.isKeyPath)(e)&&(0,z.isKeyPath)(r)?(0,z.isEqual)(e,r):e===r)}));return t.withMutations((function(t){r.forEach((function(e){return l(t,e)}))}))}function l(t,e){return t.withMutations((function(t){var n=e.get("id"),r=e.get("storeDeps");0===r.size?t.update("any",(function(t){return t.remove(n)})):r.forEach((function(e){t.updateIn(["stores",e],(function(t){return t?t.remove(n):t}))})),t.removeIn(["observersMap",n])}))}function p(t){var e=t.get("state");return t.withMutations((function(t){var n=t.get("stores"),r=n.keySeq().toJS();n.forEach((function(n,r){var i=e.get(r),o=n.handleReset(i);if(void 0===o&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(o))throw new Error("Store reset state must be an immutable value, did you forget to call toImmutable");t.setIn(["state",r],o)})),t.update("storeStates",(function(t){return I(t,r)})),v(t)}))}function _(t,e){var n=t.get("state");if((0,z.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,C.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");if(g(t,e))return i(b(t,e),t);var r=(0,C.getDeps)(e).map((function(e){return _(t,e).result})),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,S(t,e,o))}function d(t){var e={};return t.get("stores").forEach((function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)})),e}function v(t){return t.set("dirtyStores",w.default.Set())}function y(t){return t}function m(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=m(t,e);if(!n)return!1;var r=n.get("storeStates");return 0!==r.size&&r.every((function(e,n){return t.getIn(["storeStates",n])===e}))}function S(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),u=(0,D.toImmutable)({}).withMutations((function(e){o.forEach((function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)}))}));return t.setIn(["cache",r],w.default.Map({value:n,storeStates:u,dispatchId:i}))}function b(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function E(t){return t.update("dispatchId",(function(t){return t+1}))}function I(t,e){return t.withMutations((function(t){e.forEach((function(e){var n=t.has(e)?t.get(e)+1:1;t.set(e,n)}))}))}Object.defineProperty(e,"__esModule",{value:!0}),e.registerStores=o,e.replaceStores=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var O=n(3),w=r(O),T=n(9),A=r(T),D=n(5),C=n(10),z=n(11),R=n(4),M=w.default.Record({result:null,reactorState:null})},function(t,e,n){var r=n(8);e.dispatchStart=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.groupCollapsed("Dispatch: %s",e),console.group("payload"),console.debug(n),console.groupEnd())},e.dispatchError=function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},e.dispatchEnd=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&((0,r.getOption)(t,"logDirtyStores")&&console.log("Stores updated:",n.toList().toJS()),(0,r.getOption)(t,"logAppState")&&console.debug("Dispatch done, new state: ",e.toJS()),console.groupEnd())}},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h.default.Set());var n=h.default.Set().withMutations((function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach((function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}}))}));return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(t).map((function(t){return t.first()})).filter((function(t){return!!t}));return Object.defineProperty(t,"__storeDeps",{enumerable:!1,configurable:!1,writable:!1,value:e}),e}Object.defineProperty(e,"__esModule",{value:!0});var f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e.default={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,s.isArray)(t)&&!(0,s.isFunction)(t[t.length-1])}function o(t,e){var n=a.default.List(t),r=a.default.List(e);return a.default.is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=i;var o=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=o;var u=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,r.Map)(),storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:i});e.ReactorState=u;var a=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=a}])}))})),Ne=t(je),ke=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n},Pe=ke,Ue=Pe({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),He=Ne.Store,xe=Ne.toImmutable,Ve=new He({getInitialState:function(){return xe({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Ue.VALIDATING_AUTH_TOKEN,n),this.on(Ue.VALID_AUTH_TOKEN,r),this.on(Ue.INVALID_AUTH_TOKEN,i)}}),qe=Ne.Store,Fe=Ne.toImmutable,Ge=new qe({getInitialState:function(){return Fe({authToken:null,host:""})},initialize:function(){this.on(Ue.VALID_AUTH_TOKEN,o),this.on(Ue.LOG_OUT,u)}}),Ke=Ne.Store,Be=new Ke({getInitialState:function(){return!0},initialize:function(){this.on(Ue.VALID_AUTH_TOKEN,a)}}),Ye=Pe({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),Je=Ne.Store,We=Ne.toImmutable,Xe=new Je({getInitialState:function(){return We({isStreaming:!1,hasError:!1})},initialize:function(){this.on(Ye.STREAM_START,s),this.on(Ye.STREAM_ERROR,c),this.on(Ye.LOG_OUT,f)}}),Qe=1,Ze=2,$e=3,tn=function(t,e){this.url=t,this.options=e||{},this.commandId=1,this.commands={},this.connectionTries=0,this.eventListeners={},this.closeRequested=!1};tn.prototype.addEventListener=function(t,e){var n=this.eventListeners[t];n||(n=this.eventListeners[t]=[]),n.push(e)},tn.prototype.fireEvent=function(t){var e=this;(this.eventListeners[t]||[]).forEach((function(t){return t(e)}))},tn.prototype.connect=function(){var t=this;return new Promise(function(e,n){var r=t.commands;Object.keys(r).forEach((function(t){var e=r[t];e.reject&&e.reject(S($e,"Connection lost"))}));var i=!1;t.connectionTries+=1,t.socket=new WebSocket(t.url),t.socket.addEventListener("open",(function(){t.connectionTries=0})),t.socket.addEventListener("message",(function(o){var u=JSON.parse(o.data);switch(u.type){case"event":t.commands[u.id].eventCallback(u.event);break;case"result":u.success?t.commands[u.id].resolve(u):t.commands[u.id].reject(u.error),delete t.commands[u.id];break;case"pong":break;case"auth_required":t.sendMessage(h(t.options.authToken));break;case"auth_invalid":n(Ze),i=!0;break;case"auth_ok":e(t),t.fireEvent("ready"),t.commandId=1,t.commands={},Object.keys(r).forEach((function(e){var n=r[e];n.eventType&&t.subscribeEvents(n.eventCallback,n.eventType).then((function(t){n.unsubscribe=t}))}))}})),t.socket.addEventListener("close",(function(){if(!i&&!t.closeRequested){0===t.connectionTries?t.fireEvent("disconnected"):n(Qe);var e=1e3*Math.min(t.connectionTries,5);setTimeout((function(){return t.connect()}),e)}}))})},tn.prototype.close=function(){this.closeRequested=!0,this.socket.close()},tn.prototype.getStates=function(){return this.sendMessagePromise(l()).then(b)},tn.prototype.getServices=function(){return this.sendMessagePromise(_()).then(b)},tn.prototype.getPanels=function(){return this.sendMessagePromise(d()).then(b)},tn.prototype.getConfig=function(){return this.sendMessagePromise(p()).then(b)},tn.prototype.callService=function(t,e,n){return this.sendMessagePromise(v(t,e,n))},tn.prototype.subscribeEvents=function(t,e){var n=this;return this.sendMessagePromise(y(e)).then((function(r){var i={eventCallback:t,eventType:e,unsubscribe:function(){return n.sendMessagePromise(m(r.id)).then((function(){delete n.commands[r.id]}))}};return n.commands[r.id]=i,function(){return i.unsubscribe()}}))},tn.prototype.ping=function(){return this.sendMessagePromise(g())},tn.prototype.sendMessage=function(t){this.socket.send(JSON.stringify(t))},tn.prototype.sendMessagePromise=function(t){var e=this;return new Promise(function(n,r){e.commandId+=1;var i=e.commandId;t.id=i,e.commands[i]={resolve:n,reject:r},e.sendMessage(t)})};var en=Pe({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),nn=Ne.Store,rn=new nn({getInitialState:function(){return!0},initialize:function(){this.on(en.API_FETCH_ALL_START,(function(){return!0})),this.on(en.API_FETCH_ALL_SUCCESS,(function(){return!1})),this.on(en.API_FETCH_ALL_FAIL,(function(){return!1})),this.on(en.LOG_OUT,(function(){return!1}))}}),on=I,un=Pe({API_FETCH_SUCCESS:null,API_FETCH_START:null,API_FETCH_FAIL:null,API_SAVE_SUCCESS:null,API_SAVE_START:null,API_SAVE_FAIL:null,API_DELETE_SUCCESS:null,API_DELETE_START:null,API_DELETE_FAIL:null,LOG_OUT:null}),an=Ne.Store,sn=Ne.toImmutable,cn=new an({getInitialState:function(){return sn({})},initialize:function(){var t=this;this.on(un.API_FETCH_SUCCESS,O),this.on(un.API_SAVE_SUCCESS,O),this.on(un.API_DELETE_SUCCESS,w),this.on(un.LOG_OUT,(function(){return t.getInitialState()}))}}),fn=Object.prototype.hasOwnProperty,hn=Object.prototype.propertyIsEnumerable,ln=A()?Object.assign:function(t,e){for(var n,r,i=arguments,o=T(t),u=1;u199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function O(t,e){var n=e.model,r=e.result,i=e.params,o=n.entity;if(!r)return t;var u=i.replace?sn({}):t.get(o),a=Array.isArray(r)?r:[r],s=n.fromJSON||sn;return t.set(o,u.withMutations((function(t){for(var e=0;e6e4}function mt(t,e){var n=e.date;return n.toISOString()}function gt(){return Qr.getInitialState()}function St(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,$r({})):t.withMutations((function(t){r.forEach((function(e){return t.setIn([n,e[0].entity_id],$r(e.map(In.fromJSON)))}))}))}function bt(){return ti.getInitialState()}function Et(t,e){var n=e.stateHistory;return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,ii(e.map(In.fromJSON)))}))}))}function It(){return oi.getInitialState()}function Ot(t,e){var n=e.stateHistory,r=(new Date).getTime();return t.withMutations((function(t){n.forEach((function(e){return t.set(e[0].entity_id,r)})),history.length>1&&t.set(si,r)}))}function wt(){return ci.getInitialState()}function Tt(t,e){t.dispatch(Wr.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function At(t,e){void 0===e&&(e=null),t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),on(t,"GET",n).then((function(e){return t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})}),(function(){return t.dispatch(Wr.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})}))}function Dt(t,e){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_START,{date:e}),on(t,"GET","history/period/"+e).then((function(n){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})}),(function(){return t.dispatch(Wr.ENTITY_HISTORY_FETCH_ERROR,{})}))}function Ct(t){var e=t.evaluate(li);return Dt(t,e)}function zt(t){t.registerStores({currentEntityHistoryDate:Qr,entityHistory:ti,isLoadingEntityHistory:ni,recentEntityHistory:oi,recentEntityHistoryUpdated:ci})}function Rt(t){t.registerStores({moreInfoEntityId:Yr})}function Mt(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;o0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function Yt(t){var e=fo[t.hassId];e&&(e.scheduleHealthCheck.clear(),e.conn.close(),fo[t.hassId]=!1)}function Jt(t,e){void 0===e&&(e={});var n=e.syncOnInitialConnect;void 0===n&&(n=!0),Yt(t);var r=t.evaluate(Mo.authToken),i="https:"===document.location.protocol?"wss://":"ws://";i+=document.location.hostname,document.location.port&&(i+=":"+document.location.port),i+="/api/websocket",E(i,{authToken:r}).then((function(e){var r=Bt((function(){return e.ping()}),so);r(),e.socket.addEventListener("message",r),fo[t.hassId]={conn:e,scheduleHealthCheck:r},co.forEach((function(n){return e.subscribeEvents(ao.bind(null,t),n)})),t.batch((function(){t.dispatch(Ye.STREAM_START),n&&io.fetchAll(t)})),e.addEventListener("disconnected",(function(){t.dispatch(Ye.STREAM_ERROR)})),e.addEventListener("ready",(function(){t.batch((function(){t.dispatch(Ye.STREAM_START),io.fetchAll(t)}))}))}))}function Wt(t){t.registerStores({streamStatus:Xe})}function Xt(t,e,n){void 0===n&&(n={});var r=n.rememberAuth;void 0===r&&(r=!1);var i=n.host;void 0===i&&(i=""),t.dispatch(Ue.VALIDATING_AUTH_TOKEN,{authToken:e,host:i}),io.fetchAll(t).then((function(){t.dispatch(Ue.VALID_AUTH_TOKEN,{authToken:e,host:i,rememberAuth:r}),vo.start(t,{syncOnInitialConnect:!1})}),(function(e){void 0===e&&(e={});var n=e.message;void 0===n&&(n=go),t.dispatch(Ue.INVALID_AUTH_TOKEN,{errorMessage:n})}))}function Qt(t){t.dispatch(Ue.LOG_OUT,{})}function Zt(t){t.registerStores({authAttempt:Ve,authCurrent:Ge,rememberAuth:Be})}function $t(){if(!("localStorage"in window))return{};var t=window.localStorage,e="___test";try{return t.setItem(e,e),t.removeItem(e),t}catch(t){return{}}}function te(){var t=new Uo({debug:!1});return t.hassId=Ho++,t}function ee(t,e,n){Object.keys(n).forEach((function(r){var i=n[r];if("register"in i&&i.register(e),"getters"in i&&Object.defineProperty(t,r+"Getters",{value:i.getters,enumerable:!0}),"actions"in i){var o={};Object.getOwnPropertyNames(i.actions).forEach((function(t){"function"==typeof i.actions[t]&&Object.defineProperty(o,t,{value:i.actions[t].bind(null,e),enumerable:!0})})),Object.defineProperty(t,r+"Actions",{value:o,enumerable:!0})}}))}function ne(t,e){return xo(t.attributes.entity_id.map((function(t){return e.get(t)})).filter((function(t){return!!t})))}function re(t){return on(t,"GET","error_log")}function ie(t,e){var n=e.date;return n.toISOString()}function oe(){return Jo.getInitialState()}function ue(t,e){var n=e.date,r=e.entries;return t.set(n,eu(r.map($o.fromJSON)))}function ae(){return nu.getInitialState()}function se(t,e){var n=e.date;return t.set(n,(new Date).getTime())}function ce(){return ou.getInitialState()}function fe(t,e){t.dispatch(Bo.LOGBOOK_DATE_SELECTED,{date:e})}function he(t,e){t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_START,{date:e}),on(t,"GET","logbook/"+e).then((function(n){return t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_SUCCESS,{date:e,entries:n})}),(function(){return t.dispatch(Bo.LOGBOOK_ENTRIES_FETCH_ERROR,{})}))}function le(t){return!t||(new Date).getTime()-t>su}function pe(t){t.registerStores({currentLogbookDate:Jo,isLoadingLogbookEntries:Xo,logbookEntries:nu,logbookEntriesUpdated:ou})}function _e(t){return t.set("active",!0)}function de(t){return t.set("active",!1)}function ve(){return Su.getInitialState()}function ye(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",on(t,"POST","notify.html5",{subscription:e,browser:n}).then((function(){return t.dispatch(yu.PUSH_NOTIFICATIONS_SUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Vn.createNotification(t,n),!1}))}function me(t){return navigator.serviceWorker.getRegistration().then((function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})})).then((function(e){return on(t,"DELETE","notify.html5",{subscription:e}).then((function(){return e.unsubscribe()})).then((function(){return t.dispatch(yu.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})})).then((function(){return!0}))})).catch((function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Vn.createNotification(t,n),!1}))}function ge(t){t.registerStores({pushNotifications:Su})}function Se(t,e){return on(t,"POST","template",{template:e})}function be(t){return t.set("isListening",!0)}function Ee(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)}))}function Ie(t,e){var n=e.finalTranscript;return t.withMutations((function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)}))}function Oe(){return ku.getInitialState()}function we(){return ku.getInitialState()}function Te(){return ku.getInitialState()}function Ae(t){return Pu[t.hassId]}function De(t){var e=Ae(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(ju.VOICE_TRANSMITTING,{finalTranscript:n}),tr.callService(t,"conversation","process",{text:n}).then((function(){t.dispatch(ju.VOICE_DONE)}),(function(){t.dispatch(ju.VOICE_ERROR)}))}}function Ce(t){var e=Ae(t);e&&(e.recognition.stop(),Pu[t.hassId]=!1)}function ze(t){De(t),Ce(t)}function Re(t){var e=ze.bind(null,t);e();var n=new webkitSpeechRecognition;Pu[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(ju.VOICE_START)},n.onerror=function(){return t.dispatch(ju.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ae(t);if(n){for(var r="",i="",o=e.resultIndex;o=n)}function c(t,e){return h(t,e,0)}function f(t,e){return h(t,e,e)}function h(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function l(t){return v(t)?t:C(t)}function p(t){return y(t)?t:z(t)}function _(t){return m(t)?t:R(t)}function d(t){return v(t)&&!g(t)?t:M(t)}function v(t){return!(!t||!t[dn])}function y(t){return!(!t||!t[vn])}function m(t){return!(!t||!t[yn])}function g(t){return y(t)||m(t)}function S(t){return!(!t||!t[mn])}function b(t){this.next=t}function E(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function I(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(En&&t[En]||t[In]);if("function"==typeof e)return e}function D(t){return t&&"number"==typeof t.length}function C(t){return null===t||void 0===t?U():v(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?U().toKeyedSeq():v(t)?y(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?U():v(t)?y(t)?t.entrySeq():t.toIndexedSeq():x(t)}function M(t){return(null===t||void 0===t?U():v(t)?y(t)?t.entrySeq():t:x(t)).toSetSeq()}function j(t){this._array=t,this.size=t.length}function L(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function P(t){return!(!t||!t[wn])}function U(){return Tn||(Tn=new j([]))}function H(t){var e=Array.isArray(t)?new j(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new L(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=q(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=q(t)||"object"==typeof t&&new L(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function q(t){return D(t)?new j(t):w(t)?new k(t):O(t)?new N(t):void 0}function F(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new b(function(){var t=i[n?o-u:u];return u++>o?I():E(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(){throw TypeError("Abstract")}function B(){}function Y(){}function J(){}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){return e?Q(e,t,"",{"":t}):Z(t)}function Q(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map((function(n,r){return Q(t,n,r,e)}))):$(e)?t.call(r,n,z(e).map((function(n,r){return Q(t,n,r,e)}))):e}function Z(t){return Array.isArray(t)?R(t).map(Z).toList():$(t)?z(t).map(Z).toMap():t}function $(t){return t&&(t.constructor===Object||void 0===t.constructor)}function tt(t){return t>>>1&1073741824|3221225471&t}function et(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return tt(n)}return"string"===e?t.length>Ln?nt(t):rt(t):"function"==typeof t.hashCode?t.hashCode():it(t)}function nt(t){var e=Pn[t];return void 0===e&&(e=rt(t),kn===Nn&&(kn=0,Pn={}),kn++,Pn[t]=e),e}function rt(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ut(t,e){if(!t)throw new Error(e)}function at(t){ut(t!==1/0,"Cannot perform this action with an infinite size.")}function st(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ct(t){this._iter=t,this.size=t.size}function ft(t){this._iter=t,this.size=t.size}function ht(t){this._iter=t,this.size=t.size}function lt(t){var e=jt(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=Lt,e.__iterateUncached=function(e,n){var r=this;return t.__iterate((function(t,n){return e(n,t,r)!==!1}),n)},e.__iteratorUncached=function(e,n){if(e===bn){var r=t.__iterator(e,n);return new b(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===Sn?gn:Sn,n)},e}function pt(t,e,n){var r=jt(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,ln);return o===ln?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate((function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1}),i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(bn,i);return new b(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return E(r,a,e.call(n,u[1],a,t),i)})},r}function _t(t,e){var n=jt(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=lt(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=Lt,n.__iterate=function(e,n){var r=this;return t.__iterate((function(t,n){return e(t,n,r)}),!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function dt(t,e,n,r){var i=jt(t);return r&&(i.has=function(r){var i=t.get(r,ln);return i!==ln&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,ln);return o!==ln&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate((function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)}),o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(bn,o),a=0;return new b(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return E(i,r?c:a++,f,o)}})},i}function vt(t,e,n){var r=Pt().asMutable();return t.__iterate((function(i,o){r.update(e.call(n,i,o,t),0,(function(t){return t+1}))})),r.asImmutable()}function yt(t,e,n){var r=y(t),i=(S(t)?Ie():Pt()).asMutable();t.__iterate((function(o,u){i.update(e.call(n,o,u,t),(function(t){return t=t||[],t.push(r?[u,o]:o),t}))}));var o=Mt(t);return i.map((function(e){return Ct(t,o(e))}))}function mt(t,e,n,r){var i=t.size;if(void 0!==e&&(e|=0),void 0!==n&&(n|=0),s(e,n,i))return t;var o=c(e,i),a=f(n,i);if(o!==o||a!==a)return mt(t.toSeq().cacheResult(),e,n,r);var h,l=a-o;l===l&&(h=l<0?0:l);var p=jt(t);return p.size=0===h?h:t.size&&h||void 0,!r&&P(t)&&h>=0&&(p.get=function(e,n){return e=u(this,e),e>=0&&eh)return I();var t=i.next();return r||e===Sn?t:e===gn?E(e,a-1,void 0,t):E(e,a-1,t.value[1],t)})},p}function gt(t,e,n){var r=jt(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate((function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)})),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(bn,i),a=!0;return new b(function(){if(!a)return I();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===bn?t:E(r,s,c,t):(a=!1,I())})},r}function St(t,e,n,r){var i=jt(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate((function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)})),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(bn,o),s=!0,c=0;return new b(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===Sn?t:i===gn?E(i,c++,void 0,t):E(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===bn?t:E(i,o,f,t)})},i}function bt(t,e){var n=y(t),r=[t].concat(e).map((function(t){return v(t)?n&&(t=p(t)):t=n?H(t):x(Array.isArray(t)?t:[t]),t})).filter((function(t){return 0!==t.size}));if(0===r.length)return t;if(1===r.length){var i=r[0];if(i===t||n&&y(i)||m(t)&&m(i))return i}var o=new j(r);return n?o=o.toKeyedSeq():m(t)||(o=o.toSetSeq()),o=o.flatten(!0),o.size=r.reduce((function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}}),0),o}function Et(t,e,n){var r=jt(t);return r.__iterateUncached=function(r,i){function o(t,s){var c=this;t.__iterate((function(t,i){return(!e||s0}function Dt(t,e,n){var r=jt(t);return r.size=new j(n).map((function(t){return t.size})).min(),r.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(Sn,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},r.__iteratorUncached=function(t,r){var i=n.map((function(t){return t=l(t),T(r?t.reverse():t)})),o=0,u=!1; +return new b(function(){var n;return u||(n=i.map((function(t){return t.next()})),u=n.some((function(t){return t.done}))),u?I():E(t,o++,e.apply(null,n.map((function(t){return t.value}))))})},r}function Ct(t,e){return P(t)?e:t.constructor(e)}function zt(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Rt(t){return at(t.size),o(t)}function Mt(t){return y(t)?p:m(t)?_:d}function jt(t){return Object.create((y(t)?z:m(t)?R:M).prototype)}function Lt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):C.prototype.cacheResult.call(this)}function Nt(t,e){return t>e?1:t>>n)&hn,a=(0===n?r:r>>>n)&hn,s=u===a?[Zt(t,e,n+cn,r,i)]:(o=new Ft(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new Vt(t,o+1,u)}function ne(t,e,n){for(var r=[],i=0;i>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function ae(t,e,n,r){var o=r?t:i(t);return o[e]=n,o}function se(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&ro?0:o-n,c=u-n;return c>fn&&(c=fn),function(){if(i===c)return Yn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>fn&&(f=fn),function(){for(;;){if(a){var t=a();if(t!==Yn)return t;a=null}if(c===f)return Yn;var o=e?--f:c++;a=n(s&&s[o],r-cn,i+(o<=t.size||n<0)return t.withMutations((function(t){n<0?Se(t,n).set(0,r):Se(t,0,n+1).set(n,r)}));n+=t._origin;var i=t._tail,o=t._root,a=e(_n);return n>=Ee(t._capacity)?i=ye(i,t.__ownerID,0,n,r,a):o=ye(o,t.__ownerID,t._level,n,r,a),a.value?t.__ownerID?(t._root=o,t._tail=i,t.__hash=void 0,t.__altered=!0,t):_e(t._origin,t._capacity,t._level,o,i):t}function ye(t,e,r,i,o,u){var a=i>>>r&hn,s=t&&a0){var f=t&&t.array[a],h=ye(f,e,r-cn,i,o,u);return h===f?t:(c=me(t,e),c.array[a]=h,c)}return s&&t.array[a]===o?t:(n(u),c=me(t,e),void 0===o&&a===c.array.length-1?c.array.pop():c.array[a]=o,c)}function me(t,e){return e&&t&&e===t.ownerID?t:new le(t?t.array.slice():[],e)}function ge(t,e){if(e>=Ee(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&hn],r-=cn;return n}}function Se(t,e,n){void 0!==e&&(e|=0),void 0!==n&&(n|=0);var i=t.__ownerID||new r,o=t._origin,u=t._capacity,a=o+e,s=void 0===n?u:n<0?u+n:o+n;if(a===o&&s===u)return t;if(a>=s)return t.clear();for(var c=t._level,f=t._root,h=0;a+h<0;)f=new le(f&&f.array.length?[void 0,f]:[],i),c+=cn,h+=1<=1<l?new le([],i):_;if(_&&p>l&&acn;y-=cn){var m=l>>>y&hn;v=v.array[m]=me(v.array[m],i)}v.array[l>>>cn&hn]=_}if(s=p)a-=p,s-=p,c=cn,f=null,d=d&&d.removeBefore(i,0,a);else if(a>o||p>>c&hn;if(g!==p>>>c&hn)break;g&&(h+=(1<o&&(f=f.removeBefore(i,c,a-h)),f&&pi&&(i=a.size),v(u)||(a=a.map((function(t){return X(t)}))),r.push(a)}return i>t.size&&(t=t.setSize(i)),ie(t,e,r)}function Ee(t){return t>>cn<=fn&&u.size>=2*o.size?(i=u.filter((function(t,e){return void 0!==t&&a!==e})),r=i.toKeyedSeq().map((function(t){return t[0]})).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):we(r,i)}function De(t){return null===t||void 0===t?Re():Ce(t)?t:Re().unshiftAll(t)}function Ce(t){return!(!t||!t[Wn])}function ze(t,e,n,r){var i=Object.create(Xn);return i.size=t,i._head=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Re(){return Qn||(Qn=ze(0))}function Me(t){return null===t||void 0===t?ke():je(t)&&!S(t)?t:ke().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function je(t){return!(!t||!t[Zn])}function Le(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function Ne(t,e){var n=Object.create($n);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function ke(){return tr||(tr=Ne(Jt()))}function Pe(t){return null===t||void 0===t?xe():Ue(t)?t:xe().withMutations((function(e){var n=d(t);at(n.size),n.forEach((function(t){return e.add(t)}))}))}function Ue(t){return je(t)&&S(t)}function He(t,e){var n=Object.create(er);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function xe(){return nr||(nr=He(Te()))}function Ve(t,e){var n,r=function(o){if(o instanceof r)return o;if(!(this instanceof r))return new r(o);if(!n){n=!0;var u=Object.keys(t);Ge(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=Pt(o)},i=r.prototype=Object.create(rr);return i.constructor=r,r}function qe(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return r._map=e,r.__ownerID=n,r}function Fe(t){return t._name||t.constructor.name||"Record"}function Ge(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(t){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){ut(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Be(t,e){if(t===e)return!0;if(!v(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||y(t)!==y(e)||m(t)!==m(e)||S(t)!==S(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!g(t);if(S(t)){var r=t.entries();return e.every((function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))}))&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var o=t;t=e,e=o}var u=!0,a=e.__iterate((function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,ln)):!W(t.get(r,ln),e))return u=!1,!1}));return u&&t.size===a}function Ye(t,e,n){if(!(this instanceof Ye))return new Ye(t,e,n);if(ut(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),ee?-1:0}function rn(t){if(t.size===1/0)return 0;var e=S(t),n=y(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+un(et(t),et(e))|0}:function(t,e){r=r+un(et(t),et(e))|0}:e?function(t){r=31*r+et(t)|0}:function(t){r=r+et(t)|0});return on(i,r)}function on(t,e){return e=Dn(e,3432918353),e=Dn(e<<15|e>>>-15,461845907),e=Dn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Dn(e^e>>>16,2246822507),e=Dn(e^e>>>13,3266489909),e=tt(e^e>>>16)}function un(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var an=Array.prototype.slice,sn="delete",cn=5,fn=1<r?I():E(t,i,n[e?r-i++:i++])})},t(L,z),L.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},L.prototype.has=function(t){return this._object.hasOwnProperty(t)},L.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},L.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new b(function(){var u=r[e?i-o:o];return o++>i?I():E(t,u,n[u])})},L.prototype[mn]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new b(I);var i=0;return new b(function(){var e=r.next();return e.done?e:E(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return E(t,i,r[i++])})};var Tn;t(K,l),t(B,K),t(Y,K),t(J,K),K.Keyed=B,K.Indexed=Y,K.Set=J;var An,Dn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t|=0,e|=0;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Cn=Object.isExtensible,zn=(function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}})(),Rn="function"==typeof WeakMap;Rn&&(An=new WeakMap);var Mn=0,jn="__immutablehash__";"function"==typeof Symbol&&(jn=Symbol(jn));var Ln=16,Nn=255,kn=0,Pn={};t(st,z),st.prototype.get=function(t,e){return this._iter.get(t,e)},st.prototype.has=function(t){return this._iter.has(t)},st.prototype.valueSeq=function(){return this._iter.valueSeq()},st.prototype.reverse=function(){var t=this,e=_t(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=pt(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},st.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Rt(this):0,function(i){return t(i,e?--n:n++,r)}),e)},st.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(Sn,e),r=e?Rt(this):0;return new b(function(){var i=n.next();return i.done?i:E(t,e?--r:r++,i.value,i)})},st.prototype[mn]=!0,t(ct,R),ct.prototype.includes=function(t){return this._iter.includes(t)},ct.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate((function(e){return t(e,r++,n)}),e)},ct.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e),r=0;return new b(function(){var e=n.next();return e.done?e:E(t,r++,e.value,e)})},t(ft,M),ft.prototype.has=function(t){return this._iter.includes(t)},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){return t(e,e,n)}),e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e);return new b(function(){var e=n.next();return e.done?e:E(t,e.value,e.value,e)})},t(ht,z),ht.prototype.entrySeq=function(){return this._iter.toSeq()},ht.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate((function(e){if(e){zt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}}),e)},ht.prototype.__iterator=function(t,e){var n=this._iter.__iterator(Sn,e);return new b(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){zt(r);var i=v(r);return E(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=ft.prototype.cacheResult=ht.prototype.cacheResult=Lt,t(Pt,B),Pt.prototype.toString=function(){return this.__toString("Map {","}")},Pt.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},Pt.prototype.set=function(t,e){return Wt(this,t,e)},Pt.prototype.setIn=function(t,e){return this.updateIn(t,ln,(function(){return e}))},Pt.prototype.remove=function(t){return Wt(this,t,ln)},Pt.prototype.deleteIn=function(t){return this.updateIn(t,(function(){return ln}))},Pt.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},Pt.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,kt(t),e,n);return r===ln?void 0:r},Pt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Jt()},Pt.prototype.merge=function(){return ne(this,void 0,arguments)},Pt.prototype.mergeWith=function(t){var e=an.call(arguments,1);return ne(this,t,e)},Pt.prototype.mergeIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]}))},Pt.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},Pt.prototype.mergeDeepWith=function(t){var e=an.call(arguments,1);return ne(this,re(t),e)},Pt.prototype.mergeDeepIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),(function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]}))},Pt.prototype.sort=function(t){return Ie(wt(this,t))},Pt.prototype.sortBy=function(t,e){return Ie(wt(this,e,t))},Pt.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},Pt.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},Pt.prototype.asImmutable=function(){return this.__ensureOwner()},Pt.prototype.wasAltered=function(){return this.__altered},Pt.prototype.__iterator=function(t,e){return new Gt(this,t,e)},Pt.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate((function(e){return r++,t(e[1],e[0],n)}),e),r},Pt.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Yt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Pt.isMap=Ut;var Un="@@__IMMUTABLE_MAP__@@",Hn=Pt.prototype;Hn[Un]=!0,Hn[sn]=Hn.remove,Hn.removeIn=Hn.deleteIn,Ht.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Vn)return $t(t,f,o,u);var _=t&&t===this.ownerID,d=_?f:i(f);return p?c?h===l-1?d.pop():d[h]=d.pop():d[h]=[o,u]:d.push([o,u]),_?(this.entries=d,this):new Ht(t,d)}},xt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&hn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ue(o&i-1)].get(t+cn,e,n,r)},xt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=1<=qn)return ee(t,l,c,a,_);if(f&&!_&&2===l.length&&Qt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&Qt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?ae(l,h,_,d):ce(l,h,d):se(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new xt(t,v,y)},Vt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&hn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Vt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=i===ln,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Xt(f,t,e+cn,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&hn;if(r>=this.array.length)return new le([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-cn,n),i===u&&o)return this}if(o&&!i)return this;var a=me(this,t);if(!o)for(var s=0;s>>e&hn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-cn,n),i===o&&r===this.array.length-1)return this}var u=me(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Bn,Yn={};t(Ie,Pt),Ie.of=function(){return this(arguments)},Ie.prototype.toString=function(){return this.__toString("OrderedMap {","}")},Ie.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},Ie.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Te()},Ie.prototype.set=function(t,e){return Ae(this,t,e)},Ie.prototype.remove=function(t){return Ae(this,t,ln)},Ie.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Ie.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate((function(e){return e&&t(e[1],e[0],n)}),e)},Ie.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},Ie.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?we(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},Ie.isOrderedMap=Oe,Ie.prototype[mn]=!0,Ie.prototype[sn]=Ie.prototype.remove;var Jn;t(De,Y),De.of=function(){return this(arguments)},De.prototype.toString=function(){return this.__toString("Stack [","]")},De.prototype.get=function(t,e){var n=this._head;for(t=u(this,t);n&&t--;)n=n.next;return n?n.value:e},De.prototype.peek=function(){return this._head&&this._head.value},De.prototype.push=function(){var t=arguments;if(0===arguments.length)return this;for(var e=this.size+arguments.length,n=this._head,r=arguments.length-1;r>=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pushAll=function(t){if(t=_(t),0===t.size)return this;at(t.size);var e=this.size,n=this._head;return t.reverse().forEach((function(t){e++,n={value:t,next:n}})),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pop=function(){return this.slice(1)},De.prototype.unshift=function(){return this.push.apply(this,arguments)},De.prototype.unshiftAll=function(t){return this.pushAll(t)},De.prototype.shift=function(){return this.pop.apply(this,arguments)},De.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Re()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=f(e,this.size);if(r!==this.size)return Y.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):ze(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?ze(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new b(function(){if(r){var e=r.value;return r=r.next,E(t,n++,e)}return I()})},De.isStack=Ce;var Wn="@@__IMMUTABLE_STACK__@@",Xn=De.prototype;Xn[Wn]=!0,Xn.withMutations=Hn.withMutations,Xn.asMutable=Hn.asMutable,Xn.asImmutable=Hn.asImmutable,Xn.wasAltered=Hn.wasAltered;var Qn;t(Me,J),Me.of=function(){return this(arguments)},Me.fromKeys=function(t){return this(p(t).keySeq())},Me.prototype.toString=function(){return this.__toString("Set {","}")},Me.prototype.has=function(t){return this._map.has(t)},Me.prototype.add=function(t){return Le(this,this._map.set(t,!0))},Me.prototype.remove=function(t){return Le(this,this._map.remove(t))},Me.prototype.clear=function(){return Le(this,this._map.clear())},Me.prototype.union=function(){var t=an.call(arguments,0);return t=t.filter((function(t){return 0!==t.size})),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations((function(e){for(var n=0;n1?" by "+this._step:"")+" ]"},Ye.prototype.get=function(t,e){return this.has(t)?this._start+u(this,t)*this._step:e},Ye.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=0&&nn?I():E(t,o++,u)})},Ye.prototype.equals=function(t){return t instanceof Ye?this._start===t._start&&this._end===t._end&&this._step===t._step:Be(this,t)};var ir;t(Je,R),Je.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Je.prototype.get=function(t,e){return this.has(t)?this._value:e},Je.prototype.includes=function(t){return W(this._value,t)},Je.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Je(this._value,f(e,n)-c(t,n))},Je.prototype.reverse=function(){return this},Je.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Je.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Je.prototype.__iterate=function(t,e){for(var n=this,r=0;rthis.size?e:this.find((function(e,n){return n===t}),void 0,e)},has:function(t){return t=u(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){var e=this.reactorState.get("dirtyStores");if(0!==e.size){var n=c.default.Set().withMutations((function(n){n.union(t.observerState.get("any")),e.forEach((function(e){var r=t.observerState.getIn(["stores",e]);r&&n.union(r)}))}));n.forEach((function(e){var n=t.observerState.getIn(["observersMap",e]);if(n){var r=n.get("getter"),i=n.get("handler"),o=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c.default.is(a,s)||i.call(null,s)}}));var r=p.resetDirtyStores(this.reactorState);this.prevReactorState=r,this.reactorState=r}}}},{key:"batchStart",value:function(){this.__batchDepth++}},{key:"batchEnd",value:function(){if(this.__batchDepth--,this.__batchDepth<=0){this.__isDispatching=!0;try{this.__notify()}catch(t){throw this.__isDispatching=!1,t}this.__isDispatching=!1}}}]),t})();e.default=(0,y.toFactory)(g),t.exports=e.default},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n={};return(0,o.each)(e,(function(e,r){n[r]=t.evaluate(e)})),n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(4);e.default=function(t){return{getInitialState:function(){return i(t,this.getDataBindings())},componentDidMount:function(){var e=this;this.__unwatchFns=[],(0,o.each)(this.getDataBindings(),(function(n,i){var o=t.observe(n,(function(t){e.setState(r({},i,t))}));e.__unwatchFns.push(o)}))},componentWillUnmount:function(){for(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){return new M({result:t,reactorState:e})}function o(t,e){return t.withMutations((function(t){(0,R.each)(e,(function(e,n){t.getIn(["stores",n])&&console.warn("Store already defined for id = "+n);var r=e.getInitialState();if(void 0===r&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(r))throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable");t.update("stores",(function(t){return t.set(n,e)})).update("state",(function(t){return t.set(n,r)})).update("dirtyStores",(function(t){return t.add(n)})).update("storeStates",(function(t){return I(t,[n])}))})),E(t)}))}function u(t,e){return t.withMutations((function(t){(0,R.each)(e,(function(e,n){t.update("stores",(function(t){return t.set(n,e)}))}))}))}function a(t,e,n){if(void 0===e&&f(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var r=t.get("state"),i=t.get("dirtyStores"),o=r.withMutations((function(r){A.default.dispatchStart(t,e,n),t.get("stores").forEach((function(o,u){var a=r.get(u),s=void 0;try{s=o.handle(a,e,n)}catch(e){throw A.default.dispatchError(t,e.message),e}if(void 0===s&&f(t,"throwOnUndefinedStoreReturnValue")){var c="Store handler must return a value, did you forget a return statement";throw A.default.dispatchError(t,c),new Error(c)}r.set(u,s),a!==s&&(i=i.add(u))})),A.default.dispatchEnd(t,r,i)})),u=t.set("state",o).set("dirtyStores",i).update("storeStates",(function(t){return I(t,i)}));return E(u)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations((function(r){(0,R.each)(e,(function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),n.push(i))}}))})),i=w.default.Set(n);return t.update("state",(function(t){return t.merge(r)})).update("dirtyStores",(function(t){return t.union(i)})).update("storeStates",(function(t){return I(t,n)}))}function c(t,e,n){var r=e;(0,z.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),u=w.default.Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=0===o.size?t.update("any",(function(t){return t.add(i)})):t.withMutations((function(t){o.forEach((function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,w.default.Set()),t.updateIn(["stores",e],(function(t){return t.add(i)}))}))})),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(t,e,n){var r=t.get("observersMap").filter((function(t){var r=t.get("getterKey"),i=!n||t.get("handler")===n;return!!i&&((0,z.isKeyPath)(e)&&(0,z.isKeyPath)(r)?(0,z.isEqual)(e,r):e===r)}));return t.withMutations((function(t){r.forEach((function(e){return l(t,e)}))}))}function l(t,e){return t.withMutations((function(t){var n=e.get("id"),r=e.get("storeDeps");0===r.size?t.update("any",(function(t){return t.remove(n)})):r.forEach((function(e){t.updateIn(["stores",e],(function(t){return t?t.remove(n):t}))})),t.removeIn(["observersMap",n])}))}function p(t){var e=t.get("state");return t.withMutations((function(t){var n=t.get("stores"),r=n.keySeq().toJS();n.forEach((function(n,r){var i=e.get(r),o=n.handleReset(i);if(void 0===o&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(o))throw new Error("Store reset state must be an immutable value, did you forget to call toImmutable");t.setIn(["state",r],o)})),t.update("storeStates",(function(t){return I(t,r)})),v(t)}))}function _(t,e){var n=t.get("state");if((0,z.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,C.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");if(g(t,e))return i(b(t,e),t);var r=(0,C.getDeps)(e).map((function(e){return _(t,e).result})),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,S(t,e,o))}function d(t){var e={};return t.get("stores").forEach((function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)})),e}function v(t){return t.set("dirtyStores",w.default.Set())}function y(t){return t}function m(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=m(t,e);if(!n)return!1;var r=n.get("storeStates");return 0!==r.size&&r.every((function(e,n){return t.getIn(["storeStates",n])===e}))}function S(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),u=(0,D.toImmutable)({}).withMutations((function(e){o.forEach((function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)}))}));return t.setIn(["cache",r],w.default.Map({value:n,storeStates:u,dispatchId:i}))}function b(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function E(t){return t.update("dispatchId",(function(t){return t+1}))}function I(t,e){return t.withMutations((function(t){e.forEach((function(e){var n=t.has(e)?t.get(e)+1:1;t.set(e,n)}))}))}Object.defineProperty(e,"__esModule",{value:!0}),e.registerStores=o,e.replaceStores=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var O=n(3),w=r(O),T=n(9),A=r(T),D=n(5),C=n(10),z=n(11),R=n(4),M=w.default.Record({result:null,reactorState:null})},function(t,e,n){var r=n(8);e.dispatchStart=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.groupCollapsed("Dispatch: %s",e),console.group("payload"),console.debug(n),console.groupEnd())},e.dispatchError=function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},e.dispatchEnd=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&((0,r.getOption)(t,"logDirtyStores")&&console.log("Stores updated:",n.toList().toJS()),(0,r.getOption)(t,"logAppState")&&console.debug("Dispatch done, new state: ",e.toJS()),console.groupEnd())}},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h.default.Set());var n=h.default.Set().withMutations((function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach((function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}}))}));return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(t).map((function(t){return t.first()})).filter((function(t){return!!t}));return Object.defineProperty(t,"__storeDeps",{enumerable:!1,configurable:!1,writable:!1,value:e}),e}Object.defineProperty(e,"__esModule",{value:!0});var f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e.default={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,s.isArray)(t)&&!(0,s.isFunction)(t[t.length-1])}function o(t,e){var n=a.default.List(t),r=a.default.List(e);return a.default.is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=i;var o=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=o;var u=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,r.Map)(),storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:i});e.ReactorState=u;var a=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=a}])}))})),Ne=t(Le),ke=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n},Pe=ke,Ue=Pe({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),He=Ne.Store,xe=Ne.toImmutable,Ve=new He({getInitialState:function(){return xe({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Ue.VALIDATING_AUTH_TOKEN,n),this.on(Ue.VALID_AUTH_TOKEN,r),this.on(Ue.INVALID_AUTH_TOKEN,i)}}),qe=Ne.Store,Fe=Ne.toImmutable,Ge=new qe({getInitialState:function(){return Fe({authToken:null,host:""})},initialize:function(){this.on(Ue.VALID_AUTH_TOKEN,o),this.on(Ue.LOG_OUT,u)}}),Ke=Ne.Store,Be=new Ke({getInitialState:function(){return!0},initialize:function(){this.on(Ue.VALID_AUTH_TOKEN,a)}}),Ye=Pe({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),Je=Ne.Store,We=Ne.toImmutable,Xe=new Je({getInitialState:function(){return We({isStreaming:!1,hasError:!1})},initialize:function(){this.on(Ye.STREAM_START,s),this.on(Ye.STREAM_ERROR,c),this.on(Ye.LOG_OUT,f)}}),Qe=1,Ze=2,$e=3,tn=function(t,e){this.url=t,this.options=e||{},this.commandId=1,this.commands={},this.connectionTries=0,this.eventListeners={},this.closeRequested=!1};tn.prototype.addEventListener=function(t,e){var n=this.eventListeners[t];n||(n=this.eventListeners[t]=[]),n.push(e)},tn.prototype.fireEvent=function(t){var e=this;(this.eventListeners[t]||[]).forEach((function(t){return t(e)}))},tn.prototype.connect=function(){var t=this;return new Promise(function(e,n){var r=t.commands;Object.keys(r).forEach((function(t){var e=r[t];e.reject&&e.reject(S($e,"Connection lost"))}));var i=!1;t.connectionTries+=1,t.socket=new WebSocket(t.url),t.socket.addEventListener("open",(function(){t.connectionTries=0})),t.socket.addEventListener("message",(function(o){var u=JSON.parse(o.data);switch(u.type){case"event":t.commands[u.id].eventCallback(u.event);break;case"result":u.success?t.commands[u.id].resolve(u):t.commands[u.id].reject(u.error),delete t.commands[u.id];break;case"pong":break;case"auth_required":t.sendMessage(h(t.options.authToken));break;case"auth_invalid":n(Ze),i=!0;break;case"auth_ok":e(t),t.fireEvent("ready"),t.commandId=1,t.commands={},Object.keys(r).forEach((function(e){var n=r[e];n.eventType&&t.subscribeEvents(n.eventCallback,n.eventType).then((function(t){n.unsubscribe=t}))}))}})),t.socket.addEventListener("close",(function(){if(!i&&!t.closeRequested){0===t.connectionTries?t.fireEvent("disconnected"):n(Qe);var e=1e3*Math.min(t.connectionTries,5);setTimeout((function(){return t.connect()}),e)}}))})},tn.prototype.close=function(){this.closeRequested=!0,this.socket.close()},tn.prototype.getStates=function(){return this.sendMessagePromise(l()).then(b)},tn.prototype.getServices=function(){return this.sendMessagePromise(_()).then(b)},tn.prototype.getPanels=function(){return this.sendMessagePromise(d()).then(b)},tn.prototype.getConfig=function(){return this.sendMessagePromise(p()).then(b)},tn.prototype.callService=function(t,e,n){return this.sendMessagePromise(v(t,e,n))},tn.prototype.subscribeEvents=function(t,e){var n=this;return this.sendMessagePromise(y(e)).then((function(r){var i={eventCallback:t,eventType:e,unsubscribe:function(){return n.sendMessagePromise(m(r.id)).then((function(){delete n.commands[r.id]}))}};return n.commands[r.id]=i,function(){return i.unsubscribe()}}))},tn.prototype.ping=function(){return this.sendMessagePromise(g())},tn.prototype.sendMessage=function(t){this.socket.send(JSON.stringify(t))},tn.prototype.sendMessagePromise=function(t){var e=this;return new Promise(function(n,r){e.commandId+=1;var i=e.commandId;t.id=i,e.commands[i]={resolve:n,reject:r},e.sendMessage(t)})};var en=Pe({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),nn=Ne.Store,rn=new nn({getInitialState:function(){return!0},initialize:function(){this.on(en.API_FETCH_ALL_START,(function(){return!0})),this.on(en.API_FETCH_ALL_SUCCESS,(function(){return!1})),this.on(en.API_FETCH_ALL_FAIL,(function(){return!1})),this.on(en.LOG_OUT,(function(){return!1}))}}),on=I,un=Pe({API_FETCH_SUCCESS:null,API_FETCH_START:null,API_FETCH_FAIL:null,API_SAVE_SUCCESS:null,API_SAVE_START:null,API_SAVE_FAIL:null,API_DELETE_SUCCESS:null,API_DELETE_START:null,API_DELETE_FAIL:null,LOG_OUT:null}),an=Ne.Store,sn=Ne.toImmutable,cn=new an({getInitialState:function(){return sn({})},initialize:function(){var t=this;this.on(un.API_FETCH_SUCCESS,O),this.on(un.API_SAVE_SUCCESS,O),this.on(un.API_DELETE_SUCCESS,w),this.on(un.LOG_OUT,(function(){return t.getInitialState()}))}}),fn=Object.prototype.hasOwnProperty,hn=Object.prototype.propertyIsEnumerable,ln=A()?Object.assign:function(t,e){for(var n,r,i=arguments,o=T(t),u=1;u \ No newline at end of file +n&&e.updateNativeStyleProperties(document.documentElement,this.customStyle)}};return r}(),function(){"use strict";var e=Polymer.Base.serializeValueToAttribute,t=Polymer.StyleProperties,n=Polymer.StyleTransformer,r=Polymer.StyleDefaults,s=Polymer.Settings.useNativeShadow,i=Polymer.Settings.useNativeCSSProperties;Polymer.Base._addFeature({_prepStyleProperties:function(){i||(this._ownStylePropertyNames=this._styles&&this._styles.length?t.decorateStyles(this._styles,this):null)},customStyle:null,getComputedStyleValue:function(e){return i||this._styleProperties||this._computeStyleProperties(),!i&&this._styleProperties&&this._styleProperties[e]||getComputedStyle(this).getPropertyValue(e)},_setupStyleProperties:function(){this.customStyle={},this._styleCache=null,this._styleProperties=null,this._scopeSelector=null,this._ownStyleProperties=null,this._customStyle=null},_needsStyleProperties:function(){return Boolean(!i&&this._ownStylePropertyNames&&this._ownStylePropertyNames.length)},_validateApplyShim:function(){if(this.__applyShimInvalid){Polymer.ApplyShim.transform(this._styles,this.__proto__);var e=n.elementStyles(this);if(s){var t=this._template.content.querySelector("style");t&&(t.textContent=e)}else{var r=this._scopeStyle&&this._scopeStyle.nextSibling;r&&(r.textContent=e)}}},_beforeAttached:function(){this._scopeSelector&&!this.__stylePropertiesInvalid||!this._needsStyleProperties()||(this.__stylePropertiesInvalid=!1,this._updateStyleProperties())},_findStyleHost:function(){for(var e,t=this;e=Polymer.dom(t).getOwnerRoot();){if(Polymer.isInstance(e.host))return e.host;t=e.host}return r},_updateStyleProperties:function(){var e,n=this._findStyleHost();n._styleProperties||n._computeStyleProperties(),n._styleCache||(n._styleCache=new Polymer.StyleCache);var r=t.propertyDataFromStyles(n._styles,this),i=!this.__notStyleScopeCacheable;i&&(r.key.customStyle=this.customStyle,e=n._styleCache.retrieve(this.is,r.key,this._styles));var a=Boolean(e);a?this._styleProperties=e._styleProperties:this._computeStyleProperties(r.properties),this._computeOwnStyleProperties(),a||(e=o.retrieve(this.is,this._ownStyleProperties,this._styles));var l=Boolean(e)&&!a,c=this._applyStyleProperties(e);a||(c=c&&s?c.cloneNode(!0):c,e={style:c,_scopeSelector:this._scopeSelector,_styleProperties:this._styleProperties},i&&(r.key.customStyle={},this.mixin(r.key.customStyle,this.customStyle),n._styleCache.store(this.is,e,r.key,this._styles)),l||o.store(this.is,Object.create(e),this._ownStyleProperties,this._styles))},_computeStyleProperties:function(e){var n=this._findStyleHost();n._styleProperties||n._computeStyleProperties();var r=Object.create(n._styleProperties),s=t.hostAndRootPropertiesForScope(this);this.mixin(r,s.hostProps),e=e||t.propertyDataFromStyles(n._styles,this).properties,this.mixin(r,e),this.mixin(r,s.rootProps),t.mixinCustomStyle(r,this.customStyle),t.reify(r),this._styleProperties=r},_computeOwnStyleProperties:function(){for(var e,t={},n=0;n0&&l.push(t);return[{removed:a,added:l}]}},Polymer.Collection.get=function(e){return Polymer._collections.get(e)||new Polymer.Collection(e)},Polymer.Collection.applySplices=function(e,t){var n=Polymer._collections.get(e);return n?n._applySplices(t):null},Polymer({is:"dom-repeat",extends:"template",_template:null,properties:{items:{type:Array},as:{type:String,value:"item"},indexAs:{type:String,value:"index"},sort:{type:Function,observer:"_sortChanged"},filter:{type:Function,observer:"_filterChanged"},observe:{type:String,observer:"_observeChanged"},delay:Number,renderedItemCount:{type:Number,notify:!0,readOnly:!0},initialCount:{type:Number,observer:"_initializeChunking"},targetFramerate:{type:Number,value:20},_targetFrameTime:{type:Number,computed:"_computeFrameTime(targetFramerate)"}},behaviors:[Polymer.Templatizer],observers:["_itemsChanged(items.*)"],created:function(){this._instances=[],this._pool=[],this._limit=1/0;var e=this;this._boundRenderChunk=function(){e._renderChunk()}},detached:function(){this.__isDetached=!0;for(var e=0;e=0;t--){var n=this._instances[t];n.isPlaceholder&&t=this._limit&&(n=this._downgradeInstance(t,n.__key__)),e[n.__key__]=t,n.isPlaceholder||n.__setProperty(this.indexAs,t,!0)}this._pool.length=0,this._setRenderedItemCount(this._instances.length),this.fire("dom-change"),this._tryRenderChunk()},_applyFullRefresh:function(){var e,t=this.collection;if(this._sortFn)e=t?t.getKeys():[];else{e=[];var n=this.items;if(n)for(var r=0;r=r;a--)this._detachAndRemoveInstance(a)},_numericSort:function(e,t){return e-t},_applySplicesUserSort:function(e){for(var t,n,r=this.collection,s={},i=0;i=0;i--){var c=a[i];void 0!==c&&this._detachAndRemoveInstance(c)}var h=this;if(l.length){this._filterFn&&(l=l.filter(function(e){return h._filterFn(r.getItem(e))})),l.sort(function(e,t){return h._sortFn(r.getItem(e),r.getItem(t))});var u=0;for(i=0;i>1,a=this._instances[o].__key__,l=this._sortFn(n.getItem(a),r);if(l<0)e=o+1;else{if(!(l>0)){i=o;break}s=o-1}}return i<0&&(i=s+1),this._insertPlaceholder(i,t),i},_applySplicesArrayOrder:function(e){for(var t,n=0;n=0?(e=this.as+"."+e.substring(n+1),i._notifyPath(e,t,!0)):i.__setProperty(this.as,t,!0))}},itemForElement:function(e){var t=this.modelForElement(e);return t&&t[this.as]},keyForElement:function(e){var t=this.modelForElement(e);return t&&t.__key__},indexForElement:function(e){var t=this.modelForElement(e);return t&&t[this.indexAs]}}),Polymer({is:"array-selector",_template:null,properties:{items:{type:Array,observer:"clearSelection"},multi:{type:Boolean,value:!1,observer:"clearSelection"},selected:{type:Object,notify:!0},selectedItem:{type:Object,notify:!0},toggle:{type:Boolean,value:!1}},clearSelection:function(){if(Array.isArray(this.selected))for(var e=0;e \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 3708f52a280..a8fa716d1ea 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 b5c3575cb5f..988ac002816 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit b5c3575cb5f284178e52d75db24c46131afb4cfa +Subproject commit 988ac0028163cfc970e781718bc9459ed486ea61 diff --git a/homeassistant/components/frontend/www_static/icons/home-assistant-icon.svg b/homeassistant/components/frontend/www_static/icons/home-assistant-icon.svg new file mode 100644 index 00000000000..1ff4c190f59 --- /dev/null +++ b/homeassistant/components/frontend/www_static/icons/home-assistant-icon.svg @@ -0,0 +1,2814 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/homeassistant/components/frontend/www_static/images/config_insteon.png b/homeassistant/components/frontend/www_static/images/config_insteon.png new file mode 100644 index 00000000000..0039cf3d160 Binary files /dev/null and b/homeassistant/components/frontend/www_static/images/config_insteon.png differ diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index e9f69984a47..ce1d5d24574 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 e219e5bcd45..774ce87fa36 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-dev-event.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html index 7eba3c45d42..531167d98a8 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz index d259eb65eaa..22fde6f1a5f 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html index 1a01891f25f..0ecddfec1f3 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html @@ -1,2 +1,2 @@ - \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz index a7ff46fcb8a..8c840c85985 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html index 9ce5304213e..8364b2e9991 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz index e9007a00c93..0215ae8c97c 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html index 53c28a1109f..da96f77d3d4 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz index 4c211bbe30d..ee640b9c7cb 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html index 56d67d09e27..e122c075f9d 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html @@ -1,2 +1,2 @@ -