From 9521dad263932092b5393ef3becb94d2ce6f276d Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 11:25:26 +0100 Subject: [PATCH 001/186] Added a command line notification platform that could be used for all kind of custom notifications --- .../components/notify/command_line.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 homeassistant/components/notify/command_line.py diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py new file mode 100644 index 00000000000..2beb78ccdca --- /dev/null +++ b/homeassistant/components/notify/command_line.py @@ -0,0 +1,46 @@ +""" +homeassistant.components.notify.command_line +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +command_line notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.command_line/ +""" +import logging +import subprocess +import shlex +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, BaseNotificationService) + +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config): + """ Get the Command Line notification service. """ + + if not validate_config({DOMAIN: config}, + {DOMAIN: ['command']}, + _LOGGER): + return None + + command = config['command'] + + return CommandLineNotificationService(command) + + +# pylint: disable=too-few-public-methods +class CommandLineNotificationService(BaseNotificationService): + """ Implements notification service for the Command Line service. """ + + def __init__(self, command): + self.command = command + + def send_message(self, message="", **kwargs): + """ Send a message to a command_line. """ + try: + subprocess.call("{} \"{}\"".format(self.command, + shlex.quote(message)), + shell=True) + except subprocess.CalledProcessError: + _LOGGER.error('Command failed: %s', self.command) From 10a20f802bbc4c7cb2fb2de642f720304cb40a8f Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 11:26:21 +0100 Subject: [PATCH 002/186] Updated coverage --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 8fefa8a44b8..96db38ea4e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -103,6 +103,7 @@ omit = homeassistant/components/notify/free_mobile.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py + homeassistant/components/notify/command_line.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushover.py From 5a03ddd7e0e9bf947c29d442cff3cb058a8cce31 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 19:31:28 +0100 Subject: [PATCH 003/186] Removed "" and changed the call to check_call to make it race the appropriate error --- homeassistant/components/notify/command_line.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index 2beb78ccdca..aa753e4f02f 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -39,8 +39,9 @@ class CommandLineNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """ Send a message to a command_line. """ try: - subprocess.call("{} \"{}\"".format(self.command, - shlex.quote(message)), - shell=True) + subprocess.check_call( + "{} {}".format(self.command, + shlex.quote(message)), + shell=True) except subprocess.CalledProcessError: _LOGGER.error('Command failed: %s', self.command) From c584b6b28d4dd24df2de3d57bd732225f15fa930 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Fri, 12 Feb 2016 20:52:02 +0100 Subject: [PATCH 004/186] Support for Mousedetectors connected in Verisure systems --- homeassistant/components/sensor/verisure.py | 38 +++++++++++++++++++++ homeassistant/components/verisure.py | 17 +++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index dec678677b4..f7e80e27549 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -39,6 +39,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hasattr(value, 'humidity') and value.humidity ]) + sensors.extend([ + VerisureMouseDetection(value) + for value in verisure.MOUSEDETECTION_STATUS.values() + if verisure.SHOW_MOUSEDETECTION and + hasattr(value, 'amountText') and value.amountText + ]) + add_devices(sensors) @@ -98,3 +105,34 @@ class VerisureHygrometer(Entity): def update(self): """ update sensor """ verisure.update_climate() + + +class VerisureMouseDetection(Entity): + """ represents a Verisure mousedetector within home assistant. """ + + def __init__(self, mousedetection_status): + self._id = mousedetection_status.deviceLabel + + @property + def name(self): + """ Returns the name of the device. """ + return '{} {}'.format( + verisure.MOUSEDETECTION_STATUS[self._id].location, + "Mouse") + + @property + def state(self): + """ Returns the state of the device. """ + return verisure.MOUSEDETECTION_STATUS[self._id].count + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity """ + if verisure.MOUSEDETECTION_STATUS[self._id].count >= 1: + return "Mice" + else: + return "Mouse" + + def update(self): + """ update sensor """ + verisure.update_mousedetection() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 98a2356954a..e85ee5d7850 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -38,6 +38,7 @@ ALARM_STATUS = {} SMARTPLUG_STATUS = {} CLIMATE_STATUS = {} LOCK_STATUS = {} +MOUSEDETECTION_STATUS = {} VERISURE_LOGIN_ERROR = None VERISURE_ERROR = None @@ -47,6 +48,7 @@ SHOW_HYGROMETERS = True SHOW_ALARM = True SHOW_SMARTPLUGS = True SHOW_LOCKS = True +SHOW_MOUSEDETECTION = True CODE_DIGITS = 4 # if wrong password was given don't try again @@ -66,12 +68,14 @@ def setup(hass, config): from verisure import MyPages, LoginError, Error global SHOW_THERMOMETERS, SHOW_HYGROMETERS,\ - SHOW_ALARM, SHOW_SMARTPLUGS, SHOW_LOCKS, CODE_DIGITS + SHOW_ALARM, SHOW_SMARTPLUGS, SHOW_LOCKS, SHOW_MOUSEDETECTION,\ + CODE_DIGITS SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1')) SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1')) SHOW_ALARM = int(config[DOMAIN].get('alarm', '1')) SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1')) SHOW_LOCKS = int(config[DOMAIN].get('locks', '1')) + SHOW_MOUSEDETECTION = int(config[DOMAIN].get('mouse', '1')) CODE_DIGITS = int(config[DOMAIN].get('code_digits', '4')) global MY_PAGES @@ -92,6 +96,7 @@ def setup(hass, config): update_climate() update_smartplug() update_lock() + update_mousedetection() # Load components for the devices in the ISY controller that we support for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), @@ -145,6 +150,11 @@ def update_lock(): update_component(MY_PAGES.lock.get, LOCK_STATUS) +def update_mousedetection(): + """ Updates the status of mouse detectors. """ + update_component(MY_PAGES.mousedetection.get, MOUSEDETECTION_STATUS) + + def update_component(get_function, status): """ Updates the status of verisure components. """ if WRONG_PASSWORD_GIVEN: @@ -152,7 +162,10 @@ def update_component(get_function, status): return try: for overview in get_function(): - status[overview.id] = overview + try: + status[overview.id] = overview + except IndexError: + status[overview.deviceLabel] = overview except (ConnectionError, VERISURE_ERROR) as ex: _LOGGER.error('Caught connection error %s, tries to reconnect', ex) reconnect() From 4210291e5bea2ec3bdc2cf80a6e252b982e72ab9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Feb 2016 21:55:40 -0800 Subject: [PATCH 005/186] Version bump to 0.14.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index dbbec17c0dd..084f269e036 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" -__version__ = "0.13.0" +__version__ = "0.14.0.dev0" # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' From a29be5455c9a0c68c6f3fec54961659ab1d08ff7 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Sat, 13 Feb 2016 09:05:18 +0100 Subject: [PATCH 006/186] Fix unit and wrong errorhandling --- homeassistant/components/sensor/verisure.py | 5 +---- homeassistant/components/verisure.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index f7e80e27549..1ba750aa3e9 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -128,10 +128,7 @@ class VerisureMouseDetection(Entity): @property def unit_of_measurement(self): """ Unit of measurement of this entity """ - if verisure.MOUSEDETECTION_STATUS[self._id].count >= 1: - return "Mice" - else: - return "Mouse" + return "Mice" def update(self): """ update sensor """ diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index e85ee5d7850..841339863ca 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -164,7 +164,7 @@ def update_component(get_function, status): for overview in get_function(): try: status[overview.id] = overview - except IndexError: + except AttributeError: status[overview.deviceLabel] = overview except (ConnectionError, VERISURE_ERROR) as ex: _LOGGER.error('Caught connection error %s, tries to reconnect', ex) From 9dc055e5375bfa3adca358147e59326f96f6a9fd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Feb 2016 09:35:31 +0100 Subject: [PATCH 007/186] Add link to docs --- homeassistant/components/graphite.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 5b3e4869307..a84d4bf8847 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -4,16 +4,8 @@ homeassistant.components.graphite Component that records all events and state changes and feeds the data to a graphite installation. -Example configuration: - - graphite: - host: foobar - port: 2003 - prefix: ha - -All config elements are optional, and assumed to be on localhost at the -default port if not specified. Prefix is the metric prefix in graphite, -and defaults to 'ha'. +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/graphite/ """ import logging import queue From 34b91cf6cef6bad571683acac8d6829977a1e61e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Feb 2016 09:38:56 +0100 Subject: [PATCH 008/186] Remove config details (already covered in docs) --- homeassistant/components/ecobee.py | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index f1ce746b48e..215e32594b5 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -1,31 +1,11 @@ """ homeassistant.components.ecobee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ecobee Component - -This component adds support for Ecobee3 Wireless Thermostats. -You will need to setup developer access to your thermostat, -and create and API key on the ecobee website. - -The first time you run this component you will see a configuration -component card in Home Assistant. This card will contain a PIN code -that you will need to use to authorize access to your thermostat. You -can do this at https://www.ecobee.com/consumerportal/index.html -Click My Apps, Add application, Enter Pin and click Authorize. - -After authorizing the application click the button in the configuration -card. Now your thermostat and sensors should shown in home-assistant. - -You can use the optional hold_temp parameter to set whether or not holds -are set indefintely or until the next scheduled event. - -ecobee: - api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf - hold_temp: True +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ecobee component +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ecobee/ """ - from datetime import timedelta import logging import os @@ -82,7 +62,7 @@ def request_configuration(network, hass, config): def setup_ecobee(hass, network, config): - """ Setup ecobee thermostat """ + """ Setup Ecobee thermostat. """ # If ecobee has a PIN then it needs to be configured. if network.pin is not None: request_configuration(network, hass, config) From 00afaac54cb12f4eec330c78dc3188730af6d2b5 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Feb 2016 14:19:11 +0100 Subject: [PATCH 009/186] Update for file header, docstrings, and PEP8/PEP257 --- tests/__init__.py | 6 ++ tests/common.py | 4 +- tests/components/automation/test_init.py | 1 + .../automation/test_numeric_state.py | 18 ++--- tests/components/automation/test_state.py | 2 +- tests/components/automation/test_template.py | 2 +- tests/components/automation/test_zone.py | 4 +- .../binary_sensor/test_command_sensor.py | 3 +- tests/components/device_tracker/test_init.py | 4 +- .../device_tracker/test_locative.py | 11 ++- tests/components/device_tracker/test_mqtt.py | 6 ++ .../device_tracker/test_owntracks.py | 22 +++--- tests/components/garage_door/test_demo.py | 2 +- tests/components/light/test_init.py | 8 +- tests/components/light/test_mqtt.py | 2 +- tests/components/media_player/test_demo.py | 5 -- tests/components/notify/test_demo.py | 3 +- tests/components/rollershutter/test_mqtt.py | 2 +- .../components/sensor/test_command_sensor.py | 15 ++-- tests/components/sensor/test_mfi.py | 2 +- tests/components/sensor/test_mqtt.py | 2 +- tests/components/sensor/test_template.py | 5 +- tests/components/sensor/test_yr.py | 4 +- .../components/switch/test_command_switch.py | 6 +- tests/components/switch/test_init.py | 4 +- tests/components/switch/test_mfi.py | 2 +- tests/components/switch/test_mqtt.py | 4 +- tests/components/switch/test_template.py | 7 +- tests/components/test_alexa.py | 6 +- tests/components/test_api.py | 4 +- tests/components/test_configurator.py | 4 +- tests/components/test_conversation.py | 2 +- tests/components/test_demo.py | 2 +- .../test_device_sun_light_trigger.py | 2 +- tests/components/test_frontend.py | 2 +- tests/components/test_graphite.py | 2 +- tests/components/test_group.py | 2 +- tests/components/test_history.py | 4 +- tests/components/test_influx.py | 2 +- tests/components/test_init.py | 4 +- tests/components/test_introduction.py | 7 +- tests/components/test_logbook.py | 4 +- tests/components/test_logger.py | 4 +- tests/components/test_mqtt.py | 14 ++-- tests/components/test_mqtt_eventstream.py | 4 +- tests/components/test_proximity.py | 78 +++++++++---------- tests/components/test_recorder.py | 2 +- tests/components/test_shell_command.py | 6 +- tests/components/test_splunk.py | 3 +- tests/components/test_statsd.py | 4 +- tests/components/test_sun.py | 4 +- tests/components/test_updater.py | 4 +- tests/components/test_weblink.py | 7 +- tests/components/test_zone.py | 2 +- .../thermostat/test_heat_control.py | 3 +- .../custom_components/device_tracker/test.py | 4 +- tests/config/custom_components/light/test.py | 4 +- tests/config/custom_components/switch/test.py | 4 +- tests/helpers/test_entity.py | 4 +- tests/helpers/test_entity_component.py | 4 +- tests/helpers/test_event.py | 2 +- tests/helpers/test_event_decorators.py | 2 +- tests/helpers/test_init.py | 4 +- tests/helpers/test_service.py | 4 +- tests/helpers/test_state.py | 12 ++- tests/test_config.py | 3 +- tests/test_core.py | 2 +- tests/test_loader.py | 4 +- tests/test_remote.py | 2 +- tests/util/test_color.py | 3 + tests/util/test_dt.py | 4 +- tests/util/test_init.py | 4 +- tests/util/test_package.py | 13 ++-- tests/util/test_template.py | 6 +- 74 files changed, 212 insertions(+), 211 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 4ae2e497414..e582287f941 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,9 @@ +""" +tests.__init__ +~~~~~~~~~~~~~~ + +Tests initialization. +""" import betamax from homeassistant.util import location diff --git a/tests/common.py b/tests/common.py index 436304b34ef..0751281874b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,6 +1,6 @@ """ -tests.helper -~~~~~~~~~~~~~ +tests.common +~~~~~~~~~~~~ Helper method for writing tests. """ diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3f6b0dab6f1..cf7a0567c25 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,6 +1,7 @@ """ tests.components.automation.test_init ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Tests automation component. """ import unittest diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 49246e48117..5d6a2c6596e 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -267,7 +267,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is below 10 - self.hass.states.set('test.entity', 9, { 'test_attribute': 11 }) + self.hass.states.set('test.entity', 9, {'test_attribute': 11}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -285,7 +285,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 11 is not below 10 - self.hass.states.set('test.entity', 11, { 'test_attribute': 9 }) + self.hass.states.set('test.entity', 11, {'test_attribute': 9}) self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) @@ -304,7 +304,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': 9 }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': 9}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -323,7 +323,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 11 is not below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': 11 }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': 11}) self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) @@ -342,7 +342,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 11 is not below 10, entity state value should not be tested - self.hass.states.set('test.entity', '9', { 'test_attribute': 11 }) + self.hass.states.set('test.entity', '9', {'test_attribute': 11}) self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) @@ -380,7 +380,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is not below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': 9, 'not_test_attribute': 11 }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': 9, 'not_test_attribute': 11}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -399,7 +399,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 3 is below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': [11, 15, 3] }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': [11, 15, 3]}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -418,7 +418,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': '0.9' }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': '0.9'}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -437,7 +437,7 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 11 is not below 10 - self.hass.states.set('test.entity', 'entity', { 'test_attribute': 11, 'not_test_attribute': 9 }) + self.hass.states.set('test.entity', 'entity', {'test_attribute': 11, 'not_test_attribute': 9}) self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index a31f694f8c0..5e30f505092 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,6 +1,6 @@ """ tests.components.automation.test_state -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests state automation. """ diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 0c63d03d6c3..3ccd523af7b 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -1,6 +1,6 @@ """ tests.components.automation.test_template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests template automation. """ diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index bfb92bb0b1a..12bd35c8ae4 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,6 +1,6 @@ """ -tests.components.automation.test_location -±±±~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_zone +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests location automation. """ diff --git a/tests/components/binary_sensor/test_command_sensor.py b/tests/components/binary_sensor/test_command_sensor.py index aa6a87c2061..20aca38e0e6 100644 --- a/tests/components/binary_sensor/test_command_sensor.py +++ b/tests/components/binary_sensor/test_command_sensor.py @@ -1,10 +1,9 @@ """ tests.components.binary_sensor.command_sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests command binary sensor. """ - import unittest import homeassistant.core as ha diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index fb368bf863a..ac44be640ad 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -1,6 +1,6 @@ """ -tests.test_component_device_tracker -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.device_tracker.test_init +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the device tracker compoments. """ diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index cd02380d324..b64fbda8345 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -1,10 +1,9 @@ """ -tests.components.device_tracker.locative -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.device_tracker.test_locative +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the locative device tracker component. """ - import unittest from unittest.mock import patch @@ -24,7 +23,8 @@ hass = None def _url(data={}): """ Helper method to generate urls. """ - data = "&".join(["{}={}".format(name, value) for name, value in data.items()]) + data = "&".join(["{}={}".format(name, value) for + name, value in data.items()]) return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data) @@ -60,6 +60,7 @@ def tearDownModule(): # pylint: disable=invalid-name """ Stops the Home Assistant server. """ hass.stop() + # Stub out update_config or else Travis CI raises an exception @patch('homeassistant.components.device_tracker.update_config') class TestLocative(unittest.TestCase): @@ -114,7 +115,6 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(copy)) self.assertEqual(422, req.status_code) - def test_enter_and_exit(self, update_config): """ Test when there is a known zone """ data = { @@ -166,7 +166,6 @@ class TestLocative(unittest.TestCase): state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state self.assertEqual(state_name, 'work') - def test_exit_after_enter(self, update_config): """ Test when an exit message comes after an enter message """ diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py index 6e219621a60..8a00ed7dae1 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -1,3 +1,9 @@ +""" +tests.components.device_tracker.test_mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the MQTT device tracker component. +""" import unittest import os diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index c4d60d63693..ac9243ae5f8 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -1,8 +1,8 @@ """ -tests.components.sensor.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.device_tracker.test_owntracks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests template sensor. +Tests Owntracks device tracker. """ import json import os @@ -111,14 +111,14 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): }) self.hass.states.set( - 'zone.passive', 'zoning', - { - 'name': 'zone', - 'latitude': 3.0, - 'longitude': 1.0, - 'radius': 10, - 'passive': True - }) + 'zone.passive', 'zoning', + { + 'name': 'zone', + 'latitude': 3.0, + 'longitude': 1.0, + 'radius': 10, + 'passive': True + }) # Clear state between teste self.hass.states.set(DEVICE_TRACKER_STATE, None) owntracks.REGIONS_ENTERED = defaultdict(list) diff --git a/tests/components/garage_door/test_demo.py b/tests/components/garage_door/test_demo.py index 7c959709c48..781b47bb3d7 100644 --- a/tests/components/garage_door/test_demo.py +++ b/tests/components/garage_door/test_demo.py @@ -1,6 +1,6 @@ """ tests.components.garage_door.test_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests demo garage door component. """ diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index fcbe89ee27b..5830e371f6e 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,8 +1,8 @@ """ -tests.test_component_switch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_init +~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests switch component. +Tests light component. """ # pylint: disable=too-many-public-methods,protected-access import unittest @@ -18,7 +18,7 @@ from tests.common import mock_service, get_test_home_assistant class TestLight(unittest.TestCase): - """ Test the switch module. """ + """ Test the light module. """ def setUp(self): # pylint: disable=invalid-name self.hass = get_test_home_assistant() diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 552c4f1e028..834573b7c6e 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -1,6 +1,6 @@ """ tests.components.light.test_mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests mqtt light. diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index c19fd59e97f..c67d5d47623 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -6,13 +6,9 @@ Tests demo media_player component. """ import unittest from unittest.mock import patch -from pprint import pprint import homeassistant.core as ha -from homeassistant.const import ( - STATE_OFF, STATE_ON, STATE_UNKNOWN, STATE_PLAYING, STATE_PAUSED) import homeassistant.components.media_player as mp - entity_id = 'media_player.walkman' @@ -138,4 +134,3 @@ class TestDemoMediaPlayer(unittest.TestCase): mp.media_seek(self.hass, 100, ent_id) self.hass.pool.block_till_done() assert mock_seek.called - diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 182a2a38610..41b59e96e4f 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -1,7 +1,8 @@ """ tests.components.notify.test_demo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests notify demo component + +Tests notify demo component. """ import unittest diff --git a/tests/components/rollershutter/test_mqtt.py b/tests/components/rollershutter/test_mqtt.py index 261618a5e02..df206b3047d 100644 --- a/tests/components/rollershutter/test_mqtt.py +++ b/tests/components/rollershutter/test_mqtt.py @@ -1,6 +1,6 @@ """ tests.components.rollershutter.test_mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests MQTT rollershutter. """ diff --git a/tests/components/sensor/test_command_sensor.py b/tests/components/sensor/test_command_sensor.py index ae6c9452d3f..0f4133ddb6e 100644 --- a/tests/components/sensor/test_command_sensor.py +++ b/tests/components/sensor/test_command_sensor.py @@ -1,10 +1,9 @@ """ -tests.components.sensor.command_sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.sensor.test_command_sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests command sensor. """ - import unittest import homeassistant.core as ha @@ -12,7 +11,7 @@ from homeassistant.components.sensor import command_sensor class TestCommandSensorSensor(unittest.TestCase): - """ Test the Template sensor. """ + """ Test the Command line sensor. """ def setUp(self): self.hass = ha.HomeAssistant() @@ -22,7 +21,7 @@ class TestCommandSensorSensor(unittest.TestCase): self.hass.stop() def test_setup(self): - """ Test sensor setup """ + """ Test sensor setup. """ config = {'name': 'Test', 'unit_of_measurement': 'in', 'command': 'echo 5'} @@ -43,7 +42,7 @@ class TestCommandSensorSensor(unittest.TestCase): self.assertEqual('5', entity.state) def test_setup_bad_config(self): - """ Test setup with a bad config """ + """ Test setup with a bad configuration. """ config = {} devices = [] @@ -59,7 +58,7 @@ class TestCommandSensorSensor(unittest.TestCase): self.assertEqual(0, len(devices)) def test_template(self): - """ Test command sensor with template """ + """ Test command sensor with template. """ data = command_sensor.CommandSensorData('echo 50') entity = command_sensor.CommandSensor( @@ -68,7 +67,7 @@ class TestCommandSensorSensor(unittest.TestCase): self.assertEqual(5, float(entity.state)) def test_bad_command(self): - """ Test bad command """ + """ Test bad command. """ data = command_sensor.CommandSensorData('asdfasdf') data.update() diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index bda8032fb49..9f8efcc6245 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -1,6 +1,6 @@ """ tests.components.sensor.test_mfi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests mFi sensor. """ diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index ce98a9399a5..81aeeae6d3c 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -2,7 +2,7 @@ tests.components.sensor.test_mqtt ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests mqtt sensor. +Tests MQTT sensor. """ import unittest diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index 96de2f6e875..26eb2c4b1a7 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -1,10 +1,9 @@ """ -tests.components.sensor.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.sensor.test_template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests template sensor. """ - import homeassistant.core as ha import homeassistant.components.sensor as sensor diff --git a/tests/components/sensor/test_yr.py b/tests/components/sensor/test_yr.py index 59f2b6b676b..c4979720a58 100644 --- a/tests/components/sensor/test_yr.py +++ b/tests/components/sensor/test_yr.py @@ -1,6 +1,6 @@ """ tests.components.sensor.test_yr -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Yr sensor. """ @@ -73,7 +73,7 @@ class TestSensorYr: assert '1025.1' == state.state state = self.hass.states.get('sensor.yr_wind_direction') - assert '°'== state.attributes.get('unit_of_measurement') + assert '°' == state.attributes.get('unit_of_measurement') assert '81.8' == state.state state = self.hass.states.get('sensor.yr_humidity') diff --git a/tests/components/switch/test_command_switch.py b/tests/components/switch/test_command_switch.py index 3684f78fff4..43055413e09 100644 --- a/tests/components/switch/test_command_switch.py +++ b/tests/components/switch/test_command_switch.py @@ -1,6 +1,6 @@ """ tests.components.switch.test_command_switch -~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests command switch. """ @@ -55,7 +55,6 @@ class TestCommandSwitch(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) - def test_state_value(self): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'switch_status') @@ -79,7 +78,7 @@ class TestCommandSwitch(unittest.TestCase): switch.turn_on(self.hass, 'switch.test') self.hass.pool.block_till_done() - + state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) @@ -89,7 +88,6 @@ class TestCommandSwitch(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) - def test_state_json_value(self): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'switch_status') diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index dc7129ca541..6860ca85c3b 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -1,6 +1,6 @@ """ -tests.test_component_switch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.switch.test_init +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests switch component. """ diff --git a/tests/components/switch/test_mfi.py b/tests/components/switch/test_mfi.py index 8201152352c..0f763dc3392 100644 --- a/tests/components/switch/test_mfi.py +++ b/tests/components/switch/test_mfi.py @@ -1,6 +1,6 @@ """ tests.components.switch.test_mfi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests mFi switch. """ diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index b7c20e5ff94..e5058f7826e 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -2,7 +2,7 @@ tests.components.switch.test_mqtt ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests mqtt switch. +Tests MQTT switch. """ import unittest @@ -107,4 +107,4 @@ class TestSensorMQTT(unittest.TestCase): self.hass.pool.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) \ No newline at end of file + self.assertEqual(STATE_OFF, state.state) diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py index aeffe9ff194..e4bd20b27cb 100644 --- a/tests/components/switch/test_template.py +++ b/tests/components/switch/test_template.py @@ -1,10 +1,9 @@ """ tests.components.switch.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests template switch. """ - import homeassistant.core as ha import homeassistant.components as core import homeassistant.components.switch as switch @@ -27,7 +26,6 @@ class TestTemplateSwitch: self.hass.services.register('test', 'automation', record_call) - def teardown_method(self, method): """ Stop down stuff we started. """ self.hass.stop() @@ -53,7 +51,6 @@ class TestTemplateSwitch: } }) - state = self.hass.states.set('switch.test_state', STATE_ON) self.hass.pool.block_till_done() @@ -66,7 +63,6 @@ class TestTemplateSwitch: state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_OFF - def test_template_state_boolean_on(self): assert switch.setup(self.hass, { 'switch': { @@ -278,7 +274,6 @@ class TestTemplateSwitch: assert 1 == len(self.calls) - def test_off_action(self): assert switch.setup(self.hass, { 'switch': { diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index 42acf5b3f62..60dbcbb0157 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -1,6 +1,6 @@ """ -tests.test_component_alexa -~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_alexa +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Home Assistant Alexa component does what it should do. """ @@ -33,7 +33,7 @@ calls = [] @patch('homeassistant.components.http.util.get_local_ip', return_value='127.0.0.1') def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name - """Initalize a Home Assistant server for testing this module.""" + """ Initalize a Home Assistant server for testing this module. """ global hass hass = ha.HomeAssistant() diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 17cfd24f832..e2e6341c4d6 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -1,6 +1,6 @@ """ -tests.test_component_http -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_api +~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Home Assistant HTTP component does what it should do. """ diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index f41a5319ffd..4e2f30f6c32 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -1,6 +1,6 @@ """ -tests.test_component_configurator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_configurator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Configurator component. """ diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 243fe128b28..2b41349c126 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -1,6 +1,6 @@ """ tests.components.test_conversation -~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Conversation component. """ diff --git a/tests/components/test_demo.py b/tests/components/test_demo.py index 13cc55ed7dc..39418852597 100644 --- a/tests/components/test_demo.py +++ b/tests/components/test_demo.py @@ -1,6 +1,6 @@ """ tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ Tests demo component. """ diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index f3ec23a96bf..586d67c2f70 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -1,6 +1,6 @@ """ tests.test_component_device_sun_light_trigger -~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests device sun light trigger component. """ diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index e8c0c53d13e..0f4a2dc4134 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -1,6 +1,6 @@ """ tests.test_component_http -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Home Assistant HTTP component does what it should do. """ diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 720da54930f..c0ca228efe2 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -1,6 +1,6 @@ """ tests.components.test_graphite -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests graphite feeder. """ diff --git a/tests/components/test_group.py b/tests/components/test_group.py index d70828852e4..2301a15f59e 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -1,6 +1,6 @@ """ tests.test_component_group -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the group compoments. """ diff --git a/tests/components/test_history.py b/tests/components/test_history.py index f9b8e94d286..db0c154283d 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -1,6 +1,6 @@ """ -tests.test_component_history -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_history +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the history component. """ diff --git a/tests/components/test_influx.py b/tests/components/test_influx.py index 365b788da0d..d9bc00b4f60 100644 --- a/tests/components/test_influx.py +++ b/tests/components/test_influx.py @@ -1,6 +1,6 @@ """ tests.components.test_influxdb -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests influxdb component. """ diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 723149b19c6..51d84aab731 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -1,6 +1,6 @@ """ -tests.test_component_core -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_init +~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests core compoments. """ diff --git a/tests/components/test_introduction.py b/tests/components/test_introduction.py index 42c16081d1e..4c7d104ba49 100644 --- a/tests/components/test_introduction.py +++ b/tests/components/test_introduction.py @@ -1,12 +1,9 @@ """ -tests.components.introduction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_introduction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Test introduction. - -This test is primarily to ensure that default components don't crash HASS. """ - import unittest import homeassistant.core as ha diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index d2879b1f308..b6bc764660d 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -1,6 +1,6 @@ """ -tests.test_component_logbook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_logbook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the logbook component. """ diff --git a/tests/components/test_logger.py b/tests/components/test_logger.py index 5e3aeda88d3..2be5ca5c740 100644 --- a/tests/components/test_logger.py +++ b/tests/components/test_logger.py @@ -1,6 +1,6 @@ """ -tests.test_logger -~~~~~~~~~~~~~~~~~~ +tests.components.test_logger +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests logger component. """ diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py index 1a33eb6276b..9ca51c687bc 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -1,6 +1,6 @@ """ -tests.test_component_mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests MQTT component. """ @@ -63,10 +63,12 @@ class TestMQTT(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic', - self.calls[0][0].data['service_data'][mqtt.ATTR_TOPIC]) - self.assertEqual('test-payload', - self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD]) + self.assertEqual( + 'test-topic', + self.calls[0][0].data['service_data'][mqtt.ATTR_TOPIC]) + self.assertEqual( + 'test-payload', + self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD]) def test_service_call_without_topic_does_not_publish(self): self.hass.bus.fire(EVENT_CALL_SERVICE, { diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py index 5e1680ad2a4..269b672055e 100644 --- a/tests/components/test_mqtt_eventstream.py +++ b/tests/components/test_mqtt_eventstream.py @@ -1,6 +1,6 @@ """ -tests.test_component_mqtt_eventstream -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_mqtt_eventstream +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests MQTT eventstream component. """ diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index feca18a7a4e..c66b34a0acf 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -1,13 +1,13 @@ """ -tests.components.proximity -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_proximity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests proximity component. """ - import homeassistant.core as ha from homeassistant.components import proximity + class TestProximity: """ Test the Proximity component. """ @@ -25,7 +25,7 @@ class TestProximity: def teardown_method(self, method): """ Stop down stuff we started. """ self.hass.stop() - + def test_proximity(self): assert proximity.setup(self.hass, { 'proximity': { @@ -45,12 +45,12 @@ class TestProximity: assert state.state == 'not set' assert state.attributes.get('nearest') == 'not set' assert state.attributes.get('dir_of_travel') == 'not set' - + self.hass.states.set('proximity.home', '0') self.hass.pool.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == '0' - + def test_no_devices_in_config(self): assert not proximity.setup(self.hass, { 'proximity': { @@ -61,7 +61,7 @@ class TestProximity: 'tolerance': '1' } }) - + def test_no_tolerance_in_config(self): assert proximity.setup(self.hass, { 'proximity': { @@ -75,7 +75,7 @@ class TestProximity: } } }) - + def test_no_ignored_zones_in_config(self): assert proximity.setup(self.hass, { 'proximity': { @@ -87,7 +87,7 @@ class TestProximity: 'tolerance': '1' } }) - + def test_no_zone_in_config(self): assert proximity.setup(self.hass, { 'proximity': { @@ -100,8 +100,8 @@ class TestProximity: }, 'tolerance': '1' } - }) - + }) + def test_device_tracker_test1_in_zone(self): assert proximity.setup(self.hass, { 'proximity': { @@ -115,7 +115,7 @@ class TestProximity: 'tolerance': '1' } }) - + self.hass.states.set( 'device_tracker.test1', 'home', { @@ -128,7 +128,7 @@ class TestProximity: assert state.state == '0' assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'arrived' - + def test_device_trackers_in_zone(self): assert proximity.setup(self.hass, { 'proximity': { @@ -143,7 +143,7 @@ class TestProximity: 'tolerance': '1' } }) - + self.hass.states.set( 'device_tracker.test1', 'home', { @@ -164,7 +164,7 @@ class TestProximity: assert state.state == '0' assert (state.attributes.get('nearest') == 'test1, test2') or (state.attributes.get('nearest') == 'test2, test1') assert state.attributes.get('dir_of_travel') == 'arrived' - + def test_device_tracker_test1_away(self): assert proximity.setup(self.hass, { 'proximity': { @@ -178,7 +178,7 @@ class TestProximity: 'tolerance': '1' } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -190,7 +190,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - + def test_device_tracker_test1_awayfurther(self): assert proximity.setup(self.hass, { 'proximity': { @@ -203,7 +203,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -226,7 +226,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'away_from' - + def test_device_tracker_test1_awaycloser(self): assert proximity.setup(self.hass, { 'proximity': { @@ -239,7 +239,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -262,7 +262,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'towards' - + def test_all_device_trackers_in_ignored_zone(self): assert proximity.setup(self.hass, { 'proximity': { @@ -275,7 +275,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'work', { @@ -286,7 +286,7 @@ class TestProximity: assert state.state == 'not set' assert state.attributes.get('nearest') == 'not set' assert state.attributes.get('dir_of_travel') == 'not set' - + def test_device_tracker_test1_no_coordinates(self): assert proximity.setup(self.hass, { 'proximity': { @@ -300,7 +300,7 @@ class TestProximity: 'tolerance': '1' } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -310,7 +310,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'not set' assert state.attributes.get('dir_of_travel') == 'not set' - + def test_device_tracker_test1_awayfurther_than_test2_first_test1(self): self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -336,7 +336,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -359,7 +359,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - + def test_device_tracker_test1_awayfurther_than_test2_first_test2(self): self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -385,7 +385,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test2', 'not_home', { @@ -408,7 +408,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - + def test_device_tracker_test1_awayfurther_test2_in_ignored_zone(self): self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -434,7 +434,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -446,7 +446,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - + def test_device_tracker_test1_awayfurther_than_test2_first_test1_than_test2_than_test1(self): self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -472,7 +472,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -481,7 +481,7 @@ class TestProximity: 'longitude': 5.1 }) self.hass.pool.block_till_done() - + self.hass.states.set( 'device_tracker.test2', 'not_home', { @@ -515,7 +515,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' - + def test_device_tracker_test1_awayfurther_a_bit(self): assert proximity.setup(self.hass, { 'proximity': { @@ -529,7 +529,7 @@ class TestProximity: 'tolerance': 1000 } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -552,7 +552,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'stationary' - + def test_device_tracker_test1_nearest_after_test2_in_ignored_zone(self): self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -578,7 +578,7 @@ class TestProximity: } } }) - + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -590,7 +590,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - + self.hass.states.set( 'device_tracker.test2', 'not_home', { @@ -602,7 +602,7 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' - + self.hass.states.set( 'device_tracker.test2', 'work', { diff --git a/tests/components/test_recorder.py b/tests/components/test_recorder.py index 26e5fdfd6b7..0d42da865a0 100644 --- a/tests/components/test_recorder.py +++ b/tests/components/test_recorder.py @@ -1,5 +1,5 @@ """ -tests.test_component_recorder +tests.components.test_recorder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Recorder component. diff --git a/tests/components/test_shell_command.py b/tests/components/test_shell_command.py index eefbdc5b230..6fdc671f5ad 100644 --- a/tests/components/test_shell_command.py +++ b/tests/components/test_shell_command.py @@ -1,8 +1,8 @@ """ -tests.test_shell_command -~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_shell_command +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests Shell command component. """ import os import tempfile diff --git a/tests/components/test_splunk.py b/tests/components/test_splunk.py index 05c020c88ed..811878d537b 100644 --- a/tests/components/test_splunk.py +++ b/tests/components/test_splunk.py @@ -1,6 +1,6 @@ """ tests.components.test_splunk -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests splunk component. """ @@ -40,7 +40,6 @@ class TestSplunk(unittest.TestCase): self.assertEqual(EVENT_STATE_CHANGED, hass.bus.listen.call_args_list[0][0][0]) - def _setup(self, mock_requests): self.mock_post = mock_requests.post self.mock_request_exception = Exception diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py index 72c61a22f54..a793e519a2e 100644 --- a/tests/components/test_statsd.py +++ b/tests/components/test_statsd.py @@ -1,8 +1,8 @@ """ tests.components.test_statsd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests statsd feeder. +Tests StatsD feeder. """ import unittest from unittest import mock diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index 366b483d3ff..44f428dc9a0 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -1,6 +1,6 @@ """ -tests.test_component_sun -~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.test_sun +~~~~~~~~~~~~~~~~~~~~~~~~~ Tests Sun component. """ diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index 3e5cf55e0b0..e1e2f5a7950 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -1,6 +1,6 @@ """ -tests.test_updater -~~~~~~~~~~~~~~~~~~ +tests.components.test_updater +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests updater component. """ diff --git a/tests/components/test_weblink.py b/tests/components/test_weblink.py index 6a8354c549c..f93a6ba0840 100644 --- a/tests/components/test_weblink.py +++ b/tests/components/test_weblink.py @@ -1,4 +1,9 @@ -# -*- coding: utf-8 -*- +""" +tests.components.test_weblink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests weblink component. +""" import unittest import homeassistant.core as ha diff --git a/tests/components/test_zone.py b/tests/components/test_zone.py index df27bcf1fe5..12f9a04792f 100644 --- a/tests/components/test_zone.py +++ b/tests/components/test_zone.py @@ -1,6 +1,6 @@ """ tests.components.test_zone -±±±~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests zone component. """ diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index 4ec305574e2..aa5a43c9c0e 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -15,7 +15,7 @@ from homeassistant.const import ( TEMP_CELCIUS, ) import homeassistant.core as ha -from homeassistant.components import switch, thermostat +from homeassistant.components import thermostat entity = 'thermostat.test' @@ -138,4 +138,3 @@ class TestThermostatHeatControl(unittest.TestCase): self.hass.services.register('switch', SERVICE_TURN_ON, log_call) self.hass.services.register('switch', SERVICE_TURN_OFF, log_call) - diff --git a/tests/config/custom_components/device_tracker/test.py b/tests/config/custom_components/device_tracker/test.py index 635d400316f..11c0a090420 100644 --- a/tests/config/custom_components/device_tracker/test.py +++ b/tests/config/custom_components/device_tracker/test.py @@ -1,6 +1,6 @@ """ -custom_components.device_tracker.test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +config.custom_components.device_tracker.test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a mock device scanner. """ diff --git a/tests/config/custom_components/light/test.py b/tests/config/custom_components/light/test.py index 1512d080b05..6999f2436ca 100644 --- a/tests/config/custom_components/light/test.py +++ b/tests/config/custom_components/light/test.py @@ -1,6 +1,6 @@ """ -custom_components.light.test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +config.custom_components.light.test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a mock switch platform. diff --git a/tests/config/custom_components/switch/test.py b/tests/config/custom_components/switch/test.py index bb95154a94b..5d3c2585e5d 100644 --- a/tests/config/custom_components/switch/test.py +++ b/tests/config/custom_components/switch/test.py @@ -1,6 +1,6 @@ """ -custom_components.switch.test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +config.custom_components.switch.test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a mock switch platform. diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 8845bb622dc..37661623880 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1,6 +1,6 @@ """ -tests.test_helper_entity -~~~~~~~~~~~~~~~~~~~~~~~~ +tests.helpers.test_entity +~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the entity helper. """ diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 68aecd32f5b..6bd1a4e22f0 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -1,6 +1,6 @@ """ -tests.test_helper_entity_component -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.helpers.test_entity_component +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests the entity component helper. """ diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index aef7b834016..e1c17ba6c06 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -1,6 +1,6 @@ """ tests.helpers.event_test -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~ Tests event helpers. """ diff --git a/tests/helpers/test_event_decorators.py b/tests/helpers/test_event_decorators.py index db836e372ae..1ef8c042e69 100644 --- a/tests/helpers/test_event_decorators.py +++ b/tests/helpers/test_event_decorators.py @@ -1,6 +1,6 @@ """ tests.helpers.test_event_decorators -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests event decorator helpers. """ diff --git a/tests/helpers/test_init.py b/tests/helpers/test_init.py index 9d8afeb2b6d..fc6ec914046 100644 --- a/tests/helpers/test_init.py +++ b/tests/helpers/test_init.py @@ -1,6 +1,6 @@ """ -tests.test_helpers -~~~~~~~~~~~~~~~~~~~~ +tests.helpers.test_init +~~~~~~~~~~~~~~~~~~~~~~~ Tests component helpers. """ diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index de730e7e919..73081a7da7e 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -15,9 +15,7 @@ from tests.common import get_test_home_assistant, mock_service class TestServiceHelpers(unittest.TestCase): - """ - Tests the Home Assistant service helpers. - """ + """ Tests the Home Assistant service helpers. """ def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index e222e72fe4b..3924bd0489e 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -24,12 +24,10 @@ from tests.common import get_test_home_assistant, mock_service class TestStateHelpers(unittest.TestCase): - """ - Tests the Home Assistant event helpers. - """ + """ Tests the Home Assistant event helpers. """ def setUp(self): # pylint: disable=invalid-name - """ things to be run when tests are started. """ + """ Things to be run when tests are started. """ self.hass = get_test_home_assistant() core_components.setup(self.hass, {}) @@ -166,9 +164,9 @@ class TestStateHelpers(unittest.TestCase): def test_as_number_coercion(self): for _state in ('0', '0.0'): - self.assertEqual( - 0.0, float(state.state_as_number( - ha.State('domain.test', _state, {})))) + self.assertEqual(0.0, float( + state.state_as_number( + ha.State('domain.test', _state, {})))) for _state in ('1', '1.0'): self.assertEqual( 1.0, float(state.state_as_number( diff --git a/tests/test_config.py b/tests/test_config.py index 94557eda16e..87f0c4e5b03 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,6 @@ """ tests.test_config -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~ Tests config utils. """ @@ -102,7 +102,6 @@ class TestConfig(unittest.TestCase): with self.assertRaises(HomeAssistantError): config_util.load_yaml_config_file(YAML_PATH) - def test_load_yaml_config_preserves_key_order(self): with open(YAML_PATH, 'w') as f: f.write('hello: 0\n') diff --git a/tests/test_core.py b/tests/test_core.py index 0fbf0b0bdd9..6a19a2aa7e8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ """ tests.test_core -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~ Provides tests to verify that Home Assistant core works. """ diff --git a/tests/test_loader.py b/tests/test_loader.py index 124a5c87d16..7651cdee649 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,6 +1,6 @@ """ -ha_tests.test_loader -~~~~~~~~~~~~~~~~~~~~~~ +tests.test_loader +~~~~~~~~~~~~~~~~~ Provides tests to verify that we can load components. """ diff --git a/tests/test_remote.py b/tests/test_remote.py index 31ccad8f7aa..777f1c30e84 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,6 +1,6 @@ """ tests.remote -~~~~~~~~~~~~~~ +~~~~~~~~~~~~ Tests Home Assistant remote methods and classes. Uses port 8122 for master, 8123 for slave diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 4b4a70ebe9c..79bbcc01495 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -1,4 +1,7 @@ """ +tests.util.test_color +~~~~~~~~~~~~~~~~~~~~~ + Tests Home Assistant color util methods. """ import unittest diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 5deafb58040..78e77d8f14e 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -1,6 +1,6 @@ """ -tests.test_util -~~~~~~~~~~~~~~~~~ +tests.util.test_dt +~~~~~~~~~~~~~~~~~~ Tests Home Assistant date util methods. """ diff --git a/tests/util/test_init.py b/tests/util/test_init.py index f0a3eb8a109..bd546d4e5e1 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -1,6 +1,6 @@ """ -tests.test_util -~~~~~~~~~~~~~~~~~ +tests.util.test_init +~~~~~~~~~~~~~~~~~~~~ Tests Home Assistant util methods. """ diff --git a/tests/util/test_package.py b/tests/util/test_package.py index db5a8a88e94..f17eb681c08 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -1,4 +1,7 @@ """ +tests.util.test_packages +~~~~~~~~~~~~~~~~~~~~~~~~ + Tests Home Assistant package util methods. """ import os @@ -18,26 +21,26 @@ TEST_ZIP_REQ = 'file://{}#{}' \ class TestPackageUtil(unittest.TestCase): - """ Tests for homeassistant.util.package module """ + """ Tests for homeassistant.util.package module. """ def setUp(self): - """ Create local library for testing """ + """ Create local library for testing. """ self.tmp_dir = tempfile.TemporaryDirectory() self.lib_dir = os.path.join(self.tmp_dir.name, 'lib') def tearDown(self): - """ Remove local library """ + """ Remove local library. """ self.tmp_dir.cleanup() def test_install_existing_package(self): - """ Test an install attempt on an existing package """ + """ Test an install attempt on an existing package. """ self.assertTrue(package.check_package_exists( TEST_EXIST_REQ, self.lib_dir)) self.assertTrue(package.install_package(TEST_EXIST_REQ)) def test_install_package_zip(self): - """ Test an install attempt from a zip path """ + """ Test an install attempt from a zip path. """ self.assertFalse(package.check_package_exists( TEST_ZIP_REQ, self.lib_dir)) self.assertFalse(package.check_package_exists( diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 844826f80d5..314f6b887b7 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -1,8 +1,8 @@ """ -tests.test_util -~~~~~~~~~~~~~~~~~ +tests.util.test_template +~~~~~~~~~~~~~~~~~~~~~~~~ -Tests Home Assistant util methods. +Tests Home Assistant template util methods. """ # pylint: disable=too-many-public-methods import unittest From 66c2fa127084e31aa0f26087f46756aa034af775 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sat, 13 Feb 2016 18:30:34 +0000 Subject: [PATCH 010/186] Make it easier to run cibuild locally Just treat lack of travis environment as "run everything" --- script/cibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/cibuild b/script/cibuild index 3a8dbd69c6e..95c9e48d6f8 100755 --- a/script/cibuild +++ b/script/cibuild @@ -5,7 +5,7 @@ cd "$(dirname "$0")/.." -if [ "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then +if [ -z "$TRAVIS_PYTHON_VERSION" -o "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then echo "Verifying requirements_all.txt..." python3 setup.py -q develop tput setaf 1 From 92334495512c0dc22a09db475a7ffdb4410eedf4 Mon Sep 17 00:00:00 2001 From: Flyte Date: Sat, 13 Feb 2016 16:35:52 +0000 Subject: [PATCH 011/186] Add colorlog to Dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index a1f9d459295..0d41841f452 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ VOLUME /config RUN mkdir -p /usr/src/app WORKDIR /usr/src/app +RUN pip3 install --no-cache-dir colorlog + # For the nmap tracker RUN apt-get update && \ apt-get install -y --no-install-recommends nmap net-tools && \ From c51dd64bd860b20e003b69c65222f5be03d4b73b Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sat, 13 Feb 2016 22:00:47 +0000 Subject: [PATCH 012/186] Convert Honeywell platform to use somecomfort library --- .coveragerc | 1 - .../components/thermostat/honeywell.py | 183 ++++-------------- requirements_all.txt | 3 + tests/components/thermostat/test_honeywell.py | 127 ++++++++++++ 4 files changed, 167 insertions(+), 147 deletions(-) create mode 100644 tests/components/thermostat/test_honeywell.py diff --git a/.coveragerc b/.coveragerc index 06cfc7d7471..27016770693 100644 --- a/.coveragerc +++ b/.coveragerc @@ -149,7 +149,6 @@ omit = homeassistant/components/switch/wemo.py homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/homematic.py - homeassistant/components/thermostat/honeywell.py homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/radiotherm.py diff --git a/homeassistant/components/thermostat/honeywell.py b/homeassistant/components/thermostat/honeywell.py index d4365512e96..8d72db0a288 100644 --- a/homeassistant/components/thermostat/honeywell.py +++ b/homeassistant/components/thermostat/honeywell.py @@ -9,21 +9,16 @@ https://home-assistant.io/components/thermostat.honeywell/ import logging import socket -import requests - from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) -REQUIREMENTS = ['evohomeclient==0.2.4'] +REQUIREMENTS = ['evohomeclient==0.2.4', + 'somecomfort==0.2.0'] _LOGGER = logging.getLogger(__name__) CONF_AWAY_TEMP = "away_temperature" -US_SYSTEM_SWITCH_POSITIONS = {1: 'Heat', - 2: 'Off', - 3: 'Cool'} -US_BASEURL = 'https://mytotalconnectcomfort.com/portal' def _setup_round(username, password, config, add_devices): @@ -55,20 +50,21 @@ def _setup_round(username, password, config, add_devices): # config will be used later # pylint: disable=unused-argument def _setup_us(username, password, config, add_devices): - session = requests.Session() - if not HoneywellUSThermostat.do_login(session, username, password): + import somecomfort + + try: + client = somecomfort.SomeComfort(username, password) + except somecomfort.AuthError: _LOGGER.error('Failed to login to honeywell account %s', username) return False - - thermostats = HoneywellUSThermostat.get_devices(session) - if not thermostats: - _LOGGER.error('No thermostats found in account %s', username) + except somecomfort.SomeComfortError as ex: + _LOGGER.error('Failed to initialize honeywell client: %s', str(ex)) return False - add_devices([HoneywellUSThermostat(id_, username, password, - name=name, - session=session) - for id_, name in thermostats.items()]) + add_devices([HoneywellUSThermostat(client, device) + for location in client.locations_by_id.values() + for device in location.devices_by_id.values()]) + return True # pylint: disable=unused-argument @@ -179,157 +175,52 @@ class RoundThermostat(ThermostatDevice): class HoneywellUSThermostat(ThermostatDevice): """ Represents a Honeywell US Thermostat. """ - # pylint: disable=too-many-arguments - def __init__(self, ident, username, password, name='honeywell', - session=None): - self._ident = ident - self._username = username - self._password = password - self._name = name - if not session: - self._session = requests.Session() - self._login() - self._session = session - # Maybe this should be configurable? - self._timeout = 30 - # Yeah, really. - self._session.headers['X-Requested-With'] = 'XMLHttpRequest' - self._update() - - @staticmethod - def get_devices(session): - """ Return a dict of devices. - - :param session: A session already primed from do_login - :returns: A dict of devices like: device_id=name - """ - url = '%s/Location/GetLocationListData' % US_BASEURL - resp = session.post(url, params={'page': 1, 'filter': ''}) - if resp.status_code == 200: - return {device['DeviceID']: device['Name'] - for device in resp.json()[0]['Devices']} - else: - return None - - @staticmethod - def do_login(session, username, password, timeout=30): - """ Log into mytotalcomfort.com - - :param session: A requests.Session object to use - :param username: Account username - :param password: Account password - :param timeout: Timeout to use with requests - :returns: A boolean indicating success - """ - session.headers['X-Requested-With'] = 'XMLHttpRequest' - session.get(US_BASEURL, timeout=timeout) - params = {'UserName': username, - 'Password': password, - 'RememberMe': 'false', - 'timeOffset': 480} - resp = session.post(US_BASEURL, params=params, - timeout=timeout) - if resp.status_code != 200: - _LOGGER('Login failed for user %s', username) - return False - else: - return True - - def _login(self): - return self.do_login(self._session, self._username, self._password, - timeout=self._timeout) - - def _keepalive(self): - resp = self._session.get('%s/Account/KeepAlive') - if resp.status_code != 200: - if self._login(): - _LOGGER.info('Re-logged into honeywell account') - else: - _LOGGER.error('Failed to re-login to honeywell account') - return False - else: - _LOGGER.debug('Keepalive succeeded') - return True - - def _get_data(self): - if not self._keepalive: - return {'error': 'not logged in'} - url = '%s/Device/CheckDataSession/%s' % (US_BASEURL, self._ident) - resp = self._session.get(url, timeout=self._timeout) - if resp.status_code < 300: - return resp.json() - else: - return {'error': resp.status_code} - - def _set_data(self, data): - if not self._keepalive: - return {'error': 'not logged in'} - url = '%s/Device/SubmitControlScreenChanges' % US_BASEURL - data['DeviceID'] = self._ident - resp = self._session.post(url, data=data, timeout=self._timeout) - if resp.status_code < 300: - return resp.json() - else: - return {'error': resp.status_code} - - def _update(self): - data = self._get_data()['latestData'] - if 'error' not in data: - self._data = data + def __init__(self, client, device): + self._client = client + self._device = device @property def is_fan_on(self): - return self._data['fanData']['fanIsRunning'] + return self._device.fan_running @property def name(self): - return self._name + return self._device.name @property def unit_of_measurement(self): - unit = self._data['uiData']['DisplayUnits'] - if unit == 'F': - return TEMP_FAHRENHEIT - else: - return TEMP_CELCIUS + return (TEMP_CELCIUS if self._device.temperature_unit == 'C' + else TEMP_FAHRENHEIT) @property def current_temperature(self): - self._update() - return self._data['uiData']['DispTemperature'] + self._device.refresh() + return self._device.current_temperature @property def target_temperature(self): - setpoint = US_SYSTEM_SWITCH_POSITIONS.get( - self._data['uiData']['SystemSwitchPosition'], - 'Off') - return self._data['uiData']['%sSetpoint' % setpoint] + if self._device.system_mode == 'cool': + return self._device.setpoint_cool + else: + return self._device.setpoint_heat def set_temperature(self, temperature): """ Set target temperature. """ - data = {'SystemSwitch': None, - 'HeatSetpoint': None, - 'CoolSetpoint': None, - 'HeatNextPeriod': None, - 'CoolNextPeriod': None, - 'StatusHeat': None, - 'StatusCool': None, - 'FanMode': None} - setpoint = US_SYSTEM_SWITCH_POSITIONS.get( - self._data['uiData']['SystemSwitchPosition'], - 'Off') - data['%sSetpoint' % setpoint] = temperature - self._set_data(data) + import somecomfort + try: + if self._device.system_mode == 'cool': + self._device.setpoint_cool = temperature + else: + self._device.setpoint_heat = temperature + except somecomfort.SomeComfortError: + _LOGGER.error('Temperature %.1f out of range', temperature) @property def device_state_attributes(self): """ Return device specific state attributes. """ - fanmodes = {0: "auto", - 1: "on", - 2: "circulate"} - return {"fan": (self._data['fanData']['fanIsRunning'] and - 'running' or 'idle'), - "fanmode": fanmodes[self._data['fanData']['fanMode']]} + return {'fan': (self.is_fan_on and 'running' or 'idle'), + 'fanmode': self._device.fan_mode, + 'system_mode': self._device.system_mode} def turn_away_mode_on(self): pass diff --git a/requirements_all.txt b/requirements_all.txt index bfd1e8c7c45..ef448f2c4c3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -230,6 +230,9 @@ sleekxmpp==1.3.1 # homeassistant.components.media_player.snapcast snapcast==1.1.1 +# homeassistant.components.thermostat.honeywell +somecomfort==0.2.0 + # homeassistant.components.sensor.speedtest speedtest-cli==0.3.4 diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py new file mode 100644 index 00000000000..0edc479d59a --- /dev/null +++ b/tests/components/thermostat/test_honeywell.py @@ -0,0 +1,127 @@ +""" +tests.components.thermostat.honeywell +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the Honeywell thermostat module. +""" +import unittest +from unittest import mock + +import somecomfort + +from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, + TEMP_CELCIUS, TEMP_FAHRENHEIT) +import homeassistant.components.thermostat.honeywell as honeywell + + +class TestHoneywell(unittest.TestCase): + @mock.patch('somecomfort.SomeComfort') + @mock.patch('homeassistant.components.thermostat.' + 'honeywell.HoneywellUSThermostat') + def test_setup_us(self, mock_ht, mock_sc): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + 'region': 'us', + } + hass = mock.MagicMock() + add_devices = mock.MagicMock() + + locations = [ + mock.MagicMock(), + mock.MagicMock(), + ] + devices_1 = [mock.MagicMock()] + devices_2 = [mock.MagicMock(), mock.MagicMock] + mock_sc.return_value.locations_by_id.values.return_value = \ + locations + locations[0].devices_by_id.values.return_value = devices_1 + locations[1].devices_by_id.values.return_value = devices_2 + + result = honeywell.setup_platform(hass, config, add_devices) + self.assertTrue(result) + mock_sc.assert_called_once_with('user', 'pass') + mock_ht.assert_has_calls([ + mock.call(mock_sc.return_value, devices_1[0]), + mock.call(mock_sc.return_value, devices_2[0]), + mock.call(mock_sc.return_value, devices_2[1]), + ]) + + @mock.patch('somecomfort.SomeComfort') + def test_setup_us_failures(self, mock_sc): + hass = mock.MagicMock() + add_devices = mock.MagicMock() + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + 'region': 'us', + } + + mock_sc.side_effect = somecomfort.AuthError + result = honeywell.setup_platform(hass, config, add_devices) + self.assertFalse(result) + self.assertFalse(add_devices.called) + + mock_sc.side_effect = somecomfort.SomeComfortError + result = honeywell.setup_platform(hass, config, add_devices) + self.assertFalse(result) + self.assertFalse(add_devices.called) + + +class TestHoneywellUS(unittest.TestCase): + def setup_method(self, method): + self.client = mock.MagicMock() + self.device = mock.MagicMock() + self.honeywell = honeywell.HoneywellUSThermostat( + self.client, self.device) + + self.device.fan_running = True + self.device.name = 'test' + self.device.temperature_unit = 'F' + self.device.current_temperature = 72 + self.device.setpoint_cool = 78 + self.device.setpoint_heat = 65 + self.device.system_mode = 'heat' + self.device.fan_mode = 'auto' + + def test_properties(self): + self.assertTrue(self.honeywell.is_fan_on) + self.assertEqual('test', self.honeywell.name) + self.assertEqual(72, self.honeywell.current_temperature) + + def test_unit_of_measurement(self): + self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.unit_of_measurement) + self.device.temperature_unit = 'C' + self.assertEqual(TEMP_CELCIUS, self.honeywell.unit_of_measurement) + + def test_target_temp(self): + self.assertEqual(65, self.honeywell.target_temperature) + self.device.system_mode = 'cool' + self.assertEqual(78, self.honeywell.target_temperature) + + def test_set_temp(self): + self.honeywell.set_temperature(70) + self.assertEqual(70, self.device.setpoint_heat) + self.assertEqual(70, self.honeywell.target_temperature) + + self.device.system_mode = 'cool' + self.assertEqual(78, self.honeywell.target_temperature) + self.honeywell.set_temperature(74) + self.assertEqual(74, self.device.setpoint_cool) + self.assertEqual(74, self.honeywell.target_temperature) + + def test_set_temp_fail(self): + self.device.setpoint_heat = mock.MagicMock( + side_effect=somecomfort.SomeComfortError) + self.honeywell.set_temperature(123) + + def test_attributes(self): + expected = { + 'fan': 'running', + 'fanmode': 'auto', + 'system_mode': 'heat', + } + self.assertEqual(expected, self.honeywell.device_state_attributes) + expected['fan'] = 'idle' + self.device.fan_running = False + self.assertEqual(expected, self.honeywell.device_state_attributes) From 5921e65d838b39dc031fde6f524d395cba632eda Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sat, 13 Feb 2016 23:10:08 +0000 Subject: [PATCH 013/186] Allow specifying location and/or thermostat for Honeywell US This lets you optionally only add thermostats by location or specific device id, instead of all the thermostats in your account. This would be helpful if you have two devices in different houses (i.e vacation home), etc. --- .../components/thermostat/honeywell.py | 7 ++- tests/components/thermostat/test_honeywell.py | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/thermostat/honeywell.py b/homeassistant/components/thermostat/honeywell.py index 8d72db0a288..4baa8e5c9ed 100644 --- a/homeassistant/components/thermostat/honeywell.py +++ b/homeassistant/components/thermostat/honeywell.py @@ -61,9 +61,14 @@ def _setup_us(username, password, config, add_devices): _LOGGER.error('Failed to initialize honeywell client: %s', str(ex)) return False + dev_id = config.get('thermostat') + loc_id = config.get('location') + add_devices([HoneywellUSThermostat(client, device) for location in client.locations_by_id.values() - for device in location.devices_by_id.values()]) + for device in location.devices_by_id.values() + if ((not loc_id or location.locationid == loc_id) and + (not dev_id or device.deviceid == dev_id))]) return True diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index 0edc479d59a..5cb063846a5 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -67,6 +67,69 @@ class TestHoneywell(unittest.TestCase): self.assertFalse(result) self.assertFalse(add_devices.called) + @mock.patch('somecomfort.SomeComfort') + @mock.patch('homeassistant.components.thermostat.' + 'honeywell.HoneywellUSThermostat') + def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + 'region': 'us', + 'location': loc, + 'thermostat': dev, + } + locations = { + 1: mock.MagicMock(locationid=mock.sentinel.loc1, + devices_by_id={ + 11: mock.MagicMock( + deviceid=mock.sentinel.loc1dev1), + 12: mock.MagicMock( + deviceid=mock.sentinel.loc1dev2), + }), + 2: mock.MagicMock(locationid=mock.sentinel.loc2, + devices_by_id={ + 21: mock.MagicMock( + deviceid=mock.sentinel.loc2dev1), + }), + 3: mock.MagicMock(locationid=mock.sentinel.loc3, + devices_by_id={ + 31: mock.MagicMock( + deviceid=mock.sentinel.loc3dev1), + }), + } + mock_sc.return_value = mock.MagicMock(locations_by_id=locations) + hass = mock.MagicMock() + add_devices = mock.MagicMock() + self.assertEqual(True, + honeywell.setup_platform(hass, config, add_devices)) + + return mock_ht.call_args_list, mock_sc + + def test_us_filtered_thermostat_1(self): + result, client = self._test_us_filtered_devices( + dev=mock.sentinel.loc1dev1) + devices = [x[0][1].deviceid for x in result] + self.assertEqual([mock.sentinel.loc1dev1], devices) + + def test_us_filtered_thermostat_2(self): + result, client = self._test_us_filtered_devices( + dev=mock.sentinel.loc2dev1) + devices = [x[0][1].deviceid for x in result] + self.assertEqual([mock.sentinel.loc2dev1], devices) + + def test_us_filtered_location_1(self): + result, client = self._test_us_filtered_devices( + loc=mock.sentinel.loc1) + devices = [x[0][1].deviceid for x in result] + self.assertEqual([mock.sentinel.loc1dev1, + mock.sentinel.loc1dev2], devices) + + def test_us_filtered_location_2(self): + result, client = self._test_us_filtered_devices( + loc=mock.sentinel.loc2) + devices = [x[0][1].deviceid for x in result] + self.assertEqual([mock.sentinel.loc2dev1], devices) + class TestHoneywellUS(unittest.TestCase): def setup_method(self, method): From 0fbd947426c7d83c6fcbe9af80b75196dc06106e Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 01:05:18 +0000 Subject: [PATCH 014/186] Test Honeywell Round thermostat This includes two changes to the round code: - Return True on setup success - Break out the default away temp into a constant --- .../components/thermostat/honeywell.py | 4 +- tests/components/thermostat/test_honeywell.py | 120 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/thermostat/honeywell.py b/homeassistant/components/thermostat/honeywell.py index 4baa8e5c9ed..8411ba08fa3 100644 --- a/homeassistant/components/thermostat/honeywell.py +++ b/homeassistant/components/thermostat/honeywell.py @@ -19,13 +19,14 @@ REQUIREMENTS = ['evohomeclient==0.2.4', _LOGGER = logging.getLogger(__name__) CONF_AWAY_TEMP = "away_temperature" +DEFAULT_AWAY_TEMP = 16 def _setup_round(username, password, config, add_devices): from evohomeclient import EvohomeClient try: - away_temp = float(config.get(CONF_AWAY_TEMP, 16)) + away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP)) except ValueError: _LOGGER.error("value entered for item %s should convert to a number", CONF_AWAY_TEMP) @@ -45,6 +46,7 @@ def _setup_round(username, password, config, add_devices): "Connection error logging into the honeywell evohome web service" ) return False + return True # config will be used later diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index 5cb063846a5..e0e3f1bd758 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -4,6 +4,7 @@ tests.components.thermostat.honeywell Tests the Honeywell thermostat module. """ +import socket import unittest from unittest import mock @@ -131,6 +132,125 @@ class TestHoneywell(unittest.TestCase): self.assertEqual([mock.sentinel.loc2dev1], devices) + @mock.patch('evohomeclient.EvohomeClient') + @mock.patch('homeassistant.components.thermostat.honeywell.' + 'RoundThermostat') + def test_eu_setup_full_config(self, mock_round, mock_evo): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + honeywell.CONF_AWAY_TEMP: 20, + 'region': 'eu', + } + mock_evo.return_value.temperatures.return_value = [ + {'id': 'foo'}, {'id': 'bar'}] + hass = mock.MagicMock() + add_devices = mock.MagicMock() + self.assertTrue(honeywell.setup_platform(hass, config, add_devices)) + mock_evo.assert_called_once_with('user', 'pass') + mock_evo.return_value.temperatures.assert_called_once_with( + force_refresh=True) + mock_round.assert_has_calls([ + mock.call(mock_evo.return_value, 'foo', True, 20), + mock.call(mock_evo.return_value, 'bar', False, 20), + ]) + self.assertEqual(2, add_devices.call_count) + + @mock.patch('evohomeclient.EvohomeClient') + @mock.patch('homeassistant.components.thermostat.honeywell.' + 'RoundThermostat') + def test_eu_setup_partial_config(self, mock_round, mock_evo): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + 'region': 'eu', + } + mock_evo.return_value.temperatures.return_value = [ + {'id': 'foo'}, {'id': 'bar'}] + hass = mock.MagicMock() + add_devices = mock.MagicMock() + self.assertTrue(honeywell.setup_platform(hass, config, add_devices)) + default = honeywell.DEFAULT_AWAY_TEMP + mock_round.assert_has_calls([ + mock.call(mock_evo.return_value, 'foo', True, default), + mock.call(mock_evo.return_value, 'bar', False, default), + ]) + + @mock.patch('evohomeclient.EvohomeClient') + @mock.patch('homeassistant.components.thermostat.honeywell.' + 'RoundThermostat') + def test_eu_setup_bad_temp(self, mock_round, mock_evo): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + honeywell.CONF_AWAY_TEMP: 'ponies', + 'region': 'eu', + } + self.assertFalse(honeywell.setup_platform(None, config, None)) + + @mock.patch('evohomeclient.EvohomeClient') + @mock.patch('homeassistant.components.thermostat.honeywell.' + 'RoundThermostat') + def test_eu_setup_error(self, mock_round, mock_evo): + config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + honeywell.CONF_AWAY_TEMP: 20, + 'region': 'eu', + } + mock_evo.return_value.temperatures.side_effect = socket.error + add_devices = mock.MagicMock() + hass = mock.MagicMock() + self.assertFalse(honeywell.setup_platform(hass, config, add_devices)) + + +class TestHoneywellRound(unittest.TestCase): + def setup_method(self, method): + def fake_temperatures(force_refresh=None): + temps = [ + {'id': '1', 'temp': 20, 'setpoint': 21, + 'thermostat': 'main', 'name': 'House'}, + {'id': '2', 'temp': 21, 'setpoint': 22, + 'thermostat': 'DOMESTIC_HOT_WATER'}, + ] + return temps + + self.device = mock.MagicMock() + self.device.temperatures.side_effect = fake_temperatures + self.round1 = honeywell.RoundThermostat(self.device, '1', + True, 16) + self.round2 = honeywell.RoundThermostat(self.device, '2', + False, 17) + + def test_attributes(self): + self.assertEqual('House', self.round1.name) + self.assertEqual(TEMP_CELCIUS, self.round1.unit_of_measurement) + self.assertEqual(20, self.round1.current_temperature) + self.assertEqual(21, self.round1.target_temperature) + self.assertFalse(self.round1.is_away_mode_on) + + self.assertEqual('Hot Water', self.round2.name) + self.assertEqual(TEMP_CELCIUS, self.round2.unit_of_measurement) + self.assertEqual(21, self.round2.current_temperature) + self.assertEqual(None, self.round2.target_temperature) + self.assertFalse(self.round2.is_away_mode_on) + + def test_away_mode(self): + self.assertFalse(self.round1.is_away_mode_on) + self.round1.turn_away_mode_on() + self.assertTrue(self.round1.is_away_mode_on) + self.device.set_temperature.assert_called_once_with('House', 16) + + self.device.set_temperature.reset_mock() + self.round1.turn_away_mode_off() + self.assertFalse(self.round1.is_away_mode_on) + self.device.cancel_temp_override.assert_called_once_with('House') + + def test_set_temperature(self): + self.round1.set_temperature(25) + self.device.set_temperature.assert_called_once_with('House', 25) + + class TestHoneywellUS(unittest.TestCase): def setup_method(self, method): self.client = mock.MagicMock() From e170484f1668eefe79eb8a4966f51958e7becdd4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 13 Feb 2016 21:20:49 -0800 Subject: [PATCH 015/186] Change helpers.extract_domain_configs from generator to list bc concurrency --- homeassistant/helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index ec7996ff6df..b94530e525c 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -61,4 +61,4 @@ def config_per_platform(config, domain, logger): def extract_domain_configs(config, domain): """ Extract keys from config for given domain name. """ pattern = re.compile(r'^{}(| .+)$'.format(domain)) - return (key for key in config.keys() if pattern.match(key)) + return [key for key in config.keys() if pattern.match(key)] From 9e816cfd3fc360138f6a61fbe1ad5a07683cbf42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Sun, 14 Feb 2016 06:34:00 +0100 Subject: [PATCH 016/186] lock _states to prevent size change during iteration --- homeassistant/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 37f909e218a..9d4321a39ab 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -404,8 +404,9 @@ class StateMachine(object): domain_filter = domain_filter.lower() - return [state.entity_id for state in self._states.values() - if state.domain == domain_filter] + with self._lock: + return [state.entity_id for state in self._states.values() + if state.domain == domain_filter] def all(self): """Create a list of all states.""" From ada2fb4ec0b0565ccb0c5fccdbbd884f90a21ebd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 13 Feb 2016 22:57:40 -0800 Subject: [PATCH 017/186] Fire event when we remove a state --- homeassistant/core.py | 24 +++++++++++++++++++----- homeassistant/helpers/event.py | 15 +++++++++------ tests/helpers/test_event.py | 30 ++++++++++++++++++++++-------- tests/test_core.py | 21 +++++++++++++++++---- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 37f909e218a..1cddc43ba43 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -438,7 +438,20 @@ class StateMachine(object): entity_id = entity_id.lower() with self._lock: - return self._states.pop(entity_id, None) is not None + old_state = self._states.pop(entity_id, None) + + if old_state is None: + return False + + event_data = { + 'entity_id': entity_id, + 'old_state': old_state, + 'new_state': None, + } + + self._bus.fire(EVENT_STATE_CHANGED, event_data) + + return True def set(self, entity_id, new_state, attributes=None): """Set the state of an entity, add entity if it does not exist. @@ -468,10 +481,11 @@ class StateMachine(object): state = State(entity_id, new_state, attributes, last_changed) self._states[entity_id] = state - event_data = {'entity_id': entity_id, 'new_state': state} - - if old_state: - event_data['old_state'] = old_state + event_data = { + 'entity_id': entity_id, + 'old_state': old_state, + 'new_state': state, + } self._bus.fire(EVENT_STATE_CHANGED, event_data) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 0f0deac58b1..d602fa5641f 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -34,16 +34,19 @@ def track_state_change(hass, entity_ids, action, from_state=None, if event.data['entity_id'] not in entity_ids: return - if 'old_state' in event.data: - old_state = event.data['old_state'].state - else: + if event.data['old_state'] is None: old_state = None + else: + old_state = event.data['old_state'].state - if _matcher(old_state, from_state) and \ - _matcher(event.data['new_state'].state, to_state): + if event.data['new_state'] is None: + new_state = None + else: + new_state = event.data['new_state'].state + if _matcher(old_state, from_state) and _matcher(new_state, to_state): action(event.data['entity_id'], - event.data.get('old_state'), + event.data['old_state'], event.data['new_state']) hass.bus.listen(EVENT_STATE_CHANGED, state_change_listener) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index e1c17ba6c06..d69d0f198f5 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -24,8 +24,6 @@ class TestEventHelpers(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ self.hass = ha.HomeAssistant() - self.hass.states.set("light.Bowl", "on") - self.hass.states.set("switch.AC", "off") def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ @@ -87,7 +85,7 @@ class TestEventHelpers(unittest.TestCase): self.assertEqual(3, len(wildcard_runs)) def test_track_state_change(self): - """ Test track_state_change. """ + """Test track_state_change.""" # 2 lists to track how often our callbacks get called specific_runs = [] wildcard_runs = [] @@ -97,32 +95,48 @@ class TestEventHelpers(unittest.TestCase): 'on', 'off') track_state_change( - self.hass, 'light.Bowl', lambda a, b, c: wildcard_runs.append(1), + self.hass, 'light.Bowl', + lambda _, old_s, new_s: wildcard_runs.append((old_s, new_s)), ha.MATCH_ALL, ha.MATCH_ALL) + # Adding state to state machine + self.hass.states.set("light.Bowl", "on") + self.hass.pool.block_till_done() + self.assertEqual(0, len(specific_runs)) + self.assertEqual(1, len(wildcard_runs)) + self.assertIsNone(wildcard_runs[-1][0]) + self.assertIsNotNone(wildcard_runs[-1][1]) + # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(0, len(specific_runs)) - self.assertEqual(0, len(wildcard_runs)) + self.assertEqual(1, len(wildcard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'off') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) + self.assertEqual(2, len(wildcard_runs)) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) - self.assertEqual(2, len(wildcard_runs)) + self.assertEqual(3, len(wildcard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) - self.assertEqual(3, len(wildcard_runs)) + self.assertEqual(4, len(wildcard_runs)) + + self.hass.states.remove('light.bowl') + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(5, len(wildcard_runs)) + self.assertIsNotNone(wildcard_runs[-1][0]) + self.assertIsNone(wildcard_runs[-1][1]) def test_track_sunrise(self): """ Test track sunrise """ diff --git a/tests/test_core.py b/tests/test_core.py index 6a19a2aa7e8..d1b2221998e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -20,7 +20,6 @@ import homeassistant.core as ha from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError) import homeassistant.util.dt as dt_util -from homeassistant.helpers.event import track_state_change from homeassistant.const import ( __version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, TEMP_CELCIUS, @@ -150,7 +149,7 @@ class TestEventBus(unittest.TestCase): self.bus._pool.add_worker() old_count = len(self.bus.listeners) - listener = lambda x: len + def listener(_): pass self.bus.listen('test', listener) @@ -280,12 +279,26 @@ class TestStateMachine(unittest.TestCase): def test_remove(self): """ Test remove method. """ - self.assertTrue('light.bowl' in self.states.entity_ids()) + self.pool.add_worker() + events = [] + self.bus.listen(EVENT_STATE_CHANGED, + lambda event: events.append(event)) + + self.assertIn('light.bowl', self.states.entity_ids()) self.assertTrue(self.states.remove('light.bowl')) - self.assertFalse('light.bowl' in self.states.entity_ids()) + self.pool.block_till_done() + + self.assertNotIn('light.bowl', self.states.entity_ids()) + self.assertEqual(1, len(events)) + self.assertEqual('light.bowl', events[0].data.get('entity_id')) + self.assertIsNotNone(events[0].data.get('old_state')) + self.assertEqual('light.bowl', events[0].data['old_state'].entity_id) + self.assertIsNone(events[0].data.get('new_state')) # If it does not exist, we should get False self.assertFalse(self.states.remove('light.Bowl')) + self.pool.block_till_done() + self.assertEqual(1, len(events)) def test_case_insensitivty(self): self.pool.add_worker() From 06de73ff809261fc067d4480c0cfbd61113be079 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 13 Feb 2016 23:00:38 -0800 Subject: [PATCH 018/186] Allow removing a state via API + remote StateMachine --- homeassistant/components/api.py | 20 ++++++++++++++++++++ homeassistant/remote.py | 32 +++++++++++++++++++++++++++++++- tests/test_remote.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 11f1826549e..0764e84915f 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -63,6 +63,9 @@ def setup(hass, config): hass.http.register_path( 'PUT', re.compile(r'/api/states/(?P[a-zA-Z\._0-9]+)'), _handle_post_state_entity) + hass.http.register_path( + 'DELETE', re.compile(r'/api/states/(?P[a-zA-Z\._0-9]+)'), + _handle_delete_state_entity) # /events hass.http.register_path('GET', URL_API_EVENTS, _handle_get_api_events) @@ -240,6 +243,22 @@ def _handle_post_state_entity(handler, path_match, data): location=URL_API_STATES_ENTITY.format(entity_id)) +def _handle_delete_state_entity(handler, path_match, data): + """Handle request to delete an entity from state machine. + + This handles the following paths: + /api/states/ + """ + entity_id = path_match.group('entity_id') + + if handler.server.hass.states.remove(entity_id): + handler.write_json_message( + "Entity not found", HTTP_NOT_FOUND) + else: + handler.write_json_message( + "Entity removed", HTTP_OK) + + def _handle_get_api_events(handler, path_match, data): """ Handles getting overview of event listeners. """ handler.write_json(_events_json(handler.server.hass)) @@ -258,6 +277,7 @@ def _handle_api_post_events_event(handler, path_match, event_data): if event_data is not None and not isinstance(event_data, dict): handler.write_json_message( "event_data should be an object", HTTP_UNPROCESSABLE_ENTITY) + return event_origin = ha.EventOrigin.remote diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 3b47b60365c..6b55622adce 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -247,6 +247,13 @@ class StateMachine(ha.StateMachine): bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener) + def remove(self, entity_id): + """Remove the state of an entity. + + Returns boolean to indicate if an entity was removed. + """ + return remove_state(self._api, entity_id) + def set(self, entity_id, new_state, attributes=None): """ Calls set_state on remote API . """ set_state(self._api, entity_id, new_state, attributes) @@ -258,7 +265,10 @@ class StateMachine(ha.StateMachine): def _state_changed_listener(self, event): """ Listens for state changed events and applies them. """ - self._states[event.data['entity_id']] = event.data['new_state'] + if event.data['new_state'] is None: + self._states.pop(event.data['entity_id'], None) + else: + self._states[event.data['entity_id']] = event.data['new_state'] class JSONEncoder(json.JSONEncoder): @@ -415,6 +425,26 @@ def get_states(api): return [] +def remove_state(api, entity_id): + """Call API to remove state for entity_id. + + Returns True if entity is gone (removed/never existed). + """ + try: + req = api(METHOD_DELETE, URL_API_STATES_ENTITY.format(entity_id)) + + if req.status_code in (200, 404): + return True + + _LOGGER.error("Error removing state: %d - %s", + req.status_code, req.text) + return False + except HomeAssistantError: + _LOGGER.exception("Error removing state") + + return False + + def set_state(api, entity_id, new_state, attributes=None): """ Tells API to update state for entity_id. diff --git a/tests/test_remote.py b/tests/test_remote.py index 777f1c30e84..bf6a916f22c 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -135,9 +135,17 @@ class TestRemoteMethods(unittest.TestCase): self.assertEqual(hass.states.all(), remote.get_states(master_api)) self.assertEqual([], remote.get_states(broken_api)) + def test_remove_state(self): + """ Test Python API set_state. """ + hass.states.set('test.remove_state', 'set_test') + + self.assertIn('test.remove_state', hass.states.entity_ids()) + remote.remove_state(master_api, 'test.remove_state') + self.assertNotIn('test.remove_state', hass.states.entity_ids()) + def test_set_state(self): """ Test Python API set_state. """ - hass.states.set('test.test', 'set_test') + remote.set_state(master_api, 'test.test', 'set_test') state = hass.states.get('test.test') @@ -225,6 +233,29 @@ class TestRemoteClasses(unittest.TestCase): self.assertEqual("remote.statemachine test", slave.states.get("remote.test").state) + def test_statemachine_remove_from_master(self): + hass.states.set("remote.master_remove", "remove me!") + hass.pool.block_till_done() + + self.assertIn('remote.master_remove', slave.states.entity_ids()) + + hass.states.remove("remote.master_remove") + hass.pool.block_till_done() + + self.assertNotIn('remote.master_remove', slave.states.entity_ids()) + + def test_statemachine_remove_from_slave(self): + hass.states.set("remote.slave_remove", "remove me!") + hass.pool.block_till_done() + + self.assertIn('remote.slave_remove', slave.states.entity_ids()) + + self.assertTrue(slave.states.remove("remote.slave_remove")) + slave.pool.block_till_done() + hass.pool.block_till_done() + + self.assertNotIn('remote.slave_remove', slave.states.entity_ids()) + def test_eventbus_fire(self): """ Test if events fired from the eventbus get fired. """ test_value = [] From 8bea5c06de0b9259594b1fc03abd8438ce7aac23 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 13 Feb 2016 23:42:11 -0800 Subject: [PATCH 019/186] Add assumed_state property to entity --- homeassistant/components/switch/demo.py | 12 +++++++++--- homeassistant/const.py | 3 +++ homeassistant/helpers/entity.py | 12 ++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 16676cb239e..0f6529dff52 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -12,17 +12,18 @@ from homeassistant.const import DEVICE_DEFAULT_NAME def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return demo switches. """ add_devices_callback([ - DemoSwitch('Decorative Lights', True, None), - DemoSwitch('AC', False, 'mdi:air-conditioner') + DemoSwitch('Decorative Lights', True, None, True), + DemoSwitch('AC', False, 'mdi:air-conditioner', False) ]) class DemoSwitch(SwitchDevice): """ Provides a demo switch. """ - def __init__(self, name, state, icon): + def __init__(self, name, state, icon, assumed): self._name = name or DEVICE_DEFAULT_NAME self._state = state self._icon = icon + self._assumed = assumed @property def should_poll(self): @@ -39,6 +40,11 @@ class DemoSwitch(SwitchDevice): """ Returns the icon to use for device if any. """ return self._icon + @property + def assumed_state(self): + """Return if the state is based on assumptions.""" + return self._assumed + @property def current_power_mwh(self): """ Current power usage in mwh. """ diff --git a/homeassistant/const.py b/homeassistant/const.py index 084f269e036..45fe2e0bd91 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -124,6 +124,9 @@ ATTR_LONGITUDE = "longitude" # Accuracy of location in meters ATTR_GPS_ACCURACY = 'gps_accuracy' +# If state is assumed +ATTR_ASSUMED_STATE = 'assumed_state' + # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" SERVICE_HOMEASSISTANT_RESTART = "restart" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 742b877946a..624029dc7fd 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -13,7 +13,7 @@ from homeassistant.util import ensure_unique_string, slugify from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON, DEVICE_DEFAULT_NAME, STATE_ON, STATE_OFF, STATE_UNKNOWN, STATE_UNAVAILABLE, - TEMP_CELCIUS, TEMP_FAHRENHEIT) + TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_ASSUMED_STATE) # Dict mapping entity_id to a boolean that overwrites the hidden property _OVERWRITE = defaultdict(dict) @@ -116,6 +116,11 @@ class Entity(object): """Return True if entity is available.""" return True + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return False + def update(self): """Retrieve latest state.""" pass @@ -164,12 +169,15 @@ class Entity(object): if ATTR_FRIENDLY_NAME not in attr and self.name is not None: attr[ATTR_FRIENDLY_NAME] = str(self.name) - if ATTR_ICON not in attr and self.icon is not None: + if self.icon is not None: attr[ATTR_ICON] = str(self.icon) if self.hidden: attr[ATTR_HIDDEN] = bool(self.hidden) + if self.assumed_state: + attr[ATTR_ASSUMED_STATE] = bool(self.assumed_state) + # overwrite properties that have been set in the config file attr.update(_OVERWRITE.get(self.entity_id, {})) From cafa4043b3652bb173d386d93e2327486660bc18 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 00:04:08 -0800 Subject: [PATCH 020/186] Move bootstrap endpoint from api to frontend --- homeassistant/components/api.py | 26 ++++--------------- homeassistant/components/frontend/__init__.py | 23 +++++++++++++--- homeassistant/const.py | 1 - 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 11f1826549e..aa57d1f57f7 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -20,7 +20,7 @@ from homeassistant.bootstrap import ERROR_LOG_FILENAME from homeassistant.const import ( URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM, URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS, - URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG, URL_API_LOG_OUT, + URL_API_CONFIG, URL_API_ERROR_LOG, URL_API_LOG_OUT, URL_API_TEMPLATE, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL, HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_UNPROCESSABLE_ENTITY, HTTP_HEADER_CONTENT_TYPE, @@ -48,10 +48,6 @@ def setup(hass, config): # /api/config hass.http.register_path('GET', URL_API_CONFIG, _handle_get_api_config) - # /api/bootstrap - hass.http.register_path( - 'GET', URL_API_BOOTSTRAP, _handle_get_api_bootstrap) - # /states hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states) hass.http.register_path( @@ -180,18 +176,6 @@ def _handle_get_api_config(handler, path_match, data): handler.write_json(handler.server.hass.config.as_dict()) -def _handle_get_api_bootstrap(handler, path_match, data): - """ Returns all data needed to bootstrap Home Assistant. """ - hass = handler.server.hass - - handler.write_json({ - 'config': hass.config.as_dict(), - 'states': hass.states.all(), - 'events': _events_json(hass), - 'services': _services_json(hass), - }) - - def _handle_get_api_states(handler, path_match, data): """ Returns a dict containing all entity ids and their state. """ handler.write_json(handler.server.hass.states.all()) @@ -242,7 +226,7 @@ def _handle_post_state_entity(handler, path_match, data): def _handle_get_api_events(handler, path_match, data): """ Handles getting overview of event listeners. """ - handler.write_json(_events_json(handler.server.hass)) + handler.write_json(events_json(handler.server.hass)) def _handle_api_post_events_event(handler, path_match, event_data): @@ -277,7 +261,7 @@ def _handle_api_post_events_event(handler, path_match, event_data): def _handle_get_api_services(handler, path_match, data): """ Handles getting overview of services. """ - handler.write_json(_services_json(handler.server.hass)) + handler.write_json(services_json(handler.server.hass)) # pylint: disable=invalid-name @@ -390,13 +374,13 @@ def _handle_post_api_template(handler, path_match, data): return -def _services_json(hass): +def services_json(hass): """ Generate services data to JSONify. """ return [{"domain": key, "services": value} for key, value in hass.services.services.items()] -def _events_json(hass): +def events_json(hass): """ Generate event data to JSONify. """ return [{"event": key, "listener_count": value} for key, value in hass.bus.listeners.items()] diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 06438c02140..b5e6fffb3d9 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -11,6 +11,7 @@ import logging from . import version, mdi_version import homeassistant.util as util from homeassistant.const import URL_ROOT, HTTP_OK +from homeassistant.components import api DOMAIN = 'frontend' DEPENDENCIES = ['api'] @@ -25,21 +26,23 @@ FRONTEND_URLS = [ re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'), ] +URL_API_BOOTSTRAP = "/api/bootstrap" + _FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) def setup(hass, config): """ Setup serving the frontend. """ - if 'http' not in hass.config.components: - _LOGGER.error('Dependency http is not loaded') - return False - for url in FRONTEND_URLS: hass.http.register_path('GET', url, _handle_get_root, False) hass.http.register_path('GET', '/service_worker.js', _handle_get_service_worker, False) + # Bootstrap API + hass.http.register_path( + 'GET', URL_API_BOOTSTRAP, _handle_get_api_bootstrap) + # Static files hass.http.register_path( 'GET', re.compile(r'/static/(?P[a-zA-Z\._\-0-9/]+)'), @@ -54,6 +57,18 @@ def setup(hass, config): return True +def _handle_get_api_bootstrap(handler, path_match, data): + """ Returns all data needed to bootstrap Home Assistant. """ + hass = handler.server.hass + + handler.write_json({ + 'config': hass.config.as_dict(), + 'states': hass.states.all(), + 'events': api.events_json(hass), + 'services': api.services_json(hass), + }) + + def _handle_get_root(handler, path_match, data): """ Renders the frontend. """ handler.send_response(HTTP_OK) diff --git a/homeassistant/const.py b/homeassistant/const.py index 084f269e036..0887db12785 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -173,7 +173,6 @@ URL_API_SERVICES = "/api/services" URL_API_SERVICES_SERVICE = "/api/services/{}/{}" URL_API_EVENT_FORWARD = "/api/event_forwarding" URL_API_COMPONENTS = "/api/components" -URL_API_BOOTSTRAP = "/api/bootstrap" URL_API_ERROR_LOG = "/api/error_log" URL_API_LOG_OUT = "/api/log_out" URL_API_TEMPLATE = "/api/template" From b29f2f6d6f558d824b1abeb90680f86dad6ab413 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 00:21:20 -0800 Subject: [PATCH 021/186] Remove usage of ATTR_FRIENDLY_NAME within components/platforms --- homeassistant/components/isy994.py | 5 ++--- homeassistant/components/light/tellstick.py | 4 +--- homeassistant/components/proximity.py | 7 +++++-- homeassistant/helpers/entity.py | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 5dc74b13bf0..6a0cee5dd17 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -16,8 +16,7 @@ from homeassistant.helpers import validate_config from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED, - EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED, - ATTR_FRIENDLY_NAME) + EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED) DOMAIN = "isy994" REQUIREMENTS = ['PyISY==1.0.5'] @@ -147,7 +146,7 @@ class ISYDeviceABC(ToggleEntity): @property def state_attributes(self): """ Returns the state attributes for the node. """ - attr = {ATTR_FRIENDLY_NAME: self.name} + attr = {} for name, prop in self._attrs.items(): attr[name] = getattr(self, prop) attr = self._attr_filter(attr) diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 48a5a3ed814..564d95f421b 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -7,8 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.tellstick/ """ from homeassistant.components.light import Light, ATTR_BRIGHTNESS -from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, - ATTR_FRIENDLY_NAME) +from homeassistant.const import EVENT_HOMEASSISTANT_STOP REQUIREMENTS = ['tellcore-py==1.1.2'] SIGNAL_REPETITIONS = 1 @@ -58,7 +57,6 @@ class TellstickLight(Light): import tellcore.constants as tellcore_constants self.tellstick_device = tellstick_device - self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name} self.signal_repetitions = signal_repetitions self._brightness = 0 diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index 31ff3b5d081..1bad6a7c485 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -26,7 +26,6 @@ DEFAULT_PROXIMITY_ZONE = 'home' ATTR_DIST_FROM = 'dist_to_zone' ATTR_DIR_OF_TRAVEL = 'dir_of_travel' ATTR_NEAREST = 'nearest' -ATTR_FRIENDLY_NAME = 'friendly_name' _LOGGER = logging.getLogger(__name__) @@ -94,6 +93,11 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes self.tolerance = tolerance self.proximity_zone = proximity_zone + @property + def name(self): + """Return the name of the entity.""" + return self.friendly_name + @property def state(self): """ Returns the state. """ @@ -110,7 +114,6 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes return { ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest, - ATTR_FRIENDLY_NAME: self.friendly_name } def check_proximity_state_change(self, entity, old_state, new_state): diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 742b877946a..2c0efdd6366 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -24,7 +24,7 @@ ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$") def generate_entity_id(entity_id_format, name, current_ids=None, hass=None): """Generate a unique entity ID based on given entity IDs or used ids.""" - name = name.lower() or DEVICE_DEFAULT_NAME.lower() + name = (name or DEVICE_DEFAULT_NAME).lower() if current_ids is None: if hass is None: raise RuntimeError("Missing required parameter currentids or hass") @@ -71,7 +71,7 @@ class Entity(object): @property def name(self): """Return the name of the entity.""" - return DEVICE_DEFAULT_NAME + return None @property def state(self): @@ -161,7 +161,7 @@ class Entity(object): state = STATE_UNAVAILABLE attr = {} - if ATTR_FRIENDLY_NAME not in attr and self.name is not None: + if self.name is not None: attr[ATTR_FRIENDLY_NAME] = str(self.name) if ATTR_ICON not in attr and self.icon is not None: From 01df1f8458a9c94478b2637a79f2946a5904d027 Mon Sep 17 00:00:00 2001 From: miniconfig Date: Sun, 14 Feb 2016 10:47:46 -0500 Subject: [PATCH 022/186] Converted state.attributes to dict. Fixes Issue #1252 --- homeassistant/components/splunk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk.py index e7c9de6e073..fd9a15b67ae 100644 --- a/homeassistant/components/splunk.py +++ b/homeassistant/components/splunk.py @@ -71,7 +71,7 @@ def setup(hass, config): { 'domain': state.domain, 'entity_id': state.object_id, - 'attributes': state.attributes, + 'attributes': dict(state.attributes), 'time': str(event.time_fired), 'value': _state, } From 263839a3365c7249ef6cdf3c4fc986c82e687136 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 16:36:51 +0000 Subject: [PATCH 023/186] Handle connection errors talking to UVC cameras during image fetch This requires uvcclient==0.6 which breaks out exceptions for us. Fixes #1244 --- homeassistant/components/camera/uvc.py | 13 ++++++++++--- requirements_all.txt | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index eeb447be05a..cb01fb4371d 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -14,7 +14,7 @@ import requests from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN, Camera -REQUIREMENTS = ['uvcclient==0.5'] +REQUIREMENTS = ['uvcclient==0.6'] _LOGGER = logging.getLogger(__name__) @@ -82,10 +82,17 @@ class UnifiVideoCamera(Camera): dict(name=self._name, addr=addr)) except socket.error: pass + except uvc_camera.CameraConnectError: + pass + except uvc_camera.CameraAuthError: + pass if not camera: _LOGGER.error('Unable to login to camera') return None - camera.login() - return camera.get_snapshot() + try: + camera.login() + return camera.get_snapshot() + except uvc_camera.CameraConnectError: + _LOGGER.error('Failed to connect to camera %s', self._name) diff --git a/requirements_all.txt b/requirements_all.txt index ef448f2c4c3..e4299f3e80c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -249,7 +249,7 @@ tellive-py==0.5.2 transmissionrpc==0.11 # homeassistant.components.camera.uvc -uvcclient==0.5 +uvcclient==0.6 # homeassistant.components.verisure vsure==0.5.0 From 0ba7fb40a42bc089d848cf960ff68d82a41fb5af Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 17:06:46 +0000 Subject: [PATCH 024/186] Improve UVC performance by not logging in on each image fetch This makes the UVC camera_image() method not log into the camera on every single image fetch, which reduces load on hass and the camera, and lowers the latency to get an actual image fetched. --- homeassistant/components/camera/uvc.py | 44 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index cb01fb4371d..7bc5f9fa9dd 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -58,6 +58,8 @@ class UnifiVideoCamera(Camera): self._uuid = uuid self._name = name self.is_streaming = False + self._connect_addr = None + self._camera = None @property def name(self): @@ -68,31 +70,55 @@ class UnifiVideoCamera(Camera): caminfo = self._nvr.get_camera(self._uuid) return caminfo['recordingSettings']['fullTimeRecordEnabled'] - def camera_image(self): + def _login(self): from uvcclient import camera as uvc_camera - caminfo = self._nvr.get_camera(self._uuid) + if self._connect_addr: + addrs = [self._connect_addr] + else: + addrs = [caminfo['host'], caminfo['internalHost']] + camera = None - for addr in [caminfo['host'], caminfo['internalHost']]: + for addr in addrs: try: camera = uvc_camera.UVCCameraClient(addr, caminfo['username'], 'ubnt') + camera.login() _LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s', dict(name=self._name, addr=addr)) + self._connect_addr = addr except socket.error: pass except uvc_camera.CameraConnectError: pass except uvc_camera.CameraAuthError: pass - if not camera: _LOGGER.error('Unable to login to camera') return None - try: - camera.login() - return camera.get_snapshot() - except uvc_camera.CameraConnectError: - _LOGGER.error('Failed to connect to camera %s', self._name) + self._camera = camera + return True + + def camera_image(self): + from uvcclient import camera as uvc_camera + if not self._camera: + if not self._login(): + return + + def _get_image(retry=True): + try: + return self._camera.get_snapshot() + except uvc_camera.CameraConnectError: + _LOGGER.error('Unable to contact camera') + except uvc_camera.CameraAuthError: + if retry: + self._login() + return _get_image(retry=False) + else: + _LOGGER.error('Unable to log into camera, unable ' + 'to get snapshot') + raise + + return _get_image() From 885b61a750b9b2b9648cb0ae01a316fee7a417bf Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 17:09:54 +0000 Subject: [PATCH 025/186] Implement brand and model for UVC cameras --- homeassistant/components/camera/uvc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index 7bc5f9fa9dd..19cd7c4b4be 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -70,6 +70,15 @@ class UnifiVideoCamera(Camera): caminfo = self._nvr.get_camera(self._uuid) return caminfo['recordingSettings']['fullTimeRecordEnabled'] + @property + def brand(self): + return 'Ubiquiti' + + @property + def model(self): + caminfo = self._nvr.get_camera(self._uuid) + return caminfo['model'] + def _login(self): from uvcclient import camera as uvc_camera caminfo = self._nvr.get_camera(self._uuid) From 97c0f5bb5aaddc9c788195310d8189727d198502 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Sat, 13 Feb 2016 19:56:32 -0500 Subject: [PATCH 026/186] convert testing infrastructure to tox This converts the testing infrastructure to tox for both locally testing and travis. This is nearly equivalent to the previous testing with the only exception that linting fails with the first tool to fail and won't process all of them. Slightly tricky thing is that tox resets *all* of the environment for it's subprocess runs by default. A couple of the dependencies we have will not install in non UTF8 locales: temper-python & XBee. --- .travis.yml | 27 ++++++++++++++++----------- CONTRIBUTING.md | 27 +++++++++++++++++++++++++-- script/cibuild | 42 ------------------------------------------ script/lint | 26 +++++--------------------- script/test | 32 ++++---------------------------- setup.cfg | 3 +++ tox.ini | 29 +++++++++++++++++++++++++++++ 7 files changed, 82 insertions(+), 104 deletions(-) delete mode 100755 script/cibuild create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index c01b0750360..1501b397770 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,20 @@ sudo: false -language: python +matrix: + fast_finish: true + include: + - python: "3.4" + env: TOXENV=py34 + - python: "3.4" + env: TOXENV=requirements + - python: "3.5" + env: TOXENV=lint + - python: "3.5" + env: TOXENV=py35 cache: directories: - $HOME/.cache/pip - # - "$HOME/virtualenv/python$TRAVIS_PYTHON_VERSION" -python: - - 3.4 - - 3.5 -install: - - "true" -script: - - script/cibuild -matrix: - fast_finish: true +install: pip install -U tox +language: python +script: tox +after_success: + - coveralls diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1606149a1c7..f63e5ffca34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ The process is straight-forward. - Fork the Home Assistant [git repository](https://github.com/balloob/home-assistant). - Write the code for your device, notification service, sensor, or IoT thing. - - Check it with ``pylint`` and ``flake8``. + - Ensure tests work - Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant. Still interested? Then you should read the next sections and get more details. @@ -66,6 +66,29 @@ The frontend is composed of [Polymer](https://www.polymer-project.org) web-compo When you are done with development and ready to commit your changes, run `build_frontend`, set `development=0` in your config and validate that everything still works. +## Testing your code + +To test your code before submission, used the `tox` tool. + + ```shell + > pip install -U tox + > tox + ``` + +This will run unit tests against python 3.4 and 3.5 (if both are +available locally), as well as run a set of tests which validate +`pep8` and `pylint` style of the code. + +You can optionally run tests on only one tox target using the `-e` +option to select an environment. + +For instance `tox -e lint` will run the linters only, `tox -e py34` +will run unit tests only on python 3.4. + ### Notes on PyLint and PEP8 validation -In case a PyLint warning cannot be avoided, add a comment to disable the PyLint check for that line. This can be done using the format `# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint warning is if you do not use the passed in datetime if you're listening for time change. +In case a PyLint warning cannot be avoided, add a comment to disable +the PyLint check for that line. This can be done using the format +`# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint +warning is if you do not use the passed in datetime if you're +listening for time change. diff --git a/script/cibuild b/script/cibuild deleted file mode 100755 index 95c9e48d6f8..00000000000 --- a/script/cibuild +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# script/cibuild: Setup environment for CI to run tests. This is primarily -# designed to run on the continuous integration server. - -cd "$(dirname "$0")/.." - -if [ -z "$TRAVIS_PYTHON_VERSION" -o "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then - echo "Verifying requirements_all.txt..." - python3 setup.py -q develop - tput setaf 1 - script/gen_requirements_all.py validate - VERIFY_REQUIREMENTS_STATUS=$? - tput sgr0 -else - VERIFY_REQUIREMENTS_STATUS=0 -fi - -if [ "$VERIFY_REQUIREMENTS_STATUS" != "0" ]; then - exit $VERIFY_REQUIREMENTS_STATUS -fi - -script/bootstrap_server > /dev/null -DEP_INSTALL_STATUS=$? - -if [ "$DEP_INSTALL_STATUS" != "0" ]; then - exit $DEP_INSTALL_STATUS -fi - -if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then - NO_LINT=1 -fi - -export NO_LINT - -script/test coverage - -STATUS=$? - -coveralls - -exit $STATUS diff --git a/script/lint b/script/lint index d99d030c86d..4a517ef7494 100755 --- a/script/lint +++ b/script/lint @@ -1,22 +1,6 @@ -# Run style checks +#!/bin/sh +# +# NOTE: all testing is now driven through tox. The tox command below +# performs roughly what this test did in the past. -cd "$(dirname "$0")/.." - -echo "Checking style with flake8..." -tput setaf 1 -flake8 --exclude www_static homeassistant -FLAKE8_STATUS=$? -tput sgr0 - -echo "Checking style with pylint..." -tput setaf 1 -pylint homeassistant -PYLINT_STATUS=$? -tput sgr0 - -if [ $FLAKE8_STATUS -eq 0 ] -then - exit $PYLINT_STATUS -else - exit $FLAKE8_STATUS -fi +tox -e lint diff --git a/script/test b/script/test index ea51783f4d3..dac5c43d2de 100755 --- a/script/test +++ b/script/test @@ -1,30 +1,6 @@ #!/bin/sh +# +# NOTE: all testing is now driven through tox. The tox command below +# performs roughly what this test did in the past. -# script/test: Run test suite for application. Optionally pass in a path to an -# individual test file to run a single test. - -cd "$(dirname "$0")/.." - -echo "Running tests..." - -if [ "$1" = "coverage" ]; then - py.test -v --timeout=30 --cov --cov-report= - TEST_STATUS=$? -else - py.test -v --timeout=30 - TEST_STATUS=$? -fi - -if [ "$NO_LINT" = "1" ]; then - LINT_STATUS=0 -else - script/lint - LINT_STATUS=$? -fi - -if [ $LINT_STATUS -eq 0 ] -then - exit $TEST_STATUS -else - exit $LINT_STATUS -fi +tox -e py34 diff --git a/setup.cfg b/setup.cfg index aab4b18bc12..3a41bcfdbee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,8 @@ universal = 1 [pytest] testpaths = tests +[flake8] +exclude = .venv,.git,.tox,docs,www_static,tests + [pep257] ignore = D203,D105 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000000..86287cd1df4 --- /dev/null +++ b/tox.ini @@ -0,0 +1,29 @@ +[tox] +envlist = py34, py35, lint, requirements +skip_missing_interpreters = True + +[testenv] +setenv = +; both temper-python and XBee modules have utf8 in their README files +; which get read in from setup.py. If we don't force our locale to a +; utf8 one, tox's env is reset. And the install of these 2 packages +; fail. + LANG=en_US.UTF-8 + PYTHONPATH = {toxinidir}:{toxinidir}/homeassistant +commands = + py.test -v --timeout=30 --cov --cov-report= {posargs} +deps = + -r{toxinidir}/requirements_all.txt + -r{toxinidir}/requirements_test.txt + +[testenv:lint] +basepython = python3 +commands = + flake8 + pylint homeassistant + +[testenv:requirements] +basepython = python3 +deps = +commands = + python script/gen_requirements_all.py validate \ No newline at end of file From 92a11819b44a581e27da7569a7b7ee55025bc2e3 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 18:23:58 +0000 Subject: [PATCH 027/186] Exclude venv directories from linting Exclude venv/ (a common virtualenv name) and bin/,lib/ if the local directory is configured as a venv. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3a41bcfdbee..93b0187a015 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ universal = 1 testpaths = tests [flake8] -exclude = .venv,.git,.tox,docs,www_static,tests +exclude = .venv,.git,.tox,docs,www_static,tests,venv,bin,lib [pep257] ignore = D203,D105 From dd2aec0a08a5e3e18cef7559ee823c881c11c488 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 12:54:16 -0800 Subject: [PATCH 028/186] Restructure tests to ensure unique ports --- tests/common.py | 16 +++- .../device_tracker/test_locative.py | 31 +++++--- tests/components/test_alexa.py | 75 +++++++++++-------- tests/components/test_api.py | 12 +-- tests/components/test_frontend.py | 10 +-- tests/components/test_group.py | 6 -- tests/test_remote.py | 22 +++--- 7 files changed, 97 insertions(+), 75 deletions(-) diff --git a/tests/common.py b/tests/common.py index 0751281874b..32f64ba2949 100644 --- a/tests/common.py +++ b/tests/common.py @@ -13,9 +13,11 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, - ATTR_DISCOVERED) + ATTR_DISCOVERED, SERVER_PORT) from homeassistant.components import sun, mqtt +_TEST_INSTANCE_PORT = SERVER_PORT + def get_test_config_dir(): """ Returns a path to a test config dir. """ @@ -43,6 +45,18 @@ def get_test_home_assistant(num_threads=None): return hass +def get_test_instance_port(): + """Return unused port for running test instance. + + The socket that holds the default port does not get released when we stop + HA in a different test case. Until I have figured out what is going on, + let's run each test on a different port. + """ + global _TEST_INSTANCE_PORT + _TEST_INSTANCE_PORT += 1 + return _TEST_INSTANCE_PORT + + def mock_service(hass, domain, service): """ Sets up a fake service. diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index b64fbda8345..32a63d0962f 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -13,9 +13,9 @@ from homeassistant import bootstrap, const import homeassistant.components.device_tracker as device_tracker import homeassistant.components.http as http -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, get_test_instance_port -SERVER_PORT = 8126 +SERVER_PORT = get_test_instance_port() HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT) hass = None @@ -128,7 +128,8 @@ class TestLocative(unittest.TestCase): # Enter the Home req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + state_name = hass.states.get('{}.{}'.format('device_tracker', + data['device'])).state self.assertEqual(state_name, 'home') data['id'] = 'HOME' @@ -137,7 +138,8 @@ class TestLocative(unittest.TestCase): # Exit Home req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + state_name = hass.states.get('{}.{}'.format('device_tracker', + data['device'])).state self.assertEqual(state_name, 'not_home') data['id'] = 'hOmE' @@ -146,7 +148,8 @@ class TestLocative(unittest.TestCase): # Enter Home again req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + state_name = hass.states.get('{}.{}'.format('device_tracker', + data['device'])).state self.assertEqual(state_name, 'home') data['trigger'] = 'exit' @@ -154,7 +157,8 @@ class TestLocative(unittest.TestCase): # Exit Home req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + state_name = hass.states.get('{}.{}'.format('device_tracker', + data['device'])).state self.assertEqual(state_name, 'not_home') data['id'] = 'work' @@ -163,7 +167,8 @@ class TestLocative(unittest.TestCase): # Enter Work req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + state_name = hass.states.get('{}.{}'.format('device_tracker', + data['device'])).state self.assertEqual(state_name, 'work') def test_exit_after_enter(self, update_config): @@ -181,7 +186,8 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + state = hass.states.get('{}.{}'.format('device_tracker', + data['device'])) self.assertEqual(state.state, 'home') data['id'] = 'Work' @@ -190,7 +196,8 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + state = hass.states.get('{}.{}'.format('device_tracker', + data['device'])) self.assertEqual(state.state, 'work') data['id'] = 'Home' @@ -200,7 +207,8 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + state = hass.states.get('{}.{}'.format('device_tracker', + data['device'])) self.assertEqual(state.state, 'work') def test_exit_first(self, update_config): @@ -218,5 +226,6 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(data)) self.assertEqual(200, req.status_code) - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + state = hass.states.get('{}.{}'.format('device_tracker', + data['device'])) self.assertEqual(state.state, 'not_home') diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index 60dbcbb0157..a309bf73668 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -15,17 +15,17 @@ from homeassistant import bootstrap, const import homeassistant.core as ha from homeassistant.components import alexa, http +from tests.common import get_test_instance_port + API_PASSWORD = "test1234" - -# Somehow the socket that holds the default port does not get released -# when we close down HA in a different test case. Until I have figured -# out what is going on, let's run this test on a different port. -SERVER_PORT = 8119 - +SERVER_PORT = get_test_instance_port() API_URL = "http://127.0.0.1:{}{}".format(SERVER_PORT, alexa.API_ENDPOINT) - HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD} +SESSION_ID = 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000' +APPLICATION_ID = 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' +REQUEST_ID = 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000' + hass = None calls = [] @@ -53,10 +53,16 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name 'type': 'plaintext', 'text': """ - {%- if is_state('device_tracker.paulus', 'home') and is_state('device_tracker.anne_therese', 'home') -%} + {%- if is_state('device_tracker.paulus', 'home') + and is_state('device_tracker.anne_therese', + 'home') -%} You are both home, you silly {%- else -%} - Anne Therese is at {{ states("device_tracker.anne_therese") }} and Paulus is at {{ states("device_tracker.paulus") }} + Anne Therese is at {{ + states("device_tracker.anne_therese") + }} and Paulus is at {{ + states("device_tracker.paulus") + }} {% endif %} """, } @@ -105,9 +111,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': True, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': {}, 'user': { @@ -116,7 +122,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'LaunchRequest', - 'requestId': 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z' } } @@ -130,9 +136,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': False, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': { 'supportedHoroscopePeriods': { @@ -147,7 +153,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'IntentRequest', - 'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z', 'intent': { 'name': 'GetZodiacHoroscopeIntent', @@ -162,7 +168,8 @@ class TestAlexa(unittest.TestCase): } req = _req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', {}).get('text') + text = req.json().get('response', {}).get('outputSpeech', + {}).get('text') self.assertEqual('You told us your sign is virgo.', text) def test_intent_request_with_slots_but_no_value(self): @@ -170,9 +177,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': False, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': { 'supportedHoroscopePeriods': { @@ -187,7 +194,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'IntentRequest', - 'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z', 'intent': { 'name': 'GetZodiacHoroscopeIntent', @@ -201,7 +208,8 @@ class TestAlexa(unittest.TestCase): } req = _req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', {}).get('text') + text = req.json().get('response', {}).get('outputSpeech', + {}).get('text') self.assertEqual('You told us your sign is .', text) def test_intent_request_without_slots(self): @@ -209,9 +217,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': False, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': { 'supportedHoroscopePeriods': { @@ -226,7 +234,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'IntentRequest', - 'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z', 'intent': { 'name': 'WhereAreWeIntent', @@ -235,16 +243,19 @@ class TestAlexa(unittest.TestCase): } req = _req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', {}).get('text') + text = req.json().get('response', {}).get('outputSpeech', + {}).get('text') - self.assertEqual('Anne Therese is at unknown and Paulus is at unknown', text) + self.assertEqual('Anne Therese is at unknown and Paulus is at unknown', + text) hass.states.set('device_tracker.paulus', 'home') hass.states.set('device_tracker.anne_therese', 'home') req = _req(data) self.assertEqual(200, req.status_code) - text = req.json().get('response', {}).get('outputSpeech', {}).get('text') + text = req.json().get('response', {}).get('outputSpeech', + {}).get('text') self.assertEqual('You are both home, you silly', text) def test_intent_request_calling_service(self): @@ -252,9 +263,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': False, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': {}, 'user': { @@ -263,7 +274,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'IntentRequest', - 'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z', 'intent': { 'name': 'CallServiceIntent', @@ -285,9 +296,9 @@ class TestAlexa(unittest.TestCase): 'version': '1.0', 'session': { 'new': False, - 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'sessionId': SESSION_ID, 'application': { - 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + 'applicationId': APPLICATION_ID }, 'attributes': { 'supportedHoroscopePeriods': { @@ -302,7 +313,7 @@ class TestAlexa(unittest.TestCase): }, 'request': { 'type': 'SessionEndedRequest', - 'requestId': 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'requestId': REQUEST_ID, 'timestamp': '2015-05-13T12:34:56Z', 'reason': 'USER_INITIATED' } diff --git a/tests/components/test_api.py b/tests/components/test_api.py index e2e6341c4d6..21a8b7e19ba 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -17,15 +17,11 @@ from homeassistant import bootstrap, const import homeassistant.core as ha import homeassistant.components.http as http +from tests.common import get_test_instance_port + API_PASSWORD = "test1234" - -# Somehow the socket that holds the default port does not get released -# when we close down HA in a different test case. Until I have figured -# out what is going on, let's run this test on a different port. -SERVER_PORT = 8120 - +SERVER_PORT = get_test_instance_port() HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT) - HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD} hass = None @@ -386,7 +382,7 @@ class TestAPI(unittest.TestCase): data=json.dumps({ 'api_password': 'bla-di-bla', 'host': '127.0.0.1', - 'port': '8125' + 'port': get_test_instance_port() }), headers=HA_HEADERS) self.assertEqual(422, req.status_code) diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index 0f4a2dc4134..fcd4daddadb 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -16,15 +16,11 @@ import homeassistant.bootstrap as bootstrap import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH +from tests.common import get_test_instance_port + API_PASSWORD = "test1234" - -# Somehow the socket that holds the default port does not get released -# when we close down HA in a different test case. Until I have figured -# out what is going on, let's run this test on a different port. -SERVER_PORT = 8121 - +SERVER_PORT = get_test_instance_port() HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT) - HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD} hass = None diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 2301a15f59e..19ca2cbf7c5 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -6,7 +6,6 @@ Tests the group compoments. """ # pylint: disable=protected-access,too-many-public-methods import unittest -import logging import homeassistant.core as ha from homeassistant.const import ( @@ -14,11 +13,6 @@ from homeassistant.const import ( import homeassistant.components.group as group -def setUpModule(): # pylint: disable=invalid-name - """ Setup to ignore group errors. """ - logging.disable(logging.CRITICAL) - - class TestComponentsGroup(unittest.TestCase): """ Tests homeassistant.components.group module. """ diff --git a/tests/test_remote.py b/tests/test_remote.py index bf6a916f22c..4f2fb75e6c9 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -3,8 +3,6 @@ tests.remote ~~~~~~~~~~~~ Tests Home Assistant remote methods and classes. -Uses port 8122 for master, 8123 for slave -Uses port 8125 as a port that nothing runs on """ # pylint: disable=protected-access,too-many-public-methods import unittest @@ -16,9 +14,13 @@ import homeassistant.remote as remote import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH -API_PASSWORD = "test1234" +from tests.common import get_test_instance_port -HTTP_BASE_URL = "http://127.0.0.1:8122" +API_PASSWORD = "test1234" +MASTER_PORT = get_test_instance_port() +SLAVE_PORT = get_test_instance_port() +BROKEN_PORT = get_test_instance_port() +HTTP_BASE_URL = "http://127.0.0.1:{}".format(MASTER_PORT) HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD} @@ -44,25 +46,25 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name bootstrap.setup_component( hass, http.DOMAIN, {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: 8122}}) + http.CONF_SERVER_PORT: MASTER_PORT}}) bootstrap.setup_component(hass, 'api') hass.start() - master_api = remote.API("127.0.0.1", API_PASSWORD, 8122) + master_api = remote.API("127.0.0.1", API_PASSWORD, MASTER_PORT) # Start slave slave = remote.HomeAssistant(master_api) bootstrap.setup_component( slave, http.DOMAIN, {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: 8130}}) + http.CONF_SERVER_PORT: SLAVE_PORT}}) slave.start() # Setup API pointing at nothing - broken_api = remote.API("127.0.0.1", "", 8125) + broken_api = remote.API("127.0.0.1", "", BROKEN_PORT) def tearDownModule(): # pylint: disable=invalid-name @@ -83,7 +85,7 @@ class TestRemoteMethods(unittest.TestCase): self.assertEqual( remote.APIStatus.INVALID_PASSWORD, remote.validate_api( - remote.API("127.0.0.1", API_PASSWORD + "A", 8122))) + remote.API("127.0.0.1", API_PASSWORD + "A", MASTER_PORT))) self.assertEqual( remote.APIStatus.CANNOT_CONNECT, remote.validate_api(broken_api)) @@ -210,7 +212,7 @@ class TestRemoteClasses(unittest.TestCase): # Wrong port self.assertRaises( ha.HomeAssistantError, remote.HomeAssistant, - remote.API('127.0.0.1', API_PASSWORD, 8125)) + remote.API('127.0.0.1', API_PASSWORD, BROKEN_PORT)) def test_statemachine_init(self): """ Tests if remote.StateMachine copies all states on init. """ From bade0e0d716ce636a5b28fe64381021279d3ed4f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 13:07:21 -0800 Subject: [PATCH 029/186] Make tests pass flake8 --- setup.cfg | 2 +- .../automation/test_numeric_state.py | 22 ++++++++++++------- tests/components/automation/test_template.py | 3 ++- tests/components/media_player/test_demo.py | 3 ++- tests/components/sensor/test_template.py | 2 +- tests/components/test_proximity.py | 5 +++-- tests/components/thermostat/test_honeywell.py | 1 - tests/helpers/test_event.py | 13 +++++++++-- tests/helpers/test_state.py | 4 ++-- tests/util/test_template.py | 21 +++++++++++++----- 10 files changed, 51 insertions(+), 25 deletions(-) diff --git a/setup.cfg b/setup.cfg index 93b0187a015..c05470737f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ universal = 1 testpaths = tests [flake8] -exclude = .venv,.git,.tox,docs,www_static,tests,venv,bin,lib +exclude = .venv,.git,.tox,docs,www_static,venv,bin,lib [pep257] ignore = D203,D105 diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 5d6a2c6596e..ebfc30f2c14 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -365,7 +365,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) - def test_if_fires_on_attribute_change_with_attribute_below_multiple_attributes(self): + def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'trigger': { @@ -380,7 +380,8 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is not below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': 9, 'not_test_attribute': 11}) + self.hass.states.set('test.entity', 'entity', + {'test_attribute': 9, 'not_test_attribute': 11}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -390,7 +391,8 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute[2] }}', + 'value_template': + '{{ state.attributes.test_attribute[2] }}', 'below': 10, }, 'action': { @@ -399,7 +401,8 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 3 is below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': [11, 15, 3]}) + self.hass.states.set('test.entity', 'entity', + {'test_attribute': [11, 15, 3]}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -409,7 +412,8 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute | multiply(10) }}', + 'value_template': + '{{ state.attributes.test_attribute | multiply(10) }}', 'below': 10, }, 'action': { @@ -418,11 +422,12 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 9 is below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': '0.9'}) + self.hass.states.set('test.entity', 'entity', + {'test_attribute': '0.9'}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_not_fires_on_attribute_change_with_attribute_not_below_multiple_attributes(self): + def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'trigger': { @@ -437,7 +442,8 @@ class TestAutomationNumericState(unittest.TestCase): } })) # 11 is not below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': 11, 'not_test_attribute': 9}) + self.hass.states.set('test.entity', 'entity', + {'test_attribute': 11, 'not_test_attribute': 9}) self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 3ccd523af7b..09ec85cbf36 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -256,7 +256,8 @@ class TestAutomationTemplate(unittest.TestCase): automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': '{{ not is_state("test.entity", "world") }}', + 'value_template': + '{{ not is_state("test.entity", "world") }}', }, 'action': { 'service': 'test.automation' diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index c67d5d47623..6565d49dd4e 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -114,7 +114,8 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) - @patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.media_seek') + @patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.' + 'media_seek') def test_play_media(self, mock_seek): assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) ent_id = 'media_player.living_room' diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index 26eb2c4b1a7..88c4e766aef 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -64,7 +64,7 @@ class TestTemplateSensor: 'sensors': { 'test_template_sensor': { 'value_template': - "It {{ states.sensor.test_state.attributes.missing }}." + "It {{ states.sensor.test_state.attributes.missing }}." } } } diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index c66b34a0acf..d75f131d2de 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -162,7 +162,8 @@ class TestProximity: self.hass.pool.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == '0' - assert (state.attributes.get('nearest') == 'test1, test2') or (state.attributes.get('nearest') == 'test2, test1') + assert ((state.attributes.get('nearest') == 'test1, test2') or + (state.attributes.get('nearest') == 'test2, test1')) assert state.attributes.get('dir_of_travel') == 'arrived' def test_device_tracker_test1_away(self): @@ -447,7 +448,7 @@ class TestProximity: assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' - def test_device_tracker_test1_awayfurther_than_test2_first_test1_than_test2_than_test1(self): + def test_device_tracker_test1_awayfurther_test2_first(self): self.hass.states.set( 'device_tracker.test1', 'not_home', { diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index e0e3f1bd758..f27a318b8da 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -131,7 +131,6 @@ class TestHoneywell(unittest.TestCase): devices = [x[0][1].deviceid for x in result] self.assertEqual([mock.sentinel.loc2dev1], devices) - @mock.patch('evohomeclient.EvohomeClient') @mock.patch('homeassistant.components.thermostat.honeywell.' 'RoundThermostat') diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index d69d0f198f5..3f97684984f 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -7,13 +7,22 @@ Tests event helpers. # pylint: disable=protected-access,too-many-public-methods # pylint: disable=too-few-public-methods import unittest -from datetime import datetime +from datetime import datetime, timedelta from astral import Astral import homeassistant.core as ha -from homeassistant.helpers.event import * +from homeassistant.helpers.event import ( + track_point_in_utc_time, + track_point_in_time, + track_utc_time_change, + track_time_change, + track_state_change, + track_sunrise, + track_sunset, +) from homeassistant.components import sun +import homeassistant.util.dt as dt_util class TestEventHelpers(unittest.TestCase): diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index e11426e2f95..a19968d21ae 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -14,8 +14,8 @@ from homeassistant.const import SERVICE_TURN_ON from homeassistant.util import dt as dt_util from homeassistant.helpers import state from homeassistant.const import ( - STATE_OFF, STATE_OPEN, STATE_CLOSED, - STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, + STATE_OPEN, STATE_CLOSED, + STATE_LOCKED, STATE_UNLOCKED, STATE_ON, STATE_OFF) from homeassistant.components.sun import (STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 314f6b887b7..32189f899a7 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -45,7 +45,9 @@ class TestUtilTemplate(unittest.TestCase): 'open10', template.render( self.hass, - '{% for state in states.sensor %}{{ state.state }}{% endfor %}')) + """ +{% for state in states.sensor %}{{ state.state }}{% endfor %} + """)) def test_rounding_value(self): self.hass.states.set('sensor.temperature', 12.78) @@ -63,7 +65,8 @@ class TestUtilTemplate(unittest.TestCase): '128', template.render( self.hass, - '{{ states.sensor.temperature.state | multiply(10) | round }}')) + '{{ states.sensor.temperature.state | multiply(10) | round }}' + )) def test_passing_vars_as_keywords(self): self.assertEqual( @@ -91,7 +94,7 @@ class TestUtilTemplate(unittest.TestCase): template.render_with_possible_json_value( self.hass, '{{ value_json', 'hello')) - def test_render_with_possible_json_value_with_template_error_error_value(self): + def test_render_with_possible_json_value_with_template_error_value(self): self.assertEqual( '-', template.render_with_possible_json_value( @@ -107,7 +110,9 @@ class TestUtilTemplate(unittest.TestCase): 'exists', template.render( self.hass, - '{% if states.test.object %}exists{% else %}not exists{% endif %}')) + """ +{% if states.test.object %}exists{% else %}not exists{% endif %} + """)) def test_is_state(self): self.hass.states.set('test.object', 'available') @@ -115,7 +120,9 @@ class TestUtilTemplate(unittest.TestCase): 'yes', template.render( self.hass, - '{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}')) + """ +{% if is_state("test.object", "available") %}yes{% else %}no{% endif %} + """)) def test_is_state_attr(self): self.hass.states.set('test.object', 'available', {'mode': 'on'}) @@ -123,7 +130,9 @@ class TestUtilTemplate(unittest.TestCase): 'yes', template.render( self.hass, - '{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %}')) + """ +{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %} + """)) def test_states_function(self): self.hass.states.set('test.object', 'available') From fa8857dfc5375064b377bdda493aafdfbb20e3fd Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 14 Feb 2016 22:22:11 +0100 Subject: [PATCH 030/186] Changed process communication to use stdin for the message because of security concerns. --- homeassistant/components/notify/command_line.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index aa753e4f02f..025046f1e7c 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -8,7 +8,6 @@ https://home-assistant.io/components/notify.command_line/ """ import logging import subprocess -import shlex from homeassistant.helpers import validate_config from homeassistant.components.notify import ( DOMAIN, BaseNotificationService) @@ -38,10 +37,12 @@ class CommandLineNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """ Send a message to a command_line. """ + try: - subprocess.check_call( - "{} {}".format(self.command, - shlex.quote(message)), - shell=True) - except subprocess.CalledProcessError: - _LOGGER.error('Command failed: %s', self.command) + proc = subprocess.Popen(self.command, universal_newlines=True, + stdin=subprocess.PIPE, shell=True) + proc.communicate(input=message) + if proc.returncode != 0: + _LOGGER.error('Command failed: %s', self.command) + except subprocess.SubprocessError: + _LOGGER.error('Error trying to exec Command: %s', self.command) From 09ab3e95c0b0f53892df9e061ff10e743c5b15e3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 15:08:23 -0800 Subject: [PATCH 031/186] Tests should all use test HA --- .../alarm_control_panel/test_manual.py | 5 ++--- .../components/alarm_control_panel/test_mqtt.py | 6 +++--- tests/components/automation/test_event.py | 5 +++-- tests/components/automation/test_init.py | 5 +++-- tests/components/automation/test_mqtt.py | 6 +++--- .../components/automation/test_numeric_state.py | 5 +++-- tests/components/automation/test_state.py | 5 +++-- tests/components/automation/test_sun.py | 5 ++--- tests/components/automation/test_template.py | 5 +++-- tests/components/automation/test_time.py | 5 ++--- .../binary_sensor/test_command_sensor.py | 5 +++-- tests/components/binary_sensor/test_mqtt.py | 5 +++-- tests/components/device_tracker/test_init.py | 8 +++++--- tests/components/garage_door/test_demo.py | 5 +++-- tests/components/light/test_mqtt.py | 6 +++--- tests/components/lock/test_demo.py | 5 +++-- tests/components/media_player/test_demo.py | 5 +++-- tests/components/media_player/test_universal.py | 5 ++--- tests/components/notify/test_demo.py | 5 +++-- tests/components/rollershutter/test_mqtt.py | 5 +++-- tests/components/sensor/test_command_sensor.py | 5 +++-- tests/components/sensor/test_mfi.py | 7 ++++--- tests/components/sensor/test_mqtt.py | 5 +++-- tests/components/sensor/test_template.py | 5 +++-- tests/components/sensor/test_yr.py | 5 +++-- tests/components/switch/test_command_switch.py | 5 +++-- tests/components/switch/test_mfi.py | 5 +++-- tests/components/switch/test_mqtt.py | 6 +++--- tests/components/switch/test_template.py | 5 +++-- tests/components/test_alexa.py | 5 ++--- tests/components/test_api.py | 4 ++-- tests/components/test_configurator.py | 5 +++-- tests/components/test_demo.py | 13 +++++++++---- .../components/test_device_sun_light_trigger.py | 17 ++++++++--------- tests/components/test_frontend.py | 5 ++--- tests/components/test_graphite.py | 4 +++- tests/components/test_group.py | 5 +++-- tests/components/test_introduction.py | 5 +++-- tests/components/test_logbook.py | 4 ++-- tests/components/test_proximity.py | 5 +++-- tests/components/test_shell_command.py | 5 +++-- tests/components/test_sun.py | 4 +++- tests/components/test_updater.py | 5 ++--- tests/components/test_weblink.py | 14 ++++++++++---- .../components/thermostat/test_heat_control.py | 5 +++-- tests/helpers/test_entity.py | 5 +++-- tests/helpers/test_event.py | 4 +++- tests/helpers/test_event_decorators.py | 4 +++- tests/test_bootstrap.py | 10 ++++++---- tests/test_core.py | 4 +++- tests/test_remote.py | 4 ++-- tests/util/test_template.py | 5 +++-- 52 files changed, 170 insertions(+), 125 deletions(-) diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 182935080cc..dc5cf8dbd7d 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -8,14 +8,13 @@ from datetime import timedelta import unittest from unittest.mock import patch -import homeassistant.core as ha from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) from homeassistant.components import alarm_control_panel import homeassistant.util.dt as dt_util -from tests.common import fire_time_changed +from tests.common import fire_time_changed, get_test_home_assistant CODE = 'HELLO_CODE' @@ -24,7 +23,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): """ Test the manual alarm module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index 58c55350cd2..5af6664b52f 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -7,13 +7,13 @@ Tests manual alarm control panel component. import unittest from unittest.mock import patch -import homeassistant.core as ha from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN) from homeassistant.components import alarm_control_panel -from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import ( + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) CODE = 'HELLO_CODE' @@ -22,7 +22,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): """ Test the manual alarm module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 465faf4ec8f..893fd47a639 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -6,15 +6,16 @@ Tests event automation. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation +from tests.common import get_test_home_assistant + class TestAutomationEvent(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.calls = [] def record_call(service): diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index cf7a0567c25..938ec6b6eac 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -6,16 +6,17 @@ Tests automation component. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation from homeassistant.const import ATTR_ENTITY_ID +from tests.common import get_test_home_assistant + class TestAutomation(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.calls = [] def record_call(service): diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 516eda53947..2c0a5612213 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -6,16 +6,16 @@ Tests mqtt automation. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation -from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import ( + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) class TestAutomationMQTT(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) self.calls = [] diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index ebfc30f2c14..0960acbdcce 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -6,15 +6,16 @@ Tests numeric state automation. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation +from tests.common import get_test_home_assistant + class TestAutomationNumericState(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.calls = [] def record_call(service): diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 5e30f505092..fcd1fb616e9 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -6,16 +6,17 @@ Tests state automation. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation import homeassistant.components.automation.state as state +from tests.common import get_test_home_assistant + class TestAutomationState(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set('test.entity', 'hello') self.calls = [] diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index db4782cfd46..4083500c293 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -8,19 +8,18 @@ from datetime import datetime import unittest from unittest.mock import patch -import homeassistant.core as ha from homeassistant.components import sun import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util -from tests.common import fire_time_changed +from tests.common import fire_time_changed, get_test_home_assistant class TestAutomationSun(unittest.TestCase): """ Test the sun automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.components.append('sun') self.calls = [] diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 09ec85cbf36..489187a496b 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -6,15 +6,16 @@ Tests template automation. """ import unittest -import homeassistant.core as ha import homeassistant.components.automation as automation +from tests.common import get_test_home_assistant + class TestAutomationTemplate(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set('test.entity', 'hello') self.calls = [] diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index dd3f5a6e9fa..41e200a5731 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -8,20 +8,19 @@ from datetime import timedelta import unittest from unittest.mock import patch -import homeassistant.core as ha import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation from homeassistant.components.automation import time, event from homeassistant.const import CONF_PLATFORM -from tests.common import fire_time_changed +from tests.common import fire_time_changed, get_test_home_assistant class TestAutomationTime(unittest.TestCase): """ Test the event automation. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.calls = [] def record_call(service): diff --git a/tests/components/binary_sensor/test_command_sensor.py b/tests/components/binary_sensor/test_command_sensor.py index 20aca38e0e6..c2ec3b2f6f6 100644 --- a/tests/components/binary_sensor/test_command_sensor.py +++ b/tests/components/binary_sensor/test_command_sensor.py @@ -6,16 +6,17 @@ Tests command binary sensor. """ import unittest -import homeassistant.core as ha from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.components.binary_sensor import command_sensor +from tests.common import get_test_home_assistant + class TestCommandSensorBinarySensor(unittest.TestCase): """ Test the Template sensor. """ def setUp(self): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): """ Stop down stuff we started. """ diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py index 83fa532d051..f030800e95b 100644 --- a/tests/components/binary_sensor/test_mqtt.py +++ b/tests/components/binary_sensor/test_mqtt.py @@ -6,17 +6,18 @@ Tests MQTT binary sensor. """ import unittest -import homeassistant.core as ha import homeassistant.components.binary_sensor as binary_sensor from tests.common import mock_mqtt_component, fire_mqtt_message from homeassistant.const import (STATE_OFF, STATE_ON) +from tests.common import get_test_home_assistant + class TestSensorMQTT(unittest.TestCase): """ Test the MQTT sensor. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index ac44be640ad..49fe33366a9 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -123,12 +123,14 @@ class TestComponentsDeviceTracker(unittest.TestCase): scanner = get_component('device_tracker.test').SCANNER scanner.reset() scanner.come_home('DEV1') + self.assertTrue(device_tracker.setup(self.hass, { device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}})) config = device_tracker.load_config(self.yaml_devices, self.hass, - timedelta(seconds=0), 0)[0] - self.assertEqual('dev1', config.dev_id) - self.assertEqual(True, config.track) + timedelta(seconds=0), 0) + assert len(config) == 1 + assert config[0].dev_id == 'dev1' + assert config[0].track def test_discovery(self): scanner = get_component('device_tracker.test').SCANNER diff --git a/tests/components/garage_door/test_demo.py b/tests/components/garage_door/test_demo.py index 781b47bb3d7..b500bc01013 100644 --- a/tests/components/garage_door/test_demo.py +++ b/tests/components/garage_door/test_demo.py @@ -6,9 +6,10 @@ Tests demo garage door component. """ import unittest -import homeassistant.core as ha import homeassistant.components.garage_door as gd +from tests.common import get_test_home_assistant + LEFT = 'garage_door.left_garage_door' RIGHT = 'garage_door.right_garage_door' @@ -18,7 +19,7 @@ class TestGarageDoorDemo(unittest.TestCase): """ Test the demo garage door. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.assertTrue(gd.setup(self.hass, { 'garage_door': { 'platform': 'demo' diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 834573b7c6e..ff55c22f0e3 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -46,16 +46,16 @@ light: import unittest from homeassistant.const import STATE_ON, STATE_OFF -import homeassistant.core as ha import homeassistant.components.light as light -from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import ( + get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) class TestLightMQTT(unittest.TestCase): """ Test the MQTT light. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py index 7320b1aa69a..d116a9bcd7c 100644 --- a/tests/components/lock/test_demo.py +++ b/tests/components/lock/test_demo.py @@ -6,9 +6,10 @@ Tests demo lock component. """ import unittest -import homeassistant.core as ha from homeassistant.components import lock +from tests.common import get_test_home_assistant + FRONT = 'lock.front_door' KITCHEN = 'lock.kitchen_door' @@ -18,7 +19,7 @@ class TestLockDemo(unittest.TestCase): """ Test the demo lock. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.assertTrue(lock.setup(self.hass, { 'lock': { 'platform': 'demo' diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 6565d49dd4e..3af558cb735 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -6,9 +6,10 @@ Tests demo media_player component. """ import unittest from unittest.mock import patch -import homeassistant.core as ha import homeassistant.components.media_player as mp +from tests.common import get_test_home_assistant + entity_id = 'media_player.walkman' @@ -16,7 +17,7 @@ class TestDemoMediaPlayer(unittest.TestCase): """ Test the media_player module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index e359700f2fa..8412db29214 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -7,14 +7,13 @@ Tests universal media_player component. from copy import copy import unittest -import homeassistant.core as ha from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, STATE_PLAYING, STATE_PAUSED) import homeassistant.components.switch as switch import homeassistant.components.media_player as media_player import homeassistant.components.media_player.universal as universal -from tests.common import mock_service +from tests.common import mock_service, get_test_home_assistant class MockMediaPlayer(media_player.MediaPlayerDevice): @@ -87,7 +86,7 @@ class TestMediaPlayer(unittest.TestCase): """ Test the media_player module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.mock_mp_1 = MockMediaPlayer(self.hass, 'mock1') self.mock_mp_1.update_ha_state() diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 41b59e96e4f..7cf14c0064f 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -6,16 +6,17 @@ Tests notify demo component. """ import unittest -import homeassistant.core as ha import homeassistant.components.notify as notify from homeassistant.components.notify import demo +from tests.common import get_test_home_assistant + class TestNotifyDemo(unittest.TestCase): """ Test the demo notify. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.assertTrue(notify.setup(self.hass, { 'notify': { 'platform': 'demo' diff --git a/tests/components/rollershutter/test_mqtt.py b/tests/components/rollershutter/test_mqtt.py index df206b3047d..3a5fe936af9 100644 --- a/tests/components/rollershutter/test_mqtt.py +++ b/tests/components/rollershutter/test_mqtt.py @@ -7,16 +7,17 @@ Tests MQTT rollershutter. import unittest from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN -import homeassistant.core as ha import homeassistant.components.rollershutter as rollershutter from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import get_test_home_assistant + class TestRollershutterMQTT(unittest.TestCase): """ Test the MQTT rollershutter. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/sensor/test_command_sensor.py b/tests/components/sensor/test_command_sensor.py index 0f4133ddb6e..17b4aab5fb3 100644 --- a/tests/components/sensor/test_command_sensor.py +++ b/tests/components/sensor/test_command_sensor.py @@ -6,15 +6,16 @@ Tests command sensor. """ import unittest -import homeassistant.core as ha from homeassistant.components.sensor import command_sensor +from tests.common import get_test_home_assistant + class TestCommandSensorSensor(unittest.TestCase): """ Test the Command line sensor. """ def setUp(self): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): """ Stop down stuff we started. """ diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index 9f8efcc6245..58506bdff1b 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -7,11 +7,12 @@ Tests mFi sensor. import unittest import unittest.mock as mock -import homeassistant.core as ha import homeassistant.components.sensor as sensor import homeassistant.components.sensor.mfi as mfi from homeassistant.const import TEMP_CELCIUS +from tests.common import get_test_home_assistant + class TestMfiSensorSetup(unittest.TestCase): PLATFORM = mfi @@ -28,7 +29,7 @@ class TestMfiSensorSetup(unittest.TestCase): } def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 @@ -87,7 +88,7 @@ class TestMfiSensorSetup(unittest.TestCase): class TestMfiSensor(unittest.TestCase): def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 self.port = mock.MagicMock() diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 81aeeae6d3c..2d460b8ac3e 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -6,16 +6,17 @@ Tests MQTT sensor. """ import unittest -import homeassistant.core as ha import homeassistant.components.sensor as sensor from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import get_test_home_assistant + class TestSensorMQTT(unittest.TestCase): """ Test the MQTT sensor. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index 88c4e766aef..14603850296 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -4,15 +4,16 @@ tests.components.sensor.test_template Tests template sensor. """ -import homeassistant.core as ha import homeassistant.components.sensor as sensor +from tests.common import get_test_home_assistant + class TestTemplateSensor: """ Test the Template sensor. """ def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def teardown_method(self, method): """ Stop down stuff we started. """ diff --git a/tests/components/sensor/test_yr.py b/tests/components/sensor/test_yr.py index c4979720a58..b01e51d78ba 100644 --- a/tests/components/sensor/test_yr.py +++ b/tests/components/sensor/test_yr.py @@ -9,17 +9,18 @@ from unittest.mock import patch import pytest -import homeassistant.core as ha import homeassistant.components.sensor as sensor import homeassistant.util.dt as dt_util +from tests.common import get_test_home_assistant + @pytest.mark.usefixtures('betamax_session') class TestSensorYr: """ Test the Yr sensor. """ def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 diff --git a/tests/components/switch/test_command_switch.py b/tests/components/switch/test_command_switch.py index 43055413e09..3aa7bfeb0c0 100644 --- a/tests/components/switch/test_command_switch.py +++ b/tests/components/switch/test_command_switch.py @@ -9,16 +9,17 @@ import os import tempfile import unittest -from homeassistant import core from homeassistant.const import STATE_ON, STATE_OFF import homeassistant.components.switch as switch +from tests.common import get_test_home_assistant + class TestCommandSwitch(unittest.TestCase): """ Test the command switch. """ def setUp(self): # pylint: disable=invalid-name - self.hass = core.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/switch/test_mfi.py b/tests/components/switch/test_mfi.py index 0f763dc3392..85b676eef58 100644 --- a/tests/components/switch/test_mfi.py +++ b/tests/components/switch/test_mfi.py @@ -7,11 +7,12 @@ Tests mFi switch. import unittest import unittest.mock as mock -import homeassistant.core as ha import homeassistant.components.switch as switch import homeassistant.components.switch.mfi as mfi from tests.components.sensor import test_mfi as test_mfi_sensor +from tests.common import get_test_home_assistant + class TestMfiSwitchSetup(test_mfi_sensor.TestMfiSensorSetup): PLATFORM = mfi @@ -45,7 +46,7 @@ class TestMfiSwitchSetup(test_mfi_sensor.TestMfiSensorSetup): class TestMfiSwitch(unittest.TestCase): def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 self.port = mock.MagicMock() diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index e5058f7826e..d412b5293a0 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -7,16 +7,16 @@ Tests MQTT switch. import unittest from homeassistant.const import STATE_ON, STATE_OFF -import homeassistant.core as ha import homeassistant.components.switch as switch -from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import ( + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) class TestSensorMQTT(unittest.TestCase): """ Test the MQTT switch. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.mock_publish = mock_mqtt_component(self.hass) def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py index e4bd20b27cb..73e82e6fdd6 100644 --- a/tests/components/switch/test_template.py +++ b/tests/components/switch/test_template.py @@ -4,7 +4,6 @@ tests.components.switch.template Tests template switch. """ -import homeassistant.core as ha import homeassistant.components as core import homeassistant.components.switch as switch @@ -12,12 +11,14 @@ from homeassistant.const import ( STATE_ON, STATE_OFF) +from tests.common import get_test_home_assistant + class TestTemplateSwitch: """ Test the Template switch. """ def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.calls = [] diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index a309bf73668..fa08f8e9129 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -12,10 +12,9 @@ from unittest.mock import patch import requests from homeassistant import bootstrap, const -import homeassistant.core as ha from homeassistant.components import alexa, http -from tests.common import get_test_instance_port +from tests.common import get_test_instance_port, get_test_home_assistant API_PASSWORD = "test1234" SERVER_PORT = get_test_instance_port() @@ -36,7 +35,7 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name """ Initalize a Home Assistant server for testing this module. """ global hass - hass = ha.HomeAssistant() + hass = get_test_home_assistant() bootstrap.setup_component( hass, http.DOMAIN, diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 21a8b7e19ba..6e72666454f 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -17,7 +17,7 @@ from homeassistant import bootstrap, const import homeassistant.core as ha import homeassistant.components.http as http -from tests.common import get_test_instance_port +from tests.common import get_test_instance_port, get_test_home_assistant API_PASSWORD = "test1234" SERVER_PORT = get_test_instance_port() @@ -38,7 +38,7 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name """ Initializes a Home Assistant server. """ global hass - hass = ha.HomeAssistant() + hass = get_test_home_assistant() hass.bus.listen('test_event', lambda _: _) hass.states.set('test.test', 'a_state') diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index 4e2f30f6c32..28b4ea126c9 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -7,16 +7,17 @@ Tests Configurator component. # pylint: disable=too-many-public-methods,protected-access import unittest -import homeassistant.core as ha import homeassistant.components.configurator as configurator from homeassistant.const import EVENT_TIME_CHANGED +from tests.common import get_test_home_assistant + class TestConfigurator(unittest.TestCase): """ Test the chromecast module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/test_demo.py b/tests/components/test_demo.py index 39418852597..ec53e33e8fa 100644 --- a/tests/components/test_demo.py +++ b/tests/components/test_demo.py @@ -5,14 +5,14 @@ tests.test_component_demo Tests demo component. """ import json +import os import unittest from unittest.mock import patch -import homeassistant.core as ha -import homeassistant.components.demo as demo +from homeassistant.components import demo, device_tracker from homeassistant.remote import JSONEncoder -from tests.common import mock_http_component +from tests.common import mock_http_component, get_test_home_assistant @patch('homeassistant.components.sun.setup') @@ -20,13 +20,18 @@ class TestDemo(unittest.TestCase): """ Test the demo module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() mock_http_component(self.hass) def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ self.hass.stop() + try: + os.remove(self.hass.config.path(device_tracker.YAML_DEVICES)) + except FileNotFoundError: + pass + def test_if_demo_state_shows_by_default(self, mock_sun_setup): """ Test if demo state shows if we give no configuration. """ demo.setup(self.hass, {demo.DOMAIN: {}}) diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index 586d67c2f70..255f371b9a7 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -19,17 +19,15 @@ from tests.common import ( ensure_sun_set) -KNOWN_DEV_PATH = None +KNOWN_DEV_CSV_PATH = os.path.join(get_test_config_dir(), + device_tracker.CSV_DEVICES) +KNOWN_DEV_YAML_PATH = os.path.join(get_test_config_dir(), + device_tracker.YAML_DEVICES) def setUpModule(): # pylint: disable=invalid-name """ Initalizes a Home Assistant server. """ - global KNOWN_DEV_PATH - - KNOWN_DEV_PATH = os.path.join(get_test_config_dir(), - device_tracker.CSV_DEVICES) - - with open(KNOWN_DEV_PATH, 'w') as fil: + with open(KNOWN_DEV_CSV_PATH, 'w') as fil: fil.write('device,name,track,picture\n') fil.write('DEV1,device 1,1,http://example.com/dev1.jpg\n') fil.write('DEV2,device 2,1,http://example.com/dev2.jpg\n') @@ -37,8 +35,9 @@ def setUpModule(): # pylint: disable=invalid-name def tearDownModule(): # pylint: disable=invalid-name """ Stops the Home Assistant server. """ - os.remove(os.path.join(get_test_config_dir(), - device_tracker.YAML_DEVICES)) + for fil in (KNOWN_DEV_CSV_PATH, KNOWN_DEV_YAML_PATH): + if os.path.isfile(fil): + os.remove(fil) class TestDeviceSunLightTrigger(unittest.TestCase): diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index fcd4daddadb..a81cea899f6 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -11,12 +11,11 @@ from unittest.mock import patch import requests -import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH -from tests.common import get_test_instance_port +from tests.common import get_test_instance_port, get_test_home_assistant API_PASSWORD = "test1234" SERVER_PORT = get_test_instance_port() @@ -37,7 +36,7 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name """ Initalizes a Home Assistant server. """ global hass - hass = ha.HomeAssistant() + hass = get_test_home_assistant() hass.bus.listen('test_event', lambda _: _) hass.states.set('test.test', 'a_state') diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 34ad93a86c4..bdcda193863 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -15,10 +15,12 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_ON, STATE_OFF) +from tests.common import get_test_home_assistant + class TestGraphite(unittest.TestCase): def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 self.gf = graphite.GraphiteFeeder(self.hass, 'foo', 123, 'ha') diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 19ca2cbf7c5..8a3eeadcb14 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -7,18 +7,19 @@ Tests the group compoments. # pylint: disable=protected-access,too-many-public-methods import unittest -import homeassistant.core as ha from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN) import homeassistant.components.group as group +from tests.common import get_test_home_assistant + class TestComponentsGroup(unittest.TestCase): """ Tests homeassistant.components.group module. """ def setUp(self): # pylint: disable=invalid-name """ Init needed objects. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) diff --git a/tests/components/test_introduction.py b/tests/components/test_introduction.py index 4c7d104ba49..f4a53c65566 100644 --- a/tests/components/test_introduction.py +++ b/tests/components/test_introduction.py @@ -6,15 +6,16 @@ Test introduction. """ import unittest -import homeassistant.core as ha from homeassistant.components import introduction +from tests.common import get_test_home_assistant + class TestIntroduction(unittest.TestCase): """ Test Introduction. """ def setUp(self): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): """ Stop down stuff we started. """ diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index b6bc764660d..f83ecd548f0 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -14,7 +14,7 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from homeassistant.components import logbook -from tests.common import mock_http_component +from tests.common import mock_http_component, get_test_home_assistant class TestComponentHistory(unittest.TestCase): @@ -22,7 +22,7 @@ class TestComponentHistory(unittest.TestCase): def setUp(self): """ Test setup method. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() mock_http_component(self.hass) self.assertTrue(logbook.setup(self.hass, {})) diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index d75f131d2de..7c7ae0b954f 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -4,15 +4,16 @@ tests.components.test_proximity Tests proximity component. """ -import homeassistant.core as ha from homeassistant.components import proximity +from tests.common import get_test_home_assistant + class TestProximity: """ Test the Proximity component. """ def setup_method(self, method): - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set( 'zone.home', 'zoning', { diff --git a/tests/components/test_shell_command.py b/tests/components/test_shell_command.py index 6fdc671f5ad..cf0c09bb052 100644 --- a/tests/components/test_shell_command.py +++ b/tests/components/test_shell_command.py @@ -10,15 +10,16 @@ import unittest from unittest.mock import patch from subprocess import SubprocessError -from homeassistant import core from homeassistant.components import shell_command +from tests.common import get_test_home_assistant + class TestShellCommand(unittest.TestCase): """ Test the demo module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = core.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index 44f428dc9a0..3a1b6340ba0 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -14,12 +14,14 @@ import homeassistant.core as ha import homeassistant.util.dt as dt_util import homeassistant.components.sun as sun +from tests.common import get_test_home_assistant + class TestSun(unittest.TestCase): """ Test the sun module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index e1e2f5a7950..440974d4457 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -9,11 +9,10 @@ from unittest.mock import patch import requests -import homeassistant.core as ha from homeassistant.const import __version__ as CURRENT_VERSION from homeassistant.components import updater import homeassistant.util.dt as dt_util -from tests.common import fire_time_changed +from tests.common import fire_time_changed, get_test_home_assistant NEW_VERSION = '10000.0' @@ -22,7 +21,7 @@ class TestUpdater(unittest.TestCase): """ Test the demo lock. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/components/test_weblink.py b/tests/components/test_weblink.py index f93a6ba0840..29809722e68 100644 --- a/tests/components/test_weblink.py +++ b/tests/components/test_weblink.py @@ -6,21 +6,22 @@ Tests weblink component. """ import unittest -import homeassistant.core as ha from homeassistant.components import weblink +from tests.common import get_test_home_assistant -class TestComponentHistory(unittest.TestCase): + +class TestComponentWeblink(unittest.TestCase): """ Tests homeassistant.components.history module. """ def setUp(self): """ Test setup method. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): self.hass.stop() - def test_setup(self): + def test_entities_get_created(self): self.assertTrue(weblink.setup(self.hass, { weblink.DOMAIN: { 'entities': [ @@ -32,3 +33,8 @@ class TestComponentHistory(unittest.TestCase): ] } })) + + state = self.hass.states.get('weblink.my_router') + + assert state is not None + assert state.state == 'http://127.0.0.1/' diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index aa5a43c9c0e..9d91d3c70cb 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -14,9 +14,10 @@ from homeassistant.const import ( STATE_OFF, TEMP_CELCIUS, ) -import homeassistant.core as ha from homeassistant.components import thermostat +from tests.common import get_test_home_assistant + entity = 'thermostat.test' ent_sensor = 'sensor.test' @@ -30,7 +31,7 @@ class TestThermostatHeatControl(unittest.TestCase): """ Test the Heat Control thermostat. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.temperature_unit = TEMP_CELCIUS thermostat.setup(self.hass, {'thermostat': { 'platform': 'heat_control', diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 37661623880..7e305454228 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -7,10 +7,11 @@ Tests the entity helper. # pylint: disable=protected-access,too-many-public-methods import unittest -import homeassistant.core as ha import homeassistant.helpers.entity as entity from homeassistant.const import ATTR_HIDDEN +from tests.common import get_test_home_assistant + class TestHelpersEntity(unittest.TestCase): """ Tests homeassistant.helpers.entity module. """ @@ -19,7 +20,7 @@ class TestHelpersEntity(unittest.TestCase): """ Init needed objects. """ self.entity = entity.Entity() self.entity.entity_id = 'test.overwrite_hidden_true' - self.hass = self.entity.hass = ha.HomeAssistant() + self.hass = self.entity.hass = get_test_home_assistant() self.entity.update_ha_state() def tearDown(self): # pylint: disable=invalid-name diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 3f97684984f..91cf252443c 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -24,6 +24,8 @@ from homeassistant.helpers.event import ( from homeassistant.components import sun import homeassistant.util.dt as dt_util +from tests.common import get_test_home_assistant + class TestEventHelpers(unittest.TestCase): """ @@ -32,7 +34,7 @@ class TestEventHelpers(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ diff --git a/tests/helpers/test_event_decorators.py b/tests/helpers/test_event_decorators.py index 1ef8c042e69..0d87be43740 100644 --- a/tests/helpers/test_event_decorators.py +++ b/tests/helpers/test_event_decorators.py @@ -19,6 +19,8 @@ from homeassistant.helpers.event_decorators import ( track_sunrise, track_sunset) from homeassistant.components import sun +from tests.common import get_test_home_assistant + class TestEventDecoratorHelpers(unittest.TestCase): """ @@ -27,7 +29,7 @@ class TestEventDecoratorHelpers(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set("light.Bowl", "on") self.hass.states.set("switch.AC", "off") diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 2fbe92f8023..52d146aa8c9 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -9,12 +9,14 @@ import os import tempfile import unittest -from homeassistant import core, bootstrap +from homeassistant import bootstrap from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_CUSTOMIZE) import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity +from tests.common import get_test_home_assistant + class TestBootstrap(unittest.TestCase): """ Test the bootstrap utils. """ @@ -53,7 +55,7 @@ class TestBootstrap(unittest.TestCase): with open(check_file, 'w'): pass - hass = core.HomeAssistant() + hass = get_test_home_assistant() hass.config.config_dir = config_dir self.assertTrue(os.path.isfile(check_file)) @@ -74,7 +76,7 @@ class TestBootstrap(unittest.TestCase): with open(check_file, 'w'): pass - hass = core.HomeAssistant() + hass = get_test_home_assistant() hass.config.config_dir = config_dir bootstrap.process_ha_config_upgrade(hass) @@ -88,7 +90,7 @@ class TestBootstrap(unittest.TestCase): CONF_NAME: 'Test', CONF_CUSTOMIZE: {'test.test': {'hidden': True}}} - hass = core.HomeAssistant() + hass = get_test_home_assistant() bootstrap.process_ha_core_config(hass, config) diff --git a/tests/test_core.py b/tests/test_core.py index d1b2221998e..61bfddf7548 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -25,6 +25,8 @@ from homeassistant.const import ( EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from tests.common import get_test_home_assistant + PST = pytz.timezone('America/Los_Angeles') @@ -35,7 +37,7 @@ class TestHomeAssistant(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.states.set("light.Bowl", "on") self.hass.states.set("switch.AC", "off") diff --git a/tests/test_remote.py b/tests/test_remote.py index 4f2fb75e6c9..725ba25ac80 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -14,7 +14,7 @@ import homeassistant.remote as remote import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH -from tests.common import get_test_instance_port +from tests.common import get_test_instance_port, get_test_home_assistant API_PASSWORD = "test1234" MASTER_PORT = get_test_instance_port() @@ -38,7 +38,7 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name """ Initalizes a Home Assistant server and Slave instance. """ global hass, slave, master_api, broken_api - hass = ha.HomeAssistant() + hass = get_test_home_assistant() hass.bus.listen('test_event', lambda _: _) hass.states.set('test.test', 'a_state') diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 32189f899a7..09e0e154888 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -6,15 +6,16 @@ Tests Home Assistant template util methods. """ # pylint: disable=too-many-public-methods import unittest -import homeassistant.core as ha from homeassistant.exceptions import TemplateError from homeassistant.util import template +from tests.common import get_test_home_assistant + class TestUtilTemplate(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ From 366595fd9052750c1c3f4f2d46e98238a336b358 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 15:39:24 -0800 Subject: [PATCH 032/186] Catch socket.gaierror in graphite driver If you specify a name that can't be looked up in DNS, socket.connect() throws socket.gaierror. We should catch and log that situation properly. --- homeassistant/components/graphite.py | 2 ++ tests/components/test_graphite.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index a84d4bf8847..0b45714074a 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -98,6 +98,8 @@ class GraphiteFeeder(threading.Thread): _LOGGER.debug('Sending to graphite: %s', lines) try: self._send_to_graphite('\n'.join(lines)) + except socket.gaierror: + _LOGGER.error('Unable to connect to host %s', self._host) except socket.error: _LOGGER.exception('Failed to send data to graphite') diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 34ad93a86c4..746e72f04c6 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -132,6 +132,16 @@ class TestGraphite(unittest.TestCase): actual = mock_send.call_args_list[0][0][0].split('\n') self.assertEqual(sorted(expected), sorted(actual)) + @mock.patch('time.time') + def test_send_to_graphite_errors(self, mock_time): + mock_time.return_value = 12345 + state = ha.State('domain.entity', STATE_ON, {'foo': 1.0}) + with mock.patch.object(self.gf, '_send_to_graphite') as mock_send: + mock_send.side_effect = socket.error + self.gf._report_attributes('entity', state) + mock_send.side_effect = socket.gaierror + self.gf._report_attributes('entity', state) + @mock.patch('socket.socket') def test_send_to_graphite(self, mock_socket): self.gf._send_to_graphite('foo') From 3610f40a6a49c66b30691c59346eaf8040b60734 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sun, 14 Feb 2016 15:57:03 -0800 Subject: [PATCH 033/186] Handle EVENT_STATE_CHANGED with no new_state in graphite I noticed some events that came in with new_state=None. Make graphite defensive about this. --- homeassistant/components/graphite.py | 2 +- tests/components/test_graphite.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 0b45714074a..662e3872eee 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -110,7 +110,7 @@ class GraphiteFeeder(threading.Thread): self._queue.task_done() return elif (event.event_type == EVENT_STATE_CHANGED and - 'new_state' in event.data): + event.data.get('new_state')): self._report_attributes(event.data['entity_id'], event.data['new_state']) self._queue.task_done() diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 746e72f04c6..14803d885d4 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -171,7 +171,8 @@ class TestGraphite(unittest.TestCase): return self.gf._quit_object elif runs: runs.append(1) - return mock.MagicMock(event_type='somethingelse') + return mock.MagicMock(event_type='somethingelse', + data={'new_event': None}) else: runs.append(1) return event From 8d366a736731c10b4d6b56a9939e2b8299051ee0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 22:01:30 -0800 Subject: [PATCH 034/186] Tests: Mock get_local_ip by default too --- tests/__init__.py | 2 ++ tests/components/device_tracker/test_locative.py | 4 +--- tests/components/test_alexa.py | 5 +---- tests/components/test_api.py | 4 +--- tests/components/test_frontend.py | 5 +---- tests/test_remote.py | 11 +++-------- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index e582287f941..1f18116b24b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,7 @@ Tests initialization. """ import betamax +from homeassistant import util from homeassistant.util import location with betamax.Betamax.configure() as config: @@ -28,3 +29,4 @@ location.detect_location_info = lambda: location.LocationInfo( ) location.elevation = lambda latitude, longitude: 0 +util.get_local_ip = lambda: '127.0.0.1' diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index 32a63d0962f..5182a536034 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -28,9 +28,7 @@ def _url(data={}): return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data) -@patch('homeassistant.components.http.util.get_local_ip', - return_value='127.0.0.1') -def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name +def setUpModule(): # pylint: disable=invalid-name """ Initalizes a Home Assistant server. """ global hass diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index fa08f8e9129..f1de566a57e 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -7,7 +7,6 @@ Tests Home Assistant Alexa component does what it should do. # pylint: disable=protected-access,too-many-public-methods import unittest import json -from unittest.mock import patch import requests @@ -29,9 +28,7 @@ hass = None calls = [] -@patch('homeassistant.components.http.util.get_local_ip', - return_value='127.0.0.1') -def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name +def setUpModule(): # pylint: disable=invalid-name """ Initalize a Home Assistant server for testing this module. """ global hass diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 6e72666454f..3dbaedd8c1a 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -32,9 +32,7 @@ def _url(path=""): return HTTP_BASE_URL + path -@patch('homeassistant.components.http.util.get_local_ip', - return_value='127.0.0.1') -def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name +def setUpModule(): # pylint: disable=invalid-name """ Initializes a Home Assistant server. """ global hass diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index a81cea899f6..8455bd5a8f9 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -7,7 +7,6 @@ Tests Home Assistant HTTP component does what it should do. # pylint: disable=protected-access,too-many-public-methods import re import unittest -from unittest.mock import patch import requests @@ -30,9 +29,7 @@ def _url(path=""): return HTTP_BASE_URL + path -@patch('homeassistant.components.http.util.get_local_ip', - return_value='127.0.0.1') -def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name +def setUpModule(): # pylint: disable=invalid-name """ Initalizes a Home Assistant server. """ global hass diff --git a/tests/test_remote.py b/tests/test_remote.py index 725ba25ac80..435cbde79f9 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -6,7 +6,6 @@ Tests Home Assistant remote methods and classes. """ # pylint: disable=protected-access,too-many-public-methods import unittest -from unittest.mock import patch import homeassistant.core as ha import homeassistant.bootstrap as bootstrap @@ -24,7 +23,8 @@ HTTP_BASE_URL = "http://127.0.0.1:{}".format(MASTER_PORT) HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD} -hass, slave, master_api, broken_api = None, None, None, None +broken_api = remote.API('127.0.0.1', BROKEN_PORT) +hass, slave, master_api = None, None, None def _url(path=""): @@ -32,9 +32,7 @@ def _url(path=""): return HTTP_BASE_URL + path -@patch('homeassistant.components.http.util.get_local_ip', - return_value='127.0.0.1') -def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name +def setUpModule(): # pylint: disable=invalid-name """ Initalizes a Home Assistant server and Slave instance. """ global hass, slave, master_api, broken_api @@ -63,9 +61,6 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name slave.start() - # Setup API pointing at nothing - broken_api = remote.API("127.0.0.1", "", BROKEN_PORT) - def tearDownModule(): # pylint: disable=invalid-name """ Stops the Home Assistant server and slave. """ From 68803a46b6d912ac21d21b01d17d8335c308a1f5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 23:01:49 -0800 Subject: [PATCH 035/186] Thread pool tweaks --- homeassistant/core.py | 5 --- homeassistant/remote.py | 7 +--- homeassistant/util/__init__.py | 19 ++++----- .../device_tracker/test_locative.py | 3 ++ tests/components/test_alexa.py | 3 ++ tests/components/test_api.py | 3 ++ tests/components/test_frontend.py | 3 ++ tests/test_remote.py | 40 ++++++++++--------- 8 files changed, 45 insertions(+), 38 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 25062952ed0..839058d25dd 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -103,12 +103,7 @@ class HomeAssistant(object): def stop(self): """Stop Home Assistant and shuts down all threads.""" _LOGGER.info("Stopping") - self.bus.fire(EVENT_HOMEASSISTANT_STOP) - - # Wait till all responses to homeassistant_stop are done - self.pool.block_till_done() - self.pool.stop() diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 6b55622adce..29ac7eeb1db 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -148,14 +148,11 @@ class HomeAssistant(ha.HomeAssistant): self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP, origin=ha.EventOrigin.remote) + self.pool.stop() + # Disconnect master event forwarding disconnect_remote_events(self.remote_api, self.config.api) - # Wait till all responses to homeassistant_stop are done - self.pool.block_till_done() - - self.pool.stop() - class EventBus(ha.EventBus): """ EventBus implementation that forwards fire_event to remote API. """ diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 89b2ab0e1f3..16650fe549d 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -284,7 +284,7 @@ class Throttle(object): class ThreadPool(object): - """ A priority queue-based thread pool. """ + """A priority queue-based thread pool.""" # pylint: disable=too-many-instance-attributes def __init__(self, job_handler, worker_count=0, busy_callback=None): @@ -311,7 +311,7 @@ class ThreadPool(object): self.add_worker() def add_worker(self): - """ Adds a worker to the thread pool. Resets warning limit. """ + """Add worker to the thread pool and reset warning limit.""" with self._lock: if not self.running: raise RuntimeError("ThreadPool not running") @@ -324,7 +324,7 @@ class ThreadPool(object): self.busy_warning_limit = self.worker_count * 3 def remove_worker(self): - """ Removes a worker from the thread pool. Resets warning limit. """ + """Remove worker from the thread pool and reset warning limit.""" with self._lock: if not self.running: raise RuntimeError("ThreadPool not running") @@ -354,18 +354,19 @@ class ThreadPool(object): self._work_queue.qsize()) def block_till_done(self): - """ Blocks till all work is done. """ + """Block till current work is done.""" self._work_queue.join() + # import traceback + # traceback.print_stack() def stop(self): - """ Stops all the threads. """ + """Finish all the jobs and stops all the threads.""" + self.block_till_done() + with self._lock: if not self.running: return - # Ensure all current jobs finish - self.block_till_done() - # Tell the workers to quit for _ in range(self.worker_count): self.remove_worker() @@ -376,7 +377,7 @@ class ThreadPool(object): self.block_till_done() def _worker(self): - """ Handles jobs for the thread pool. """ + """Handle jobs for the thread pool.""" while True: # Get new item from work_queue job = self._work_queue.get().item diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index 5182a536034..5e8babca2d9 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -64,6 +64,9 @@ def tearDownModule(): # pylint: disable=invalid-name class TestLocative(unittest.TestCase): """ Test Locative """ + def tearDown(self): + hass.pool.block_till_done() + def test_missing_data(self, update_config): data = { 'latitude': 1.0, diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index f1de566a57e..fcdc4b09937 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -102,6 +102,9 @@ def _req(data={}): class TestAlexa(unittest.TestCase): """ Test Alexa. """ + def tearDown(self): + hass.pool.block_till_done() + def test_launch_request(self): data = { 'version': '1.0', diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 3dbaedd8c1a..87f67414e0f 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -59,6 +59,9 @@ def tearDownModule(): # pylint: disable=invalid-name class TestAPI(unittest.TestCase): """ Test the API. """ + def tearDown(self): + hass.pool.block_till_done() + # TODO move back to http component and test with use_auth. def test_access_denied_without_password(self): req = requests.get(_url(const.URL_API)) diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index 8455bd5a8f9..a9fb6550a9f 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -56,6 +56,9 @@ def tearDownModule(): # pylint: disable=invalid-name class TestFrontend(unittest.TestCase): """ Test the frontend. """ + def tearDown(self): + hass.pool.block_till_done() + def test_frontend_and_static(self): """ Tests if we can get the frontend. """ req = requests.get(_url("")) diff --git a/tests/test_remote.py b/tests/test_remote.py index 435cbde79f9..b49aea25244 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -34,7 +34,7 @@ def _url(path=""): def setUpModule(): # pylint: disable=invalid-name """ Initalizes a Home Assistant server and Slave instance. """ - global hass, slave, master_api, broken_api + global hass, slave, master_api hass = get_test_home_assistant() @@ -64,8 +64,6 @@ def setUpModule(): # pylint: disable=invalid-name def tearDownModule(): # pylint: disable=invalid-name """ Stops the Home Assistant server and slave. """ - global hass, slave - slave.stop() hass.stop() @@ -73,6 +71,10 @@ def tearDownModule(): # pylint: disable=invalid-name class TestRemoteMethods(unittest.TestCase): """ Test the homeassistant.remote module. """ + def tearDown(self): + slave.pool.block_till_done() + hass.pool.block_till_done() + def test_validate_api(self): """ Test Python API validate_api. """ self.assertEqual(remote.APIStatus.OK, remote.validate_api(master_api)) @@ -193,10 +195,24 @@ class TestRemoteMethods(unittest.TestCase): # Should not raise an exception remote.call_service(broken_api, "test_domain", "test_service") + def test_json_encoder(self): + """ Test the JSON Encoder. """ + ha_json_enc = remote.JSONEncoder() + state = hass.states.get('test.test') + + self.assertEqual(state.as_dict(), ha_json_enc.default(state)) + + # Default method raises TypeError if non HA object + self.assertRaises(TypeError, ha_json_enc.default, 1) + class TestRemoteClasses(unittest.TestCase): """ Test the homeassistant.remote module. """ + def tearDown(self): + slave.pool.block_till_done() + hass.pool.block_till_done() + def test_home_assistant_init(self): """ Test HomeAssistant init. """ # Wrong password @@ -211,12 +227,8 @@ class TestRemoteClasses(unittest.TestCase): def test_statemachine_init(self): """ Tests if remote.StateMachine copies all states on init. """ - self.assertEqual(len(hass.states.all()), - len(slave.states.all())) - - for state in hass.states.all(): - self.assertEqual( - state, slave.states.get(state.entity_id)) + self.assertEqual(sorted(hass.states.all()), + sorted(slave.states.all())) def test_statemachine_set(self): """ Tests if setting the state on a slave is recorded. """ @@ -271,13 +283,3 @@ class TestRemoteClasses(unittest.TestCase): hass.pool.block_till_done() self.assertEqual(1, len(test_value)) - - def test_json_encoder(self): - """ Test the JSON Encoder. """ - ha_json_enc = remote.JSONEncoder() - state = hass.states.get('test.test') - - self.assertEqual(state.as_dict(), ha_json_enc.default(state)) - - # Default method raises TypeError if non HA object - self.assertRaises(TypeError, ha_json_enc.default, 1) From c287520432859a48d5aa6825216d3dcb214d1e9a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 23:16:54 -0800 Subject: [PATCH 036/186] MQTT Light test - switch order --- tests/components/light/test_mqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index ff55c22f0e3..f08e84677a5 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -172,12 +172,12 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get('brightness')) self.assertIsNone(state.attributes.get('rgb_color')) + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', + '{"hello": [1, 2, 3]}') fire_mqtt_message(self.hass, 'test_light_rgb/status', '{"hello": "ON"}') fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '{"hello": "50"}') - fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', - '{"hello": [1, 2, 3]}') self.hass.pool.block_till_done() state = self.hass.states.get('light.test') From bca3207e0ce2ad905278fd5579175547e462acb7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 15 Feb 2016 01:02:15 -0800 Subject: [PATCH 037/186] Update frontend with assumed_state and delete state support --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 26 ++++++++++++------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index f207bcae379..0be85327b08 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "833d09737fec24f9219efae87c5bfd2a" +VERSION = "c11382dc4f1efd426f94d77cf498b954" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 09d82fce309..e9e19192018 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2969,10 +2969,16 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym white-space: normal; pointer-events: none; color: var(--paper-toggle-button-label-color, --primary-text-color); - }
\ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 1380e59e182..75cc118cb12 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 1380e59e182c7d5468c55c67c8c363ff9248349a +Subproject commit 75cc118cb121f10cd2e4dd98c9b99ce1219c485a From 0d45470ea69824b09970ea16a4c694074056017a Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Feb 2016 10:05:47 +0100 Subject: [PATCH 038/186] Support on-off device in rfxtrx light --- homeassistant/components/light/rfxtrx.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 08f3dcc7c60..0905891a093 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -155,16 +155,18 @@ class RfxtrxLight(Light): def turn_on(self, **kwargs): """ Turn the light on. """ brightness = kwargs.get(ATTR_BRIGHTNESS) + if not self._event: + return if brightness is None: self._brightness = 100 + self._event.device.send_on(rfxtrx.RFXOBJECT.transport) else: self._brightness = ((brightness + 4) * 100 // 255 - 1) - - if hasattr(self, '_event') and self._event: self._event.device.send_dim(rfxtrx.RFXOBJECT.transport, self._brightness) + self._brightness = (self._brightness * 255 // 100) self._state = True self.update_ha_state() From eacfac6fa8eeb30c0da35b91a84308b8e4a1d742 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Feb 2016 10:10:29 +0100 Subject: [PATCH 039/186] Support on-off device in rfxtrx light --- homeassistant/components/light/rfxtrx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 0905891a093..91c0b14be67 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -166,7 +166,6 @@ class RfxtrxLight(Light): self._event.device.send_dim(rfxtrx.RFXOBJECT.transport, self._brightness) - self._brightness = (self._brightness * 255 // 100) self._state = True self.update_ha_state() From a3e8994fcc0166e05180f1d763168da1674e444e Mon Sep 17 00:00:00 2001 From: Flyte Date: Mon, 15 Feb 2016 10:00:46 +0000 Subject: [PATCH 040/186] Execute all lint tests even if flake8 reports errors. --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 86287cd1df4..1a1999264e1 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ deps = [testenv:lint] basepython = python3 +ignore_errors = True commands = flake8 pylint homeassistant @@ -26,4 +27,4 @@ commands = basepython = python3 deps = commands = - python script/gen_requirements_all.py validate \ No newline at end of file + python script/gen_requirements_all.py validate From 3d83eea5f76f011a5df39d22a83852921e636623 Mon Sep 17 00:00:00 2001 From: Flyte Date: Sun, 14 Feb 2016 00:03:56 +0000 Subject: [PATCH 041/186] Add tcp component. --- homeassistant/components/binary_sensor/tcp.py | 30 +++++ homeassistant/components/sensor/tcp.py | 20 +++ homeassistant/components/tcp.py | 125 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 homeassistant/components/binary_sensor/tcp.py create mode 100644 homeassistant/components/sensor/tcp.py create mode 100644 homeassistant/components/tcp.py diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py new file mode 100644 index 00000000000..4e6e75e3555 --- /dev/null +++ b/homeassistant/components/binary_sensor/tcp.py @@ -0,0 +1,30 @@ +""" +homeassistant.components.binary_sensor.tcp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Provides a binary_sensor which gets its values from a TCP socket. +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components import tcp + + +DEPENDENCIES = [tcp.DOMAIN] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """ Create the BinarySensor. """ + if not BinarySensor.validate_config(config): + return False + add_entities((BinarySensor(config),)) + + +class BinarySensor(tcp.TCPEntity, BinarySensorDevice): + """ A binary sensor which is on when its state == CONF_VALUE_ON. """ + required = (tcp.CONF_VALUE_ON,) + + @property + def is_on(self): + return self._state == self._config[tcp.CONF_VALUE_ON] diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py new file mode 100644 index 00000000000..53e91c6c728 --- /dev/null +++ b/homeassistant/components/sensor/tcp.py @@ -0,0 +1,20 @@ +""" +homeassistant.components.sensor.tcp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Provides a sensor which gets its values from a TCP socket. +""" +import logging + +from homeassistant.components import tcp + + +DEPENDENCIES = [tcp.DOMAIN] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """ Create the Sensor. """ + if not tcp.TCPEntity.validate_config(config): + return False + add_entities((tcp.TCPEntity(config),)) diff --git a/homeassistant/components/tcp.py b/homeassistant/components/tcp.py new file mode 100644 index 00000000000..89c39e9f0db --- /dev/null +++ b/homeassistant/components/tcp.py @@ -0,0 +1,125 @@ +""" +homeassistant.components.tcp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A generic TCP socket component. +""" +import logging +import socket +import re +from select import select + +from homeassistant.const import CONF_NAME, CONF_HOST +from homeassistant.helpers.entity import Entity + + +DOMAIN = "tcp" + +CONF_PORT = "port" +CONF_TIMEOUT = "timeout" +CONF_PAYLOAD = "payload" +CONF_UNIT = "unit" +CONF_VALUE_REGEX = "value_regex" +CONF_VALUE_ON = "value_on" +CONF_BUFFER_SIZE = "buffer_size" + +DEFAULT_TIMEOUT = 10 +DEFAULT_BUFFER_SIZE = 1024 + +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Nothing to do! """ + return True + + +class TCPEntity(Entity): + """ Generic Entity which gets its value from a TCP socket. """ + required = tuple() + + def __init__(self, config): + """ Set all the config values if they exist and get initial state. """ + self._config = { + CONF_NAME: config.get(CONF_NAME), + CONF_HOST: config[CONF_HOST], + CONF_PORT: config[CONF_PORT], + CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + CONF_PAYLOAD: config[CONF_PAYLOAD], + CONF_UNIT: config.get(CONF_UNIT), + CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), + CONF_VALUE_ON: config.get(CONF_VALUE_ON), + CONF_BUFFER_SIZE: config.get( + CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), + } + self._state = None + self.update() + + @classmethod + def validate_config(cls, config): + """ Ensure the config has all of the necessary values. """ + always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) + for key in always_required + tuple(cls.required): + if key not in config: + _LOGGER.error( + "You must provide %r to create any TCP entity.", key) + return False + return True + + @property + def name(self): + name = self._config[CONF_NAME] + if name is not None: + return name + return super(TCPEntity, self).name + + @property + def state(self): + return self._state + + @property + def unit_of_measurement(self): + return self._config[CONF_UNIT] + + def update(self): + """ Get the latest value for this sensor. """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) + except socket.error as err: + _LOGGER.error( + "Unable to connect to %s on port %s: %s", + self._config[CONF_HOST], self._config[CONF_PORT], err) + return + try: + sock.send(self._config[CONF_PAYLOAD].encode()) + except socket.error as err: + _LOGGER.error( + "Unable to send payload %r to %s on port %s: %s", + self._config[CONF_PAYLOAD], self._config[CONF_HOST], + self._config[CONF_PORT], err) + return + readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) + if not readable: + _LOGGER.warning( + "Timeout (%s second(s)) waiting for a response after sending " + "%r to %s on port %s.", + self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], + self._config[CONF_HOST], self._config[CONF_PORT]) + return + value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() + if self._config[CONF_VALUE_REGEX] is not None: + match = re.match(self._config[CONF_VALUE_REGEX], value) + if match is None: + _LOGGER.warning( + "Unable to match value using value_regex of %r: %r", + self._config[CONF_VALUE_REGEX], value) + return + try: + self._state = match.groups()[0] + except IndexError: + _LOGGER.error( + "You must include a capture group in the regex for %r: %r", + self.name, self._config[CONF_VALUE_REGEX]) + return + return + self._state = value From f429a6c4ffe8a0a8352e72e544104eeff768c66b Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 15 Feb 2016 18:59:11 +0100 Subject: [PATCH 042/186] Add assumed_state for rfxtrx switch and light. --- homeassistant/components/light/rfxtrx.py | 6 ++++++ homeassistant/components/switch/rfxtrx.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 08f3dcc7c60..91419ed6b0f 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -152,6 +152,12 @@ class RfxtrxLight(Light): """ Brightness of this light between 0..255. """ return self._brightness + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return True + + def turn_on(self, **kwargs): """ Turn the light on. """ brightness = kwargs.get(ATTR_BRIGHTNESS) diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index b3d19a5db3b..5a90db77b5b 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -132,6 +132,11 @@ class RfxtrxSwitch(SwitchDevice): """ True if light is on. """ return self._state + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return True + def turn_on(self, **kwargs): """ Turn the device on. """ if self._event: From a7ce9ba49e62802ebc5998b4e5acf9853633c174 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 15 Feb 2016 19:09:53 +0100 Subject: [PATCH 043/186] CI fix --- homeassistant/components/light/rfxtrx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 91419ed6b0f..60b3c9bf114 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -157,7 +157,6 @@ class RfxtrxLight(Light): """Return True if unable to access real state of entity.""" return True - def turn_on(self, **kwargs): """ Turn the light on. """ brightness = kwargs.get(ATTR_BRIGHTNESS) From 0010cadd39cc885c8a3dda05a44eff9956d4aea3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Feb 2016 19:41:40 +0100 Subject: [PATCH 044/186] Add signal repetition to rfxtrx --- homeassistant/components/light/rfxtrx.py | 23 ++++++++++++++++------- homeassistant/components/switch/rfxtrx.py | 20 +++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 91c0b14be67..a94f346319d 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -19,6 +19,7 @@ from homeassistant.components.rfxtrx import ( DEPENDENCIES = ['rfxtrx'] +SIGNAL_REPETITIONS = 1 _LOGGER = logging.getLogger(__name__) @@ -29,6 +30,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): lights = [] devices = config.get('devices', None) + signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) if devices: for entity_id, entity_info in devices.items(): @@ -41,7 +43,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) new_light = RfxtrxLight( - entity_info[ATTR_NAME], rfxobject, datas + entity_info[ATTR_NAME], rfxobject, datas, signal_repetitions ) rfxtrx.RFX_DEVICES[entity_id] = new_light lights.append(new_light) @@ -70,7 +72,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): pkt_id = "".join("{0:02x}".format(x) for x in event.data) entity_name = "%s : %s" % (entity_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} - new_light = RfxtrxLight(entity_name, event, datas) + signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) + new_light = RfxtrxLight(entity_name, event, datas, signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = new_light add_devices_callback([new_light]) @@ -120,11 +123,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class RfxtrxLight(Light): """ Provides a RFXtrx light. """ - def __init__(self, name, event, datas): + def __init__(self, name, event, datas, signal_repetitions): self._name = name self._event = event self._state = datas[ATTR_STATE] self._should_fire_event = datas[ATTR_FIREEVENT] + self.signal_repetitions = signal_repetitions self._brightness = 0 @property @@ -160,11 +164,13 @@ class RfxtrxLight(Light): if brightness is None: self._brightness = 100 - self._event.device.send_on(rfxtrx.RFXOBJECT.transport) + for _ in range(self.signal_repetitions): + self._eventent.device.send_on(rfxtrx.RFXOBJECT.transport) else: self._brightness = ((brightness + 4) * 100 // 255 - 1) - self._event.device.send_dim(rfxtrx.RFXOBJECT.transport, - self._brightness) + for _ in range(self.signal_repetitions): + self._event.device.send_dim(rfxtrx.RFXOBJECT.transport, + self._brightness) self._brightness = (self._brightness * 255 // 100) self._state = True @@ -173,7 +179,10 @@ class RfxtrxLight(Light): def turn_off(self, **kwargs): """ Turn the light off. """ - if hasattr(self, '_event') and self._event: + if not self._event: + return + + for _ in range(self.signal_repetitions): self._event.device.send_off(rfxtrx.RFXOBJECT.transport) self._brightness = 0 diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index b3d19a5db3b..a9c949f7008 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -19,6 +19,7 @@ from homeassistant.components.rfxtrx import ( DEPENDENCIES = ['rfxtrx'] +SIGNAL_REPETITIONS = 1 _LOGGER = logging.getLogger(__name__) @@ -30,6 +31,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # Add switch from config file switchs = [] devices = config.get('devices') + signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) if devices: for entity_id, entity_info in devices.items(): if entity_id not in rfxtrx.RFX_DEVICES: @@ -41,7 +43,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) newswitch = RfxtrxSwitch( - entity_info[ATTR_NAME], rfxobject, datas) + entity_info[ATTR_NAME], rfxobject, datas, signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = newswitch switchs.append(newswitch) @@ -69,7 +71,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): pkt_id = "".join("{0:02x}".format(x) for x in event.data) entity_name = "%s : %s" % (entity_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} - new_switch = RfxtrxSwitch(entity_name, event, datas) + signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) + new_switch = RfxtrxSwitch(entity_name, event, datas, signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = new_switch add_devices_callback([new_switch]) @@ -106,11 +109,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class RfxtrxSwitch(SwitchDevice): """ Provides a RFXtrx switch. """ - def __init__(self, name, event, datas): + def __init__(self, name, event, datas, signal_repetitions): self._name = name self._event = event self._state = datas[ATTR_STATE] self._should_fire_event = datas[ATTR_FIREEVENT] + self.signal_repetitions = signal_repetitions @property def should_poll(self): @@ -134,7 +138,10 @@ class RfxtrxSwitch(SwitchDevice): def turn_on(self, **kwargs): """ Turn the device on. """ - if self._event: + if not self._event: + return + + for _ in range(self.signal_repetitions): self._event.device.send_on(rfxtrx.RFXOBJECT.transport) self._state = True @@ -142,7 +149,10 @@ class RfxtrxSwitch(SwitchDevice): def turn_off(self, **kwargs): """ Turn the device off. """ - if self._event: + if not self._event: + return + + for _ in range(self.signal_repetitions): self._event.device.send_off(rfxtrx.RFXOBJECT.transport) self._state = False From b93ebe1936da9239dbcc1387b82162d7753f977c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Feb 2016 19:45:33 +0100 Subject: [PATCH 045/186] Add signal repetition to rfxtrx --- homeassistant/components/light/rfxtrx.py | 10 ++++++---- homeassistant/components/switch/rfxtrx.py | 9 ++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index a94f346319d..30d5b0cbf59 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -43,8 +43,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) new_light = RfxtrxLight( - entity_info[ATTR_NAME], rfxobject, datas, signal_repetitions - ) + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = new_light lights.append(new_light) @@ -72,8 +72,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): pkt_id = "".join("{0:02x}".format(x) for x in event.data) entity_name = "%s : %s" % (entity_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} - signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) - new_light = RfxtrxLight(entity_name, event, datas, signal_repetitions) + signal_repetitions = config.get('signal_repetitions', + SIGNAL_REPETITIONS) + new_light = RfxtrxLight(entity_name, event, datas, + signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = new_light add_devices_callback([new_light]) diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index a9c949f7008..4737ef6ae1f 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -43,7 +43,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) newswitch = RfxtrxSwitch( - entity_info[ATTR_NAME], rfxobject, datas, signal_repetitions) + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = newswitch switchs.append(newswitch) @@ -71,8 +72,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): pkt_id = "".join("{0:02x}".format(x) for x in event.data) entity_name = "%s : %s" % (entity_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} - signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) - new_switch = RfxtrxSwitch(entity_name, event, datas, signal_repetitions) + signal_repetitions = config.get('signal_repetitions', + SIGNAL_REPETITIONS) + new_switch = RfxtrxSwitch(entity_name, event, datas, + signal_repetitions) rfxtrx.RFX_DEVICES[entity_id] = new_switch add_devices_callback([new_switch]) From aec269050f03475daee3e62cbcdf4e678d7382eb Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Feb 2016 19:52:39 +0100 Subject: [PATCH 046/186] Add signal repetition to rfxtrx --- homeassistant/components/light/rfxtrx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 30d5b0cbf59..faa7ad9ef7f 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -167,7 +167,7 @@ class RfxtrxLight(Light): if brightness is None: self._brightness = 100 for _ in range(self.signal_repetitions): - self._eventent.device.send_on(rfxtrx.RFXOBJECT.transport) + self._event.device.send_on(rfxtrx.RFXOBJECT.transport) else: self._brightness = ((brightness + 4) * 100 // 255 - 1) for _ in range(self.signal_repetitions): From 16865b82d3a85f9464302534fb7b765620b7a06b Mon Sep 17 00:00:00 2001 From: infamy Date: Mon, 15 Feb 2016 15:54:07 -0800 Subject: [PATCH 047/186] initial commit of the neurio_energy sensor --- .../components/sensor/neurio_energy.py | 87 +++++++++++++++++++ requirements_all.txt | 3 + 2 files changed, 90 insertions(+) create mode 100644 homeassistant/components/sensor/neurio_energy.py diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py new file mode 100644 index 00000000000..9a2b0652789 --- /dev/null +++ b/homeassistant/components/sensor/neurio_energy.py @@ -0,0 +1,87 @@ +""" +homeassistant.components.sensor.neurio_energy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Monitors home energy use as measured by an neurio hub using its official API. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.neurio_energy/ +""" +import logging +import requests.exceptions + +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['neurio==0.2.10'] + +_LOGGER = logging.getLogger(__name__) + +ICON = 'mdi:flash' + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Neurio sensor. """ + api_key = config.get("api_key") + api_secret = config.get("api_secret") + sensor_id = config.get("sensor_id") + if not api_key and not api_secret: + _LOGGER.error( + "Configuration Error" + "Please make sure you have configured your api key and api secret") + return None + if not sensor_id: + import neurio + neurio_tp = neurio.TokenProvider(key=api_key, secret=api_secret) + neurio_client = neurio.Client(token_provider=neurio_tp) + user_info = neurio_client.get_user_information() + _LOGGER.warning('Sensor ID auto-detected, set api_sensor_id: "%s"', + user_info["locations"][0]["sensors"][0]["sensorId"]) + sensor_id = user_info["locations"][0]["sensors"][0]["sensorId"] + dev = [] + dev.append(NeurioEnergy(api_key, api_secret, sensor_id)) + add_devices(dev) + + +# pylint: disable=too-many-instance-attributes +class NeurioEnergy(Entity): + """ Implements an Neurio energy. """ + + # pylint: disable=too-many-arguments + def __init__(self, api_key, api_secret, sensor_id): + self._name = "Energy Usage" + self.api_key = api_key + self.api_secret = api_secret + self.sensor_id = sensor_id + self._state = None + self._unit_of_measurement = "W" + + @property + def name(self): + """ Returns the name. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement + + @property + def icon(self): + """ Icon to use in the frontend, if any. """ + return ICON + + def update(self): + """ Gets the Neurio monitor data from the web service. """ + import neurio + try: + neurio_tp = neurio.TokenProvider(key=self.api_key, + secret=self.api_secret) + neurio_client = neurio.Client(token_provider=neurio_tp) + sample = neurio_client.get_samples_live_last( + sensor_id=self.sensor_id) + self._state = sample['consumptionPower'] + except (requests.exceptions.RequestException, ValueError): + _LOGGER.warning('Could not update status for %s', self.name) diff --git a/requirements_all.txt b/requirements_all.txt index e4299f3e80c..e5a73bf4760 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -115,6 +115,9 @@ mficlient==0.2.2 # homeassistant.components.discovery netdisco==0.5.2 +# homeassistant.components.sensor.neurio_energy +neurio==0.2.10 + # homeassistant.components.switch.orvibo orvibo==1.1.1 From 9a6c99264e466a70b3444fa08ed51786d487d427 Mon Sep 17 00:00:00 2001 From: pavoni Date: Tue, 16 Feb 2016 12:05:00 +0000 Subject: [PATCH 048/186] Catch KeyError as well as ValueError when handling efergy errors. --- homeassistant/components/sensor/efergy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/efergy.py b/homeassistant/components/sensor/efergy.py index 8eba0149408..23023b90fe1 100644 --- a/homeassistant/components/sensor/efergy.py +++ b/homeassistant/components/sensor/efergy.py @@ -97,5 +97,5 @@ class EfergySensor(Entity): self._state = response.json()['sum'] else: self._state = 'Unknown' - except (RequestException, ValueError): + except (RequestException, ValueError, KeyError): _LOGGER.warning('Could not update status for %s', self.name) From 008d65677b203e70d59255da50206d55147b0a33 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 16 Feb 2016 17:18:49 +0100 Subject: [PATCH 049/186] add assumed state property --- homeassistant/components/switch/tellduslive.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/switch/tellduslive.py index 7edab40054f..078ca912bd2 100644 --- a/homeassistant/components/switch/tellduslive.py +++ b/homeassistant/components/switch/tellduslive.py @@ -40,6 +40,11 @@ class TelldusLiveSwitch(ToggleEntity): """ Tells Home Assistant to poll this entity. """ return True + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return True + @property def name(self): """ Returns the name of the switch if any. """ From 307b2c629b5e7516669722451adb7995dd2ab866 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 16 Feb 2016 17:36:09 +0100 Subject: [PATCH 050/186] catch exception and log --- homeassistant/components/tellduslive.py | 35 +++++++++++-------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index 18e2af54890..1b602d953b0 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -86,10 +86,8 @@ class TelldusLiveData(object): def validate_session(self): """ Make a dummy request to see if the session is valid """ - try: - return 'email' in self.request("user/profile") - except RuntimeError: - return False + response = self.request("user/profile") + return response and 'email' in response def discover(self): """ Update states, will trigger discover """ @@ -143,10 +141,13 @@ class TelldusLiveData(object): # re-use sessions, instead it opens a new session for each request # this needs to be fixed - response = self._client.request(what, params) - - _LOGGER.debug("got response %s", response) - return response + try: + response = self._client.request(what, params) + _LOGGER.debug("got response %s", response) + return response + except (ConnectionError, TimeoutError): + _LOGGER.error("failed to make request to Tellduslive servers") + return None def update_devices(self, local_devices, @@ -172,21 +173,15 @@ class TelldusLiveData(object): def update_sensors(self): """ update local list of sensors """ - try: - self._sensors = self.update_devices(self._sensors, - request_sensors(), - "sensor") - except OSError: - _LOGGER.warning("could not update sensors") + self._sensors = self.update_devices(self._sensors, + request_sensors(), + "sensor") def update_switches(self): """ update local list of switches """ - try: - self._switches = self.update_devices(self._switches, - request_switches(), - "switch") - except OSError: - _LOGGER.warning("could not update switches") + self._switches = self.update_devices(self._switches, + request_switches(), + "switch") def _check_request(self, what, **params): """ Make request, check result if successful """ From 44a39636b1e23780de45cfe5d4914a822e172161 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 16 Feb 2016 18:12:52 +0100 Subject: [PATCH 051/186] improve error handling --- homeassistant/components/sensor/eliqonline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index 4e6b8a5c342..4f30e1efd00 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.eliqonline/ """ import logging +from urllib.error import URLError from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_UNKNOWN, CONF_ACCESS_TOKEN, CONF_NAME) @@ -73,5 +74,5 @@ class EliqSensor(Entity): try: response = self.api.get_data_now(channelid=self.channel_id) self._state = int(response.power) - except TypeError: # raised by eliqonline library on any HTTP error - pass + except (TypeError, URLError): + _LOGGER.error("could not connect to the eliqonline servers") From 22865e5d9694a9adfd9e7c36d250c0482fa09184 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 15 Feb 2016 15:07:31 -0800 Subject: [PATCH 052/186] Add nx584 as a sensor platform This allows you to get every door, window, smoke, etc zone from your security panel into HA. This uses the live eventing feature of pynx584, which means you get instantaneous signaling into HA when a door opens or something happens, which is handy for automating lights on when doors open after dark, etc. Requires update to pynx584 0.2 --- .../components/alarm_control_panel/nx584.py | 2 +- .../components/binary_sensor/nx584.py | 115 ++++++++++++ requirements_all.txt | 3 +- tests/components/binary_sensor/test_nx584.py | 173 ++++++++++++++++++ 4 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/binary_sensor/nx584.py create mode 100644 tests/components/binary_sensor/test_nx584.py diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index 55ad9b25b9f..c77fab341de 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -14,7 +14,7 @@ from homeassistant.const import (STATE_UNKNOWN, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY) import homeassistant.components.alarm_control_panel as alarm -REQUIREMENTS = ['pynx584==0.1'] +REQUIREMENTS = ['pynx584==0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py new file mode 100644 index 00000000000..0565bb5d857 --- /dev/null +++ b/homeassistant/components/binary_sensor/nx584.py @@ -0,0 +1,115 @@ +""" +homeassistant.components.sensor.nx584 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for exposing nx584 elements as sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.nx584/ +""" +import logging +import threading +import time + +import requests + +from homeassistant.components.binary_sensor import BinarySensorDevice + +REQUIREMENTS = ['pynx584==0.2'] +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup nx584 sensors.""" + from nx584 import client as nx584_client + + host = config.get('host', 'localhost:5007') + exclude = config.get('exclude_zones', []) + + if not all(isinstance(zone, int) for zone in exclude): + _LOGGER.error('Invalid excluded zone specified (use zone number)') + return False + + try: + client = nx584_client.Client('http://%s' % host) + zones = client.list_zones() + except requests.exceptions.ConnectionError as ex: + _LOGGER.error('Unable to connect to NX584: %s', str(ex)) + return False + + version = [int(v) for v in client.get_version().split('.')] + if version < [1, 1]: + _LOGGER.error('NX584 is too old to use for sensors (>=0.2 required)') + return False + + zone_sensors = { + zone['number']: NX584ZoneSensor(zone) + for zone in zones + if zone['number'] not in exclude} + if zone_sensors: + add_devices(zone_sensors.values()) + watcher = NX584Watcher(client, zone_sensors) + watcher.start() + else: + _LOGGER.warning('No zones found on NX584') + + return True + + +class NX584ZoneSensor(BinarySensorDevice): + """Represents a NX584 zone as a sensor.""" + + def __init__(self, zone): + self._zone = zone + + @property + def should_poll(self): + return False + + @property + def name(self): + return self._zone['name'] + + @property + def is_on(self): + # True means "faulted" or "open" or "abnormal state" + return self._zone['state'] + + +class NX584Watcher(threading.Thread): + """Event listener thread to process NX584 events.""" + + def __init__(self, client, zone_sensors): + super(NX584Watcher, self).__init__() + self.daemon = True + self._client = client + self._zone_sensors = zone_sensors + + def _process_zone_event(self, event): + zone = event['zone'] + zone_sensor = self._zone_sensors.get(zone) + # pylint: disable=protected-access + if not zone_sensor: + return + zone_sensor._zone['state'] = event['zone_state'] + zone_sensor.update_ha_state() + + def _process_events(self, events): + for event in events: + if event.get('type') == 'zone_status': + self._process_zone_event(event) + + def _run(self): + # Throw away any existing events so we don't replay history + self._client.get_events() + while True: + events = self._client.get_events() + if events: + self._process_events(events) + + def run(self): + while True: + try: + self._run() + except requests.exceptions.ConnectionError: + _LOGGER.error('Failed to reach NX584 server') + time.sleep(10) diff --git a/requirements_all.txt b/requirements_all.txt index e4299f3e80c..9d9fd17e887 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -161,7 +161,8 @@ pyicloud==0.7.2 pynetgear==0.3.2 # homeassistant.components.alarm_control_panel.nx584 -pynx584==0.1 +# homeassistant.components.binary_sensor.nx584 +pynx584==0.2 # homeassistant.components.sensor.openweathermap pyowm==2.3.0 diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py new file mode 100644 index 00000000000..67dbe18e866 --- /dev/null +++ b/tests/components/binary_sensor/test_nx584.py @@ -0,0 +1,173 @@ +""" +tests.components.binary_sensor.nx584 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests for nx584 sensor. +""" + +import requests +import unittest +from unittest import mock + +from homeassistant.components.binary_sensor import nx584 +from nx584 import client as nx584_client + + +class StopMe(Exception): + pass + + +class TestNX584SensorSetup(unittest.TestCase): + def setUp(self): + self._mock_client = mock.patch.object(nx584_client, 'Client') + self._mock_client.start() + + self.fake_zones = [ + {'name': 'front', 'number': 1}, + {'name': 'back', 'number': 2}, + {'name': 'inside', 'number': 3}, + ] + + client = nx584_client.Client.return_value + client.list_zones.return_value = self.fake_zones + client.get_version.return_value = '1.1' + + def tearDown(self): + self._mock_client.stop() + + @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') + @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') + def test_setup_no_config(self, mock_nx, mock_watcher): + add_devices = mock.MagicMock() + hass = mock.MagicMock() + self.assertTrue(nx584.setup_platform(hass, {}, add_devices)) + mock_nx.assert_has_calls([ + mock.call(zone) + for zone in self.fake_zones]) + self.assertTrue(add_devices.called) + nx584_client.Client.assert_called_once_with('http://localhost:5007') + + @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') + @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') + def test_setup_full_config(self, mock_nx, mock_watcher): + config = { + 'host': 'foo:123', + 'exclude_zones': [2], + 'zone_types': {3: 'motion'}, + } + add_devices = mock.MagicMock() + hass = mock.MagicMock() + self.assertTrue(nx584.setup_platform(hass, config, add_devices)) + mock_nx.assert_has_calls([ + mock.call(self.fake_zones[0]), + mock.call(self.fake_zones[2]), + ]) + self.assertTrue(add_devices.called) + nx584_client.Client.assert_called_once_with('http://foo:123') + self.assertTrue(mock_watcher.called) + + def _test_assert_graceful_fail(self, config): + hass = add_devices = mock.MagicMock() + self.assertFalse(nx584.setup_platform(hass, config, + add_devices)) + self.assertFalse(add_devices.called) + + def test_setup_bad_config(self): + bad_configs = [ + {'exclude_zones': ['a']}, + ] + for config in bad_configs: + self._test_assert_graceful_fail(config) + + def test_setup_connect_failed(self): + nx584_client.Client.return_value.list_zones.side_effect = \ + requests.exceptions.ConnectionError + self._test_assert_graceful_fail({}) + + def test_setup_version_too_old(self): + nx584_client.Client.return_value.get_version.return_value = '1.0' + self._test_assert_graceful_fail({}) + + def test_setup_no_zones(self): + nx584_client.Client.return_value.list_zones.return_value = [] + hass = add_devices = mock.MagicMock() + self.assertTrue(nx584.setup_platform(hass, {}, + add_devices)) + self.assertFalse(add_devices.called) + + +class TestNX584ZoneSensor(unittest.TestCase): + def test_sensor_normal(self): + zone = {'number': 1, 'name': 'foo', 'state': True} + sensor = nx584.NX584ZoneSensor(zone) + self.assertEqual('foo', sensor.name) + self.assertFalse(sensor.should_poll) + self.assertTrue(sensor.is_on) + + zone['state'] = False + self.assertFalse(sensor.is_on) + + +class TestNX584Watcher(unittest.TestCase): + @mock.patch.object(nx584.NX584ZoneSensor, 'update_ha_state') + def test_process_zone_event(self, mock_update): + zone1 = {'number': 1, 'name': 'foo', 'state': True} + zone2 = {'number': 2, 'name': 'bar', 'state': True} + zones = { + 1: nx584.NX584ZoneSensor(zone1), + 2: nx584.NX584ZoneSensor(zone2), + } + watcher = nx584.NX584Watcher(None, zones) + watcher._process_zone_event({'zone': 1, 'zone_state': False}) + self.assertFalse(zone1['state']) + self.assertEqual(1, mock_update.call_count) + + @mock.patch.object(nx584.NX584ZoneSensor, 'update_ha_state') + def test_process_zone_event_missing_zone(self, mock_update): + watcher = nx584.NX584Watcher(None, {}) + watcher._process_zone_event({'zone': 1, 'zone_state': False}) + self.assertFalse(mock_update.called) + + def test_run_with_zone_events(self): + empty_me = [1, 2] + + def fake_get_events(): + """Return nothing twice, then some events""" + if empty_me: + empty_me.pop() + else: + return fake_events + + client = mock.MagicMock() + fake_events = [ + {'zone': 1, 'zone_state': True, 'type': 'zone_status'}, + {'zone': 2, 'foo': False}, + ] + client.get_events.side_effect = fake_get_events + watcher = nx584.NX584Watcher(client, {}) + + @mock.patch.object(watcher, '_process_zone_event') + def run(fake_process): + fake_process.side_effect = StopMe + self.assertRaises(StopMe, watcher._run) + fake_process.assert_called_once_with(fake_events[0]) + + run() + self.assertEqual(3, client.get_events.call_count) + + @mock.patch('time.sleep') + def test_run_retries_failures(self, mock_sleep): + empty_me = [1, 2] + + def fake_run(): + if empty_me: + empty_me.pop() + raise requests.exceptions.ConnectionError() + else: + raise StopMe() + + watcher = nx584.NX584Watcher(None, {}) + with mock.patch.object(watcher, '_run') as mock_inner: + mock_inner.side_effect = fake_run + self.assertRaises(StopMe, watcher.run) + self.assertEqual(3, mock_inner.call_count) + mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) From aea0598805e8004e3199a05e53fc3c060f91d0be Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Feb 2016 23:52:05 -0800 Subject: [PATCH 053/186] Clean up camera component --- homeassistant/components/camera/__init__.py | 55 ++++++++------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 9aefe4b3b66..7fab1fe3ae6 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -1,8 +1,6 @@ # pylint: disable=too-many-lines """ -homeassistant.components.camera -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Component to interface with various cameras. +Component to interface with cameras. For more details about this component, please refer to the documentation at https://home-assistant.io/components/camera/ @@ -24,33 +22,17 @@ from homeassistant.const import ( DOMAIN = 'camera' DEPENDENCIES = ['http'] -GROUP_NAME_ALL_CAMERAS = 'all_cameras' SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' -SWITCH_ACTION_RECORD = 'record' -SWITCH_ACTION_SNAPSHOT = 'snapshot' - -SERVICE_CAMERA = 'camera_service' - -DEFAULT_RECORDING_SECONDS = 30 - # Maps discovered services to their platforms DISCOVERY_PLATFORMS = {} -FILE_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S-%f' -DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S' - -REC_DIR_PREFIX = 'recording-' -REC_IMG_PREFIX = 'recording_image-' - STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' STATE_IDLE = 'idle' -CAMERA_PROXY_URL = '/api/camera_proxy_stream/{0}' -CAMERA_STILL_URL = '/api/camera_proxy/{0}' -ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?time={1}' +ENTITY_IMAGE_URL = '/api/camera_proxy/{0}' MULTIPART_BOUNDARY = '--jpegboundary' MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' @@ -58,8 +40,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' # pylint: disable=too-many-branches def setup(hass, config): - """ Track states and offer events for cameras. """ - + """Initialize camera component.""" component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS) @@ -78,7 +59,7 @@ def setup(hass, config): # pylint: disable=unused-argument def _proxy_camera_image(handler, path_match, data): - """ Proxies the camera image via the HA server. """ + """Serve the camera image via the HA server.""" entity_id = path_match.group(ATTR_ENTITY_ID) camera = component.entities.get(entity_id) @@ -104,7 +85,8 @@ def setup(hass, config): # pylint: disable=unused-argument def _proxy_camera_mjpeg_stream(handler, path_match, data): """ - Proxies the camera image as an mjpeg stream via the HA server. + Proxy the camera image as an mjpeg stream via the HA server. + This function takes still images from the IP camera and turns them into an MJPEG stream. This means that HA can return a live video stream even with only a still image URL available. @@ -136,35 +118,41 @@ def setup(hass, config): class Camera(Entity): - """ The base class for camera components. """ + """The base class for camera entities.""" def __init__(self): + """Initialize a camera.""" self.is_streaming = False + @property + def should_poll(self): + """No need to poll cameras.""" + return False + @property # pylint: disable=no-self-use def is_recording(self): - """ Returns true if the device is recording. """ + """Return true if the device is recording.""" return False @property # pylint: disable=no-self-use def brand(self): - """ Should return a string of the camera brand. """ + """Camera brand.""" return None @property # pylint: disable=no-self-use def model(self): - """ Returns string of camera model. """ + """Camera model.""" return None def camera_image(self): - """ Return bytes of camera image. """ + """Return bytes of camera image.""" raise NotImplementedError() def mjpeg_stream(self, handler): - """ Generate an HTTP MJPEG stream from camera images. """ + """Generate an HTTP MJPEG stream from camera images.""" handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8')) handler.request.sendall(bytes( 'Content-type: multipart/x-mixed-replace; \ @@ -193,7 +181,7 @@ class Camera(Entity): @property def state(self): - """ Returns the state of the entity. """ + """Camera state.""" if self.is_recording: return STATE_RECORDING elif self.is_streaming: @@ -203,10 +191,9 @@ class Camera(Entity): @property def state_attributes(self): - """ Returns optional state attributes. """ + """Camera state attributes.""" attr = { - ATTR_ENTITY_PICTURE: ENTITY_IMAGE_URL.format( - self.entity_id, time.time()), + ATTR_ENTITY_PICTURE: ENTITY_IMAGE_URL.format(self.entity_id), } if self.model: From ab9ac80ee0e33829c81e9583f8add80c2bec559e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Feb 2016 23:52:26 -0800 Subject: [PATCH 054/186] Update frontend with new camera UI --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 49 ++++++++++++++----- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 0be85327b08..ea3e441f6c5 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "c11382dc4f1efd426f94d77cf498b954" +VERSION = "4ae370eaaad6bc779d08713b79ce3cba" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index e9e19192018..0013746b4d2 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2854,7 +2854,32 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym .grey { --ha-label-badge-color: var(--paper-grey-500); - } \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 75cc118cb12..4cdefac2fe6 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 75cc118cb121f10cd2e4dd98c9b99ce1219c485a +Subproject commit 4cdefac2fe6f016fa09d872c8cb062ba01442b08 From b2366ce68e848a6f5948bd11ef35926d200a2634 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Wed, 17 Feb 2016 05:50:36 -0500 Subject: [PATCH 055/186] Added optional parameter to lock and unlock methods --- homeassistant/components/lock/wink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index be558dceee7..1900a864877 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -57,10 +57,10 @@ class WinkLockDevice(LockDevice): """ True if device is locked. """ return self.wink.state() - def lock(self): + def lock(self, **kwargs): """ Lock the device. """ self.wink.set_state(True) - def unlock(self): + def unlock(self, **kwargs): """ Unlock the device. """ self.wink.set_state(False) From 9aa402871836a0747d835d98808d338cd0535fda Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Feb 2016 08:16:02 -0800 Subject: [PATCH 056/186] Fix #1287 for honeywell US systems with no fan This bumps the somecomfort requirement to 0.2.1 to pull in a change that makes handling no-fan systems graceful. Adds a test that should prove it gives us what we want. If no fan, then fan is always idle and fanmode is None. --- homeassistant/components/thermostat/honeywell.py | 2 +- requirements_all.txt | 2 +- tests/components/thermostat/test_honeywell.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/thermostat/honeywell.py b/homeassistant/components/thermostat/honeywell.py index 8411ba08fa3..f8c66cb5c5d 100644 --- a/homeassistant/components/thermostat/honeywell.py +++ b/homeassistant/components/thermostat/honeywell.py @@ -14,7 +14,7 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) REQUIREMENTS = ['evohomeclient==0.2.4', - 'somecomfort==0.2.0'] + 'somecomfort==0.2.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 38467904f20..16151a6f0ad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -235,7 +235,7 @@ sleekxmpp==1.3.1 snapcast==1.1.1 # homeassistant.components.thermostat.honeywell -somecomfort==0.2.0 +somecomfort==0.2.1 # homeassistant.components.sensor.speedtest speedtest-cli==0.3.4 diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index f27a318b8da..8e4605c739f 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -307,3 +307,13 @@ class TestHoneywellUS(unittest.TestCase): expected['fan'] = 'idle' self.device.fan_running = False self.assertEqual(expected, self.honeywell.device_state_attributes) + + def test_with_no_fan(self): + self.device.fan_running = False + self.device.fan_mode = None + expected = { + 'fan': 'idle', + 'fanmode': None, + 'system_mode': 'heat', + } + self.assertEqual(expected, self.honeywell.device_state_attributes) From 96dde18ae377027f065173301847a032f4c9f655 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Feb 2016 08:41:46 -0800 Subject: [PATCH 057/186] Fix alarmdotcom requirement 0.0.7 removed Looks like alarmdotcom just released 0.1.1 and deleted 0.0.7 which is breaking our build. --- homeassistant/components/alarm_control_panel/alarmdotcom.py | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index da74c02da54..dea8151cb80 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom' - '/archive/0.0.7.zip' - '#pyalarmdotcom==0.0.7'] + '/archive/0.1.1.zip' + '#pyalarmdotcom==0.1.1'] DEFAULT_NAME = 'Alarm.com' diff --git a/requirements_all.txt b/requirements_all.txt index 38467904f20..1daebdb447f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -70,7 +70,7 @@ https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip#RFXtrx==0.4 https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0 # homeassistant.components.alarm_control_panel.alarmdotcom -https://github.com/Xorso/pyalarmdotcom/archive/0.0.7.zip#pyalarmdotcom==0.0.7 +https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1 # homeassistant.components.modbus https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0 From cf93644d54fc1ad75f956a3994121bea4d855ea6 Mon Sep 17 00:00:00 2001 From: Flyte Date: Wed, 17 Feb 2016 17:26:53 +0000 Subject: [PATCH 058/186] Move generic tcp sensor entity to specific sensor component --- homeassistant/components/binary_sensor/tcp.py | 11 +- homeassistant/components/sensor/tcp.py | 108 +++++++++++++++++- homeassistant/components/tcp.py | 103 ----------------- 3 files changed, 110 insertions(+), 112 deletions(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 4e6e75e3555..056a59fbdf1 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -6,10 +6,11 @@ Provides a binary_sensor which gets its values from a TCP socket. import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components import tcp +from homeassistant.components.tcp import DOMAIN, CONF_VALUE_ON +from homeassistant.components.sensor.tcp import Sensor -DEPENDENCIES = [tcp.DOMAIN] +DEPENDENCIES = [DOMAIN] _LOGGER = logging.getLogger(__name__) @@ -21,10 +22,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities((BinarySensor(config),)) -class BinarySensor(tcp.TCPEntity, BinarySensorDevice): +class BinarySensor(Sensor, BinarySensorDevice): """ A binary sensor which is on when its state == CONF_VALUE_ON. """ - required = (tcp.CONF_VALUE_ON,) + required = (CONF_VALUE_ON,) @property def is_on(self): - return self._state == self._config[tcp.CONF_VALUE_ON] + return self._state == self._config[CONF_VALUE_ON] diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 53e91c6c728..b45f35434af 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -4,17 +4,117 @@ homeassistant.components.sensor.tcp Provides a sensor which gets its values from a TCP socket. """ import logging +import socket +import re +from select import select -from homeassistant.components import tcp +from homeassistant.const import CONF_NAME, CONF_HOST +from homeassistant.helpers.entity import Entity +from homeassistant.components.tcp import ( + DOMAIN, CONF_PORT, CONF_TIMEOUT, CONF_PAYLOAD, CONF_UNIT, CONF_VALUE_REGEX, + CONF_VALUE_ON, CONF_BUFFER_SIZE, DEFAULT_TIMEOUT, DEFAULT_BUFFER_SIZE +) -DEPENDENCIES = [tcp.DOMAIN] +DEPENDENCIES = [DOMAIN] _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """ Create the Sensor. """ - if not tcp.TCPEntity.validate_config(config): + if not Sensor.validate_config(config): return False - add_entities((tcp.TCPEntity(config),)) + add_entities((Sensor(config),)) + + +class Sensor(Entity): + """ Sensor Entity which gets its value from a TCP socket. """ + required = tuple() + + def __init__(self, config): + """ Set all the config values if they exist and get initial state. """ + self._config = { + CONF_NAME: config.get(CONF_NAME), + CONF_HOST: config[CONF_HOST], + CONF_PORT: config[CONF_PORT], + CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + CONF_PAYLOAD: config[CONF_PAYLOAD], + CONF_UNIT: config.get(CONF_UNIT), + CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), + CONF_VALUE_ON: config.get(CONF_VALUE_ON), + CONF_BUFFER_SIZE: config.get( + CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), + } + self._state = None + self.update() + + @classmethod + def validate_config(cls, config): + """ Ensure the config has all of the necessary values. """ + always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) + for key in always_required + tuple(cls.required): + if key not in config: + _LOGGER.error( + "You must provide %r to create any TCP entity.", key) + return False + return True + + @property + def name(self): + name = self._config[CONF_NAME] + if name is not None: + return name + return super(Sensor, self).name + + @property + def state(self): + return self._state + + @property + def unit_of_measurement(self): + return self._config[CONF_UNIT] + + def update(self): + """ Get the latest value for this sensor. """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) + except socket.error as err: + _LOGGER.error( + "Unable to connect to %s on port %s: %s", + self._config[CONF_HOST], self._config[CONF_PORT], err) + return + try: + sock.send(self._config[CONF_PAYLOAD].encode()) + except socket.error as err: + _LOGGER.error( + "Unable to send payload %r to %s on port %s: %s", + self._config[CONF_PAYLOAD], self._config[CONF_HOST], + self._config[CONF_PORT], err) + return + readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) + if not readable: + _LOGGER.warning( + "Timeout (%s second(s)) waiting for a response after sending " + "%r to %s on port %s.", + self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], + self._config[CONF_HOST], self._config[CONF_PORT]) + return + value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() + if self._config[CONF_VALUE_REGEX] is not None: + match = re.match(self._config[CONF_VALUE_REGEX], value) + if match is None: + _LOGGER.warning( + "Unable to match value using value_regex of %r: %r", + self._config[CONF_VALUE_REGEX], value) + return + try: + self._state = match.groups()[0] + except IndexError: + _LOGGER.error( + "You must include a capture group in the regex for %r: %r", + self.name, self._config[CONF_VALUE_REGEX]) + return + return + self._state = value diff --git a/homeassistant/components/tcp.py b/homeassistant/components/tcp.py index 89c39e9f0db..9b09ceaf68e 100644 --- a/homeassistant/components/tcp.py +++ b/homeassistant/components/tcp.py @@ -3,15 +3,6 @@ homeassistant.components.tcp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A generic TCP socket component. """ -import logging -import socket -import re -from select import select - -from homeassistant.const import CONF_NAME, CONF_HOST -from homeassistant.helpers.entity import Entity - - DOMAIN = "tcp" CONF_PORT = "port" @@ -25,101 +16,7 @@ CONF_BUFFER_SIZE = "buffer_size" DEFAULT_TIMEOUT = 10 DEFAULT_BUFFER_SIZE = 1024 -_LOGGER = logging.getLogger(__name__) - def setup(hass, config): """ Nothing to do! """ return True - - -class TCPEntity(Entity): - """ Generic Entity which gets its value from a TCP socket. """ - required = tuple() - - def __init__(self, config): - """ Set all the config values if they exist and get initial state. """ - self._config = { - CONF_NAME: config.get(CONF_NAME), - CONF_HOST: config[CONF_HOST], - CONF_PORT: config[CONF_PORT], - CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), - CONF_PAYLOAD: config[CONF_PAYLOAD], - CONF_UNIT: config.get(CONF_UNIT), - CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), - CONF_VALUE_ON: config.get(CONF_VALUE_ON), - CONF_BUFFER_SIZE: config.get( - CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), - } - self._state = None - self.update() - - @classmethod - def validate_config(cls, config): - """ Ensure the config has all of the necessary values. """ - always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) - for key in always_required + tuple(cls.required): - if key not in config: - _LOGGER.error( - "You must provide %r to create any TCP entity.", key) - return False - return True - - @property - def name(self): - name = self._config[CONF_NAME] - if name is not None: - return name - return super(TCPEntity, self).name - - @property - def state(self): - return self._state - - @property - def unit_of_measurement(self): - return self._config[CONF_UNIT] - - def update(self): - """ Get the latest value for this sensor. """ - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) - except socket.error as err: - _LOGGER.error( - "Unable to connect to %s on port %s: %s", - self._config[CONF_HOST], self._config[CONF_PORT], err) - return - try: - sock.send(self._config[CONF_PAYLOAD].encode()) - except socket.error as err: - _LOGGER.error( - "Unable to send payload %r to %s on port %s: %s", - self._config[CONF_PAYLOAD], self._config[CONF_HOST], - self._config[CONF_PORT], err) - return - readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) - if not readable: - _LOGGER.warning( - "Timeout (%s second(s)) waiting for a response after sending " - "%r to %s on port %s.", - self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], - self._config[CONF_HOST], self._config[CONF_PORT]) - return - value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() - if self._config[CONF_VALUE_REGEX] is not None: - match = re.match(self._config[CONF_VALUE_REGEX], value) - if match is None: - _LOGGER.warning( - "Unable to match value using value_regex of %r: %r", - self._config[CONF_VALUE_REGEX], value) - return - try: - self._state = match.groups()[0] - except IndexError: - _LOGGER.error( - "You must include a capture group in the regex for %r: %r", - self.name, self._config[CONF_VALUE_REGEX]) - return - return - self._state = value From 348b7abe7d7d2c6f792a3ab7139cfd85876180d0 Mon Sep 17 00:00:00 2001 From: Flyte Date: Wed, 17 Feb 2016 18:12:36 +0000 Subject: [PATCH 059/186] Change TCP component to use Jinja2 instead of regex --- homeassistant/components/binary_sensor/tcp.py | 2 +- homeassistant/components/sensor/tcp.py | 41 +++++++++++-------- homeassistant/components/tcp.py | 1 + 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 056a59fbdf1..e6d01b893df 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -19,7 +19,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """ Create the BinarySensor. """ if not BinarySensor.validate_config(config): return False - add_entities((BinarySensor(config),)) + add_entities((BinarySensor(hass, config),)) class BinarySensor(Sensor, BinarySensorDevice): diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index b45f35434af..29d6c260fdd 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -5,14 +5,16 @@ Provides a sensor which gets its values from a TCP socket. """ import logging import socket -import re from select import select from homeassistant.const import CONF_NAME, CONF_HOST +from homeassistant.util import template +from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.components.tcp import ( DOMAIN, CONF_PORT, CONF_TIMEOUT, CONF_PAYLOAD, CONF_UNIT, CONF_VALUE_REGEX, - CONF_VALUE_ON, CONF_BUFFER_SIZE, DEFAULT_TIMEOUT, DEFAULT_BUFFER_SIZE + CONF_VALUE_TEMPLATE, CONF_VALUE_ON, CONF_BUFFER_SIZE, DEFAULT_TIMEOUT, + DEFAULT_BUFFER_SIZE ) @@ -25,15 +27,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """ Create the Sensor. """ if not Sensor.validate_config(config): return False - add_entities((Sensor(config),)) + add_entities((Sensor(hass, config),)) class Sensor(Entity): """ Sensor Entity which gets its value from a TCP socket. """ required = tuple() - def __init__(self, config): + def __init__(self, hass, config): """ Set all the config values if they exist and get initial state. """ + self._hass = hass self._config = { CONF_NAME: config.get(CONF_NAME), CONF_HOST: config[CONF_HOST], @@ -42,6 +45,7 @@ class Sensor(Entity): CONF_PAYLOAD: config[CONF_PAYLOAD], CONF_UNIT: config.get(CONF_UNIT), CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), + CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE), CONF_VALUE_ON: config.get(CONF_VALUE_ON), CONF_BUFFER_SIZE: config.get( CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), @@ -78,6 +82,7 @@ class Sensor(Entity): def update(self): """ Get the latest value for this sensor. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) except socket.error as err: @@ -85,6 +90,7 @@ class Sensor(Entity): "Unable to connect to %s on port %s: %s", self._config[CONF_HOST], self._config[CONF_PORT], err) return + try: sock.send(self._config[CONF_PAYLOAD].encode()) except socket.error as err: @@ -93,6 +99,7 @@ class Sensor(Entity): self._config[CONF_PAYLOAD], self._config[CONF_HOST], self._config[CONF_PORT], err) return + readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) if not readable: _LOGGER.warning( @@ -101,20 +108,20 @@ class Sensor(Entity): self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], self._config[CONF_HOST], self._config[CONF_PORT]) return + value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() - if self._config[CONF_VALUE_REGEX] is not None: - match = re.match(self._config[CONF_VALUE_REGEX], value) - if match is None: - _LOGGER.warning( - "Unable to match value using value_regex of %r: %r", - self._config[CONF_VALUE_REGEX], value) - return + + if self._config[CONF_VALUE_TEMPLATE] is not None: try: - self._state = match.groups()[0] - except IndexError: - _LOGGER.error( - "You must include a capture group in the regex for %r: %r", - self.name, self._config[CONF_VALUE_REGEX]) + self._state = template.render( + self._hass, + self._config[CONF_VALUE_TEMPLATE], + value=value) return - return + except TemplateError as err: + _LOGGER.error( + "Unable to render template of %r with value: %r", + self._config[CONF_VALUE_TEMPLATE], value) + return + self._state = value diff --git a/homeassistant/components/tcp.py b/homeassistant/components/tcp.py index 9b09ceaf68e..c82bf935910 100644 --- a/homeassistant/components/tcp.py +++ b/homeassistant/components/tcp.py @@ -10,6 +10,7 @@ CONF_TIMEOUT = "timeout" CONF_PAYLOAD = "payload" CONF_UNIT = "unit" CONF_VALUE_REGEX = "value_regex" +CONF_VALUE_TEMPLATE = "value_template" CONF_VALUE_ON = "value_on" CONF_BUFFER_SIZE = "buffer_size" From 99ac4524b93e1cae874a4909011199965636adb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Wed, 17 Feb 2016 19:28:26 +0100 Subject: [PATCH 060/186] update vsure to 0.5.1 --- homeassistant/components/verisure.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 841339863ca..a79e2013ab4 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -29,7 +29,7 @@ DISCOVER_ALARMS = 'verisure.alarm_control_panel' DISCOVER_LOCKS = 'verisure.lock' DEPENDENCIES = ['alarm_control_panel'] -REQUIREMENTS = ['vsure==0.5.0'] +REQUIREMENTS = ['vsure==0.5.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 1daebdb447f..2676eb5d7cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -256,7 +256,7 @@ transmissionrpc==0.11 uvcclient==0.6 # homeassistant.components.verisure -vsure==0.5.0 +vsure==0.5.1 # homeassistant.components.zigbee xbee-helper==0.0.6 From cd6780baf492c5335a4162db54c44250f1ebbecd Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Feb 2016 07:45:00 -0800 Subject: [PATCH 061/186] More graphite hardening work This adds verbose debugging which can be turned on to figure out what is going on. It also adds a broad exception handler in the worker thread to avoid dying. If you're running this such that stderr doesn't go to a log, it can be easy to miss the thread's death. I wrote all this to try to diagnose #1283, which seems to maybe have healed itself. But since I have it, I figure we might as well keep it in case we have trouble in the future. --- homeassistant/components/graphite.py | 30 +++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 662e3872eee..9ebf3a0d7da 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -49,15 +49,20 @@ class GraphiteFeeder(threading.Thread): self._prefix = prefix.rstrip('.') self._queue = queue.Queue() self._quit_object = object() + self._we_started = False hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.start_listen) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) hass.bus.listen(EVENT_STATE_CHANGED, self.event_listener) + _LOGGER.debug('Graphite feeding to %s:%i initialized', + self._host, self._port) def start_listen(self, event): """ Start event-processing thread. """ + _LOGGER.debug('Event processing thread started') + self._we_started = True self.start() def shutdown(self, event): @@ -67,11 +72,17 @@ class GraphiteFeeder(threading.Thread): clean up (and no penalty for killing in-process connections to graphite. """ + _LOGGER.debug('Event processing signaled exit') self._queue.put(self._quit_object) def event_listener(self, event): """ Queue an event for processing. """ - self._queue.put(event) + if self.is_alive() or not self._we_started: + _LOGGER.debug('Received event') + self._queue.put(event) + else: + _LOGGER.error('Graphite feeder thread has died, not ' + 'queuing event!') def _send_to_graphite(self, data): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -107,10 +118,23 @@ class GraphiteFeeder(threading.Thread): while True: event = self._queue.get() if event == self._quit_object: + _LOGGER.debug('Event processing thread stopped') self._queue.task_done() return elif (event.event_type == EVENT_STATE_CHANGED and event.data.get('new_state')): - self._report_attributes(event.data['entity_id'], - event.data['new_state']) + _LOGGER.debug('Processing STATE_CHANGED event for %s', + event.data['entity_id']) + try: + self._report_attributes(event.data['entity_id'], + event.data['new_state']) + # pylint: disable=broad-except + except Exception: + # Catch this so we can avoid the thread dying and + # make it visible. + _LOGGER.exception('Failed to process STATE_CHANGED event') + else: + _LOGGER.warning('Processing unexpected event type %s', + event.event_type) + self._queue.task_done() From 03423cc3a9f2fc4cb1a5ba00c0d2cc042fc3b649 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sat, 13 Feb 2016 18:32:13 +0100 Subject: [PATCH 062/186] Refactor mysensors light * Add a light entity class per V_LIGHT, V_DIMMER, V_RGB and V_RGBW. Make these classes inherit each other up to MySensorsLight class. * Map the entity classes to their S_TYPE in a dict. * Check if an entity class map or just an entity class have been passed to pf_callback_factory before using the entity_class variable in homeassistant/components/mysensors.py. * Add rgb_hex_to_list function in homeassistant/util/color.py. --- homeassistant/components/light/mysensors.py | 323 ++++++++++++++------ homeassistant/components/mysensors.py | 6 +- homeassistant/util/color.py | 17 +- 3 files changed, 244 insertions(+), 102 deletions(-) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index a1f01fabf6b..f4e2d1a497f 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -12,6 +12,9 @@ import logging from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) +from homeassistant.util.color import ( + rgb_hex_to_list) + from homeassistant.const import ( ATTR_BATTERY_LEVEL, STATE_ON, STATE_OFF) @@ -38,6 +41,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_LIGHT: [set_req.V_LIGHT], pres.S_DIMMER: [set_req.V_DIMMER], } + device_class_map = { + pres.S_LIGHT: MySensorsLightLight, + pres.S_DIMMER: MySensorsLightDimmer, + } if float(gateway.version) >= 1.5: # Add V_RGBW when rgb_white is implemented in the frontend map_sv_types.update({ @@ -45,10 +52,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): }) map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS) map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE) - + device_class_map.update({ + pres.S_RGB_LIGHT: MySensorsLightRGB, + }) devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( - map_sv_types, devices, add_devices, MySensorsLight)) + map_sv_types, devices, add_devices, device_class_map)) class MySensorsLight(Light): @@ -66,9 +75,6 @@ class MySensorsLight(Light): self.battery_level = 0 self._values = {} self._state = None - self._rgb = None - self._brightness = None - self._white = None @property def should_poll(self): @@ -80,21 +86,6 @@ class MySensorsLight(Light): """The name of this entity.""" return self._name - @property - def brightness(self): - """Brightness of this light between 0..255.""" - return self._brightness - - @property - def rgb_color(self): - """RGB color value [int, int, int].""" - return self._rgb - - @property - def rgb_white(self): # not implemented in the frontend yet - """White value in RGBW, value between 0..255.""" - return self._white - @property def device_state_attributes(self): """Return device specific state attributes.""" @@ -108,6 +99,11 @@ class MySensorsLight(Light): device_attr[self.gateway.const.SetReq(value_type).name] = value return device_attr + @property + def available(self): + """Return True if entity is available.""" + return self.value_type in self._values + @property def is_on(self): """True if device is on.""" @@ -115,112 +111,247 @@ class MySensorsLight(Light): def turn_on(self, **kwargs): """Turn the device on.""" - set_req = self.gateway.const.SetReq - rgb = self._rgb - brightness = self._brightness - white = self._white + if self.gateway.optimistic: + # optimistically assume that light has changed state + self.update_ha_state() - if set_req.V_LIGHT in self._values and not self._state: + def turn_off(self, **kwargs): + """Turn the device off.""" + value_type = kwargs.get('value_type') + value = kwargs.get('value') + if value_type is not None and value is not None: + self.gateway.set_child_value( + self.node_id, self.child_id, value_type, value) + else: + _LOGGER.warning( + '%s: value_type %s, value = %s, ' + 'None is not valid argument when setting child value' + '', self._name, value_type, value) + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._state = False + self.update_ha_state() + + def update(self): + """Update the controller with the latest value from a sensor.""" + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + self.battery_level = node.battery_level + for value_type, value in child.values.items(): + _LOGGER.debug( + '%s: value_type %s, value = %s', self._name, value_type, value) + self._values[value_type] = value + + +class MySensorsLightLight(MySensorsLight): + """Light child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + super().__init__(*args) + + def turn_on(self, **kwargs): + """Turn the device on.""" + set_req = self.gateway.const.SetReq + + if not self._state: self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 1) - if ATTR_BRIGHTNESS in kwargs and set_req.V_DIMMER in self._values and \ + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._state = True + super().turn_on(**kwargs) + + def turn_off(self, **kwargs): + """Turn the device off.""" + set_req = self.gateway.const.SetReq + value_type = kwargs.get('value_type') + value = kwargs.get('value') + value_type = ( + set_req.V_LIGHT + if set_req.V_LIGHT in self._values else value_type) + value = 0 if set_req.V_LIGHT in self._values else value + super().turn_off(value_type=value_type, value=value) + + def update(self): + """Update the controller with the latest value from a sensor.""" + super().update() + value_type = self.gateway.const.SetReq.V_LIGHT + if value_type in self._values: + self._values[value_type] = ( + STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF) + self._state = self._values[value_type] == STATE_ON + + +class MySensorsLightDimmer(MySensorsLightLight): + """Dimmer child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + self._brightness = None + super().__init__(*args) + + @property + def brightness(self): + """Brightness of this light between 0..255.""" + return self._brightness + + def turn_on(self, **kwargs): + """Turn the device on.""" + set_req = self.gateway.const.SetReq + brightness = self._brightness + + if ATTR_BRIGHTNESS in kwargs and \ kwargs[ATTR_BRIGHTNESS] != self._brightness: brightness = kwargs[ATTR_BRIGHTNESS] percent = round(100 * brightness / 255) self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_DIMMER, percent) + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._brightness = brightness + super().turn_on(**kwargs) + + def turn_off(self, **kwargs): + """Turn the device off.""" + set_req = self.gateway.const.SetReq + value_type = kwargs.get('value_type') + value = kwargs.get('value') + value_type = ( + set_req.V_DIMMER + if set_req.V_DIMMER in self._values else value_type) + value = 0 if set_req.V_DIMMER in self._values else value + super().turn_off(value_type=value_type, value=value) + + def update(self): + """Update the controller with the latest value from a sensor.""" + super().update() + set_req = self.gateway.const.SetReq + value_type = set_req.V_DIMMER + if value_type in self._values: + self._brightness = round(255 * int(self._values[value_type]) / 100) + if self._brightness == 0: + self._state = False + if set_req.V_LIGHT not in self._values: + self._state = self._brightness > 0 + + +class MySensorsLightRGB(MySensorsLightDimmer): + """RGB child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + self._rgb = None + super().__init__(*args) + + @property + def rgb_color(self): + """RGB color value [int, int, int].""" + return self._rgb + + def turn_on(self, **kwargs): + """Turn the device on.""" + rgb = self._rgb + if ATTR_RGB_COLOR in kwargs and kwargs[ATTR_RGB_COLOR] != self._rgb: + rgb = kwargs[ATTR_RGB_COLOR] + hex_color = '%02x%02x%02x' % tuple(rgb) + self.gateway.set_child_value( + self.node_id, self.child_id, self.value_type, hex_color) + + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._rgb = rgb + super().turn_on(**kwargs) + + def turn_off(self, **kwargs): + """Turn the device off.""" + value_type = None + value = None + if float(self.gateway.version) >= 1.5: + value_type = self.gateway.const.SetReq.V_RGB + value = '000000' + super().turn_off(value_type=value_type, value=value) + + def update(self): + """Update the controller with the latest value from a sensor.""" + super().update() + set_req = self.gateway.const.SetReq + if float(self.gateway.version) >= 1.5 and \ + set_req.V_RGB in self._values: + value = self._values[set_req.V_RGB] + self._rgb = rgb_hex_to_list(value) + if set_req.V_LIGHT not in self._values and \ + set_req.V_DIMMER not in self._values: + self._state = max(self._rgb) > 0 + + +class MySensorsLightRGBW(MySensorsLightDimmer): + """RGBW child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + self._rgb = None + self._white = None + super().__init__(*args) + + @property + def rgb_color(self): + """RGB color value [int, int, int].""" + return self._rgb + + @property + def rgb_white(self): # not implemented in the frontend yet + """White value in RGBW, value between 0..255.""" + return self._white + + def turn_on(self, **kwargs): + """Turn the device on.""" + rgb = self._rgb + white = self._white + if float(self.gateway.version) >= 1.5: if ATTR_RGB_WHITE in kwargs and \ - self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \ kwargs[ATTR_RGB_WHITE] != self._white: white = kwargs[ATTR_RGB_WHITE] if ATTR_RGB_COLOR in kwargs and \ - self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \ kwargs[ATTR_RGB_COLOR] != self._rgb: rgb = kwargs[ATTR_RGB_COLOR] - if set_req.V_RGBW == self.value_type: - hex_template = '%02x%02x%02x%02x' - color_list = rgb.append(white) - if set_req.V_RGB == self.value_type: - hex_template = '%02x%02x%02x' - color_list = rgb - hex_color = hex_template % tuple(color_list) + if white is not None: + rgb.append(white) + hex_color = '%02x%02x%02x%02x' % tuple(rgb) self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, hex_color) if self.gateway.optimistic: # optimistically assume that light has changed state - self._state = True self._rgb = rgb - self._brightness = brightness self._white = white - self.update_ha_state() + super().turn_on(**kwargs) def turn_off(self, **kwargs): """Turn the device off.""" - set_req = self.gateway.const.SetReq - v_type = set_req.V_LIGHT - value = 0 - if set_req.V_LIGHT in self._values: - self._values[set_req.V_LIGHT] = STATE_OFF - elif set_req.V_DIMMER in self._values: - v_type = set_req.V_DIMMER - elif float(self.gateway.version) >= 1.5: - if set_req.V_RGB in self._values: - v_type = set_req.V_RGB - value = '000000' - elif set_req.V_RGBW in self._values: - v_type = set_req.V_RGBW - value = '00000000' - self.gateway.set_child_value( - self.node_id, self.child_id, v_type, value) - - if self.gateway.optimistic: - # optimistically assume that light has changed state - self._state = False - self.update_ha_state() - - @property - def available(self): - """Return True if entity is available.""" - return self.value_type in self._values + value_type = None + value = None + if float(self.gateway.version) >= 1.5: + value_type = self.gateway.const.SetReq.V_RGBW + value = '00000000' + super().turn_off(value_type=value_type, value=value) def update(self): """Update the controller with the latest value from a sensor.""" - node = self.gateway.sensors[self.node_id] - child = node.children[self.child_id] + super().update() set_req = self.gateway.const.SetReq - self.battery_level = node.battery_level - for value_type, value in child.values.items(): - _LOGGER.debug( - "%s: value_type %s, value = %s", self._name, value_type, value) - if value_type == set_req.V_LIGHT: - self._values[value_type] = ( - STATE_ON if int(value) == 1 else STATE_OFF) - self._state = self._values[value_type] == STATE_ON - else: - self._values[value_type] = value - if value_type == set_req.V_DIMMER: - self._brightness = round( - 255 * int(self._values[value_type]) / 100) - if self._brightness == 0: - self._state = False - if set_req.V_LIGHT not in self._values: - self._state = self._brightness > 0 - if float(self.gateway.version) >= 1.5 and \ - value_type in (set_req.V_RGB, set_req.V_RGBW): - # convert hex color string to rgb(w) integer list - color_list = [int(value[i:i + len(value) // 3], 16) - for i in range(0, - len(value), - len(value) // 3)] - if len(color_list) > 3: - self._white = color_list.pop() - self._rgb = color_list - if set_req.V_LIGHT not in self._values or \ - set_req.V_DIMMER not in self._values: - self._state = max(color_list) > 0 + if float(self.gateway.version) >= 1.5 and \ + set_req.V_RGBW in self._values: + value = self._values[set_req.V_RGBW] + color_list = rgb_hex_to_list(value) + if set_req.V_LIGHT not in self._values and \ + set_req.V_DIMMER not in self._values: + self._state = max(color_list) > 0 + if len(color_list) > 3: + self._white = color_list.pop() + self._rgb = color_list diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 4855c838619..30775c4daae 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -146,7 +146,11 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class): continue name = '{} {}.{}'.format( gateway.sensors[node_id].sketch_name, node_id, child.id) - devices[key] = entity_class( + if isinstance(entity_class, dict): + device_class = entity_class[child.type] + else: + device_class = entity_class + devices[key] = device_class( gateway, node_id, child.id, name, value_type) _LOGGER.info('Adding new devices: %s', devices[key]) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index e5b11f2afeb..db4475b7087 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,5 +1,6 @@ """ -homeassistant.util.color +homeassistant.util.color. + ~~~~~~~~~~~~~~~~~~~~~~~~ Color util methods. """ @@ -9,7 +10,7 @@ Color util methods. # License: Code is given as is. Use at your own risk and discretion. # pylint: disable=invalid-name def color_RGB_to_xy(R, G, B): - """ Convert from RGB color to XY color. """ + """Convert from RGB color to XY color.""" if R + G + B == 0: return 0, 0 @@ -50,9 +51,7 @@ def color_RGB_to_xy(R, G, B): # Copyright (c) 2014 Benjamin Knight / MIT License. # pylint: disable=bad-builtin def color_xy_brightness_to_RGB(vX, vY, brightness): - ''' - Convert from XYZ to RGB. - ''' + """Convert from XYZ to RGB.""" brightness /= 255. if brightness == 0: return (0, 0, 0) @@ -88,3 +87,11 @@ def color_xy_brightness_to_RGB(vX, vY, brightness): r, g, b = map(lambda x: int(x * 255), [r, g, b]) return (r, g, b) + + +def rgb_hex_to_list(hex_string): + """Return an RGB color value list from a hex color string.""" + return [int(hex_string[i:i + len(hex_string) // 3], 16) + for i in range(0, + len(hex_string), + len(hex_string) // 3)] From 6e8c79d5310396987cf39d65ea40279abb799754 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Mon, 15 Feb 2016 03:47:51 +0100 Subject: [PATCH 063/186] Change refactor structure * Make a flatter one level inheritance, with MySensorsLight as parent with four children, one per light type. * Break out helper methods. One per plain light, dimmer and RGB/RGBW children and per update, turn_on and turn_off, nine in total. Put these in the parent. * Call the helper methods as needed from the child methods update, turn_on and turn_off. * Change name of MySensorsLightLight to MySensorsLightPlain. * Fix module docstrings according to pep257. * Change name of color util method from rgb_hex_to_list to rgb_hex_to_rgb_list. * Add unit tests for rgb_hex_to_rgb_list. --- homeassistant/components/light/mysensors.py | 391 ++++++++++--------- homeassistant/components/sensor/mysensors.py | 3 - homeassistant/components/switch/mysensors.py | 5 +- homeassistant/util/color.py | 9 +- tests/util/test_color.py | 33 +- 5 files changed, 237 insertions(+), 204 deletions(-) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index f4e2d1a497f..5cc8a4648d5 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -1,11 +1,8 @@ """ -homeassistant.components.light.mysensors. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for MySensors lights. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.mysensors.html +https://home-assistant.io/components/light.mysensors/ """ import logging @@ -13,7 +10,7 @@ from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) from homeassistant.util.color import ( - rgb_hex_to_list) + rgb_hex_to_rgb_list) from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -23,6 +20,8 @@ import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) ATTR_RGB_WHITE = 'rgb_white' +ATTR_VALUE = 'value' +ATTR_VALUE_TYPE = 'value_type' def setup_platform(hass, config, add_devices, discovery_info=None): @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_DIMMER: [set_req.V_DIMMER], } device_class_map = { - pres.S_LIGHT: MySensorsLightLight, + pres.S_LIGHT: MySensorsLightPlain, pres.S_DIMMER: MySensorsLightDimmer, } if float(gateway.version) >= 1.5: @@ -75,6 +74,9 @@ class MySensorsLight(Light): self.battery_level = 0 self._values = {} self._state = None + self._brightness = None + self._rgb = None + self._white = None @property def should_poll(self): @@ -86,6 +88,21 @@ class MySensorsLight(Light): """The name of this entity.""" return self._name + @property + def brightness(self): + """Brightness of this light between 0..255.""" + return self._brightness + + @property + def rgb_color(self): + """RGB color value [int, int, int].""" + return self._rgb + + @property + def rgb_white(self): # not implemented in the frontend yet + """White value in RGBW, value between 0..255.""" + return self._white + @property def device_state_attributes(self): """Return device specific state attributes.""" @@ -109,49 +126,8 @@ class MySensorsLight(Light): """True if device is on.""" return self._state - def turn_on(self, **kwargs): - """Turn the device on.""" - if self.gateway.optimistic: - # optimistically assume that light has changed state - self.update_ha_state() - - def turn_off(self, **kwargs): - """Turn the device off.""" - value_type = kwargs.get('value_type') - value = kwargs.get('value') - if value_type is not None and value is not None: - self.gateway.set_child_value( - self.node_id, self.child_id, value_type, value) - else: - _LOGGER.warning( - '%s: value_type %s, value = %s, ' - 'None is not valid argument when setting child value' - '', self._name, value_type, value) - if self.gateway.optimistic: - # optimistically assume that light has changed state - self._state = False - self.update_ha_state() - - def update(self): - """Update the controller with the latest value from a sensor.""" - node = self.gateway.sensors[self.node_id] - child = node.children[self.child_id] - self.battery_level = node.battery_level - for value_type, value in child.values.items(): - _LOGGER.debug( - '%s: value_type %s, value = %s', self._name, value_type, value) - self._values[value_type] = value - - -class MySensorsLightLight(MySensorsLight): - """Light child class to MySensorsLight.""" - - def __init__(self, *args): - """Setup instance attributes.""" - super().__init__(*args) - - def turn_on(self, **kwargs): - """Turn the device on.""" + def _turn_on_light(self): + """Turn on light child device.""" set_req = self.gateway.const.SetReq if not self._state: @@ -161,44 +137,9 @@ class MySensorsLightLight(MySensorsLight): if self.gateway.optimistic: # optimistically assume that light has changed state self._state = True - super().turn_on(**kwargs) - def turn_off(self, **kwargs): - """Turn the device off.""" - set_req = self.gateway.const.SetReq - value_type = kwargs.get('value_type') - value = kwargs.get('value') - value_type = ( - set_req.V_LIGHT - if set_req.V_LIGHT in self._values else value_type) - value = 0 if set_req.V_LIGHT in self._values else value - super().turn_off(value_type=value_type, value=value) - - def update(self): - """Update the controller with the latest value from a sensor.""" - super().update() - value_type = self.gateway.const.SetReq.V_LIGHT - if value_type in self._values: - self._values[value_type] = ( - STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF) - self._state = self._values[value_type] == STATE_ON - - -class MySensorsLightDimmer(MySensorsLightLight): - """Dimmer child class to MySensorsLight.""" - - def __init__(self, *args): - """Setup instance attributes.""" - self._brightness = None - super().__init__(*args) - - @property - def brightness(self): - """Brightness of this light between 0..255.""" - return self._brightness - - def turn_on(self, **kwargs): - """Turn the device on.""" + def _turn_on_dimmer(self, **kwargs): + """Turn on dimmer child device.""" set_req = self.gateway.const.SetReq brightness = self._brightness @@ -212,22 +153,89 @@ class MySensorsLightDimmer(MySensorsLightLight): if self.gateway.optimistic: # optimistically assume that light has changed state self._brightness = brightness - super().turn_on(**kwargs) - def turn_off(self, **kwargs): - """Turn the device off.""" + def _turn_on_rgb_and_w(self, hex_template, **kwargs): + """Turn on RGB or RGBW child device.""" + rgb = self._rgb + white = self._white + + if ATTR_RGB_WHITE in kwargs and \ + kwargs[ATTR_RGB_WHITE] != self._white: + white = kwargs[ATTR_RGB_WHITE] + + if ATTR_RGB_COLOR in kwargs and \ + kwargs[ATTR_RGB_COLOR] != self._rgb: + rgb = kwargs[ATTR_RGB_COLOR] + if white is not None and hex_template == '%02x%02x%02x%02x': + rgb.append(white) + hex_color = hex_template % tuple(rgb) + self.gateway.set_child_value( + self.node_id, self.child_id, self.value_type, hex_color) + + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._rgb = rgb + self._white = white + + def _turn_on(self): + """Turn the device on.""" + if self.gateway.optimistic: + # optimistically assume that light has changed state + self.update_ha_state() + + def _turn_off_light(self, value_type=None, value=None): + """Turn off light child device.""" + set_req = self.gateway.const.SetReq + value_type = ( + set_req.V_LIGHT + if set_req.V_LIGHT in self._values else value_type) + value = 0 if set_req.V_LIGHT in self._values else value + return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value} + + def _turn_off_dimmer(self, value_type=None, value=None): + """Turn off dimmer child device.""" set_req = self.gateway.const.SetReq - value_type = kwargs.get('value_type') - value = kwargs.get('value') value_type = ( set_req.V_DIMMER if set_req.V_DIMMER in self._values else value_type) value = 0 if set_req.V_DIMMER in self._values else value - super().turn_off(value_type=value_type, value=value) + return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value} - def update(self): - """Update the controller with the latest value from a sensor.""" - super().update() + def _turn_off_rgb_or_w(self, value_type=None, value=None): + """Turn off RGB or RGBW child device.""" + if float(self.gateway.version) >= 1.5: + set_req = self.gateway.const.SetReq + if self.value_type == set_req.V_RGB: + value = '000000' + elif self.value_type == set_req.V_RGBW: + value = '00000000' + return {ATTR_VALUE_TYPE: self.value_type, ATTR_VALUE: value} + + def _turn_off(self, value_type=None, value=None): + """Turn the device off.""" + if value_type is None or value is None: + _LOGGER.warning( + '%s: value_type %s, value = %s, ' + 'None is not valid argument when setting child value' + '', self._name, value_type, value) + return + self.gateway.set_child_value( + self.node_id, self.child_id, value_type, value) + if self.gateway.optimistic: + # optimistically assume that light has changed state + self._state = False + self.update_ha_state() + + def _update_light(self): + """Update the controller with values from light child.""" + value_type = self.gateway.const.SetReq.V_LIGHT + if value_type in self._values: + self._values[value_type] = ( + STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF) + self._state = self._values[value_type] == STATE_ON + + def _update_dimmer(self): + """Update the controller with values from dimmer child.""" set_req = self.gateway.const.SetReq value_type = set_req.V_DIMMER if value_type in self._values: @@ -237,121 +245,140 @@ class MySensorsLightDimmer(MySensorsLightLight): if set_req.V_LIGHT not in self._values: self._state = self._brightness > 0 + def _update_rgb_or_w(self): + """Update the controller with values from RGB or RGBW child.""" + set_req = self.gateway.const.SetReq + value = self._values[self.value_type] + color_list = rgb_hex_to_rgb_list(value) + if set_req.V_LIGHT not in self._values and \ + set_req.V_DIMMER not in self._values: + self._state = max(color_list) > 0 + if len(color_list) > 3: + self._white = color_list.pop() + self._rgb = color_list -class MySensorsLightRGB(MySensorsLightDimmer): + def _update(self): + """Update the controller with the latest value from a sensor.""" + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + self.battery_level = node.battery_level + for value_type, value in child.values.items(): + _LOGGER.debug( + '%s: value_type %s, value = %s', self._name, value_type, value) + self._values[value_type] = value + + +class MySensorsLightPlain(MySensorsLight): + """Light child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + super().__init__(*args) + + def turn_on(self, **kwargs): + """Turn the device on.""" + super()._turn_on_light() + super()._turn_on() + + def turn_off(self, **kwargs): + """Turn the device off.""" + ret = super()._turn_off_light() + super()._turn_off(value_type=ret[ + ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + + def update(self): + """Update the controller with the latest value from a sensor.""" + super()._update() + super()._update_light() + + +class MySensorsLightDimmer(MySensorsLight): + """Dimmer child class to MySensorsLight.""" + + def __init__(self, *args): + """Setup instance attributes.""" + super().__init__(*args) + + def turn_on(self, **kwargs): + """Turn the device on.""" + super()._turn_on_light() + super()._turn_on_dimmer(**kwargs) + super()._turn_on() + + def turn_off(self, **kwargs): + """Turn the device off.""" + ret = super()._turn_off_dimmer() + ret = super()._turn_off_light( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + super()._turn_off( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + + def update(self): + """Update the controller with the latest value from a sensor.""" + super()._update() + super()._update_light() + super()._update_dimmer() + + +class MySensorsLightRGB(MySensorsLight): """RGB child class to MySensorsLight.""" def __init__(self, *args): """Setup instance attributes.""" - self._rgb = None super().__init__(*args) - @property - def rgb_color(self): - """RGB color value [int, int, int].""" - return self._rgb - def turn_on(self, **kwargs): """Turn the device on.""" - rgb = self._rgb - if ATTR_RGB_COLOR in kwargs and kwargs[ATTR_RGB_COLOR] != self._rgb: - rgb = kwargs[ATTR_RGB_COLOR] - hex_color = '%02x%02x%02x' % tuple(rgb) - self.gateway.set_child_value( - self.node_id, self.child_id, self.value_type, hex_color) - - if self.gateway.optimistic: - # optimistically assume that light has changed state - self._rgb = rgb - super().turn_on(**kwargs) + super()._turn_on_light() + super()._turn_on_dimmer(**kwargs) + super()._turn_on_rgb_and_w('%02x%02x%02x', **kwargs) + super()._turn_on() def turn_off(self, **kwargs): """Turn the device off.""" - value_type = None - value = None - if float(self.gateway.version) >= 1.5: - value_type = self.gateway.const.SetReq.V_RGB - value = '000000' - super().turn_off(value_type=value_type, value=value) + ret = super()._turn_off_rgb_or_w() + ret = super()._turn_off_dimmer( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + ret = super()._turn_off_light( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + super()._turn_off( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super().update() - set_req = self.gateway.const.SetReq - if float(self.gateway.version) >= 1.5 and \ - set_req.V_RGB in self._values: - value = self._values[set_req.V_RGB] - self._rgb = rgb_hex_to_list(value) - if set_req.V_LIGHT not in self._values and \ - set_req.V_DIMMER not in self._values: - self._state = max(self._rgb) > 0 + super()._update() + super()._update_light() + super()._update_dimmer() + super()._update_rgb_or_w() -class MySensorsLightRGBW(MySensorsLightDimmer): +class MySensorsLightRGBW(MySensorsLight): """RGBW child class to MySensorsLight.""" def __init__(self, *args): """Setup instance attributes.""" - self._rgb = None - self._white = None super().__init__(*args) - @property - def rgb_color(self): - """RGB color value [int, int, int].""" - return self._rgb - - @property - def rgb_white(self): # not implemented in the frontend yet - """White value in RGBW, value between 0..255.""" - return self._white - def turn_on(self, **kwargs): """Turn the device on.""" - rgb = self._rgb - white = self._white - - if float(self.gateway.version) >= 1.5: - - if ATTR_RGB_WHITE in kwargs and \ - kwargs[ATTR_RGB_WHITE] != self._white: - white = kwargs[ATTR_RGB_WHITE] - - if ATTR_RGB_COLOR in kwargs and \ - kwargs[ATTR_RGB_COLOR] != self._rgb: - rgb = kwargs[ATTR_RGB_COLOR] - if white is not None: - rgb.append(white) - hex_color = '%02x%02x%02x%02x' % tuple(rgb) - self.gateway.set_child_value( - self.node_id, self.child_id, self.value_type, hex_color) - - if self.gateway.optimistic: - # optimistically assume that light has changed state - self._rgb = rgb - self._white = white - super().turn_on(**kwargs) + super()._turn_on_light() + super()._turn_on_dimmer(**kwargs) + super()._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs) + super()._turn_on() def turn_off(self, **kwargs): """Turn the device off.""" - value_type = None - value = None - if float(self.gateway.version) >= 1.5: - value_type = self.gateway.const.SetReq.V_RGBW - value = '00000000' - super().turn_off(value_type=value_type, value=value) + ret = super()._turn_off_rgb_or_w() + ret = super()._turn_off_dimmer( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + ret = super()._turn_off_light( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) + super()._turn_off( + value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super().update() - set_req = self.gateway.const.SetReq - if float(self.gateway.version) >= 1.5 and \ - set_req.V_RGBW in self._values: - value = self._values[set_req.V_RGBW] - color_list = rgb_hex_to_list(value) - if set_req.V_LIGHT not in self._values and \ - set_req.V_DIMMER not in self._values: - self._state = max(color_list) > 0 - if len(color_list) > 3: - self._white = color_list.pop() - self._rgb = color_list + super()._update() + super()._update_light() + super()._update_dimmer() + super()._update_rgb_or_w() diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index fcf5248be67..ca4df33462b 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -1,7 +1,4 @@ """ -homeassistant.components.sensor.mysensors. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for MySensors sensors. For more details about this platform, please refer to the documentation at diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index cf24fb3c401..19c8f7112f6 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -1,11 +1,8 @@ """ -homeassistant.components.switch.mysensors. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for MySensors switches. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.mysensors.html +https://home-assistant.io/components/switch.mysensors/ """ import logging diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index db4475b7087..1806d9058dc 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,9 +1,4 @@ -""" -homeassistant.util.color. - -~~~~~~~~~~~~~~~~~~~~~~~~ -Color util methods. -""" +"""Color util methods.""" # Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py @@ -89,7 +84,7 @@ def color_xy_brightness_to_RGB(vX, vY, brightness): return (r, g, b) -def rgb_hex_to_list(hex_string): +def rgb_hex_to_rgb_list(hex_string): """Return an RGB color value list from a hex color string.""" return [int(hex_string[i:i + len(hex_string) // 3], 16) for i in range(0, diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 79bbcc01495..f74a074bbe6 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -1,17 +1,14 @@ -""" -tests.util.test_color -~~~~~~~~~~~~~~~~~~~~~ - -Tests Home Assistant color util methods. -""" +"""Tests Home Assistant color util methods.""" import unittest import homeassistant.util.color as color_util class TestColorUtil(unittest.TestCase): + """Test color util methods.""" + # pylint: disable=invalid-name def test_color_RGB_to_xy(self): - """ Test color_RGB_to_xy. """ + """Test color_RGB_to_xy.""" self.assertEqual((0, 0), color_util.color_RGB_to_xy(0, 0, 0)) self.assertEqual((0.3127159072215825, 0.3290014805066623), color_util.color_RGB_to_xy(255, 255, 255)) @@ -25,7 +22,7 @@ class TestColorUtil(unittest.TestCase): color_util.color_RGB_to_xy(255, 0, 0)) def test_color_xy_brightness_to_RGB(self): - """ Test color_RGB_to_xy. """ + """Test color_RGB_to_xy.""" self.assertEqual((0, 0, 0), color_util.color_xy_brightness_to_RGB(1, 1, 0)) @@ -40,3 +37,23 @@ class TestColorUtil(unittest.TestCase): self.assertEqual((0, 83, 255), color_util.color_xy_brightness_to_RGB(0, 0, 255)) + + def test_rgb_hex_to_rgb_list(self): + """Test rgb_hex_to_rgb_list.""" + self.assertEqual([255, 255, 255], + color_util.rgb_hex_to_rgb_list('ffffff')) + + self.assertEqual([0, 0, 0], + color_util.rgb_hex_to_rgb_list('000000')) + + self.assertEqual([255, 255, 255, 255], + color_util.rgb_hex_to_rgb_list('ffffffff')) + + self.assertEqual([0, 0, 0, 0], + color_util.rgb_hex_to_rgb_list('00000000')) + + self.assertEqual([51, 153, 255], + color_util.rgb_hex_to_rgb_list('3399ff')) + + self.assertEqual([51, 153, 255, 0], + color_util.rgb_hex_to_rgb_list('3399ff00')) From 9ac53b502ffec1b1c4befb1b63fc0d254e96ee42 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 18 Feb 2016 01:28:03 +0100 Subject: [PATCH 064/186] Clean up and fix * Add check if V_LIGHT is in values before sending message in _turn_on_light. * Replace super calls with self. * Remove not needed init method in child classes. * Remove turn_on method in parent class and add update_ha_state to _turn_on_light, _turn_on_dimmer and _turn_on_rgb_or_w. --- homeassistant/components/light/mysensors.py | 103 ++++++++------------ 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 5cc8a4648d5..b50446e2f38 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -130,13 +130,14 @@ class MySensorsLight(Light): """Turn on light child device.""" set_req = self.gateway.const.SetReq - if not self._state: + if not self._state and set_req.V_LIGHT in self._values: self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 1) if self.gateway.optimistic: # optimistically assume that light has changed state self._state = True + self.update_ha_state() def _turn_on_dimmer(self, **kwargs): """Turn on dimmer child device.""" @@ -153,6 +154,7 @@ class MySensorsLight(Light): if self.gateway.optimistic: # optimistically assume that light has changed state self._brightness = brightness + self.update_ha_state() def _turn_on_rgb_and_w(self, hex_template, **kwargs): """Turn on RGB or RGBW child device.""" @@ -176,11 +178,6 @@ class MySensorsLight(Light): # optimistically assume that light has changed state self._rgb = rgb self._white = white - - def _turn_on(self): - """Turn the device on.""" - if self.gateway.optimistic: - # optimistically assume that light has changed state self.update_ha_state() def _turn_off_light(self, value_type=None, value=None): @@ -211,7 +208,7 @@ class MySensorsLight(Light): value = '00000000' return {ATTR_VALUE_TYPE: self.value_type, ATTR_VALUE: value} - def _turn_off(self, value_type=None, value=None): + def _turn_off_main(self, value_type=None, value=None): """Turn the device off.""" if value_type is None or value is None: _LOGGER.warning( @@ -257,7 +254,7 @@ class MySensorsLight(Light): self._white = color_list.pop() self._rgb = color_list - def _update(self): + def _update_main(self): """Update the controller with the latest value from a sensor.""" node = self.gateway.sensors[self.node_id] child = node.children[self.child_id] @@ -271,114 +268,94 @@ class MySensorsLight(Light): class MySensorsLightPlain(MySensorsLight): """Light child class to MySensorsLight.""" - def __init__(self, *args): - """Setup instance attributes.""" - super().__init__(*args) - def turn_on(self, **kwargs): """Turn the device on.""" - super()._turn_on_light() - super()._turn_on() + self._turn_on_light() def turn_off(self, **kwargs): """Turn the device off.""" - ret = super()._turn_off_light() - super()._turn_off(value_type=ret[ + ret = self._turn_off_light() + self._turn_off_main(value_type=ret[ ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super()._update() - super()._update_light() + self._update_main() + self._update_light() class MySensorsLightDimmer(MySensorsLight): """Dimmer child class to MySensorsLight.""" - def __init__(self, *args): - """Setup instance attributes.""" - super().__init__(*args) - def turn_on(self, **kwargs): """Turn the device on.""" - super()._turn_on_light() - super()._turn_on_dimmer(**kwargs) - super()._turn_on() + self._turn_on_light() + self._turn_on_dimmer(**kwargs) def turn_off(self, **kwargs): """Turn the device off.""" - ret = super()._turn_off_dimmer() - ret = super()._turn_off_light( + ret = self._turn_off_dimmer() + ret = self._turn_off_light( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - super()._turn_off( + self._turn_off_main( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super()._update() - super()._update_light() - super()._update_dimmer() + self._update_main() + self._update_light() + self._update_dimmer() class MySensorsLightRGB(MySensorsLight): """RGB child class to MySensorsLight.""" - def __init__(self, *args): - """Setup instance attributes.""" - super().__init__(*args) - def turn_on(self, **kwargs): """Turn the device on.""" - super()._turn_on_light() - super()._turn_on_dimmer(**kwargs) - super()._turn_on_rgb_and_w('%02x%02x%02x', **kwargs) - super()._turn_on() + self._turn_on_light() + self._turn_on_dimmer(**kwargs) + self._turn_on_rgb_and_w('%02x%02x%02x', **kwargs) def turn_off(self, **kwargs): """Turn the device off.""" - ret = super()._turn_off_rgb_or_w() - ret = super()._turn_off_dimmer( + ret = self._turn_off_rgb_or_w() + ret = self._turn_off_dimmer( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - ret = super()._turn_off_light( + ret = self._turn_off_light( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - super()._turn_off( + self._turn_off_main( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super()._update() - super()._update_light() - super()._update_dimmer() - super()._update_rgb_or_w() + self._update_main() + self._update_light() + self._update_dimmer() + self._update_rgb_or_w() class MySensorsLightRGBW(MySensorsLight): """RGBW child class to MySensorsLight.""" - def __init__(self, *args): - """Setup instance attributes.""" - super().__init__(*args) - def turn_on(self, **kwargs): """Turn the device on.""" - super()._turn_on_light() - super()._turn_on_dimmer(**kwargs) - super()._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs) - super()._turn_on() + self._turn_on_light() + self._turn_on_dimmer(**kwargs) + self._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs) def turn_off(self, **kwargs): """Turn the device off.""" - ret = super()._turn_off_rgb_or_w() - ret = super()._turn_off_dimmer( + ret = self._turn_off_rgb_or_w() + ret = self._turn_off_dimmer( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - ret = super()._turn_off_light( + ret = self._turn_off_light( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - super()._turn_off( + self._turn_off_main( value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) def update(self): """Update the controller with the latest value from a sensor.""" - super()._update() - super()._update_light() - super()._update_dimmer() - super()._update_rgb_or_w() + self._update_main() + self._update_light() + self._update_dimmer() + self._update_rgb_or_w() From 61b0d02a8873a39b276a320e49bd9e944af39cb7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 17 Feb 2016 23:16:24 -0800 Subject: [PATCH 065/186] Fix coveralls --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1501b397770..01448f315c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ cache: - $HOME/.cache/pip install: pip install -U tox language: python -script: tox -after_success: +script: + - tox + - pip install coveralls - coveralls From 9f7ce23e80a749d4ff8a34e2a9d9326632ba1f79 Mon Sep 17 00:00:00 2001 From: pavoni Date: Tue, 16 Feb 2016 12:54:36 +0000 Subject: [PATCH 066/186] Fix suspect race condition in leave region. Add safely check for double beacon entry. Remove battery for beacons. Disable lint warning. --- .../components/device_tracker/owntracks.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 2b8e612030b..3f430d798a4 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -62,7 +62,7 @@ def setup_scanner(hass, config, see): see_beacons(dev_id, kwargs) def owntracks_event_update(topic, payload, qos): - # pylint: disable=too-many-branches + # pylint: disable=too-many-branches, too-many-statements """ MQTT event (geofences) received. """ # Docs on available data: @@ -92,7 +92,10 @@ def setup_scanner(hass, config, see): if zone is None: if data['t'] == 'b': # Not a HA zone, and a beacon so assume mobile - MOBILE_BEACONS_ACTIVE[dev_id].append(location) + beacons = MOBILE_BEACONS_ACTIVE[dev_id] + if location not in beacons: + beacons.append(location) + _LOGGER.info("Added beacon %s", location) else: # Normal region if not zone.attributes.get('passive'): @@ -108,28 +111,30 @@ def setup_scanner(hass, config, see): see_beacons(dev_id, kwargs) elif data['event'] == 'leave': - regions = REGIONS_ENTERED[dev_id] - if location in regions: - regions.remove(location) - new_region = regions[-1] if regions else None + with LOCK: + regions = REGIONS_ENTERED[dev_id] + if location in regions: + regions.remove(location) + new_region = regions[-1] if regions else None - if new_region: - # Exit to previous region - zone = hass.states.get("zone.{}".format(new_region)) - if not zone.attributes.get('passive'): - kwargs['location_name'] = new_region - _set_gps_from_zone(kwargs, zone) - _LOGGER.info("Exit from to %s", new_region) + if new_region: + # Exit to previous region + zone = hass.states.get("zone.{}".format(new_region)) + if not zone.attributes.get('passive'): + kwargs['location_name'] = new_region + _set_gps_from_zone(kwargs, zone) + _LOGGER.info("Exit to %s", new_region) - else: - _LOGGER.info("Exit to GPS") + else: + _LOGGER.info("Exit to GPS") - see(**kwargs) - see_beacons(dev_id, kwargs) + see(**kwargs) + see_beacons(dev_id, kwargs) - beacons = MOBILE_BEACONS_ACTIVE[dev_id] - if location in beacons: - beacons.remove(location) + beacons = MOBILE_BEACONS_ACTIVE[dev_id] + if location in beacons: + beacons.remove(location) + _LOGGER.info("Remove beacon %s", location) else: _LOGGER.error( @@ -141,6 +146,8 @@ def setup_scanner(hass, config, see): """ Set active beacons to the current location """ kwargs = kwargs_param.copy() + # the battery state applies to the tracking device, not the beacon + kwargs.pop('battery', None) for beacon in MOBILE_BEACONS_ACTIVE[dev_id]: kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon) kwargs['host_name'] = beacon From c1d39a2fce4baf344e69cd7d32670274d3eaa286 Mon Sep 17 00:00:00 2001 From: Flyte Date: Thu, 18 Feb 2016 16:57:32 +0000 Subject: [PATCH 067/186] Remove unnecessary top-level TCP component. Fix order of inheritance on TCP BinarySensor. --- homeassistant/components/binary_sensor/tcp.py | 5 ++-- homeassistant/components/sensor/tcp.py | 21 +++++++++++------ homeassistant/components/tcp.py | 23 ------------------- 3 files changed, 16 insertions(+), 33 deletions(-) delete mode 100644 homeassistant/components/tcp.py diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index e6d01b893df..1f8bf2387c9 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -6,8 +6,7 @@ Provides a binary_sensor which gets its values from a TCP socket. import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.tcp import DOMAIN, CONF_VALUE_ON -from homeassistant.components.sensor.tcp import Sensor +from homeassistant.components.sensor.tcp import Sensor, DOMAIN, CONF_VALUE_ON DEPENDENCIES = [DOMAIN] @@ -22,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities((BinarySensor(hass, config),)) -class BinarySensor(Sensor, BinarySensorDevice): +class BinarySensor(BinarySensorDevice, Sensor): """ A binary sensor which is on when its state == CONF_VALUE_ON. """ required = (CONF_VALUE_ON,) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 29d6c260fdd..ad27ddef0bd 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -11,14 +11,22 @@ from homeassistant.const import CONF_NAME, CONF_HOST from homeassistant.util import template from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity -from homeassistant.components.tcp import ( - DOMAIN, CONF_PORT, CONF_TIMEOUT, CONF_PAYLOAD, CONF_UNIT, CONF_VALUE_REGEX, - CONF_VALUE_TEMPLATE, CONF_VALUE_ON, CONF_BUFFER_SIZE, DEFAULT_TIMEOUT, - DEFAULT_BUFFER_SIZE -) -DEPENDENCIES = [DOMAIN] +# DEPENDENCIES = [DOMAIN] + +DOMAIN = "tcp" + +CONF_PORT = "port" +CONF_TIMEOUT = "timeout" +CONF_PAYLOAD = "payload" +CONF_UNIT = "unit" +CONF_VALUE_TEMPLATE = "value_template" +CONF_VALUE_ON = "value_on" +CONF_BUFFER_SIZE = "buffer_size" + +DEFAULT_TIMEOUT = 10 +DEFAULT_BUFFER_SIZE = 1024 _LOGGER = logging.getLogger(__name__) @@ -44,7 +52,6 @@ class Sensor(Entity): CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), CONF_PAYLOAD: config[CONF_PAYLOAD], CONF_UNIT: config.get(CONF_UNIT), - CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE), CONF_VALUE_ON: config.get(CONF_VALUE_ON), CONF_BUFFER_SIZE: config.get( diff --git a/homeassistant/components/tcp.py b/homeassistant/components/tcp.py deleted file mode 100644 index c82bf935910..00000000000 --- a/homeassistant/components/tcp.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -homeassistant.components.tcp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A generic TCP socket component. -""" -DOMAIN = "tcp" - -CONF_PORT = "port" -CONF_TIMEOUT = "timeout" -CONF_PAYLOAD = "payload" -CONF_UNIT = "unit" -CONF_VALUE_REGEX = "value_regex" -CONF_VALUE_TEMPLATE = "value_template" -CONF_VALUE_ON = "value_on" -CONF_BUFFER_SIZE = "buffer_size" - -DEFAULT_TIMEOUT = 10 -DEFAULT_BUFFER_SIZE = 1024 - - -def setup(hass, config): - """ Nothing to do! """ - return True From 6578928e3c22df83854dbd8d2c747d4815935530 Mon Sep 17 00:00:00 2001 From: Alexander Fortin Date: Thu, 18 Feb 2016 18:57:09 +0100 Subject: [PATCH 068/186] Fix media_player/sonos configuration mismatch * From configuration.yaml is easy to provide iterable elements like lists, this adds the possibility to provide a list of Sonos hosts using a yaml and still supports the comma separated string version * Remove superfluous host reassignment --- homeassistant/components/media_player/sonos.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 35c5e360bf0..7a66c3d354c 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -47,10 +47,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): players = None hosts = config.get('hosts', None) if hosts: + # Support retro compatibility with comma separated list of hosts + # from config + hosts = hosts.split(',') if isinstance(hosts, str) else hosts players = [] - for host in hosts.split(","): - host = socket.gethostbyname(host) - players.append(soco.SoCo(host)) + for host in hosts: + players.append(soco.SoCo(socket.gethostbyname(host))) if not players: players = soco.discover(interface_addr=config.get('interface_addr', From 98d18c30607cbf645960ac81304d6a20dff40bc8 Mon Sep 17 00:00:00 2001 From: pavoni Date: Thu, 18 Feb 2016 19:32:30 +0000 Subject: [PATCH 069/186] Bump pywemo version. --- homeassistant/components/switch/wemo.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 43c325795c6..56823b5ddb3 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -12,7 +12,7 @@ from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['pywemo==0.3.10'] +REQUIREMENTS = ['pywemo==0.3.12'] _LOGGER = logging.getLogger(__name__) _WEMO_SUBSCRIPTION_REGISTRY = None diff --git a/requirements_all.txt b/requirements_all.txt index 4b804dc9cec..525e3b25177 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -214,7 +214,7 @@ pyuserinput==0.1.9 pyvera==0.2.8 # homeassistant.components.switch.wemo -pywemo==0.3.10 +pywemo==0.3.12 # homeassistant.components.thermostat.radiotherm radiotherm==1.2 From 2d932f89fcf64813e3f48ce66dc1adec0cf9a619 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Feb 2016 15:58:31 -0800 Subject: [PATCH 070/186] Add sensor_class to binary_sensor This adds a 'sensor_class' property and attribute, which should be either None or one of several defined SENSOR_CLASSES to indicate contextual information about what the sensor is measuring. --- .../components/binary_sensor/__init__.py | 22 ++++++++++++ .../binary_sensor/test_binary_sensor.py | 36 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/components/binary_sensor/test_binary_sensor.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index ccfd57aff8c..86889cd18df 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -17,6 +17,17 @@ DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' +SENSOR_CLASSES = [ + None, # Generic on/off + 'opening', # Door, window, etc + 'motion', # Motion sensor + 'gas', # CO, CO2, etc + 'smoke', # Smoke detector + 'moisture', # Specifically a wetness sensor + 'light', # Lightness threshold + 'power', # Power, over-current, etc + 'safety', # Generic on=unsafe, off=safe + ] def setup(hass, config): @@ -47,3 +58,14 @@ class BinarySensorDevice(Entity): def friendly_state(self): """ Returns the friendly state of the binary sensor. """ return None + + @property + def sensor_class(self): + """ Returns the class of this sensor, from SENSOR_CASSES. """ + return None + + @property + def device_state_attributes(self): + return { + 'sensor_class': self.sensor_class, + } diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py new file mode 100644 index 00000000000..9c3f7f99e0f --- /dev/null +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -0,0 +1,36 @@ +""" +tests.components.binary_sensor.test_binary_sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Test the binary_sensor base class +""" +import unittest +from unittest import mock + +from homeassistant.components import binary_sensor +from homeassistant.const import STATE_ON, STATE_OFF + + +class TestBinarySensor(unittest.TestCase): + def test_state(self): + sensor = binary_sensor.BinarySensorDevice() + self.assertEqual(STATE_OFF, sensor.state) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.is_on', + new=False): + self.assertEqual(STATE_OFF, + binary_sensor.BinarySensorDevice().state) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.is_on', + new=True): + self.assertEqual(STATE_ON, + binary_sensor.BinarySensorDevice().state) + + def test_attributes(self): + sensor = binary_sensor.BinarySensorDevice() + self.assertEqual({'sensor_class': None}, + sensor.device_state_attributes) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.sensor_class', + new='motion'): + self.assertEqual({'sensor_class': 'motion'}, + sensor.device_state_attributes) From d93883f153d9ff1533e57e442e7399ae68d13ccf Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Feb 2016 16:00:01 -0800 Subject: [PATCH 071/186] Make nx584 expose zone types (sensor classes) With this, plus https://github.com/balloob/home-assistant-polymer/pull/32, I can have nx584 sensors use a proper icon in the UI. --- .../components/binary_sensor/nx584.py | 20 ++++++++++++++++--- tests/components/binary_sensor/test_nx584.py | 15 ++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index 0565bb5d857..e7294c039c8 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -12,7 +12,8 @@ import time import requests -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import (BinarySensorDevice, + SENSOR_CLASSES) REQUIREMENTS = ['pynx584==0.2'] _LOGGER = logging.getLogger(__name__) @@ -24,11 +25,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): host = config.get('host', 'localhost:5007') exclude = config.get('exclude_zones', []) + zone_types = config.get('zone_types', {}) if not all(isinstance(zone, int) for zone in exclude): _LOGGER.error('Invalid excluded zone specified (use zone number)') return False + if not all(isinstance(zone, int) and ztype in SENSOR_CLASSES + for zone, ztype in zone_types.items()): + _LOGGER.error('Invalid zone_types entry') + return False + try: client = nx584_client.Client('http://%s' % host) zones = client.list_zones() @@ -42,7 +49,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False zone_sensors = { - zone['number']: NX584ZoneSensor(zone) + zone['number']: NX584ZoneSensor( + zone, + zone_types.get(zone['number'], 'opening')) for zone in zones if zone['number'] not in exclude} if zone_sensors: @@ -58,8 +67,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NX584ZoneSensor(BinarySensorDevice): """Represents a NX584 zone as a sensor.""" - def __init__(self, zone): + def __init__(self, zone, zone_type): self._zone = zone + self._zone_type = zone_type + + @property + def sensor_class(self): + return self._zone_type @property def should_poll(self): diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py index 67dbe18e866..dcf35850044 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/binary_sensor/test_nx584.py @@ -41,7 +41,7 @@ class TestNX584SensorSetup(unittest.TestCase): hass = mock.MagicMock() self.assertTrue(nx584.setup_platform(hass, {}, add_devices)) mock_nx.assert_has_calls([ - mock.call(zone) + mock.call(zone, 'opening') for zone in self.fake_zones]) self.assertTrue(add_devices.called) nx584_client.Client.assert_called_once_with('http://localhost:5007') @@ -58,8 +58,8 @@ class TestNX584SensorSetup(unittest.TestCase): hass = mock.MagicMock() self.assertTrue(nx584.setup_platform(hass, config, add_devices)) mock_nx.assert_has_calls([ - mock.call(self.fake_zones[0]), - mock.call(self.fake_zones[2]), + mock.call(self.fake_zones[0], 'opening'), + mock.call(self.fake_zones[2], 'motion'), ]) self.assertTrue(add_devices.called) nx584_client.Client.assert_called_once_with('http://foo:123') @@ -74,6 +74,9 @@ class TestNX584SensorSetup(unittest.TestCase): def test_setup_bad_config(self): bad_configs = [ {'exclude_zones': ['a']}, + {'zone_types': {'a': 'b'}}, + {'zone_types': {1: 'notatype'}}, + {'zone_types': {'notazone': 'motion'}}, ] for config in bad_configs: self._test_assert_graceful_fail(config) @@ -98,7 +101,7 @@ class TestNX584SensorSetup(unittest.TestCase): class TestNX584ZoneSensor(unittest.TestCase): def test_sensor_normal(self): zone = {'number': 1, 'name': 'foo', 'state': True} - sensor = nx584.NX584ZoneSensor(zone) + sensor = nx584.NX584ZoneSensor(zone, 'motion') self.assertEqual('foo', sensor.name) self.assertFalse(sensor.should_poll) self.assertTrue(sensor.is_on) @@ -113,8 +116,8 @@ class TestNX584Watcher(unittest.TestCase): zone1 = {'number': 1, 'name': 'foo', 'state': True} zone2 = {'number': 2, 'name': 'bar', 'state': True} zones = { - 1: nx584.NX584ZoneSensor(zone1), - 2: nx584.NX584ZoneSensor(zone2), + 1: nx584.NX584ZoneSensor(zone1, 'motion'), + 2: nx584.NX584ZoneSensor(zone2, 'motion'), } watcher = nx584.NX584Watcher(None, zones) watcher._process_zone_event({'zone': 1, 'zone_state': False}) From d7c4b50e3e8bc069be6f0ffe9df0c4a0288b900c Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Thu, 18 Feb 2016 15:39:05 -0500 Subject: [PATCH 072/186] Added support for nest current weather conditions --- homeassistant/components/sensor/nest.py | 37 ++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index f3a5e5dca7c..d046e56b57e 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -21,7 +21,18 @@ SENSOR_TYPES = ['humidity', 'last_connection', 'battery_level'] -SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V'} +WEATHER_VARIABLES = ['weather_condition', 'weather_temperature', + 'weather_humidity', + 'wind_speed', 'wind_direction'] + +JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity', + 'weather_temperature': 'temperature', + 'weather_condition': 'condition', + 'wind_speed': 'kph', + 'wind_direction': 'direction'} + +SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V', + 'kph': 'kph', 'temperature': '°C'} SENSOR_TEMP_TYPES = ['temperature', 'target', @@ -45,7 +56,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([NestTempSensor(structure, device, variable)]) + elif variable in WEATHER_VARIABLES: + json_variable = JSON_VARIABLE_NAMES.get(variable, None) + add_devices([NestWeatherSensor(structure, + device, + json_variable)]) else: + for test in WEATHER_VARIABLES: + logger.error('NEST VAR: %s', test) logger.error('Nest sensor type: "%s" does not exist', variable) except socket.error: @@ -109,3 +127,20 @@ class NestTempSensor(NestSensor): return None return round(temp, 1) + + +class NestWeatherSensor(NestSensor): + """ Represents a basic Nest Weather Conditions sensor. """ + + @property + def state(self): + """ Returns the state of the sensor. """ + if self.variable == 'kph' or self.variable == 'direction': + return getattr(self.structure.weather.current.wind, self.variable) + else: + return getattr(self.structure.weather.current, self.variable) + + @property + def unit_of_measurement(self): + """ Unit the value is expressed in. """ + return SENSOR_UNITS.get(self.variable, None) From 30e1569b21eb2b778e8486d65f3679254c2c4019 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Feb 2016 13:10:25 -0800 Subject: [PATCH 073/186] Fix bloomsky component importing --- homeassistant/components/camera/bloomsky.py | 3 ++- homeassistant/components/sensor/bloomsky.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/camera/bloomsky.py index 5c9314963bd..598059af13f 100644 --- a/homeassistant/components/camera/bloomsky.py +++ b/homeassistant/components/camera/bloomsky.py @@ -8,7 +8,7 @@ https://home-assistant.io/components/camera.bloomsky/ """ import logging import requests -import homeassistant.components.bloomsky as bloomsky +from homeassistant.loader import get_component from homeassistant.components.camera import Camera DEPENDENCIES = ["bloomsky"] @@ -17,6 +17,7 @@ DEPENDENCIES = ["bloomsky"] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ set up access to BloomSky cameras """ + bloomsky = get_component('bloomsky') for device in bloomsky.BLOOMSKY.devices.values(): add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)]) diff --git a/homeassistant/components/sensor/bloomsky.py b/homeassistant/components/sensor/bloomsky.py index 3a6ad04b076..ca7765d6bd6 100644 --- a/homeassistant/components/sensor/bloomsky.py +++ b/homeassistant/components/sensor/bloomsky.py @@ -7,7 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/sensor.bloomsky/ """ import logging -import homeassistant.components.bloomsky as bloomsky +from homeassistant.loader import get_component from homeassistant.helpers.entity import Entity DEPENDENCIES = ["bloomsky"] @@ -36,6 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Set up the available BloomSky weather sensors. """ logger = logging.getLogger(__name__) + bloomsky = get_component('bloomsky') for device_key in bloomsky.BLOOMSKY.devices: device = bloomsky.BLOOMSKY.devices[device_key] From 9a740bff2a7317e28dbcaba4bb7e5be090f9bf97 Mon Sep 17 00:00:00 2001 From: Justyn Shull Date: Thu, 18 Feb 2016 18:12:00 -0600 Subject: [PATCH 074/186] Return early from setup_platform when zwave NETWORK is not configured --- homeassistant/components/light/zwave.py | 2 +- homeassistant/components/sensor/zwave.py | 2 +- homeassistant/components/switch/zwave.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 62c970badc1..10d8cdef4d9 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -19,7 +19,7 @@ from homeassistant.components.zwave import ( def setup_platform(hass, config, add_devices, discovery_info=None): """ Find and add Z-Wave lights. """ - if discovery_info is None: + if discovery_info is None or NETWORK is None: return node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index fc3e27b212f..ddca961f3a5 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # platform: zwave # # `setup_platform` will be called without `discovery_info`. - if discovery_info is None: + if discovery_info is None or NETWORK is None: return node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index 97254ed702f..150b8ba42be 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -15,7 +15,7 @@ from homeassistant.components.zwave import ( # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Find and return demo switches. """ - if discovery_info is None: + if discovery_info is None or NETWORK is None: return node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] From d57df2dddea47c47b864e70942df3bd90c956898 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 18 Feb 2016 17:59:58 -0800 Subject: [PATCH 075/186] Update the sensor classes in the demo binary_sensor This updates the two demo sensors we have so they show contextual icons in the UI. --- homeassistant/components/binary_sensor/demo.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index 087d7405d9b..90c7accf512 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -9,17 +9,23 @@ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Demo binary sensors. """ add_devices([ - DemoBinarySensor('Basement Floor Wet', False), - DemoBinarySensor('Movement Backyard', True), + DemoBinarySensor('Basement Floor Wet', False, 'moisture'), + DemoBinarySensor('Movement Backyard', True, 'motion'), ]) class DemoBinarySensor(BinarySensorDevice): """ A Demo binary sensor. """ - def __init__(self, name, state): + def __init__(self, name, state, sensor_class): self._name = name self._state = state + self._sensor_type = sensor_class + + @property + def sensor_class(self): + """ Return our class. """ + return self._sensor_type @property def should_poll(self): From e80309c03c89852e92ab6e8267c66a1235a1cd76 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Feb 2016 21:27:50 -0800 Subject: [PATCH 076/186] Fix imports (using isort) --- homeassistant/__main__.py | 12 +++--- homeassistant/bootstrap.py | 22 +++++----- .../alarm_control_panel/alarmdotcom.py | 6 +-- .../components/alarm_control_panel/manual.py | 10 ++--- .../components/alarm_control_panel/mqtt.py | 6 +-- .../components/alarm_control_panel/nx584.py | 7 ++-- .../alarm_control_panel/verisure.py | 7 ++-- homeassistant/components/alexa.py | 2 +- homeassistant/components/api.py | 26 ++++++------ homeassistant/components/arduino.py | 4 +- .../components/automation/numeric_state.py | 3 +- homeassistant/components/automation/state.py | 3 +- homeassistant/components/automation/sun.py | 2 +- homeassistant/components/automation/zone.py | 3 +- .../components/binary_sensor/apcupsd.py | 2 +- .../components/binary_sensor/arest.py | 4 +- .../binary_sensor/command_sensor.py | 2 +- .../components/binary_sensor/mqtt.py | 6 +-- .../components/binary_sensor/nest.py | 4 +- .../components/binary_sensor/nx584.py | 4 +- .../components/binary_sensor/rest.py | 4 +- .../components/binary_sensor/rpi_gpio.py | 3 +- .../components/binary_sensor/zigbee.py | 1 - homeassistant/components/bloomsky.py | 6 ++- homeassistant/components/camera/bloomsky.py | 4 +- homeassistant/components/camera/demo.py | 3 +- homeassistant/components/camera/foscam.py | 2 +- homeassistant/components/camera/generic.py | 2 +- homeassistant/components/camera/mjpeg.py | 4 +- homeassistant/components/camera/uvc.py | 2 +- homeassistant/components/configurator.py | 2 +- homeassistant/components/conversation.py | 3 +- homeassistant/components/demo.py | 5 +-- .../components/device_sun_light_trigger.py | 5 ++- .../components/device_tracker/actiontec.py | 10 ++--- .../components/device_tracker/aruba.py | 6 +-- .../components/device_tracker/asuswrt.py | 8 ++-- .../components/device_tracker/ddwrt.py | 7 ++-- .../components/device_tracker/fritz.py | 4 +- .../components/device_tracker/icloud.py | 4 +- .../components/device_tracker/locative.py | 3 +- .../components/device_tracker/luci.py | 9 ++-- .../components/device_tracker/mqtt.py | 3 +- .../components/device_tracker/netgear.py | 6 +-- .../components/device_tracker/nmap_tracker.py | 8 ++-- .../components/device_tracker/snmp.py | 8 ++-- .../components/device_tracker/thomson.py | 8 ++-- .../components/device_tracker/tomato.py | 8 ++-- .../components/device_tracker/tplink.py | 7 ++-- .../components/device_tracker/ubus.py | 9 ++-- homeassistant/components/discovery.py | 4 +- homeassistant/components/downloader.py | 2 +- homeassistant/components/ecobee.py | 8 ++-- homeassistant/components/graphite.py | 3 +- homeassistant/components/group.py | 11 +++-- homeassistant/components/history.py | 4 +- homeassistant/components/http.py | 25 +++++------ homeassistant/components/ifttt.py | 1 + homeassistant/components/influxdb.py | 5 ++- homeassistant/components/input_boolean.py | 4 +- homeassistant/components/input_select.py | 2 +- homeassistant/components/insteon_hub.py | 11 ++--- homeassistant/components/isy994.py | 8 ++-- homeassistant/components/keyboard.py | 7 ++-- .../components/light/blinksticklight.py | 2 +- homeassistant/components/light/demo.py | 3 +- homeassistant/components/light/hue.py | 12 +++--- homeassistant/components/light/hyperion.py | 4 +- homeassistant/components/light/insteon_hub.py | 2 +- homeassistant/components/light/isy994.py | 6 +-- homeassistant/components/light/lifx.py | 7 ++-- .../components/light/limitlessled.py | 9 ++-- homeassistant/components/light/mqtt.py | 6 +-- homeassistant/components/light/mysensors.py | 14 ++----- homeassistant/components/light/rfxtrx.py | 13 +++--- homeassistant/components/light/scsgate.py | 3 +- homeassistant/components/light/tellstick.py | 3 +- homeassistant/components/light/vera.py | 14 ++----- homeassistant/components/light/zigbee.py | 1 - homeassistant/components/light/zwave.py | 8 ++-- homeassistant/components/lock/verisure.py | 5 +-- homeassistant/components/logbook.py | 11 ++--- homeassistant/components/media_player/cast.py | 16 ++++--- homeassistant/components/media_player/demo.py | 13 +++--- .../components/media_player/denon.py | 12 +++--- .../components/media_player/firetv.py | 14 +++---- .../components/media_player/itunes.py | 10 ++--- homeassistant/components/media_player/kodi.py | 8 ++-- homeassistant/components/media_player/mpd.py | 12 ++---- homeassistant/components/media_player/plex.py | 12 +++--- .../components/media_player/samsungtv.py | 11 ++--- .../components/media_player/snapcast.py | 7 +--- .../components/media_player/sonos.py | 12 +++--- .../components/media_player/squeezebox.py | 12 +++--- .../components/media_player/universal.py | 42 ++++++++----------- homeassistant/components/modbus.py | 4 +- homeassistant/components/mqtt_eventstream.py | 14 +++---- homeassistant/components/mysensors.py | 9 ++-- homeassistant/components/nest.py | 2 +- homeassistant/components/notify/demo.py | 1 - homeassistant/components/notify/file.py | 4 +- .../components/notify/free_mobile.py | 6 +-- .../components/notify/googlevoice.py | 7 ++-- homeassistant/components/notify/instapush.py | 6 +-- homeassistant/components/notify/nma.py | 4 +- homeassistant/components/notify/pushbullet.py | 2 +- homeassistant/components/notify/pushetta.py | 4 +- homeassistant/components/notify/pushover.py | 4 +- homeassistant/components/notify/rest.py | 5 ++- homeassistant/components/notify/slack.py | 2 +- homeassistant/components/notify/smtp.py | 4 +- homeassistant/components/notify/syslog.py | 4 +- homeassistant/components/notify/telegram.py | 4 +- homeassistant/components/notify/twitter.py | 6 +-- homeassistant/components/notify/xmpp.py | 4 +- homeassistant/components/proximity.py | 3 +- homeassistant/components/recorder.py | 16 +++---- homeassistant/components/rfxtrx.py | 1 + .../components/rollershutter/demo.py | 2 +- .../components/rollershutter/mqtt.py | 3 +- .../components/rollershutter/scsgate.py | 2 +- homeassistant/components/rpi_gpio.py | 6 ++- homeassistant/components/scene.py | 4 +- homeassistant/components/script.py | 12 +++--- homeassistant/components/scsgate.py | 1 + homeassistant/components/sensor/apcupsd.py | 2 +- homeassistant/components/sensor/arduino.py | 2 +- homeassistant/components/sensor/arest.py | 8 ++-- homeassistant/components/sensor/bitcoin.py | 3 +- homeassistant/components/sensor/bloomsky.py | 3 +- .../components/sensor/command_sensor.py | 2 +- homeassistant/components/sensor/demo.py | 2 +- homeassistant/components/sensor/dht.py | 2 +- homeassistant/components/sensor/dweet.py | 9 ++-- homeassistant/components/sensor/ecobee.py | 2 +- homeassistant/components/sensor/efergy.py | 3 +- homeassistant/components/sensor/eliqonline.py | 3 +- homeassistant/components/sensor/forecast.py | 4 +- homeassistant/components/sensor/glances.py | 6 +-- homeassistant/components/sensor/isy994.py | 8 ++-- homeassistant/components/sensor/mfi.py | 5 +-- homeassistant/components/sensor/modbus.py | 5 +-- homeassistant/components/sensor/mqtt.py | 3 +- homeassistant/components/sensor/mysensors.py | 10 ++--- homeassistant/components/sensor/nest.py | 4 +- homeassistant/components/sensor/netatmo.py | 7 ++-- .../components/sensor/neurio_energy.py | 1 + homeassistant/components/sensor/onewire.py | 5 ++- .../components/sensor/openweathermap.py | 4 +- homeassistant/components/sensor/rest.py | 7 ++-- homeassistant/components/sensor/rfxtrx.py | 4 +- homeassistant/components/sensor/sabnzbd.py | 2 +- homeassistant/components/sensor/speedtest.py | 9 ++-- .../sensor/swiss_public_transport.py | 3 +- .../components/sensor/systemmonitor.py | 2 +- .../components/sensor/tellduslive.py | 8 ++-- homeassistant/components/sensor/tellstick.py | 2 +- homeassistant/components/sensor/temper.py | 3 +- homeassistant/components/sensor/template.py | 16 +++---- homeassistant/components/sensor/torque.py | 2 +- .../components/sensor/transmission.py | 6 +-- homeassistant/components/sensor/twitch.py | 2 +- homeassistant/components/sensor/vera.py | 11 ++--- homeassistant/components/sensor/verisure.py | 3 +- homeassistant/components/sensor/wink.py | 2 +- homeassistant/components/sensor/yr.py | 8 ++-- homeassistant/components/sensor/zigbee.py | 7 ++-- homeassistant/components/sensor/zwave.py | 13 +++--- homeassistant/components/simple_alarm.py | 2 +- homeassistant/components/splunk.py | 4 +- homeassistant/components/statsd.py | 1 + homeassistant/components/sun.py | 5 ++- homeassistant/components/switch/arest.py | 1 + homeassistant/components/switch/edimax.py | 6 +-- .../components/switch/hikvisioncam.py | 4 +- homeassistant/components/switch/isy994.py | 8 ++-- homeassistant/components/switch/mfi.py | 2 +- homeassistant/components/switch/mqtt.py | 3 +- homeassistant/components/switch/mysensors.py | 8 +--- homeassistant/components/switch/mystrom.py | 1 + homeassistant/components/switch/rest.py | 1 + homeassistant/components/switch/rfxtrx.py | 13 +++--- homeassistant/components/switch/rpi_gpio.py | 3 +- homeassistant/components/switch/scsgate.py | 4 +- homeassistant/components/switch/template.py | 19 +++------ .../components/switch/transmission.py | 4 +- homeassistant/components/switch/vera.py | 13 ++---- homeassistant/components/switch/wemo.py | 2 +- homeassistant/components/switch/zigbee.py | 1 - homeassistant/components/switch/zwave.py | 6 +-- homeassistant/components/tellduslive.py | 10 ++--- homeassistant/components/thermostat/ecobee.py | 6 +-- .../components/thermostat/heat_control.py | 6 +-- .../components/thermostat/heatmiser.py | 1 + .../components/thermostat/honeywell.py | 4 +- homeassistant/components/thermostat/nest.py | 8 ++-- .../components/thermostat/proliphix.py | 8 ++-- .../components/thermostat/radiotherm.py | 8 ++-- homeassistant/components/verisure.py | 14 +++---- homeassistant/components/wink.py | 8 ++-- homeassistant/components/zigbee.py | 3 +- homeassistant/components/zwave.py | 13 +++--- homeassistant/config.py | 8 ++-- homeassistant/core.py | 36 ++++++++-------- homeassistant/helpers/entity.py | 13 +++--- homeassistant/helpers/entity_component.py | 5 +-- homeassistant/helpers/event.py | 4 +- homeassistant/helpers/state.py | 18 ++++---- homeassistant/helpers/temperature.py | 2 +- homeassistant/loader.py | 6 +-- homeassistant/remote.py | 17 ++++---- homeassistant/util/location.py | 1 + homeassistant/util/template.py | 2 + homeassistant/util/yaml.py | 3 +- 214 files changed, 644 insertions(+), 718 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 5351bcf7983..f35e0c1c1f0 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,18 +1,18 @@ """ Starts home assistant. """ from __future__ import print_function -from multiprocessing import Process +import argparse +import os import signal import sys import threading -import os -import argparse import time +from multiprocessing import Process -from homeassistant import bootstrap import homeassistant.config as config_util -from homeassistant.const import (__version__, EVENT_HOMEASSISTANT_START, - RESTART_EXIT_CODE) +from homeassistant import bootstrap +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, RESTART_EXIT_CODE, __version__) def validate_python(): diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3f88c6f4388..802da19e938 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -9,27 +9,27 @@ After bootstrapping you can add your own components or start by calling homeassistant.start_home_assistant(bus) """ -from collections import defaultdict import logging import logging.handlers import os import shutil import sys +from collections import defaultdict -import homeassistant.core as core -import homeassistant.util.dt as date_util -import homeassistant.util.package as pkg_util -import homeassistant.util.location as loc_util -import homeassistant.config as config_util -import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group +import homeassistant.config as config_util +import homeassistant.core as core +import homeassistant.loader as loader +import homeassistant.util.dt as date_util +import homeassistant.util.location as loc_util +import homeassistant.util.package as pkg_util +from homeassistant.const import ( + CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, + CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED, + TEMP_CELCIUS, TEMP_FAHRENHEIT, __version__) from homeassistant.helpers import event_decorators, service from homeassistant.helpers.entity import Entity -from homeassistant.const import ( - __version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, - CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_CUSTOMIZE, - TEMP_CELCIUS, TEMP_FAHRENHEIT) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index dea8151cb80..b563d57a686 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -9,11 +9,9 @@ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/ import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD - from homeassistant.const import ( - STATE_UNKNOWN, - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) + CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index 2658e005aea..a6e280d1dd1 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -6,15 +6,15 @@ Support for manual alarms. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.manual/ """ -import logging import datetime -import homeassistant.components.alarm_control_panel as alarm -from homeassistant.helpers.event import track_point_in_time -import homeassistant.util.dt as dt_util +import logging +import homeassistant.components.alarm_control_panel as alarm +import homeassistant.util.dt as dt_util from homeassistant.const import ( - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) +from homeassistant.helpers.event import track_point_in_time _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index 168b220db1a..7ba6ba057ed 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -7,11 +7,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.mqtt/ """ import logging -import homeassistant.components.mqtt as mqtt -import homeassistant.components.alarm_control_panel as alarm +import homeassistant.components.alarm_control_panel as alarm +import homeassistant.components.mqtt as mqtt from homeassistant.const import ( - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index c77fab341de..e696ec682b2 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -7,12 +7,13 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.nx584/ """ import logging + import requests -from homeassistant.const import (STATE_UNKNOWN, STATE_ALARM_DISARMED, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY) import homeassistant.components.alarm_control_panel as alarm +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_UNKNOWN) REQUIREMENTS = ['pynx584==0.2'] diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index ecedd163d0b..40df158dce9 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -8,12 +8,11 @@ https://home-assistant.io/components/verisure/ """ import logging -import homeassistant.components.verisure as verisure import homeassistant.components.alarm_control_panel as alarm - +import homeassistant.components.verisure as verisure from homeassistant.const import ( - STATE_UNKNOWN, - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 66ac9de0b43..5678aa509b7 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -10,8 +10,8 @@ import enum import logging from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY -from homeassistant.util import template from homeassistant.helpers.service import call_from_config +from homeassistant.util import template DOMAIN = 'alexa' DEPENDENCIES = ['http'] diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 1a84f222443..e9c25ca5dac 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -6,26 +6,24 @@ Provides a Rest API for Home Assistant. For more details about the RESTful API, please refer to the documentation at https://home-assistant.io/developers/api/ """ -import re -import logging -import threading import json +import logging +import re +import threading import homeassistant.core as ha -from homeassistant.exceptions import TemplateError -from homeassistant.helpers.state import TrackStates import homeassistant.remote as rem -from homeassistant.util import template from homeassistant.bootstrap import ERROR_LOG_FILENAME from homeassistant.const import ( - URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM, - URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS, - URL_API_CONFIG, URL_API_ERROR_LOG, URL_API_LOG_OUT, - URL_API_TEMPLATE, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL, - HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, - HTTP_UNPROCESSABLE_ENTITY, HTTP_HEADER_CONTENT_TYPE, - CONTENT_TYPE_TEXT_PLAIN) - + CONTENT_TYPE_TEXT_PLAIN, EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, + HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_HEADER_CONTENT_TYPE, HTTP_NOT_FOUND, + HTTP_OK, HTTP_UNPROCESSABLE_ENTITY, MATCH_ALL, URL_API, URL_API_COMPONENTS, + URL_API_CONFIG, URL_API_ERROR_LOG, URL_API_EVENT_FORWARD, URL_API_EVENTS, + URL_API_LOG_OUT, URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, + URL_API_STREAM, URL_API_TEMPLATE) +from homeassistant.exceptions import TemplateError +from homeassistant.helpers.state import TrackStates +from homeassistant.util import template DOMAIN = 'api' DEPENDENCIES = ['http'] diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py index f7f6240b44e..05db57394dd 100644 --- a/homeassistant/components/arduino.py +++ b/homeassistant/components/arduino.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/arduino/ """ import logging +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import validate_config -from homeassistant.const import (EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP) DOMAIN = "arduino" REQUIREMENTS = ['PyMata==2.07a'] diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 61e68aa8e8e..96ab2121688 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -6,14 +6,13 @@ Offers numeric state listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#numeric-state-trigger """ -from functools import partial import logging +from functools import partial from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers.event import track_state_change from homeassistant.util import template - CONF_ENTITY_ID = "entity_id" CONF_BELOW = "below" CONF_ABOVE = "above" diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index bcf498f509a..1eaa9e5c240 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -8,9 +8,8 @@ at https://home-assistant.io/components/automation/#state-trigger """ import logging -from homeassistant.helpers.event import track_state_change from homeassistant.const import MATCH_ALL - +from homeassistant.helpers.event import track_state_change CONF_ENTITY_ID = "entity_id" CONF_FROM = "from" diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 6abb59eede6..9cd50fedbd9 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -9,9 +9,9 @@ at https://home-assistant.io/components/automation/#sun-trigger import logging from datetime import timedelta +import homeassistant.util.dt as dt_util from homeassistant.components import sun from homeassistant.helpers.event import track_sunrise, track_sunset -import homeassistant.util.dt as dt_util DEPENDENCIES = ['sun'] diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index f0f800bd313..7dc551de32c 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -9,10 +9,9 @@ at https://home-assistant.io/components/automation/#zone-trigger import logging from homeassistant.components import zone -from homeassistant.helpers.event import track_state_change from homeassistant.const import ( ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL) - +from homeassistant.helpers.event import track_state_change CONF_ENTITY_ID = "entity_id" CONF_ZONE = "zone" diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index 796a2a0df70..6d3ddb2c2fa 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -6,8 +6,8 @@ Provides a binary sensor to track online status of a UPS. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.apcupsd/ """ -from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components import apcupsd +from homeassistant.components.binary_sensor import BinarySensorDevice DEPENDENCIES = [apcupsd.DOMAIN] DEFAULT_NAME = "UPS Online Status" diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 7eafca9f2ae..3a7eac35bb5 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -6,13 +6,13 @@ The arest sensor will consume an exposed aREST API of a device. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.arest/ """ -from datetime import timedelta import logging +from datetime import timedelta import requests -from homeassistant.util import Throttle from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/command_sensor.py b/homeassistant/components/binary_sensor/command_sensor.py index d69417a6a73..45b950cec8f 100644 --- a/homeassistant/components/binary_sensor/command_sensor.py +++ b/homeassistant/components/binary_sensor/command_sensor.py @@ -10,9 +10,9 @@ https://home-assistant.io/components/binary_sensor.command/ import logging from datetime import timedelta -from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.command_sensor import CommandSensorData +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.util import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index 916f1226c82..e8f09e80ae2 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/binary_sensor.mqtt/ """ import logging -from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.util import template import homeassistant.components.mqtt as mqtt +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import CONF_VALUE_TEMPLATE +from homeassistant.util import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index 0c250dcde67..a3c2b252b15 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/binary_sensor.nest/ """ import logging import socket -import homeassistant.components.nest as nest -from homeassistant.components.sensor.nest import NestSensor +import homeassistant.components.nest as nest from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.sensor.nest import NestSensor DEPENDENCIES = ['nest'] BINARY_TYPES = ['fan', diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index e7294c039c8..f77de7693ce 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -12,8 +12,8 @@ import time import requests -from homeassistant.components.binary_sensor import (BinarySensorDevice, - SENSOR_CLASSES) +from homeassistant.components.binary_sensor import ( + SENSOR_CLASSES, BinarySensorDevice) REQUIREMENTS = ['pynx584==0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 1a592cd905a..a7f0ad72604 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/binary_sensor.rest/ """ import logging +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.sensor.rest import RestData from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.util import template -from homeassistant.components.sensor.rest import RestData -from homeassistant.components.binary_sensor import BinarySensorDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index 64da1b5ea9f..a43d009e25f 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -8,9 +8,10 @@ https://home-assistant.io/components/binary_sensor.rpi_gpio/ """ import logging + import homeassistant.components.rpi_gpio as rpi_gpio from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import (DEVICE_DEFAULT_NAME) +from homeassistant.const import DEVICE_DEFAULT_NAME DEFAULT_PULL_MODE = "UP" DEFAULT_BOUNCETIME = 50 diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 1597cd5004f..6954ed33a82 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -10,7 +10,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.zigbee import ( ZigBeeDigitalIn, ZigBeeDigitalInConfig) - DEPENDENCIES = ["zigbee"] diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index fe2ae1cf3ba..f13b9ce85c9 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -8,10 +8,12 @@ https://home-assistant.io/components/bloomsky/ """ import logging from datetime import timedelta + import requests -from homeassistant.util import Throttle -from homeassistant.helpers import validate_config + from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle DOMAIN = "bloomsky" BLOOMSKY = None diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/camera/bloomsky.py index 598059af13f..6e03a136c6b 100644 --- a/homeassistant/components/camera/bloomsky.py +++ b/homeassistant/components/camera/bloomsky.py @@ -7,9 +7,11 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/camera.bloomsky/ """ import logging + import requests -from homeassistant.loader import get_component + from homeassistant.components.camera import Camera +from homeassistant.loader import get_component DEPENDENCIES = ["bloomsky"] diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index 0ad992db86d..89d9b9600e8 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -4,8 +4,9 @@ homeassistant.components.camera.demo Demo platform that has a fake camera. """ import os -from homeassistant.components.camera import Camera + import homeassistant.util.dt as dt_util +from homeassistant.components.camera import Camera def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index b210e1a2f1b..47ebf1b7d5a 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -10,8 +10,8 @@ import logging import requests -from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN, Camera +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index c81febccc86..514e94db1ef 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -11,8 +11,8 @@ import logging import requests from requests.auth import HTTPBasicAuth -from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN, Camera +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 7bbaa4846b5..95bf9813b39 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -6,15 +6,15 @@ Support for IP Cameras. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.mjpeg/ """ -from contextlib import closing import logging +from contextlib import closing import requests from requests.auth import HTTPBasicAuth -from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN, Camera from homeassistant.const import HTTP_OK +from homeassistant.helpers import validate_config CONTENT_TYPE_HEADER = 'Content-Type' diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index 19cd7c4b4be..a1ae128f4da 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -11,8 +11,8 @@ import socket import requests -from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN, Camera +from homeassistant.helpers import validate_config REQUIREMENTS = ['uvcclient==0.6'] diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 591cdc0dc61..681cc80cc9c 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -11,8 +11,8 @@ the user has submitted configuration information. """ import logging -from homeassistant.helpers.entity import generate_entity_id from homeassistant.const import EVENT_TIME_CHANGED +from homeassistant.helpers.entity import generate_entity_id DOMAIN = "configurator" ENTITY_ID_FORMAT = DOMAIN + ".{}" diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 18ddf8fcc8d..9cf70fa2b62 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -9,10 +9,9 @@ https://home-assistant.io/components/conversation/ import logging import re - from homeassistant import core from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) + ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON) DOMAIN = "conversation" diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index e63f5f49551..9418a037b8d 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -6,11 +6,10 @@ Sets up a demo environment that mimics interaction with devices. """ import time -import homeassistant.core as ha import homeassistant.bootstrap as bootstrap +import homeassistant.core as ha import homeassistant.loader as loader -from homeassistant.const import ( - CONF_PLATFORM, ATTR_ENTITY_ID) +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM DOMAIN = "demo" diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 4acf60bc0a2..a8cf5b5d417 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -10,10 +10,11 @@ https://home-assistant.io/components/device_sun_light_trigger/ import logging from datetime import timedelta -from homeassistant.helpers.event import track_point_in_time, track_state_change import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME -from . import light, sun, device_tracker, group +from homeassistant.helpers.event import track_point_in_time, track_state_change + +from . import device_tracker, group, light, sun DOMAIN = "device_sun_light_trigger" DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun'] diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index f363acf1902..d9cb8718a70 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -8,17 +8,17 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.actiontec/ """ import logging -from datetime import timedelta -from collections import namedtuple import re -import threading import telnetlib +import threading +from collections import namedtuple +from datetime import timedelta import homeassistant.util.dt as dt_util -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 6d94ad30d04..4bc3f9e3258 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -8,14 +8,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.aruba/ """ import logging -from datetime import timedelta import re import threading +from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 472440d7307..d9b6d1a809e 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -8,15 +8,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.asuswrt/ """ import logging -from datetime import timedelta import re -import threading import telnetlib +import threading +from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 268c4e5a22f..82a8ed81537 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -8,15 +8,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.ddwrt/ """ import logging -from datetime import timedelta import re import threading +from datetime import timedelta + import requests -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index 9fee2747c0f..3ca3461f557 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -10,10 +10,10 @@ https://home-assistant.io/components/device_tracker.fritz/ import logging from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN REQUIREMENTS = ['fritzconnection==0.4.6'] diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index 76046552551..4b13098dde0 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -7,9 +7,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.icloud/ """ import logging - import re -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD + +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 11884829600..d1912b06b3d 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -9,9 +9,8 @@ https://home-assistant.io/components/device_tracker.locative/ import logging from functools import partial -from homeassistant.const import ( - HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index 8b3e4eeb3c8..5745138bf8e 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -7,17 +7,18 @@ presence. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.luci/ """ -import logging import json -from datetime import timedelta +import logging import re import threading +from datetime import timedelta + import requests -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py index 929deaae669..6fca13e6892 100644 --- a/homeassistant/components/device_tracker/mqtt.py +++ b/homeassistant/components/device_tracker/mqtt.py @@ -7,8 +7,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.mqtt/ """ import logging -from homeassistant import util + import homeassistant.components.mqtt as mqtt +from homeassistant import util DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index 233622e076e..eebb68c043a 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -8,12 +8,12 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.netgear/ """ import logging -from datetime import timedelta import threading +from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD -from homeassistant.util import Throttle from homeassistant.components.device_tracker import DOMAIN +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) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index bc8e8768be0..596986e8bb7 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -7,16 +7,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.nmap_scanner/ """ import logging -from datetime import timedelta -from collections import namedtuple -import subprocess import re +import subprocess +from collections import namedtuple +from datetime import timedelta import homeassistant.util.dt as dt_util +from homeassistant.components.device_tracker import DOMAIN from homeassistant.const import CONF_HOSTS from homeassistant.helpers import validate_config from homeassistant.util import Throttle, convert -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/device_tracker/snmp.py index cd0e8239c38..b7b59fbf95a 100644 --- a/homeassistant/components/device_tracker/snmp.py +++ b/homeassistant/components/device_tracker/snmp.py @@ -7,15 +7,15 @@ through SNMP. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.snmp/ """ -import logging -from datetime import timedelta -import threading import binascii +import logging +import threading +from datetime import timedelta +from homeassistant.components.device_tracker import DOMAIN from homeassistant.const import CONF_HOST from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index 657bb910da2..7c7667400f5 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -8,15 +8,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.thomson/ """ import logging -from datetime import timedelta import re -import threading import telnetlib +import threading +from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index c87a50f0981..8f1e956dcb6 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -7,18 +7,18 @@ presence. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.tomato/ """ -import logging import json -from datetime import timedelta +import logging import re import threading +from datetime import timedelta import requests -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index a661dac0c1e..30a920961cb 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -9,15 +9,16 @@ https://home-assistant.io/components/device_tracker.tplink/ """ import base64 import logging -from datetime import timedelta import re import threading +from datetime import timedelta + import requests -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index 0355680a31d..073588008b0 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -7,17 +7,18 @@ presence. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.ubus/ """ -import logging import json -from datetime import timedelta +import logging import re import threading +from datetime import timedelta + import requests -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index cfd6ffd55eb..09816d73f9a 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -13,8 +13,8 @@ import threading from homeassistant import bootstrap from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_PLATFORM_DISCOVERED, - ATTR_SERVICE, ATTR_DISCOVERED) + ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START, + EVENT_PLATFORM_DISCOVERED) DOMAIN = "discovery" REQUIREMENTS = ['netdisco==0.5.2'] diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py index 3926495376c..6fbbd3f9473 100644 --- a/homeassistant/components/downloader.py +++ b/homeassistant/components/downloader.py @@ -6,8 +6,8 @@ Provides functionality to download files. For more details about this component, please refer to the documentation at https://home-assistant.io/components/downloader/ """ -import os import logging +import os import re import threading diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index 215e32594b5..f2172e7e5b6 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -6,15 +6,15 @@ Ecobee component For more details about this component, please refer to the documentation at https://home-assistant.io/components/ecobee/ """ -from datetime import timedelta import logging import os +from datetime import timedelta -from homeassistant.loader import get_component from homeassistant import bootstrap -from homeassistant.util import Throttle from homeassistant.const import ( - EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY) + ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, EVENT_PLATFORM_DISCOVERED) +from homeassistant.loader import get_component +from homeassistant.util import Throttle DOMAIN = "ecobee" DISCOVER_THERMOSTAT = "ecobee.thermostat" diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 9ebf3a0d7da..49ac6690edf 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -14,8 +14,7 @@ import threading import time from homeassistant.const import ( - EVENT_STATE_CHANGED, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED) from homeassistant.helpers import state DOMAIN = "graphite" diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 6806adb5253..a851726e69d 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -7,13 +7,12 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/group/ """ import homeassistant.core as ha -from homeassistant.helpers.event import track_state_change -from homeassistant.helpers.entity import ( - Entity, split_entity_id, generate_entity_id) from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_ON, STATE_OFF, - STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED, - STATE_UNKNOWN, CONF_NAME, CONF_ICON) + ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, + STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_UNKNOWN) +from homeassistant.helpers.entity import ( + Entity, generate_entity_id, split_entity_id) +from homeassistant.helpers.event import track_state_change DOMAIN = 'group' diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index d07fc518083..578cb265a27 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -7,12 +7,12 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/history/ """ import re +from collections import defaultdict from datetime import timedelta from itertools import groupby -from collections import defaultdict -import homeassistant.util.dt as dt_util import homeassistant.components.recorder as recorder +import homeassistant.util.dt as dt_util from homeassistant.const import HTTP_BAD_REQUEST DOMAIN = 'history' diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 073604e3b2c..878d7465128 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -6,30 +6,31 @@ This module provides an API and a HTTP interface for debug purposes. For more details about the RESTful API, please refer to the documentation at https://home-assistant.io/developers/api/ """ -from datetime import timedelta import gzip -from http import cookies -from http.server import SimpleHTTPRequestHandler, HTTPServer import json import logging import os -from socketserver import ThreadingMixIn import ssl import threading import time -from urllib.parse import urlparse, parse_qs +from datetime import timedelta +from http import cookies +from http.server import HTTPServer, SimpleHTTPRequestHandler +from socketserver import ThreadingMixIn +from urllib.parse import parse_qs, urlparse +import homeassistant.bootstrap as bootstrap import homeassistant.core as ha -from homeassistant.const import ( - SERVER_PORT, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, - HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING, - HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH, - HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED, - HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_UNPROCESSABLE_ENTITY) import homeassistant.remote as rem import homeassistant.util as util import homeassistant.util.dt as date_util -import homeassistant.bootstrap as bootstrap +from homeassistant.const import ( + CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING, + HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING, + HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES, + HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED, + HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, + SERVER_PORT) DOMAIN = "http" diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py index 6f406b24311..765cd000e2c 100644 --- a/homeassistant/components/ifttt.py +++ b/homeassistant/components/ifttt.py @@ -7,6 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/ifttt/ """ import logging + import requests from homeassistant.helpers import validate_config diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index b27cdefa9ba..3e19fd8692a 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -7,10 +7,11 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/influxdb/ """ import logging + import homeassistant.util as util -from homeassistant.helpers import validate_config +from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNKNOWN from homeassistant.helpers import state as state_helper -from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNKNOWN) +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py index 3d3c76459da..fc5a7856604 100644 --- a/homeassistant/components/input_boolean.py +++ b/homeassistant/components/input_boolean.py @@ -9,9 +9,9 @@ at https://home-assistant.io/components/input_boolean/ import logging from homeassistant.const import ( - STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.helpers.entity_component import EntityComponent + ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.util import slugify DOMAIN = 'input_boolean' diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index a211219581b..a2fdf276bef 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -9,8 +9,8 @@ at https://home-assistant.io/components/input_select/ import logging from homeassistant.const import ATTR_ENTITY_ID -from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.util import slugify DOMAIN = 'input_select' diff --git a/homeassistant/components/insteon_hub.py b/homeassistant/components/insteon_hub.py index adef1d379cf..c8f26eefd85 100644 --- a/homeassistant/components/insteon_hub.py +++ b/homeassistant/components/insteon_hub.py @@ -7,13 +7,14 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/insteon_hub/ """ import logging + import homeassistant.bootstrap as bootstrap -from homeassistant.helpers import validate_config -from homeassistant.loader import get_component -from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY, ATTR_DISCOVERED, - ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED) + ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, + EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers import validate_config +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.loader import get_component DOMAIN = "insteon_hub" REQUIREMENTS = ['insteon_hub==0.4.5'] diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 6a0cee5dd17..36a16db0d33 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -11,12 +11,12 @@ import logging from urllib.parse import urlparse from homeassistant import bootstrap -from homeassistant.loader import get_component +from homeassistant.const import ( + ATTR_DISCOVERED, ATTR_SERVICE, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) from homeassistant.helpers import validate_config from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import ( - CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED, - EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED) +from homeassistant.loader import get_component DOMAIN = "isy994" REQUIREMENTS = ['PyISY==1.0.5'] diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index ac6356ab574..b8c1e5f44d5 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -7,10 +7,9 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/keyboard/ """ from homeassistant.const import ( - SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, - SERVICE_MEDIA_PLAY_PAUSE) - + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE, + SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_UP) DOMAIN = "keyboard" REQUIREMENTS = ['pyuserinput==0.1.9'] diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/light/blinksticklight.py index fae9890c93d..5e2f026aa90 100644 --- a/homeassistant/components/light/blinksticklight.py +++ b/homeassistant/components/light/blinksticklight.py @@ -8,7 +8,7 @@ https://home-assistant.io/components/light.blinksticklight/ """ import logging -from homeassistant.components.light import Light, ATTR_RGB_COLOR +from homeassistant.components.light import ATTR_RGB_COLOR, Light _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index 73da0c51a09..fecec2d17d4 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -7,8 +7,7 @@ Demo platform that implements lights. import random from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP) - + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, Light) LIGHT_COLORS = [ [237, 224, 33], diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index b1843f4bca8..a970a8681c9 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -9,19 +9,19 @@ https://home-assistant.io/components/light.hue/ import json import logging import os -import socket import random +import socket from datetime import timedelta from urllib.parse import urlparse -from homeassistant.loader import get_component import homeassistant.util as util import homeassistant.util.color as color_util -from homeassistant.const import CONF_HOST, CONF_FILENAME, DEVICE_DEFAULT_NAME from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP, - ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT, - ATTR_EFFECT, EFFECT_COLORLOOP, EFFECT_RANDOM, ATTR_RGB_COLOR) + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR, + ATTR_TRANSITION, ATTR_XY_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM, + FLASH_LONG, FLASH_SHORT, Light) +from homeassistant.const import CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME +from homeassistant.loader import get_component REQUIREMENTS = ['phue==0.8'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index fe7d504ed4f..7eb112eccd6 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -6,12 +6,12 @@ Support for Hyperion remotes. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.hyperion/ """ +import json import logging import socket -import json +from homeassistant.components.light import ATTR_RGB_COLOR, Light from homeassistant.const import CONF_HOST -from homeassistant.components.light import (Light, ATTR_RGB_COLOR) _LOGGER = logging.getLogger(__name__) REQUIREMENTS = [] diff --git a/homeassistant/components/light/insteon_hub.py b/homeassistant/components/light/insteon_hub.py index e9f5b995777..6ce17b5622a 100644 --- a/homeassistant/components/light/insteon_hub.py +++ b/homeassistant/components/light/insteon_hub.py @@ -4,7 +4,7 @@ homeassistant.components.light.insteon Support for Insteon Hub lights. """ -from homeassistant.components.insteon_hub import (INSTEON, InsteonToggleDevice) +from homeassistant.components.insteon_hub import INSTEON, InsteonToggleDevice def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 6aa604e1b84..6d76ea45c58 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/isy994/ """ import logging -from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING, - HIDDEN_STRING) +from homeassistant.components.isy994 import ( + HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) from homeassistant.components.light import ATTR_BRIGHTNESS -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_OFF, STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 63c5e923ea6..5d58ca50811 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -8,11 +8,12 @@ https://home-assistant.io/components/light.lifx/ """ # pylint: disable=missing-docstring -import logging import colorsys +import logging + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, Light) from homeassistant.helpers.event import track_time_change -from homeassistant.components.light import \ - (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index e072c6ce962..5110c3a3570 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -8,12 +8,9 @@ https://home-assistant.io/components/light.limitlessled/ """ import logging -from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, - ATTR_RGB_COLOR, ATTR_EFFECT, - ATTR_COLOR_TEMP, ATTR_TRANSITION, - ATTR_FLASH, FLASH_LONG, - EFFECT_COLORLOOP, EFFECT_WHITE) - +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR, + ATTR_TRANSITION, EFFECT_COLORLOOP, EFFECT_WHITE, FLASH_LONG, Light) _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['limitlessled==1.0.0'] diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 3493c04db1b..3c860e074dd 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -6,12 +6,12 @@ Allows to configure a MQTT light. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.mqtt/ """ -from functools import partial import logging +from functools import partial import homeassistant.components.mqtt as mqtt -from homeassistant.components.light import (Light, - ATTR_BRIGHTNESS, ATTR_RGB_COLOR) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) from homeassistant.util.template import render_with_possible_json_value _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index b50446e2f38..3d9d2c14b13 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -6,17 +6,11 @@ https://home-assistant.io/components/light.mysensors/ """ import logging -from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) - -from homeassistant.util.color import ( - rgb_hex_to_rgb_list) - -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - STATE_ON, STATE_OFF) - import homeassistant.components.mysensors as mysensors +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON +from homeassistant.util.color import rgb_hex_to_rgb_list _LOGGER = logging.getLogger(__name__) ATTR_RGB_WHITE = 'rgb_white' diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index e690283e8d6..60d61c95a42 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -7,16 +7,13 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.rfxtrx/ """ import logging + import homeassistant.components.rfxtrx as rfxtrx - -from homeassistant.components.light import Light, ATTR_BRIGHTNESS -from homeassistant.util import slugify - -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.components.rfxtrx import ( - ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, - ATTR_NAME, EVENT_BUTTON_PRESSED) - + ATTR_FIREEVENT, ATTR_NAME, ATTR_PACKETID, ATTR_STATE, EVENT_BUTTON_PRESSED) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.util import slugify DEPENDENCIES = ['rfxtrx'] SIGNAL_REPETITIONS = 1 diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index 275e9812ace..e8d104242a5 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -7,10 +7,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.scsgate/ """ import logging + import homeassistant.components.scsgate as scsgate - from homeassistant.components.light import Light - from homeassistant.const import ATTR_ENTITY_ID DEPENDENCIES = ['scsgate'] diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 564d95f421b..c0f59abc10b 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -6,8 +6,9 @@ Support for Tellstick lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.tellstick/ """ -from homeassistant.components.light import Light, ATTR_BRIGHTNESS +from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.const import EVENT_HOMEASSISTANT_STOP + REQUIREMENTS = ['tellcore-py==1.1.2'] SIGNAL_REPETITIONS = 1 diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index dd5f4f2cbe2..179afa423ee 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -9,18 +9,12 @@ https://home-assistant.io/components/light.vera/ import logging from requests.exceptions import RequestException + import homeassistant.util.dt as dt_util - -from homeassistant.components.light import Light, ATTR_BRIGHTNESS - +from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - ATTR_TRIPPED, - ATTR_ARMED, - ATTR_LAST_TRIP_TIME, - EVENT_HOMEASSISTANT_STOP, - STATE_ON, - STATE_OFF) + ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, + EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) REQUIREMENTS = ['pyvera==0.2.8'] diff --git a/homeassistant/components/light/zigbee.py b/homeassistant/components/light/zigbee.py index fe275804627..5b6fe7fdc40 100644 --- a/homeassistant/components/light/zigbee.py +++ b/homeassistant/components/light/zigbee.py @@ -10,7 +10,6 @@ from homeassistant.components.light import Light from homeassistant.components.zigbee import ( ZigBeeDigitalOut, ZigBeeDigitalOutConfig) - DEPENDENCIES = ["zigbee"] diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 10d8cdef4d9..86cc7543073 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -10,11 +10,11 @@ https://home-assistant.io/components/light.zwave/ # pylint: disable=import-error from threading import Timer -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.components.light import Light, ATTR_BRIGHTNESS, DOMAIN +from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light from homeassistant.components.zwave import ( - COMMAND_CLASS_SWITCH_MULTILEVEL, TYPE_BYTE, GENRE_USER, NETWORK, - ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity) + ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SWITCH_MULTILEVEL, GENRE_USER, + NETWORK, TYPE_BYTE, ZWaveDeviceEntity) +from homeassistant.const import STATE_OFF, STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/lock/verisure.py index 818f8621144..6b73793e612 100644 --- a/homeassistant/components/lock/verisure.py +++ b/homeassistant/components/lock/verisure.py @@ -10,10 +10,7 @@ import logging import homeassistant.components.verisure as verisure from homeassistant.components.lock import LockDevice - -from homeassistant.const import ( - STATE_UNKNOWN, - STATE_LOCKED, STATE_UNLOCKED) +from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED _LOGGER = logging.getLogger(__name__) ATTR_CODE = 'code' diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index db1a4be3228..1e66c27686d 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -7,16 +7,17 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/logbook/ """ import logging +import re from datetime import timedelta from itertools import groupby -import re -from homeassistant.core import State, DOMAIN as HA_DOMAIN -from homeassistant.const import ( - EVENT_STATE_CHANGED, STATE_NOT_HOME, STATE_ON, STATE_OFF, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST) import homeassistant.util.dt as dt_util from homeassistant.components import recorder, sun +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, + HTTP_BAD_REQUEST, STATE_NOT_HOME, STATE_OFF, STATE_ON) +from homeassistant.core import DOMAIN as HA_DOMAIN +from homeassistant.core import State from homeassistant.helpers.entity import split_entity_id from homeassistant.util import template diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 4039c95c7fc..19b1dd46d3c 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -9,16 +9,14 @@ https://home-assistant.io/components/media_player.cast/ # pylint: disable=import-error import logging -from homeassistant.const import ( - STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF, - STATE_UNKNOWN, CONF_HOST) - from homeassistant.components.media_player import ( - MediaPlayerDevice, - SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) + MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + MediaPlayerDevice) +from homeassistant.const import ( + CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, + STATE_UNKNOWN) REQUIREMENTS = ['pychromecast==0.7.1'] CONF_IGNORE_CEC = 'ignore_cec' diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 98524275a5d..dfe8d781117 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -3,15 +3,12 @@ homeassistant.components.media_player.demo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo implementation of the media player. """ -from homeassistant.const import ( - STATE_PLAYING, STATE_PAUSED, STATE_OFF) - from homeassistant.components.media_player import ( - MediaPlayerDevice, - MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK, - SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA) + MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + MediaPlayerDevice) +from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING # pylint: disable=unused-argument diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/media_player/denon.py index 5d4b326f53e..2853dda90ac 100644 --- a/homeassistant/components/media_player/denon.py +++ b/homeassistant/components/media_player/denon.py @@ -6,16 +6,14 @@ Provides an interface to Denon Network Receivers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.denon/ """ -import telnetlib import logging +import telnetlib from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - DOMAIN) -from homeassistant.const import ( - CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN) + DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + MediaPlayerDevice) +from homeassistant.const import CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index 45e63a4534b..e151f1d516f 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -7,17 +7,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.firetv/ """ import logging + import requests -from homeassistant.const import ( - STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF, - STATE_UNKNOWN, STATE_STANDBY) - from homeassistant.components.media_player import ( - MediaPlayerDevice, - SUPPORT_PAUSE, SUPPORT_VOLUME_SET, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK) + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, MediaPlayerDevice) +from homeassistant.const import ( + STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, + STATE_UNKNOWN) SUPPORT_FIRETV = SUPPORT_PAUSE | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index 1f51885d731..584a692bd57 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -11,12 +11,12 @@ import logging import requests from homeassistant.components.media_player import ( - MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE, - SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, - SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA) + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + MediaPlayerDevice) from homeassistant.const import ( - STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON) + STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index 8b8d78fcae3..f7a7e9cf53a 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -6,14 +6,14 @@ Provides an interface to the XBMC/Kodi JSON-RPC API For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.kodi/ """ -import urllib import logging +import urllib from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK) + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) from homeassistant.const import ( - STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF) + STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['jsonrpc-requests==0.1'] diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index bef707c2711..a4a6c0cac61 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -9,15 +9,11 @@ https://home-assistant.io/components/media_player.mpd/ import logging import socket - -from homeassistant.const import ( - STATE_PLAYING, STATE_PAUSED, STATE_OFF) - from homeassistant.components.media_player import ( - MediaPlayerDevice, - SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - MEDIA_TYPE_MUSIC) + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_SET, MediaPlayerDevice) +from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['python-mpd2==0.5.4'] diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 94af635496d..0d68d7e9c30 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -6,20 +6,20 @@ Provides an interface to the Plex API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.plex/ """ -import os import json import logging +import os from datetime import timedelta from urllib.parse import urlparse -from homeassistant.loader import get_component import homeassistant.util as util from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, - SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) + MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PREVIOUS_TRACK, MediaPlayerDevice) from homeassistant.const import ( - DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING, - STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) + DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, + STATE_UNKNOWN) +from homeassistant.loader import get_component REQUIREMENTS = ['plexapi==1.1.0'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index 1d2703b0e2e..f7dc7f2a15b 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -10,14 +10,11 @@ import logging import socket from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_STEP, - SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, - SUPPORT_NEXT_TRACK, SUPPORT_TURN_OFF, - DOMAIN) + DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, + MediaPlayerDevice) from homeassistant.const import ( - CONF_HOST, CONF_NAME, STATE_OFF, - STATE_ON, STATE_UNKNOWN) - + CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN) from homeassistant.helpers import validate_config CONF_PORT = "port" diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index 23ce1515f39..5c8bd24a79d 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -10,12 +10,9 @@ https://home-assistant.io/components/media_player.snapcast/ import logging import socket -from homeassistant.const import ( - STATE_ON, STATE_OFF) - from homeassistant.components.media_player import ( - MediaPlayerDevice, - SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) +from homeassistant.const import STATE_OFF, STATE_ON SUPPORT_SNAPCAST = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE DOMAIN = 'snapcast' diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 7a66c3d354c..c0f9133a3d0 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -6,17 +6,15 @@ Provides an interface to Sonos players (via SoCo) For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.sonos/ """ -import logging import datetime +import logging from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - MEDIA_TYPE_MUSIC) - + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, MediaPlayerDevice) from homeassistant.const import ( - STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) - + STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) REQUIREMENTS = ['SoCo==0.11.1'] diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 5b0adfc7c4e..457e1bc539a 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -11,14 +11,12 @@ import telnetlib import urllib.parse from homeassistant.components.media_player import ( - MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - MEDIA_TYPE_MUSIC, DOMAIN) - + DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) from homeassistant.const import ( - CONF_HOST, CONF_USERNAME, CONF_PASSWORD, - STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_IDLE, STATE_OFF, + STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index bb5edacd79f..2bc4a8506f6 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -7,34 +7,28 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.universal/ """ +import logging # pylint: disable=import-error from copy import copy -import logging - -from homeassistant.helpers.event import track_state_change -from homeassistant.helpers.service import call_from_config - -from homeassistant.const import ( - STATE_IDLE, STATE_ON, STATE_OFF, CONF_NAME, - ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, - SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, - SERVICE_VOLUME_MUTE, - SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) from homeassistant.components.media_player import ( - MediaPlayerDevice, DOMAIN, - SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - SERVICE_PLAY_MEDIA, - ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED, - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION, - ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME, - ATTR_MEDIA_TRACK, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_ALBUM_ARTIST, - ATTR_MEDIA_SEASON, ATTR_MEDIA_EPISODE, ATTR_MEDIA_CHANNEL, - ATTR_MEDIA_PLAYLIST, ATTR_APP_ID, ATTR_APP_NAME, ATTR_MEDIA_VOLUME_LEVEL, - ATTR_MEDIA_SEEK_POSITION) + ATTR_APP_ID, ATTR_APP_NAME, ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, + ATTR_MEDIA_ARTIST, ATTR_MEDIA_CHANNEL, ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION, ATTR_MEDIA_EPISODE, + ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, + ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK, + ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, + ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, MediaPlayerDevice) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK, + SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, + SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF, + SERVICE_TURN_ON, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE, STATE_OFF, STATE_ON) +from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.service import call_from_config ATTR_ACTIVE_CHILD = 'active_child' diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 6f53c89835a..0c625968f14 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -8,8 +8,8 @@ https://home-assistant.io/components/modbus/ """ import logging -from homeassistant.const import (EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) DOMAIN = "modbus" diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py index e69639572ca..0574c07ebf9 100644 --- a/homeassistant/components/mqtt_eventstream.py +++ b/homeassistant/components/mqtt_eventstream.py @@ -7,18 +7,14 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/mqtt_eventstream.html """ import json -from homeassistant.core import EventOrigin, State + +import homeassistant.loader as loader from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH from homeassistant.const import ( - ATTR_SERVICE_DATA, - MATCH_ALL, - EVENT_TIME_CHANGED, - EVENT_CALL_SERVICE, - EVENT_SERVICE_EXECUTED, - EVENT_STATE_CHANGED, -) -import homeassistant.loader as loader + ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED, + EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) +from homeassistant.core import EventOrigin, State from homeassistant.remote import JSONEncoder # The domain of your component. Should be equal to the name of your component diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 30775c4daae..53987fa1435 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -10,14 +10,11 @@ https://home-assistant.io/components/sensor.mysensors/ """ import logging -from homeassistant.helpers import validate_config import homeassistant.bootstrap as bootstrap - from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, - EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, - TEMP_CELCIUS,) + ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS) +from homeassistant.helpers import validate_config CONF_GATEWAYS = 'gateways' CONF_PORT = 'port' diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index 1b1d940595a..c5daf7e0734 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -8,7 +8,7 @@ https://home-assistant.io/components/thermostat.nest/ """ import logging -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME REQUIREMENTS = ['python-nest==2.6.0'] DOMAIN = 'nest' diff --git a/homeassistant/components/notify/demo.py b/homeassistant/components/notify/demo.py index c9afabd0a6b..4ff4ec3931c 100644 --- a/homeassistant/components/notify/demo.py +++ b/homeassistant/components/notify/demo.py @@ -6,7 +6,6 @@ Demo notification service. """ from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService - EVENT_NOTIFY = "notify" diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/notify/file.py index 1469ac4558f..5f9befa9510 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/notify/file.py @@ -10,9 +10,9 @@ import logging import os import homeassistant.util.dt as dt_util -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/free_mobile.py b/homeassistant/components/notify/free_mobile.py index 3589dc58658..10ba96e2e73 100644 --- a/homeassistant/components/notify/free_mobile.py +++ b/homeassistant/components/notify/free_mobile.py @@ -7,10 +7,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.free_mobile/ """ import logging + +from homeassistant.components.notify import DOMAIN, BaseNotificationService +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME from homeassistant.helpers import validate_config -from homeassistant.components.notify import ( - DOMAIN, BaseNotificationService) -from homeassistant.const import CONF_USERNAME, CONF_ACCESS_TOKEN _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['freesms==0.1.0'] diff --git a/homeassistant/components/notify/googlevoice.py b/homeassistant/components/notify/googlevoice.py index e37586c5cf6..c66ec5e99a3 100644 --- a/homeassistant/components/notify/googlevoice.py +++ b/homeassistant/components/notify/googlevoice.py @@ -7,10 +7,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.free_mobile/ """ import logging -from homeassistant.helpers import validate_config + from homeassistant.components.notify import ( - DOMAIN, ATTR_TARGET, BaseNotificationService) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD + ATTR_TARGET, DOMAIN, BaseNotificationService) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['https://github.com/w1ll1am23/pygooglevoice-sms/archive/' diff --git a/homeassistant/components/notify/instapush.py b/homeassistant/components/notify/instapush.py index fbc9e3cbbbd..d3dd05d7e54 100644 --- a/homeassistant/components/notify/instapush.py +++ b/homeassistant/components/notify/instapush.py @@ -6,15 +6,15 @@ Instapush notification service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.instapush/ """ -import logging import json +import logging import requests -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://api.instapush.im/v1/' diff --git a/homeassistant/components/notify/nma.py b/homeassistant/components/notify/nma.py index 74ed796804c..5133bbf287d 100644 --- a/homeassistant/components/notify/nma.py +++ b/homeassistant/components/notify/nma.py @@ -11,10 +11,10 @@ import xml.etree.ElementTree as ET import requests -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://www.notifymyandroid.com/publicapi/' diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 941a78ac709..c7fbf5478e3 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -9,7 +9,7 @@ https://home-assistant.io/components/notify.pushbullet/ import logging from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TARGET, BaseNotificationService) + ATTR_TARGET, ATTR_TITLE, BaseNotificationService) from homeassistant.const import CONF_API_KEY _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/pushetta.py b/homeassistant/components/notify/pushetta.py index 22bbd5175c6..d2c797d2fd7 100644 --- a/homeassistant/components/notify/pushetta.py +++ b/homeassistant/components/notify/pushetta.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/notify.pushetta/ """ import logging -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index 7c776300cdb..6eaa1b91643 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/notify.pushover/ """ import logging -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config REQUIREMENTS = ['python-pushover==0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/notify/rest.py index e0bb50216bd..7f5582fd0fe 100644 --- a/homeassistant/components/notify/rest.py +++ b/homeassistant/components/notify/rest.py @@ -7,11 +7,12 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.rest/ """ import logging + import requests -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, ATTR_TARGET, BaseNotificationService) + ATTR_TARGET, ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 7a386d8864f..75034aebb32 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/notify.slack/ """ import logging -from homeassistant.helpers import validate_config from homeassistant.components.notify import DOMAIN, BaseNotificationService from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config REQUIREMENTS = ['slacker==0.6.8'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/smtp.py b/homeassistant/components/notify/smtp.py index 833fffb9f99..8ee48ed9644 100644 --- a/homeassistant/components/notify/smtp.py +++ b/homeassistant/components/notify/smtp.py @@ -10,9 +10,9 @@ import logging import smtplib from email.mime.text import MIMEText -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/syslog.py b/homeassistant/components/notify/syslog.py index 56075a6dd09..b568ce7d41c 100644 --- a/homeassistant/components/notify/syslog.py +++ b/homeassistant/components/notify/syslog.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/notify.syslog/ """ import logging -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py index c5dd917cb39..727ba578765 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/notify/telegram.py @@ -9,10 +9,10 @@ https://home-assistant.io/components/notify.telegram/ import logging import urllib -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/notify/twitter.py index e2a27d68a05..e0770381d72 100644 --- a/homeassistant/components/notify/twitter.py +++ b/homeassistant/components/notify/twitter.py @@ -7,10 +7,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.twitter/ """ import logging -from homeassistant.helpers import validate_config -from homeassistant.components.notify import ( - DOMAIN, BaseNotificationService) + +from homeassistant.components.notify import DOMAIN, BaseNotificationService from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['TwitterAPI==2.3.6'] diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index 4b688fb7a79..9b3bec0b195 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/notify.xmpp/ """ import logging -from homeassistant.helpers import validate_config from homeassistant.components.notify import ( - DOMAIN, ATTR_TITLE, BaseNotificationService) + ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0'] diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index 1bad6a7c485..ba52dce91e0 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -8,8 +8,9 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/proximity/ """ import logging -from homeassistant.helpers.event import track_state_change + from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import track_state_change from homeassistant.util.location import distance DEPENDENCIES = ['zone', 'device_tracker'] diff --git a/homeassistant/components/recorder.py b/homeassistant/components/recorder.py index 5476d082b38..eb30a930cae 100644 --- a/homeassistant/components/recorder.py +++ b/homeassistant/components/recorder.py @@ -7,20 +7,20 @@ to query this database. For more details about this component, please refer to the documentation at https://home-assistant.io/components/recorder/ """ +import atexit +import json import logging -import threading import queue import sqlite3 -from datetime import datetime, date -import json -import atexit +import threading +from datetime import date, datetime -from homeassistant.core import Event, EventOrigin, State import homeassistant.util.dt as dt_util -from homeassistant.remote import JSONEncoder from homeassistant.const import ( - MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, + EVENT_TIME_CHANGED, MATCH_ALL) +from homeassistant.core import Event, EventOrigin, State +from homeassistant.remote import JSONEncoder DOMAIN = "recorder" diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index 60736585c28..4789348c69a 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -7,6 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/rfxtrx/ """ import logging + from homeassistant.util import slugify REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip' + diff --git a/homeassistant/components/rollershutter/demo.py b/homeassistant/components/rollershutter/demo.py index 4ffeb6be6dd..d4af86d4cab 100644 --- a/homeassistant/components/rollershutter/demo.py +++ b/homeassistant/components/rollershutter/demo.py @@ -3,9 +3,9 @@ homeassistant.components.rollershutter.demo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo platform for the rollorshutter component. """ +from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.const import EVENT_TIME_CHANGED from homeassistant.helpers.event import track_utc_time_change -from homeassistant.components.rollershutter import RollershutterDevice def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/rollershutter/mqtt.py b/homeassistant/components/rollershutter/mqtt.py index 2951d772be0..97c8094f865 100644 --- a/homeassistant/components/rollershutter/mqtt.py +++ b/homeassistant/components/rollershutter/mqtt.py @@ -7,9 +7,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter.mqtt/ """ import logging + import homeassistant.components.mqtt as mqtt -from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.components.rollershutter import RollershutterDevice +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.util import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rollershutter/scsgate.py b/homeassistant/components/rollershutter/scsgate.py index e47d1574697..55261a2a617 100644 --- a/homeassistant/components/rollershutter/scsgate.py +++ b/homeassistant/components/rollershutter/scsgate.py @@ -7,10 +7,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter.scsgate/ """ import logging + import homeassistant.components.scsgate as scsgate from homeassistant.components.rollershutter import RollershutterDevice - DEPENDENCIES = ['scsgate'] diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py index 804cb555ae0..899d51c4cde 100644 --- a/homeassistant/components/rpi_gpio.py +++ b/homeassistant/components/rpi_gpio.py @@ -8,8 +8,10 @@ https://home-assistant.io/components/rpi_gpio/ """ # pylint: disable=import-error import logging -from homeassistant.const import (EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP) + +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + REQUIREMENTS = ['RPi.GPIO==0.6.1'] DOMAIN = "rpi_gpio" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py index ce1a3242542..494c224c416 100644 --- a/homeassistant/components/scene.py +++ b/homeassistant/components/scene.py @@ -9,12 +9,12 @@ https://home-assistant.io/components/scene/ import logging from collections import namedtuple +from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_OFF, STATE_ON) from homeassistant.core import State from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.state import reproduce_state -from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON) DOMAIN = 'scene' DEPENDENCIES = ['group'] diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 167ddb55957..59b55a1289b 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -8,19 +8,19 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/script/ """ import logging +import threading from datetime import timedelta from itertools import islice -import threading -from homeassistant.helpers.entity_component import EntityComponent +import homeassistant.util.dt as date_util +from homeassistant.const import ( + ATTR_ENTITY_ID, EVENT_TIME_CHANGED, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_ON) from homeassistant.helpers.entity import ToggleEntity, split_entity_id +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.service import call_from_config from homeassistant.util import slugify -import homeassistant.util.dt as date_util -from homeassistant.const import ( - ATTR_ENTITY_ID, EVENT_TIME_CHANGED, STATE_ON, SERVICE_TURN_ON, - SERVICE_TURN_OFF) DOMAIN = "script" ENTITY_ID_FORMAT = DOMAIN + '.{}' diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py index 5f913b4f608..ded340eb241 100644 --- a/homeassistant/components/scsgate.py +++ b/homeassistant/components/scsgate.py @@ -8,6 +8,7 @@ https://home-assistant.io/components/scsgate/ """ import logging from threading import Lock + from homeassistant.core import EVENT_HOMEASSISTANT_STOP REQUIREMENTS = ['scsgate==0.1.0'] diff --git a/homeassistant/components/sensor/apcupsd.py b/homeassistant/components/sensor/apcupsd.py index 8a566225438..bed8740a55e 100644 --- a/homeassistant/components/sensor/apcupsd.py +++ b/homeassistant/components/sensor/apcupsd.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/sensor.apcupsd/ """ import logging +from homeassistant.components import apcupsd from homeassistant.const import TEMP_CELCIUS from homeassistant.helpers.entity import Entity -from homeassistant.components import apcupsd DEPENDENCIES = [apcupsd.DOMAIN] DEFAULT_NAME = "UPS Status" diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py index d4020e465c3..a99765808f8 100644 --- a/homeassistant/components/sensor/arduino.py +++ b/homeassistant/components/sensor/arduino.py @@ -10,8 +10,8 @@ https://home-assistant.io/components/sensor.arduino/ import logging import homeassistant.components.arduino as arduino -from homeassistant.helpers.entity import Entity from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.helpers.entity import Entity DEPENDENCIES = ['arduino'] diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index dd9281c484a..0a1680c2aa2 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -6,16 +6,16 @@ The arest sensor will consume an exposed aREST API of a device. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.arest/ """ -from datetime import timedelta import logging +from datetime import timedelta import requests -from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, - DEVICE_DEFAULT_NAME) +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity -from homeassistant.util import template, Throttle +from homeassistant.util import Throttle, template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index fe17ff574b1..d2b05d774e4 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -9,9 +9,8 @@ https://home-assistant.io/components/sensor.bitcoin/ import logging from datetime import timedelta -from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity - +from homeassistant.util import Throttle REQUIREMENTS = ['blockchain==1.2.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/bloomsky.py b/homeassistant/components/sensor/bloomsky.py index ca7765d6bd6..902b42d6e3f 100644 --- a/homeassistant/components/sensor/bloomsky.py +++ b/homeassistant/components/sensor/bloomsky.py @@ -7,8 +7,9 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/sensor.bloomsky/ """ import logging -from homeassistant.loader import get_component + from homeassistant.helpers.entity import Entity +from homeassistant.loader import get_component DEPENDENCIES = ["bloomsky"] diff --git a/homeassistant/components/sensor/command_sensor.py b/homeassistant/components/sensor/command_sensor.py index 95751e73645..a0877ac6888 100644 --- a/homeassistant/components/sensor/command_sensor.py +++ b/homeassistant/components/sensor/command_sensor.py @@ -12,7 +12,7 @@ from datetime import timedelta from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers.entity import Entity -from homeassistant.util import template, Throttle +from homeassistant.util import Throttle, template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index a3b9b8a2692..f0ddbd4a2a0 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -3,8 +3,8 @@ homeassistant.components.sensor.demo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo platform that has a couple of fake sensors. """ +from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELCIUS from homeassistant.helpers.entity import Entity -from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL # pylint: disable=unused-argument diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index 14bb7f6bf99..a3019f2b84d 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/sensor.dht/ import logging from datetime import timedelta -from homeassistant.util import Throttle from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle # update this requirement to upstream as soon as it supports python3 REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py index 8aa55c847cf..f1b90d59f47 100644 --- a/homeassistant/components/sensor/dweet.py +++ b/homeassistant/components/sensor/dweet.py @@ -6,14 +6,13 @@ Displays values from Dweet.io. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.dweet/ """ -from datetime import timedelta -import logging import json +import logging +from datetime import timedelta -from homeassistant.util import Throttle -from homeassistant.util import template +from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from homeassistant.const import (STATE_UNKNOWN, CONF_VALUE_TEMPLATE) +from homeassistant.util import Throttle, template _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['dweepy==0.2.0'] diff --git a/homeassistant/components/sensor/ecobee.py b/homeassistant/components/sensor/ecobee.py index a0f6f817d66..39ba1d1ef50 100644 --- a/homeassistant/components/sensor/ecobee.py +++ b/homeassistant/components/sensor/ecobee.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/sensor.ecobee/ """ import logging -from homeassistant.helpers.entity import Entity from homeassistant.components import ecobee from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.helpers.entity import Entity DEPENDENCIES = ['ecobee'] SENSOR_TYPES = { diff --git a/homeassistant/components/sensor/efergy.py b/homeassistant/components/sensor/efergy.py index 23023b90fe1..71c8f8ed789 100644 --- a/homeassistant/components/sensor/efergy.py +++ b/homeassistant/components/sensor/efergy.py @@ -8,7 +8,8 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.efergy/ """ import logging -from requests import get, RequestException + +from requests import RequestException, get from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index 4f30e1efd00..fb5cd0f1211 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -8,8 +8,9 @@ https://home-assistant.io/components/sensor.eliqonline/ """ import logging from urllib.error import URLError + +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from homeassistant.const import (STATE_UNKNOWN, CONF_ACCESS_TOKEN, CONF_NAME) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 8cbc332678e..5e080a341c0 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/sensor.forecast/ import logging from datetime import timedelta -from homeassistant.util import Throttle -from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS) +from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle REQUIREMENTS = ['python-forecastio==1.3.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index eb38e3df265..be46c20ba77 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -6,14 +6,14 @@ Gathers system information of hosts which running glances. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.glances/ """ -from datetime import timedelta import logging +from datetime import timedelta import requests -from homeassistant.util import Throttle -from homeassistant.helpers.entity import Entity from homeassistant.const import STATE_UNKNOWN +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 9e4ccf4dedb..8ef42ab5b2d 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/isy994/ """ import logging -from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, - HIDDEN_STRING) -from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME, - STATE_NOT_HOME, STATE_ON, STATE_OFF) +from homeassistant.components.isy994 import ( + HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) +from homeassistant.const import ( + STATE_CLOSED, STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN) DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like', 'Temperature_Average', 'Pressure', 'Dew_Point', diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index d361fa9f690..c29d5cea7b8 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -8,11 +8,10 @@ https://home-assistant.io/components/sensor.mfi/ """ import logging -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, - TEMP_CELCIUS) from homeassistant.components.sensor import DOMAIN -from homeassistant.helpers.entity import Entity +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS from homeassistant.helpers import validate_config +from homeassistant.helpers.entity import Entity REQUIREMENTS = ['mficlient==0.2.2'] diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 269a9f2dc96..2483855c589 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -9,10 +9,9 @@ https://home-assistant.io/components/sensor.modbus/ import logging import homeassistant.components.modbus as modbus -from homeassistant.helpers.entity import Entity from homeassistant.const import ( - TEMP_CELCIUS, TEMP_FAHRENHEIT, - STATE_ON, STATE_OFF) + STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 032462430e0..1e6a49ad9c4 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -7,10 +7,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mqtt/ """ import logging + +import homeassistant.components.mqtt as mqtt from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity from homeassistant.util import template -import homeassistant.components.mqtt as mqtt _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index ca4df33462b..d33be7f2903 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -6,14 +6,10 @@ https://home-assistant.io/components/sensor.mysensors/ """ import logging -from homeassistant.helpers.entity import Entity - -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - TEMP_CELCIUS, - STATE_ON, STATE_OFF) - import homeassistant.components.mysensors as mysensors +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS) +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index f3a5e5dca7c..8aab84fae6b 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/sensor.nest/ """ import logging import socket -import homeassistant.components.nest as nest -from homeassistant.helpers.entity import Entity +import homeassistant.components.nest as nest from homeassistant.const import TEMP_CELCIUS +from homeassistant.helpers.entity import Entity DEPENDENCIES = ['nest'] SENSOR_TYPES = ['humidity', diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 0375fcf4d74..29271b82ea9 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -8,11 +8,12 @@ https://home-assistant.io/components/sensor.netatmo/ """ import logging from datetime import timedelta + from homeassistant.components.sensor import DOMAIN -from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, - TEMP_CELCIUS) -from homeassistant.helpers.entity import Entity +from homeassistant.const import ( + CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS) from homeassistant.helpers import validate_config +from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle REQUIREMENTS = [ diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py index 9a2b0652789..f3b22c0383c 100644 --- a/homeassistant/components/sensor/neurio_energy.py +++ b/homeassistant/components/sensor/neurio_energy.py @@ -6,6 +6,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.neurio_energy/ """ import logging + import requests.exceptions from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/sensor/onewire.py b/homeassistant/components/sensor/onewire.py index c1cadb71e19..5a198864c38 100644 --- a/homeassistant/components/sensor/onewire.py +++ b/homeassistant/components/sensor/onewire.py @@ -6,11 +6,12 @@ Support for DS18B20 One Wire Sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.onewire/ """ -from glob import glob import logging import os import time -from homeassistant.const import TEMP_CELCIUS, STATE_UNKNOWN +from glob import glob + +from homeassistant.const import STATE_UNKNOWN, TEMP_CELCIUS from homeassistant.helpers.entity import Entity BASE_DIR = '/sys/bus/w1/devices/' diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index ad5693cab03..49c1eadf36c 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/sensor.openweathermap/ import logging from datetime import timedelta -from homeassistant.util import Throttle -from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle REQUIREMENTS = ['pyowm==2.3.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py index fdbc1ab26e3..fb6499366f1 100644 --- a/homeassistant/components/sensor/rest.py +++ b/homeassistant/components/sensor/rest.py @@ -6,13 +6,14 @@ The rest sensor will consume responses sent by an exposed REST API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.rest/ """ -from datetime import timedelta import logging +from datetime import timedelta + import requests -from homeassistant.const import (CONF_VALUE_TEMPLATE, STATE_UNKNOWN) -from homeassistant.util import template, Throttle +from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle, template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 80485dfefcf..66e8c9f218e 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/sensor.rfxtrx/ import logging from collections import OrderedDict -from homeassistant.const import (TEMP_CELCIUS) -from homeassistant.helpers.entity import Entity import homeassistant.components.rfxtrx as rfxtrx +from homeassistant.const import TEMP_CELCIUS +from homeassistant.helpers.entity import Entity from homeassistant.util import slugify DEPENDENCIES = ['rfxtrx'] diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index 6b42453b5d3..5d8de104a5a 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -6,8 +6,8 @@ Monitors SABnzbd NZB client API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sabnzbd/ """ -from datetime import timedelta import logging +from datetime import timedelta from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py index 7bb3c77ed09..629495f2be0 100644 --- a/homeassistant/components/sensor/speedtest.py +++ b/homeassistant/components/sensor/speedtest.py @@ -7,15 +7,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.speedtest/ """ import logging -import sys import re +import sys from datetime import timedelta from subprocess import check_output -from homeassistant.util import Throttle + +import homeassistant.util.dt as dt_util +from homeassistant.components.sensor import DOMAIN from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_change -from homeassistant.components.sensor import DOMAIN -import homeassistant.util.dt as dt_util +from homeassistant.util import Throttle REQUIREMENTS = ['speedtest-cli==0.3.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 5304101900b..4d44e58619f 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -9,11 +9,12 @@ https://home-assistant.io/components/sensor.swiss_public_transport/ """ import logging from datetime import timedelta + import requests -from homeassistant.util import Throttle import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) _RESOURCE = 'http://transport.opendata.ch/v1/' diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index 63052e71b93..9e191a112b0 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -9,8 +9,8 @@ https://home-assistant.io/components/sensor.systemmonitor/ import logging import homeassistant.util.dt as dt_util +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.entity import Entity -from homeassistant.const import STATE_ON, STATE_OFF REQUIREMENTS = ['psutil==3.4.2'] SENSOR_TYPES = { diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index fd7679f89bf..001d20ee792 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -8,14 +8,12 @@ https://home-assistant.io/components/sensor.tellduslive/ """ import logging - from datetime import datetime -from homeassistant.const import (TEMP_CELCIUS, - ATTR_BATTERY_LEVEL, - DEVICE_DEFAULT_NAME) -from homeassistant.helpers.entity import Entity from homeassistant.components import tellduslive +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, TEMP_CELCIUS) +from homeassistant.helpers.entity import Entity ATTR_LAST_UPDATED = "time_last_updated" diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index c6993de462d..0b8b80c3388 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/sensor.tellstick/ import logging from collections import namedtuple +import homeassistant.util as util from homeassistant.const import TEMP_CELCIUS from homeassistant.helpers.entity import Entity -import homeassistant.util as util DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit']) diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index fe12053643c..a330ef064c0 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -7,8 +7,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.temper/ """ import logging -from homeassistant.helpers.entity import Entity + from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 17b739f88cc..0b58c075893 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -9,17 +9,13 @@ https://home-assistant.io/components/sensor.template/ """ import logging -from homeassistant.helpers.entity import Entity, generate_entity_id -from homeassistant.core import EVENT_STATE_CHANGED -from homeassistant.const import ( - ATTR_FRIENDLY_NAME, - CONF_VALUE_TEMPLATE, - ATTR_UNIT_OF_MEASUREMENT) - -from homeassistant.util import template, slugify -from homeassistant.exceptions import TemplateError - from homeassistant.components.sensor import DOMAIN +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) +from homeassistant.core import EVENT_STATE_CHANGED +from homeassistant.exceptions import TemplateError +from homeassistant.helpers.entity import Entity, generate_entity_id +from homeassistant.util import slugify, template ENTITY_ID_FORMAT = DOMAIN + '.{}' diff --git a/homeassistant/components/sensor/torque.py b/homeassistant/components/sensor/torque.py index e123aa2d18c..c3e5b8541ad 100644 --- a/homeassistant/components/sensor/torque.py +++ b/homeassistant/components/sensor/torque.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/sensor.torque/ """ import re + from homeassistant.const import HTTP_OK from homeassistant.helpers.entity import Entity - DOMAIN = 'torque' DEPENDENCIES = ['http'] SENSOR_EMAIL_FIELD = 'eml' diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index 26062cbba4d..662394f3b18 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -6,12 +6,12 @@ Monitors Transmission BitTorrent client API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.transmission/ """ -from datetime import timedelta import logging +from datetime import timedelta -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD -from homeassistant.util import Throttle +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle REQUIREMENTS = ['transmissionrpc==0.11'] SENSOR_TYPES = { diff --git a/homeassistant/components/sensor/twitch.py b/homeassistant/components/sensor/twitch.py index 7a65211268e..ea743992fb4 100644 --- a/homeassistant/components/sensor/twitch.py +++ b/homeassistant/components/sensor/twitch.py @@ -6,8 +6,8 @@ A sensor for the Twitch stream status. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.twitch/ """ -from homeassistant.helpers.entity import Entity from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.helpers.entity import Entity STATE_STREAMING = 'streaming' STATE_OFFLINE = 'offline' diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 8839cdc4e1c..d919b57e2cd 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -7,13 +7,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.vera/ """ import logging -from requests.exceptions import RequestException -import homeassistant.util.dt as dt_util -from homeassistant.helpers.entity import Entity +from requests.exceptions import RequestException + +import homeassistant.util.dt as dt_util from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME, - TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP) + ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, + EVENT_HOMEASSISTANT_STOP, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity REQUIREMENTS = ['pyvera==0.2.8'] diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 1ba750aa3e9..f397242615d 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -9,9 +9,8 @@ documentation at https://home-assistant.io/components/verisure/ import logging import homeassistant.components.verisure as verisure - -from homeassistant.helpers.entity import Entity from homeassistant.const import TEMP_CELCIUS +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index 71fadff157b..3eca0858433 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -8,8 +8,8 @@ at https://home-assistant.io/components/sensor.wink/ """ import logging +from homeassistant.const import CONF_ACCESS_TOKEN, STATE_CLOSED, STATE_OPEN from homeassistant.helpers.entity import Entity -from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED REQUIREMENTS = ['python-wink==0.6.0'] diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 85d90264462..c4a743f21a0 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -10,11 +10,11 @@ import logging import requests -from homeassistant.const import (ATTR_ENTITY_PICTURE, - CONF_LATITUDE, - CONF_LONGITUDE) +from homeassistant.const import ( + ATTR_ENTITY_PICTURE, CONF_LATITUDE, CONF_LONGITUDE) from homeassistant.helpers.entity import Entity -from homeassistant.util import location, dt as dt_util +from homeassistant.util import dt as dt_util +from homeassistant.util import location _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/zigbee.py b/homeassistant/components/sensor/zigbee.py index 78023df93f8..1f165e97c44 100644 --- a/homeassistant/components/sensor/zigbee.py +++ b/homeassistant/components/sensor/zigbee.py @@ -10,11 +10,10 @@ https://home-assistant.io/components/sensor.zigbee/ import logging from binascii import hexlify -from homeassistant.core import JobPriority -from homeassistant.const import TEMP_CELCIUS -from homeassistant.helpers.entity import Entity from homeassistant.components import zigbee - +from homeassistant.const import TEMP_CELCIUS +from homeassistant.core import JobPriority +from homeassistant.helpers.entity import Entity DEPENDENCIES = ["zigbee"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index ddca961f3a5..f011b82b9f3 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -10,17 +10,16 @@ at https://home-assistant.io/components/zwave/ # pylint: disable=import-error import datetime -from homeassistant.helpers.event import track_point_in_time import homeassistant.util.dt as dt_util from homeassistant.components.sensor import DOMAIN -from homeassistant.helpers.entity import Entity from homeassistant.components.zwave import ( - NETWORK, ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SENSOR_BINARY, - COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, TYPE_DECIMAL, - COMMAND_CLASS_ALARM, ZWaveDeviceEntity, get_config_value) - + ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_ALARM, COMMAND_CLASS_METER, + COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL, NETWORK, + TYPE_DECIMAL, ZWaveDeviceEntity, get_config_value) from homeassistant.const import ( - STATE_ON, STATE_OFF, TEMP_CELCIUS, TEMP_FAHRENHEIT) + STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import track_point_in_time PHILIO = '0x013c' PHILIO_SLIM_SENSOR = '0x0002' diff --git a/homeassistant/components/simple_alarm.py b/homeassistant/components/simple_alarm.py index 7c45e199dfc..6fb48e708c5 100644 --- a/homeassistant/components/simple_alarm.py +++ b/homeassistant/components/simple_alarm.py @@ -9,8 +9,8 @@ https://home-assistant.io/components/simple_alarm/ import logging import homeassistant.loader as loader +from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON from homeassistant.helpers.event import track_state_change -from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME DOMAIN = "simple_alarm" diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk.py index fd9a15b67ae..6db577865eb 100644 --- a/homeassistant/components/splunk.py +++ b/homeassistant/components/splunk.py @@ -13,9 +13,9 @@ import logging import requests import homeassistant.util as util -from homeassistant.helpers import validate_config -from homeassistant.helpers import state as state_helper from homeassistant.const import EVENT_STATE_CHANGED +from homeassistant.helpers import state as state_helper +from homeassistant.helpers import validate_config _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd.py index 640b703d5d9..6dbf1ae8bda 100644 --- a/homeassistant/components/statsd.py +++ b/homeassistant/components/statsd.py @@ -7,6 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/statsd/ """ import logging + import homeassistant.util as util from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.helpers import state as state_helper diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 303e352d55a..90b3432ba13 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -10,10 +10,11 @@ import logging from datetime import timedelta import homeassistant.util as util -from homeassistant.util import location as location_util, dt as dt_util +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( track_point_in_utc_time, track_utc_time_change) -from homeassistant.helpers.entity import Entity +from homeassistant.util import dt as dt_util +from homeassistant.util import location as location_util REQUIREMENTS = ['astral==0.9'] DOMAIN = "sun" diff --git a/homeassistant/components/switch/arest.py b/homeassistant/components/switch/arest.py index c42295660d4..957ae7bab92 100644 --- a/homeassistant/components/switch/arest.py +++ b/homeassistant/components/switch/arest.py @@ -8,6 +8,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.arest/ """ import logging + import requests from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 25978147853..82a5a2222a4 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -8,10 +8,10 @@ https://home-assistant.io/components/switch.edimax/ """ import logging +from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME) from homeassistant.helpers import validate_config -from homeassistant.components.switch import SwitchDevice, DOMAIN -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\ - CONF_NAME # constants DEFAULT_USERNAME = 'admin' diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index c85aae4a7f0..15ce210f6e9 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -8,9 +8,9 @@ https://home-assistant.io/components/switch.hikvision/ """ import logging +from homeassistant.const import ( + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_OFF, STATE_ON) from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import (STATE_ON, STATE_OFF, - CONF_HOST, CONF_USERNAME, CONF_PASSWORD) _LOGGING = logging.getLogger(__name__) REQUIREMENTS = ['hikvision==0.4'] diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index c8a04a66709..0ee45aef4db 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -8,9 +8,11 @@ https://home-assistant.io/components/isy994/ """ import logging -from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, - HIDDEN_STRING) -from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED +from homeassistant.components.isy994 import ( + HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) +from homeassistant.const import STATE_OFF, STATE_ON # STATE_OPEN, STATE_CLOSED + + # The frontend doesn't seem to fully support the open and closed states yet. # Once it does, the HA.doors programs should report open and closed instead of # off and on. It appears that on should be open and off should be closed. diff --git a/homeassistant/components/switch/mfi.py b/homeassistant/components/switch/mfi.py index 46cfdb56213..42abe75af60 100644 --- a/homeassistant/components/switch/mfi.py +++ b/homeassistant/components/switch/mfi.py @@ -9,7 +9,7 @@ https://home-assistant.io/components/switch.mfi/ import logging from homeassistant.components.switch import DOMAIN, SwitchDevice -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config REQUIREMENTS = ['mficlient==0.2.2'] diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index c27709cc522..01360b02bec 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -7,9 +7,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.mqtt/ """ import logging + import homeassistant.components.mqtt as mqtt -from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.components.switch import SwitchDevice +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.util import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 19c8f7112f6..4254158d745 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -6,13 +6,9 @@ https://home-assistant.io/components/switch.mysensors/ """ import logging -from homeassistant.components.switch import SwitchDevice - -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - STATE_ON, STATE_OFF) - import homeassistant.components.mysensors as mysensors +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] diff --git a/homeassistant/components/switch/mystrom.py b/homeassistant/components/switch/mystrom.py index 919ff28e4ef..991726a43f2 100644 --- a/homeassistant/components/switch/mystrom.py +++ b/homeassistant/components/switch/mystrom.py @@ -7,6 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/switch.mystrom/ """ import logging + import requests from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/switch/rest.py index 5c4b9b37e1e..89fbd69682d 100644 --- a/homeassistant/components/switch/rest.py +++ b/homeassistant/components/switch/rest.py @@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.rest/ """ import logging + import requests from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 3e678d70b9e..52cbac2319d 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -7,16 +7,13 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.rfxtrx/ """ import logging + import homeassistant.components.rfxtrx as rfxtrx - -from homeassistant.components.switch import SwitchDevice -from homeassistant.util import slugify - -from homeassistant.const import ATTR_ENTITY_ID from homeassistant.components.rfxtrx import ( - ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, - ATTR_NAME, EVENT_BUTTON_PRESSED) - + ATTR_FIREEVENT, ATTR_NAME, ATTR_PACKETID, ATTR_STATE, EVENT_BUTTON_PRESSED) +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.util import slugify DEPENDENCIES = ['rfxtrx'] SIGNAL_REPETITIONS = 1 diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index dffa4682279..6a3f50b0b3f 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -8,9 +8,10 @@ https://home-assistant.io/components/switch.rpi_gpio/ """ import logging + import homeassistant.components.rpi_gpio as rpi_gpio +from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import (DEVICE_DEFAULT_NAME) DEFAULT_INVERT_LOGIC = False diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index 7755168585e..d5808d3d08c 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -7,13 +7,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.scsgate/ """ import logging + import homeassistant.components.scsgate as scsgate - from homeassistant.components.switch import SwitchDevice - from homeassistant.const import ATTR_ENTITY_ID - DEPENDENCIES = ['scsgate'] diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 589768db9bd..5c8227346ea 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -8,21 +8,14 @@ https://home-assistant.io/components/switch.template/ """ import logging -from homeassistant.helpers.entity import generate_entity_id - -from homeassistant.components.switch import SwitchDevice - -from homeassistant.core import EVENT_STATE_CHANGED +from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import ( - STATE_ON, - STATE_OFF, - ATTR_FRIENDLY_NAME, - CONF_VALUE_TEMPLATE) - -from homeassistant.helpers.service import call_from_config -from homeassistant.util import template, slugify + ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON) +from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.exceptions import TemplateError -from homeassistant.components.switch import DOMAIN +from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.service import call_from_config +from homeassistant.util import slugify, template ENTITY_ID_FORMAT = DOMAIN + '.{}' diff --git a/homeassistant/components/switch/transmission.py b/homeassistant/components/switch/transmission.py index 1f0da4a00e0..f7b833a8ae5 100644 --- a/homeassistant/components/switch/transmission.py +++ b/homeassistant/components/switch/transmission.py @@ -8,8 +8,8 @@ https://home-assistant.io/components/switch.transmission/ """ import logging -from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, - STATE_ON, STATE_OFF) +from homeassistant.const import ( + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_OFF, STATE_ON) from homeassistant.helpers.entity import ToggleEntity _LOGGING = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index 2f6039b1766..b9b44967aa4 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -7,19 +7,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.vera/ """ import logging + from requests.exceptions import RequestException + import homeassistant.util.dt as dt_util - from homeassistant.components.switch import SwitchDevice - from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - ATTR_TRIPPED, - ATTR_ARMED, - ATTR_LAST_TRIP_TIME, - EVENT_HOMEASSISTANT_STOP, - STATE_ON, - STATE_OFF) + ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, + EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) REQUIREMENTS = ['pyvera==0.2.8'] diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 56823b5ddb3..5d1410e0776 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -10,7 +10,7 @@ import logging from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( - STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON, STATE_STANDBY) REQUIREMENTS = ['pywemo==0.3.12'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/zigbee.py b/homeassistant/components/switch/zigbee.py index bec7804dc45..cc7b8003198 100644 --- a/homeassistant/components/switch/zigbee.py +++ b/homeassistant/components/switch/zigbee.py @@ -10,7 +10,6 @@ from homeassistant.components.switch import SwitchDevice from homeassistant.components.zigbee import ( ZigBeeDigitalOut, ZigBeeDigitalOutConfig) - DEPENDENCIES = ["zigbee"] diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index 150b8ba42be..6b7dc842589 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -6,10 +6,10 @@ Zwave platform that handles simple binary switches. """ # Because we do not compile openzwave on CI # pylint: disable=import-error -from homeassistant.components.switch import SwitchDevice, DOMAIN +from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.components.zwave import ( - COMMAND_CLASS_SWITCH_BINARY, TYPE_BOOL, GENRE_USER, NETWORK, - ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity) + ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SWITCH_BINARY, GENRE_USER, + NETWORK, TYPE_BOOL, ZWaveDeviceEntity) # pylint: disable=unused-argument diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index 1b602d953b0..2f59ddb24d9 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -6,15 +6,15 @@ Tellduslive Component. For more details about this component, please refer to the documentation at https://home-assistant.io/components/tellduslive/ """ -from datetime import timedelta import logging +from datetime import timedelta -from homeassistant.loader import get_component from homeassistant import bootstrap -from homeassistant.util import Throttle -from homeassistant.helpers import validate_config from homeassistant.const import ( - EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED) + ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers import validate_config +from homeassistant.loader import get_component +from homeassistant.util import Throttle DOMAIN = "tellduslive" diff --git a/homeassistant/components/thermostat/ecobee.py b/homeassistant/components/thermostat/ecobee.py index 647676563de..f61516f5d0d 100644 --- a/homeassistant/components/thermostat/ecobee.py +++ b/homeassistant/components/thermostat/ecobee.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/thermostat.ecobee/ import logging from homeassistant.components import ecobee -from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL, - STATE_IDLE, STATE_HEAT) -from homeassistant.const import (TEMP_FAHRENHEIT, STATE_ON, STATE_OFF) +from homeassistant.components.thermostat import ( + STATE_COOL, STATE_HEAT, STATE_IDLE, ThermostatDevice) +from homeassistant.const import STATE_OFF, STATE_ON, TEMP_FAHRENHEIT DEPENDENCIES = ['ecobee'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/thermostat/heat_control.py b/homeassistant/components/thermostat/heat_control.py index 7e560a2276f..6e0ddb3ac1a 100644 --- a/homeassistant/components/thermostat/heat_control.py +++ b/homeassistant/components/thermostat/heat_control.py @@ -10,11 +10,11 @@ import logging import homeassistant.util as util from homeassistant.components import switch -from homeassistant.components.thermostat import (ThermostatDevice, STATE_IDLE, - STATE_HEAT) -from homeassistant.helpers.event import track_state_change +from homeassistant.components.thermostat import ( + STATE_HEAT, STATE_IDLE, ThermostatDevice) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.event import track_state_change DEPENDENCIES = ['switch', 'sensor'] diff --git a/homeassistant/components/thermostat/heatmiser.py b/homeassistant/components/thermostat/heatmiser.py index a7ea7699558..1ad9045a4ec 100644 --- a/homeassistant/components/thermostat/heatmiser.py +++ b/homeassistant/components/thermostat/heatmiser.py @@ -10,6 +10,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/thermostat.heatmiser/ """ import logging + from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import TEMP_CELCIUS diff --git a/homeassistant/components/thermostat/honeywell.py b/homeassistant/components/thermostat/honeywell.py index f8c66cb5c5d..8ef9f39a727 100644 --- a/homeassistant/components/thermostat/honeywell.py +++ b/homeassistant/components/thermostat/honeywell.py @@ -10,8 +10,8 @@ import logging import socket from homeassistant.components.thermostat import ThermostatDevice -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, - TEMP_FAHRENHEIT) +from homeassistant.const import ( + CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) REQUIREMENTS = ['evohomeclient==0.2.4', 'somecomfort==0.2.1'] diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index e0e1f74cdbc..be8df3fcb61 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -6,13 +6,13 @@ Adds support for Nest thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/thermostat.nest/ """ -import socket import logging +import socket -from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL, - STATE_IDLE, STATE_HEAT) -from homeassistant.const import (TEMP_CELCIUS) import homeassistant.components.nest as nest +from homeassistant.components.thermostat import ( + STATE_COOL, STATE_HEAT, STATE_IDLE, ThermostatDevice) +from homeassistant.const import TEMP_CELCIUS DEPENDENCIES = ['nest'] diff --git a/homeassistant/components/thermostat/proliphix.py b/homeassistant/components/thermostat/proliphix.py index df223186284..3b643ab4923 100644 --- a/homeassistant/components/thermostat/proliphix.py +++ b/homeassistant/components/thermostat/proliphix.py @@ -6,10 +6,10 @@ The Proliphix NT10e Thermostat is an ethernet connected thermostat. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/thermostat.proliphix/ """ -from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL, - STATE_IDLE, STATE_HEAT) -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, - CONF_HOST, TEMP_FAHRENHEIT) +from homeassistant.components.thermostat import ( + STATE_COOL, STATE_HEAT, STATE_IDLE, ThermostatDevice) +from homeassistant.const import ( + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT) REQUIREMENTS = ['proliphix==0.1.0'] diff --git a/homeassistant/components/thermostat/radiotherm.py b/homeassistant/components/thermostat/radiotherm.py index 813bf07fa94..88b3f83425c 100644 --- a/homeassistant/components/thermostat/radiotherm.py +++ b/homeassistant/components/thermostat/radiotherm.py @@ -6,13 +6,13 @@ Adds support for Radio Thermostat wifi-enabled home thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/thermostat.radiotherm/ """ -import logging import datetime +import logging from urllib.error import URLError -from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL, - STATE_IDLE, STATE_HEAT) -from homeassistant.const import (CONF_HOST, TEMP_FAHRENHEIT) +from homeassistant.components.thermostat import ( + STATE_COOL, STATE_HEAT, STATE_IDLE, ThermostatDevice) +from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT REQUIREMENTS = ['radiotherm==1.2'] HOLD_TEMP = 'hold_temp' diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index a79e2013ab4..2e8af9b3129 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -8,19 +8,15 @@ https://home-assistant.io/components/verisure/ """ import logging import time - from datetime import timedelta from homeassistant import bootstrap -from homeassistant.loader import get_component - -from homeassistant.helpers import validate_config -from homeassistant.util import Throttle from homeassistant.const import ( - EVENT_PLATFORM_DISCOVERED, - ATTR_SERVICE, ATTR_DISCOVERED, - CONF_USERNAME, CONF_PASSWORD) - + ATTR_DISCOVERED, ATTR_SERVICE, CONF_PASSWORD, CONF_USERNAME, + EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers import validate_config +from homeassistant.loader import get_component +from homeassistant.util import Throttle DOMAIN = "verisure" DISCOVER_SENSORS = 'verisure.sensors' diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index ef97e958dc0..85a7e995f98 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -8,12 +8,12 @@ https://home-assistant.io/components/wink/ import logging from homeassistant import bootstrap -from homeassistant.loader import get_component +from homeassistant.const import ( + ATTR_DISCOVERED, ATTR_SERVICE, CONF_ACCESS_TOKEN, + EVENT_PLATFORM_DISCOVERED) from homeassistant.helpers import validate_config from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import ( - EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN, - ATTR_SERVICE, ATTR_DISCOVERED) +from homeassistant.loader import get_component DOMAIN = "wink" REQUIREMENTS = ['python-wink==0.6.0'] diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py index 91122c9610c..000ce4e3b6c 100644 --- a/homeassistant/components/zigbee.py +++ b/homeassistant/components/zigbee.py @@ -10,11 +10,10 @@ https://home-assistant.io/components/zigbee/ import logging from binascii import hexlify, unhexlify -from homeassistant.core import JobPriority from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import JobPriority from homeassistant.helpers.entity import Entity - DOMAIN = "zigbee" REQUIREMENTS = ("xbee-helper==0.0.6",) diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 8ffdf119caa..49ab2bcaf40 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -6,17 +6,16 @@ Connects Home Assistant to a Z-Wave network. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zwave/ """ -import sys import os.path - +import sys from pprint import pprint -from homeassistant.util import slugify, convert + from homeassistant import bootstrap from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, - ATTR_BATTERY_LEVEL, ATTR_LOCATION, ATTR_ENTITY_ID, CONF_CUSTOMIZE) - + ATTR_BATTERY_LEVEL, ATTR_DISCOVERED, ATTR_ENTITY_ID, ATTR_LOCATION, + ATTR_SERVICE, CONF_CUSTOMIZE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) +from homeassistant.util import convert, slugify DOMAIN = "zwave" REQUIREMENTS = ['pydispatcher==2.0.5'] diff --git a/homeassistant/config.py b/homeassistant/config.py index b6d60f873cb..d87dd3d89ad 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,11 +7,11 @@ Module to help with parsing and generating configuration files. import logging import os -from homeassistant.exceptions import HomeAssistantError -from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, - CONF_TIME_ZONE) import homeassistant.util.location as loc_util +from homeassistant.const import ( + CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT, + CONF_TIME_ZONE) +from homeassistant.exceptions import HomeAssistantError from homeassistant.util.yaml import load_yaml _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/core.py b/homeassistant/core.py index 839058d25dd..991de9c6189 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -5,31 +5,31 @@ Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ -import os -import time -import logging -import signal -import threading -from types import MappingProxyType import enum import functools as ft +import logging +import os +import signal +import threading +import time +from types import MappingProxyType -from homeassistant.const import ( - __version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, - EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, - EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, - EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, - TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA, - RESTART_EXIT_CODE) -from homeassistant.exceptions import ( - HomeAssistantError, InvalidEntityFormatError) +import homeassistant.helpers.temperature as temp_helper import homeassistant.util as util import homeassistant.util.dt as dt_util import homeassistant.util.location as location -from homeassistant.helpers.entity import valid_entity_id, split_entity_id -import homeassistant.helpers.temperature as temp_helper from homeassistant.config import get_default_config_dir +from homeassistant.const import ( + ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, + ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, + EVENT_TIME_CHANGED, MATCH_ALL, RESTART_EXIT_CODE, + SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, TEMP_CELCIUS, + TEMP_FAHRENHEIT, __version__) +from homeassistant.exceptions import ( + HomeAssistantError, InvalidEntityFormatError) +from homeassistant.helpers.entity import split_entity_id, valid_entity_id DOMAIN = "homeassistant" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 2c47d73861b..afe5bc80291 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -4,16 +4,15 @@ homeassistant.helpers.entity. Provides ABC for entities in HA. """ -from collections import defaultdict import re - -from homeassistant.exceptions import NoEntitySpecifiedError -from homeassistant.util import ensure_unique_string, slugify +from collections import defaultdict from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON, - DEVICE_DEFAULT_NAME, STATE_ON, STATE_OFF, STATE_UNKNOWN, STATE_UNAVAILABLE, - TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_ASSUMED_STATE) + ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.exceptions import NoEntitySpecifiedError +from homeassistant.util import ensure_unique_string, slugify # Dict mapping entity_id to a boolean that overwrites the hidden property _OVERWRITE = defaultdict(dict) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 268e4e7b696..26dc4500e78 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,14 +1,13 @@ """Provides helpers for components that manage entities.""" from threading import Lock -from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.bootstrap import prepare_setup_platform +from homeassistant.components import discovery, group +from homeassistant.const import ATTR_ENTITY_ID, CONF_SCAN_INTERVAL from homeassistant.helpers import config_per_platform from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.service import extract_entity_ids -from homeassistant.components import group, discovery -from homeassistant.const import ATTR_ENTITY_ID DEFAULT_SCAN_INTERVAL = 15 diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index d602fa5641f..e19d1646959 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,12 +1,12 @@ """ Helpers for listening to events """ -from datetime import timedelta import functools as ft +from datetime import timedelta -from ..util import dt as dt_util from ..const import ( ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) +from ..util import dt as dt_util def track_state_change(hass, entity_ids, action, from_state=None, diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 23673e678ca..9d81b95b18a 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,19 +1,17 @@ """Helpers that help with state related things.""" -from collections import defaultdict import json import logging +from collections import defaultdict -from homeassistant.core import State import homeassistant.util.dt as dt_util +from homeassistant.components.media_player import SERVICE_PLAY_MEDIA +from homeassistant.components.sun import ( + STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) from homeassistant.const import ( - STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - STATE_PLAYING, STATE_PAUSED, ATTR_ENTITY_ID, - STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, - STATE_OPEN, STATE_CLOSED) -from homeassistant.components.sun import (STATE_ABOVE_HORIZON, - STATE_BELOW_HORIZON) -from homeassistant.components.media_player import (SERVICE_PLAY_MEDIA) + ATTR_ENTITY_ID, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_TURN_OFF, + SERVICE_TURN_ON, STATE_CLOSED, STATE_LOCKED, STATE_OFF, STATE_ON, + STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_UNLOCKED) +from homeassistant.core import State _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py index 4a867ab2f2e..90f3236d111 100644 --- a/homeassistant/helpers/temperature.py +++ b/homeassistant/helpers/temperature.py @@ -5,8 +5,8 @@ homeassistant.helpers.temperature Methods to help handle temperature in Home Assistant. """ -from homeassistant.const import TEMP_CELCIUS import homeassistant.util.temperature as temp_util +from homeassistant.const import TEMP_CELCIUS def convert(temperature, unit, to_unit): diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 8b38f5e0966..3dd831c4fb0 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -13,11 +13,11 @@ call get_component('switch.your_platform'). In both cases the config directory is checked to see if it contains a user provided version. If not available it will check the built-in components and platforms. """ -import os -import sys -import pkgutil import importlib import logging +import os +import pkgutil +import sys from homeassistant.util import OrderedSet diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 29ac7eeb1db..11dd6c6be89 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -10,22 +10,21 @@ HomeAssistantError will be raised. For more details about the Python API, please refer to the documentation at https://home-assistant.io/developers/python_api/ """ -import threading -import logging -import json import enum +import json +import logging +import threading import urllib.parse import requests -import homeassistant.core as ha -from homeassistant.exceptions import HomeAssistantError import homeassistant.bootstrap as bootstrap - +import homeassistant.core as ha from homeassistant.const import ( - SERVER_PORT, HTTP_HEADER_HA_AUTH, URL_API, URL_API_STATES, - URL_API_STATES_ENTITY, URL_API_EVENTS, URL_API_EVENTS_EVENT, - URL_API_SERVICES, URL_API_SERVICES_SERVICE, URL_API_EVENT_FORWARD) + HTTP_HEADER_HA_AUTH, SERVER_PORT, URL_API, URL_API_EVENT_FORWARD, + URL_API_EVENTS, URL_API_EVENTS_EVENT, URL_API_SERVICES, + URL_API_SERVICES_SERVICE, URL_API_STATES, URL_API_STATES_ENTITY) +from homeassistant.exceptions import HomeAssistantError METHOD_GET = "get" METHOD_POST = "post" diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 9d1b5d1c720..bb3d84466de 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -9,6 +9,7 @@ detect_location_info and elevation are mocked by default during tests. import collections import requests + from vincenty import vincenty ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json' diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index d3ff27791d3..d9b1990a252 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -6,8 +6,10 @@ Template utility methods for rendering strings with HA data. # pylint: disable=too-few-public-methods import json import logging + import jinja2 from jinja2.sandbox import ImmutableSandboxedEnvironment + from homeassistant.const import STATE_UNKNOWN from homeassistant.exceptions import TemplateError diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 50355e43799..fa7783d7e83 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -1,15 +1,14 @@ """ YAML utility functions. """ -from collections import OrderedDict import logging import os +from collections import OrderedDict import yaml from homeassistant.exceptions import HomeAssistantError - _LOGGER = logging.getLogger(__name__) From 5f3cb58d8ef9ef3ebc48aa3f36a405a15b550660 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Feb 2016 00:54:16 -0800 Subject: [PATCH 077/186] Remove simple_alarm component --- homeassistant/components/simple_alarm.py | 101 ----------------------- 1 file changed, 101 deletions(-) delete mode 100644 homeassistant/components/simple_alarm.py diff --git a/homeassistant/components/simple_alarm.py b/homeassistant/components/simple_alarm.py deleted file mode 100644 index 6fb48e708c5..00000000000 --- a/homeassistant/components/simple_alarm.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -homeassistant.components.simple_alarm -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Intruder alerts component. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/simple_alarm/ -""" -import logging - -import homeassistant.loader as loader -from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON -from homeassistant.helpers.event import track_state_change - -DOMAIN = "simple_alarm" - -DEPENDENCIES = ['group', 'device_tracker', 'light'] - -# Attribute to tell which light has to flash whem a known person comes home -# If omitted will flash all. -CONF_KNOWN_LIGHT = "known_light" - -# Attribute to tell which light has to flash whem an unknown person comes home -# If omitted will flash all. -CONF_UNKNOWN_LIGHT = "unknown_light" - -# Services to test the alarms -SERVICE_TEST_KNOWN_ALARM = "test_known" -SERVICE_TEST_UNKNOWN_ALARM = "test_unknown" - - -def setup(hass, config): - """ Sets up the simple alarms. """ - logger = logging.getLogger(__name__) - - device_tracker = loader.get_component('device_tracker') - light = loader.get_component('light') - notify = loader.get_component('notify') - - light_ids = [] - - for conf_key in (CONF_KNOWN_LIGHT, CONF_UNKNOWN_LIGHT): - light_id = config[DOMAIN].get(conf_key) or light.ENTITY_ID_ALL_LIGHTS - - if hass.states.get(light_id) is None: - logger.error( - 'Light id %s could not be found in state machine', light_id) - - return False - - else: - light_ids.append(light_id) - - # pylint: disable=unbalanced-tuple-unpacking - known_light_id, unknown_light_id = light_ids - - if hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES) is None: - logger.error('No devices are being tracked, cannot setup alarm') - - return False - - def known_alarm(): - """ Fire an alarm if a known person arrives home. """ - light.turn_on(hass, known_light_id, flash=light.FLASH_SHORT) - - def unknown_alarm(): - """ Fire an alarm if the light turns on while no one is home. """ - light.turn_on( - hass, unknown_light_id, - flash=light.FLASH_LONG, rgb_color=[255, 0, 0]) - - # Send a message to the user - notify.send_message( - hass, "The lights just got turned on while no one was home.") - - # Setup services to test the effect - hass.services.register( - DOMAIN, SERVICE_TEST_KNOWN_ALARM, lambda call: known_alarm()) - hass.services.register( - DOMAIN, SERVICE_TEST_UNKNOWN_ALARM, lambda call: unknown_alarm()) - - def unknown_alarm_if_lights_on(entity_id, old_state, new_state): - """ Called when a light has been turned on. """ - if not device_tracker.is_on(hass): - unknown_alarm() - - track_state_change( - hass, light.ENTITY_ID_ALL_LIGHTS, - unknown_alarm_if_lights_on, STATE_OFF, STATE_ON) - - def ring_known_alarm(entity_id, old_state, new_state): - """ Called when a known person comes home. """ - if light.is_on(hass, known_light_id): - known_alarm() - - # Track home coming of each device - track_state_change( - hass, hass.states.entity_ids(device_tracker.DOMAIN), - ring_known_alarm, STATE_NOT_HOME, STATE_HOME) - - return True From d65c1c2b0dc1a789b6463fdaf680bd4b492f50c8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Feb 2016 23:23:05 -0800 Subject: [PATCH 078/186] Travis: only run coveralls on success --- .travis.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01448f315c1..3e1c8869d8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,7 @@ matrix: cache: directories: - $HOME/.cache/pip -install: pip install -U tox +install: pip install -U tox coveralls language: python -script: - - tox - - pip install coveralls - - coveralls +script: tox +after_success: coveralls From 7bd4e58b9dabc65a7b68d4da99bbb7c4451d92a0 Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 19 Feb 2016 09:43:59 +0000 Subject: [PATCH 079/186] Add tests for race condition. --- .../device_tracker/test_owntracks.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index ac9243ae5f8..6f35fe0e2de 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -379,3 +379,35 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): message['lat'] = "4.0" self.send_message(LOCATION_TOPIC, LOCATION_MESSAGE) self.assert_tracker_latitude(3.0) + + def test_mobile_multiple_async_enter_exit(self): + # Test race condition + enter_message = REGION_ENTER_MESSAGE.copy() + enter_message['desc'] = IBEACON_DEVICE + exit_message = REGION_LEAVE_MESSAGE.copy() + exit_message['desc'] = IBEACON_DEVICE + + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(enter_message)) + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(exit_message)) + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(enter_message)) + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(exit_message)) + + self.hass.pool.block_till_done() + self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) + + def test_mobile_multiple_enter_exit(self): + # Should only happen if the iphone dies + enter_message = REGION_ENTER_MESSAGE.copy() + enter_message['desc'] = IBEACON_DEVICE + exit_message = REGION_LEAVE_MESSAGE.copy() + exit_message['desc'] = IBEACON_DEVICE + + self.send_message(EVENT_TOPIC, enter_message) + self.send_message(EVENT_TOPIC, enter_message) + self.send_message(EVENT_TOPIC, exit_message) + + self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) From ee62120fe59d0fc996cb025c48e6820a56355d56 Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 19 Feb 2016 10:19:14 +0000 Subject: [PATCH 080/186] Revise race condition test. --- tests/components/device_tracker/test_owntracks.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 6f35fe0e2de..39914e704a3 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -387,16 +387,17 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): exit_message = REGION_LEAVE_MESSAGE.copy() exit_message['desc'] = IBEACON_DEVICE + for i in range(0, 20): + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(enter_message)) + fire_mqtt_message( + self.hass, EVENT_TOPIC, json.dumps(exit_message)) + fire_mqtt_message( self.hass, EVENT_TOPIC, json.dumps(enter_message)) - fire_mqtt_message( - self.hass, EVENT_TOPIC, json.dumps(exit_message)) - fire_mqtt_message( - self.hass, EVENT_TOPIC, json.dumps(enter_message)) - fire_mqtt_message( - self.hass, EVENT_TOPIC, json.dumps(exit_message)) self.hass.pool.block_till_done() + self.send_message(EVENT_TOPIC, exit_message) self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) def test_mobile_multiple_enter_exit(self): From 14afd1e4095ba706d1aea9b7a829ed668aaec9d6 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Fri, 19 Feb 2016 08:26:56 -0500 Subject: [PATCH 081/186] Removed debugging logging --- homeassistant/components/sensor/nest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index d046e56b57e..700012efac1 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -62,8 +62,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device, json_variable)]) else: - for test in WEATHER_VARIABLES: - logger.error('NEST VAR: %s', test) logger.error('Nest sensor type: "%s" does not exist', variable) except socket.error: From 9e48b8815409fbc056184358a021d2e3d2440f45 Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 19 Feb 2016 14:49:11 +0000 Subject: [PATCH 082/186] Add `for` delay to state automation. --- homeassistant/components/automation/state.py | 64 ++++++++++++++++- tests/components/automation/test_state.py | 73 +++++++++++++++++++- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 1eaa9e5c240..82182304ff9 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -7,14 +7,21 @@ For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#state-trigger """ import logging +from datetime import timedelta -from homeassistant.const import MATCH_ALL -from homeassistant.helpers.event import track_state_change +import homeassistant.util.dt as dt_util + +from homeassistant.const import ( + EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) +from homeassistant.components.automation.time import ( + CONF_HOURS, CONF_MINUTES, CONF_SECONDS) +from homeassistant.helpers.event import track_state_change, track_point_in_time CONF_ENTITY_ID = "entity_id" CONF_FROM = "from" CONF_TO = "to" CONF_STATE = "state" +CONF_FOR = "for" def trigger(hass, config, action): @@ -34,9 +41,60 @@ def trigger(hass, config, action): 'Config error. Surround to/from values with quotes.') return False + if CONF_FOR in config: + hours = config[CONF_FOR].get(CONF_HOURS) + minutes = config[CONF_FOR].get(CONF_MINUTES) + seconds = config[CONF_FOR].get(CONF_SECONDS) + + if hours is None and minutes is None and seconds is None: + logging.getLogger(__name__).error( + "Received invalid value for '%s': %s", + config[CONF_FOR], CONF_FOR) + return False + + if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: + logging.getLogger(__name__).error( + "For: requires a to: value'%s': %s", + config[CONF_FOR], CONF_FOR) + return False + def state_automation_listener(entity, from_s, to_s): """ Listens for state changes and calls action. """ - action() + + def state_for_listener(now): + """ Fires on state changes after a delay and calls action. """ + logging.getLogger(__name__).error('Listener fired') + + hass.bus.remove_listener( + EVENT_STATE_CHANGED, for_state_listener) + action() + + def state_for_cancel_listener(entity, inner_from_s, inner_to_s): + """ Fires on state changes and cancels + for listener if state changed. """ + logging.getLogger(__name__).error( + 'state_for_cancel_listener') + if inner_to_s == to_s: + return + logging.getLogger(__name__).error('Listeners removed') + hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener) + hass.bus.remove_listener( + EVENT_STATE_CHANGED, for_state_listener) + + if CONF_FOR in config: + now = dt_util.now() + target_tm = now + timedelta( + hours=(hours or 0.0), + minutes=(minutes or 0.0), + seconds=(seconds or 0.0)) + logging.getLogger(__name__).error('Listeners added') + for_time_listener = track_point_in_time( + hass, state_for_listener, target_tm) + for_state_listener = track_state_change( + hass, entity_id, state_for_cancel_listener, + MATCH_ALL, MATCH_ALL) + else: + action() track_state_change( hass, entity_id, state_automation_listener, from_state, to_state) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index fcd1fb616e9..8475d284d3e 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -5,11 +5,13 @@ tests.components.automation.test_state Tests state automation. """ import unittest +from datetime import timedelta +import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation import homeassistant.components.automation.state as state -from tests.common import get_test_home_assistant +from tests.common import fire_time_changed, get_test_home_assistant class TestAutomationState(unittest.TestCase): @@ -352,3 +354,72 @@ class TestAutomationState(unittest.TestCase): 'entity_id': 'test.entity', 'from': True, }, lambda x: x)) + + def test_if_fails_setup_bad_for(self): + self.assertFalse(state.trigger( + self.hass, { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'invalid': 5 + }, + }, lambda x: x)) + + def test_if_fails_setup_for_without_to(self): + self.assertFalse(state.trigger( + self.hass, { + 'platform': 'state', + 'entity_id': 'test.entity', + 'for': { + 'seconds': 5 + }, + }, lambda x: x)) + + def test_if_not_fires_on_entity_change_with_for(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + })) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.hass.states.set('test.entity', 'not_world') + self.hass.pool.block_till_done() + fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_if_fires_on_entity_change_with_for(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + })) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) From 85bf9a49ea21c4148a793da814baf1c6f58caf6f Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 19 Feb 2016 15:39:44 +0000 Subject: [PATCH 083/186] Remove traces. --- homeassistant/components/automation/state.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 82182304ff9..7d07034fa43 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -63,8 +63,6 @@ def trigger(hass, config, action): def state_for_listener(now): """ Fires on state changes after a delay and calls action. """ - logging.getLogger(__name__).error('Listener fired') - hass.bus.remove_listener( EVENT_STATE_CHANGED, for_state_listener) action() @@ -76,7 +74,6 @@ def trigger(hass, config, action): 'state_for_cancel_listener') if inner_to_s == to_s: return - logging.getLogger(__name__).error('Listeners removed') hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener) hass.bus.remove_listener( EVENT_STATE_CHANGED, for_state_listener) @@ -87,7 +84,6 @@ def trigger(hass, config, action): hours=(hours or 0.0), minutes=(minutes or 0.0), seconds=(seconds or 0.0)) - logging.getLogger(__name__).error('Listeners added') for_time_listener = track_point_in_time( hass, state_for_listener, target_tm) for_state_listener = track_state_change( From 97e1f0be98c4f810e95488565797219bad142695 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 08:13:11 -0800 Subject: [PATCH 084/186] Add neurio to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 27016770693..376648c36f0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -126,6 +126,7 @@ omit = homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py homeassistant/components/sensor/netatmo.py + homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/onewire.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rest.py From a75833cf2b97ee52031b99d77e52f7df6d233b2d Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 19 Feb 2016 16:41:05 +0000 Subject: [PATCH 085/186] Remove debug. --- homeassistant/components/automation/state.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 7d07034fa43..2ddc0822cc9 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -70,8 +70,6 @@ def trigger(hass, config, action): def state_for_cancel_listener(entity, inner_from_s, inner_to_s): """ Fires on state changes and cancels for listener if state changed. """ - logging.getLogger(__name__).error( - 'state_for_cancel_listener') if inner_to_s == to_s: return hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener) From 4e6b755b26c41ddcde3233f6861d986052daa7e6 Mon Sep 17 00:00:00 2001 From: Flyte Date: Fri, 19 Feb 2016 17:41:51 +0000 Subject: [PATCH 086/186] Add tests for TCP component. --- homeassistant/components/sensor/tcp.py | 55 +++--- tests/components/binary_sensor/test_tcp.py | 0 tests/components/sensor/test_tcp.py | 203 +++++++++++++++++++++ 3 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 tests/components/binary_sensor/test_tcp.py create mode 100644 tests/components/sensor/test_tcp.py diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index ad27ddef0bd..a2e14f12c39 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -5,7 +5,7 @@ Provides a sensor which gets its values from a TCP socket. """ import logging import socket -from select import select +import select from homeassistant.const import CONF_NAME, CONF_HOST from homeassistant.util import template @@ -88,35 +88,36 @@ class Sensor(Entity): def update(self): """ Get the latest value for this sensor. """ - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.connect( + (self._config[CONF_HOST], self._config[CONF_PORT])) + except socket.error as err: + _LOGGER.error( + "Unable to connect to %s on port %s: %s", + self._config[CONF_HOST], self._config[CONF_PORT], err) + return - try: - sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) - except socket.error as err: - _LOGGER.error( - "Unable to connect to %s on port %s: %s", - self._config[CONF_HOST], self._config[CONF_PORT], err) - return + try: + sock.send(self._config[CONF_PAYLOAD].encode()) + except socket.error as err: + _LOGGER.error( + "Unable to send payload %r to %s on port %s: %s", + self._config[CONF_PAYLOAD], self._config[CONF_HOST], + self._config[CONF_PORT], err) + return - try: - sock.send(self._config[CONF_PAYLOAD].encode()) - except socket.error as err: - _LOGGER.error( - "Unable to send payload %r to %s on port %s: %s", - self._config[CONF_PAYLOAD], self._config[CONF_HOST], - self._config[CONF_PORT], err) - return + readable, _, _ = select.select( + [sock], [], [], self._config[CONF_TIMEOUT]) + if not readable: + _LOGGER.warning( + "Timeout (%s second(s)) waiting for a response after " + "sending %r to %s on port %s.", + self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], + self._config[CONF_HOST], self._config[CONF_PORT]) + return - readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) - if not readable: - _LOGGER.warning( - "Timeout (%s second(s)) waiting for a response after sending " - "%r to %s on port %s.", - self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], - self._config[CONF_HOST], self._config[CONF_PORT]) - return - - value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() + value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() if self._config[CONF_VALUE_TEMPLATE] is not None: try: diff --git a/tests/components/binary_sensor/test_tcp.py b/tests/components/binary_sensor/test_tcp.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/sensor/test_tcp.py b/tests/components/sensor/test_tcp.py new file mode 100644 index 00000000000..1acb8aa79c0 --- /dev/null +++ b/tests/components/sensor/test_tcp.py @@ -0,0 +1,203 @@ +""" +tests.components.sensor.tcp +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests TCP sensor. +""" +import socket +from copy import copy + +from unittest.mock import patch + +from homeassistant.components.sensor import tcp +from tests.common import get_test_home_assistant + + +TEST_CONFIG = { + tcp.CONF_NAME: "test_name", + tcp.CONF_HOST: "test_host", + tcp.CONF_PORT: 12345, + tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, + tcp.CONF_PAYLOAD: "test_payload", + tcp.CONF_UNIT: "test_unit", + tcp.CONF_VALUE_TEMPLATE: "test_template", + tcp.CONF_VALUE_ON: "test_on", + tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 +} +KEYS_AND_DEFAULTS = { + tcp.CONF_NAME: None, + tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, + tcp.CONF_UNIT: None, + tcp.CONF_VALUE_TEMPLATE: None, + tcp.CONF_VALUE_ON: None, + tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE +} + + +# class TestTCPSensor(unittest.TestCase): +class TestTCPSensor(): + """ Test the TCP Sensor. """ + + def setup_class(cls): + cls.hass = get_test_home_assistant() + + def teardown_class(cls): + cls.hass.stop() + + @patch("homeassistant.components.sensor.tcp.Sensor.update") + def test_config_valid_keys(self, *args): + """ + Should store valid keys in _config. + """ + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + for key in TEST_CONFIG: + assert key in sensor._config + + def test_validate_config_valid_keys(self): + """ + Should return True when provided with the correct keys. + """ + assert tcp.Sensor.validate_config(TEST_CONFIG) + + @patch("homeassistant.components.sensor.tcp.Sensor.update") + def test_config_invalid_keys(self, *args): + """ + Shouldn't store invalid keys in _config. + """ + config = copy(TEST_CONFIG) + config.update({ + "a": "test_a", + "b": "test_b", + "c": "test_c" + }) + sensor = tcp.Sensor(self.hass, config) + for invalid_key in tuple("abc"): + assert invalid_key not in sensor._config + + @patch("homeassistant.components.sensor.tcp.Sensor.update") + def test_validate_config_invalid_keys(self, *args): + """ + Should return True when provided with the correct keys plus some extra. + """ + config = copy(TEST_CONFIG) + config.update({ + "a": "test_a", + "b": "test_b", + "c": "test_c" + }) + assert tcp.Sensor.validate_config(config) + + @patch("homeassistant.components.sensor.tcp.Sensor.update") + def test_config_uses_defaults(self, *args): + """ + Should use defaults where appropriate. + """ + config = copy(TEST_CONFIG) + for key in KEYS_AND_DEFAULTS.keys(): + del config[key] + sensor = tcp.Sensor(self.hass, config) + for key, default in KEYS_AND_DEFAULTS.items(): + assert sensor._config[key] == default + + def test_validate_config_missing_defaults(self): + """ + Should return True when defaulted keys are not provided. + """ + config = copy(TEST_CONFIG) + for key in KEYS_AND_DEFAULTS.keys(): + del config[key] + assert tcp.Sensor.validate_config(config) + + def test_validate_config_missing_required(self): + """ + Should return False when required config items are missing. + """ + for key in TEST_CONFIG: + if key in KEYS_AND_DEFAULTS: + continue + config = copy(TEST_CONFIG) + del config[key] + assert not tcp.Sensor.validate_config(config), ( + "validate_config() should have returned False since %r was not" + "provided." % key) + + @patch("homeassistant.components.sensor.tcp.Sensor.update") + def test_init_calls_update(self, mock_update): + """ + Should call update() method during __init__(). + """ + tcp.Sensor(self.hass, TEST_CONFIG) + assert mock_update.called + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_connects_to_host_and_port(self, mock_select, mock_socket): + """ + Should connect to the configured host and port. + """ + tcp.Sensor(self.hass, TEST_CONFIG) + mock_socket = mock_socket().__enter__() + mock_socket.connect.assert_called_with(( + TEST_CONFIG[tcp.CONF_HOST], + TEST_CONFIG[tcp.CONF_PORT])) + + @patch("socket.socket.connect", side_effect=socket.error()) + def test_update_returns_if_connecting_fails(self, mock_socket): + """ + Should return if connecting to host fails. + """ + with patch("homeassistant.components.sensor.tcp.Sensor.update"): + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + assert sensor.update() is None + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_sends_payload(self, mock_select, mock_socket): + """ + Should send the configured payload as bytes. + """ + tcp.Sensor(self.hass, TEST_CONFIG) + mock_socket = mock_socket().__enter__() + mock_socket.send.assert_called_with( + TEST_CONFIG[tcp.CONF_PAYLOAD].encode() + ) + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_calls_select_with_timeout(self, mock_select, mock_socket): + """ + Should provide the timeout argument to select. + """ + tcp.Sensor(self.hass, TEST_CONFIG) + mock_socket = mock_socket().__enter__() + mock_select.assert_called_with( + [mock_socket], [], [], TEST_CONFIG[tcp.CONF_TIMEOUT]) + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_receives_packet_and_sets_as_state( + self, mock_select, mock_socket): + """ + Should receive the response from the socket and set it as the state. + """ + test_value = "test_value" + mock_socket = mock_socket().__enter__() + mock_socket.recv.return_value = test_value.encode() + config = copy(TEST_CONFIG) + del config[tcp.CONF_VALUE_TEMPLATE] + sensor = tcp.Sensor(self.hass, config) + assert sensor._state == test_value + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_renders_value_in_template(self, mock_select, mock_socket): + """ + Should render the value in the provided template. + """ + test_value = "test_value" + mock_socket = mock_socket().__enter__() + mock_socket.recv.return_value = test_value.encode() + config = copy(TEST_CONFIG) + config[tcp.CONF_VALUE_TEMPLATE] = "{{ value }} {{ 1+1 }}" + sensor = tcp.Sensor(self.hass, config) + assert sensor._state == "%s 2" % test_value From fa6d9adcba444c2860e25f3830d858c20b0a62cc Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 19 Feb 2016 22:30:38 +0100 Subject: [PATCH 087/186] Added some unittests for the command_line notification --- tests/components/notify/test_command_line.py | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/components/notify/test_command_line.py diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py new file mode 100644 index 00000000000..f920590a21c --- /dev/null +++ b/tests/components/notify/test_command_line.py @@ -0,0 +1,58 @@ +""" +tests.components.notify.test_command_line +~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests command line notification. +""" +import os +import tempfile +import unittest + +from homeassistant import core +import homeassistant.components.notify as notify +from unittest.mock import patch + + +class TestCommandLine(unittest.TestCase): + """ Test the command line. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = core.HomeAssistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_command_line_output(self): + with tempfile.TemporaryDirectory() as tempdirname: + filename = os.path.join(tempdirname, 'message.txt') + message = 'one, two, testing, testing' + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + 'command': 'echo $(cat) > {}'.format(filename) + } + })) + + self.hass.services.call('notify', 'test', {'message': message}, + blocking=True) + + result = open(filename).read() + # the echo command adds a line break + self.assertEqual(result, "{}\n".format(message)) + + @patch('homeassistant.components.notify.command_line._LOGGER.error') + def test_error_for_none_zero_exit_code(self, mock_error): + """ Test if an error if logged for non zero exit codes. """ + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + 'command': 'echo $(cat); exit 1' + } + })) + + self.hass.services.call('notify', 'test', {'message': 'error'}, + blocking=True) + self.assertEqual(1, mock_error.call_count) From c85875ddf429ed3c510e5111a02bf77d0d62390e Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 19 Feb 2016 22:34:04 +0100 Subject: [PATCH 088/186] removed command_line from coveragerc --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 96db38ea4e2..8fefa8a44b8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -103,7 +103,6 @@ omit = homeassistant/components/notify/free_mobile.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py - homeassistant/components/notify/command_line.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushover.py From f9385eb87a526410a05130ff706f04a895788afd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 19 Feb 2016 23:58:45 +0100 Subject: [PATCH 089/186] UPdate docstring --- homeassistant/components/notify/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index 025046f1e7c..4852c7312d1 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -1,6 +1,6 @@ """ homeassistant.components.notify.command_line -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ command_line notification service. For more details about this platform, please refer to the documentation at From 27f456ca70ecc9d60d46d60b5e7055343f0d1d0f Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Fri, 19 Feb 2016 15:19:03 -0800 Subject: [PATCH 090/186] Add Ubiquiti Unifi device tracker Ubiquiti's Unifi WAP infrastructure has a central controller (like mfi and uvc) that can be queried for client status. This adds a device_tracker module that can report the state of any client connected to the controller. --- .../components/device_tracker/unifi.py | 79 +++++++++++ requirements_all.txt | 6 + tests/components/device_tracker/test_unifi.py | 128 ++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 homeassistant/components/device_tracker/unifi.py create mode 100644 tests/components/device_tracker/test_unifi.py diff --git a/homeassistant/components/device_tracker/unifi.py b/homeassistant/components/device_tracker/unifi.py new file mode 100644 index 00000000000..24dd8e7db00 --- /dev/null +++ b/homeassistant/components/device_tracker/unifi.py @@ -0,0 +1,79 @@ +""" +homeassistant.components.device_tracker.unifi +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Device tracker platform that supports scanning a Unifi WAP controller +""" +import logging +import urllib + +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config + +# Unifi package doesn't list urllib3 as a requirement +REQUIREMENTS = ['urllib3', 'unifi==1.2.4'] +_LOGGER = logging.getLogger(__name__) +CONF_PORT = 'port' + + +def get_scanner(hass, config): + """ Sets up unifi device_tracker """ + from unifi.controller import Controller + + if not validate_config(config, {DOMAIN: [CONF_USERNAME, + CONF_PASSWORD]}, + _LOGGER): + _LOGGER.error('Invalid configuration') + return False + + this_config = config[DOMAIN] + host = this_config.get(CONF_HOST, 'localhost') + username = this_config.get(CONF_USERNAME) + password = this_config.get(CONF_PASSWORD) + + try: + port = int(this_config.get(CONF_PORT, 8443)) + except ValueError: + _LOGGER.error('Invalid port (must be numeric like 8443)') + return False + + try: + ctrl = Controller(host, username, password, port, 'v4') + except urllib.error.HTTPError as ex: + _LOGGER.error('Failed to connect to unifi: %s', ex) + return False + + return UnifiScanner(ctrl) + + +class UnifiScanner(object): + """Provide device_tracker support from Unifi WAP client data.""" + + def __init__(self, controller): + self._controller = controller + self._update() + + def _update(self): + try: + clients = self._controller.get_clients() + except urllib.error.HTTPError as ex: + _LOGGER.error('Failed to scan clients: %s', ex) + clients = [] + + self._clients = {client['mac']: client for client in clients} + + def scan_devices(self): + """ Scans for devices. """ + self._update() + return self._clients.keys() + + def get_device_name(self, mac): + """ Returns the name (if known) of the device. + + If a name has been set in Unifi, then return that, else + return the hostname if it has been detected. + """ + client = self._clients.get(mac, {}) + name = client.get('name') or client.get('hostname') + _LOGGER.debug('Device %s name %s', mac, name) + return name diff --git a/requirements_all.txt b/requirements_all.txt index 525e3b25177..f251d900deb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -252,6 +252,12 @@ tellive-py==0.5.2 # homeassistant.components.switch.transmission transmissionrpc==0.11 +# homeassistant.components.device_tracker.unifi +unifi==1.2.4 + +# homeassistant.components.device_tracker.unifi +urllib3 + # homeassistant.components.camera.uvc uvcclient==0.6 diff --git a/tests/components/device_tracker/test_unifi.py b/tests/components/device_tracker/test_unifi.py new file mode 100644 index 00000000000..aed89dc4d8c --- /dev/null +++ b/tests/components/device_tracker/test_unifi.py @@ -0,0 +1,128 @@ +""" +homeassistant.components.device_tracker.unifi +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Device tracker platform that supports scanning a Unifi WAP controller +""" +import unittest +from unittest import mock +import urllib + +from homeassistant.components.device_tracker import unifi as unifi +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from unifi import controller + + +class TestUnifiScanner(unittest.TestCase): + @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') + @mock.patch.object(controller, 'Controller') + def test_config_minimal(self, mock_ctrl, mock_scanner): + config = { + 'device_tracker': { + CONF_USERNAME: 'foo', + CONF_PASSWORD: 'password', + } + } + result = unifi.get_scanner(None, config) + self.assertEqual(unifi.UnifiScanner.return_value, result) + mock_ctrl.assert_called_once_with('localhost', 'foo', 'password', + 8443, 'v4') + mock_scanner.assert_called_once_with(mock_ctrl.return_value) + + @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') + @mock.patch.object(controller, 'Controller') + def test_config_full(self, mock_ctrl, mock_scanner): + config = { + 'device_tracker': { + CONF_USERNAME: 'foo', + CONF_PASSWORD: 'password', + CONF_HOST: 'myhost', + 'port': 123, + } + } + result = unifi.get_scanner(None, config) + self.assertEqual(unifi.UnifiScanner.return_value, result) + mock_ctrl.assert_called_once_with('myhost', 'foo', 'password', + 123, 'v4') + mock_scanner.assert_called_once_with(mock_ctrl.return_value) + + @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') + @mock.patch.object(controller, 'Controller') + def test_config_error(self, mock_ctrl, mock_scanner): + config = { + 'device_tracker': { + CONF_HOST: 'myhost', + 'port': 123, + } + } + result = unifi.get_scanner(None, config) + self.assertFalse(result) + self.assertFalse(mock_ctrl.called) + + @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') + @mock.patch.object(controller, 'Controller') + def test_config_badport(self, mock_ctrl, mock_scanner): + config = { + 'device_tracker': { + CONF_USERNAME: 'foo', + CONF_PASSWORD: 'password', + CONF_HOST: 'myhost', + 'port': 'foo', + } + } + result = unifi.get_scanner(None, config) + self.assertFalse(result) + self.assertFalse(mock_ctrl.called) + + @mock.patch('homeassistant.components.device_tracker.unifi.UnifiScanner') + @mock.patch.object(controller, 'Controller') + def test_config_controller_failed(self, mock_ctrl, mock_scanner): + config = { + 'device_tracker': { + CONF_USERNAME: 'foo', + CONF_PASSWORD: 'password', + } + } + mock_ctrl.side_effect = urllib.error.HTTPError( + '/', 500, 'foo', {}, None) + result = unifi.get_scanner(None, config) + self.assertFalse(result) + + def test_scanner_update(self): + ctrl = mock.MagicMock() + fake_clients = [ + {'mac': '123'}, + {'mac': '234'}, + ] + ctrl.get_clients.return_value = fake_clients + unifi.UnifiScanner(ctrl) + ctrl.get_clients.assert_called_once_with() + + def test_scanner_update_error(self): + ctrl = mock.MagicMock() + ctrl.get_clients.side_effect = urllib.error.HTTPError( + '/', 500, 'foo', {}, None) + unifi.UnifiScanner(ctrl) + + def test_scan_devices(self): + ctrl = mock.MagicMock() + fake_clients = [ + {'mac': '123'}, + {'mac': '234'}, + ] + ctrl.get_clients.return_value = fake_clients + scanner = unifi.UnifiScanner(ctrl) + self.assertEqual(set(['123', '234']), set(scanner.scan_devices())) + + def test_get_device_name(self): + ctrl = mock.MagicMock() + fake_clients = [ + {'mac': '123', 'hostname': 'foobar'}, + {'mac': '234', 'name': 'Nice Name'}, + {'mac': '456'}, + ] + ctrl.get_clients.return_value = fake_clients + scanner = unifi.UnifiScanner(ctrl) + self.assertEqual('foobar', scanner.get_device_name('123')) + self.assertEqual('Nice Name', scanner.get_device_name('234')) + self.assertEqual(None, scanner.get_device_name('456')) + self.assertEqual(None, scanner.get_device_name('unknown')) From 52131a3335c644c330251b0da6f49f5650d006a0 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sat, 20 Feb 2016 02:11:15 +0100 Subject: [PATCH 091/186] Fix temperature unit * Specify temperature unit according to gateway setting, to avoid converting to Fahrenheit when already Fahrenheit. * Fix docstrings in wrapper class. --- homeassistant/components/mysensors.py | 3 +- homeassistant/components/sensor/mysensors.py | 37 ++++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 53987fa1435..a05435b2e8c 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -158,7 +158,7 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class): class GatewayWrapper(object): - """Gateway wrapper class, by subclassing serial gateway.""" + """Gateway wrapper class.""" def __init__(self, gateway, version, optimistic): """Setup class attributes on instantiation. @@ -173,6 +173,7 @@ class GatewayWrapper(object): version (str): Version of mysensors API. platform_callbacks (list): Callback functions, one per platform. const (module): Mysensors API constants. + optimistic (bool): Send values to actuators without feedback state. __initialised (bool): True if GatewayWrapper is initialised. """ self._wrapped_gateway = gateway diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index d33be7f2903..7d70a532164 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -8,7 +8,7 @@ import logging import homeassistant.components.mysensors as mysensors from homeassistant.const import ( - ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS) + ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -129,27 +129,28 @@ class MySensorsSensor(Entity): @property def unit_of_measurement(self): """Unit of measurement of this entity.""" - # HA will convert to degrees F if needed + set_req = self.gateway.const.SetReq unit_map = { - self.gateway.const.SetReq.V_TEMP: TEMP_CELCIUS, - self.gateway.const.SetReq.V_HUM: '%', - self.gateway.const.SetReq.V_DIMMER: '%', - self.gateway.const.SetReq.V_LIGHT_LEVEL: '%', - self.gateway.const.SetReq.V_WEIGHT: 'kg', - self.gateway.const.SetReq.V_DISTANCE: 'm', - self.gateway.const.SetReq.V_IMPEDANCE: 'ohm', - self.gateway.const.SetReq.V_WATT: 'W', - self.gateway.const.SetReq.V_KWH: 'kWh', - self.gateway.const.SetReq.V_FLOW: 'm', - self.gateway.const.SetReq.V_VOLUME: 'm3', - self.gateway.const.SetReq.V_VOLTAGE: 'V', - self.gateway.const.SetReq.V_CURRENT: 'A', + set_req.V_TEMP: (TEMP_CELCIUS + if self.gateway.metric else TEMP_FAHRENHEIT), + set_req.V_HUM: '%', + set_req.V_DIMMER: '%', + set_req.V_LIGHT_LEVEL: '%', + set_req.V_WEIGHT: 'kg', + set_req.V_DISTANCE: 'm', + set_req.V_IMPEDANCE: 'ohm', + set_req.V_WATT: 'W', + set_req.V_KWH: 'kWh', + set_req.V_FLOW: 'm', + set_req.V_VOLUME: 'm3', + set_req.V_VOLTAGE: 'V', + set_req.V_CURRENT: 'A', } if float(self.gateway.version) >= 1.5: - if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values: + if set_req.V_UNIT_PREFIX in self._values: return self._values[ - self.gateway.const.SetReq.V_UNIT_PREFIX] - unit_map.update({self.gateway.const.SetReq.V_PERCENTAGE: '%'}) + set_req.V_UNIT_PREFIX] + unit_map.update({set_req.V_PERCENTAGE: '%'}) return unit_map.get(self.value_type) @property From 08aaea5444d49303bfefd8d3fdbf27b10ea6c4fb Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sat, 20 Feb 2016 03:59:06 +0100 Subject: [PATCH 092/186] Add mysensors binary sensor * Add mysensors binary sensor. * Add discovery platforms to binary_sensor base component. * Replace device_state_attributes with state_attributes in binary_sensor base class. * Fix docstrings. * Add discovery of binary sensor to mysensors component. * Add child.type as argument to mysensors device_class. * Move binary sensor types from sensor to binary_sensor module. * Fix binary_sensor attribute tests. Use state_attributes instead of device_state_attributes. --- .../components/binary_sensor/__init__.py | 26 ++- .../components/binary_sensor/mysensors.py | 163 ++++++++++++++++++ homeassistant/components/light/mysensors.py | 3 +- homeassistant/components/mysensors.py | 4 +- homeassistant/components/sensor/mysensors.py | 13 +- homeassistant/components/switch/mysensors.py | 4 +- .../binary_sensor/test_binary_sensor.py | 8 +- 7 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/binary_sensor/mysensors.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 86889cd18df..3934feae179 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,6 +12,7 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) +from homeassistant.components import (mysensors, ) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -27,13 +28,19 @@ SENSOR_CLASSES = [ 'light', # Lightness threshold 'power', # Power, over-current, etc 'safety', # Generic on=unsafe, off=safe - ] +] + +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = { + mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', +} def setup(hass, config): - """ Track states and offer events for binary sensors. """ + """Track states and offer events for binary sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, + DISCOVERY_PLATFORMS) component.setup(config) @@ -42,30 +49,31 @@ def setup(hass, config): # pylint: disable=no-self-use class BinarySensorDevice(Entity): - """ Represents a binary sensor. """ + """Represent a binary sensor.""" @property def is_on(self): - """ True if the binary sensor is on. """ + """Return True if the binary sensor is on.""" return None @property def state(self): - """ Returns the state of the binary sensor. """ + """Return the state of the binary sensor.""" return STATE_ON if self.is_on else STATE_OFF @property def friendly_state(self): - """ Returns the friendly state of the binary sensor. """ + """Return the friendly state of the binary sensor.""" return None @property def sensor_class(self): - """ Returns the class of this sensor, from SENSOR_CASSES. """ + """Return the class of this sensor, from SENSOR_CASSES.""" return None @property - def device_state_attributes(self): + def state_attributes(self): + """Return device specific state attributes.""" return { 'sensor_class': self.sensor_class, } diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py new file mode 100644 index 00000000000..ffa6742697a --- /dev/null +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -0,0 +1,163 @@ +""" +Support for MySensors binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.mysensors/ +""" +import logging + +import homeassistant.components.mysensors as mysensors +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, SENSOR_CLASSES) + +_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = [] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the mysensors platform for sensors.""" + # Only act if loaded via mysensors by discovery event. + # Otherwise gateway is not setup. + if discovery_info is None: + return + + for gateway in mysensors.GATEWAYS.values(): + # Define the S_TYPES and V_TYPES that the platform should handle as + # states. Map them in a dict of lists. + pres = gateway.const.Presentation + set_req = gateway.const.SetReq + map_sv_types = { + pres.S_DOOR: [set_req.V_TRIPPED], + pres.S_MOTION: [set_req.V_TRIPPED], + pres.S_SMOKE: [set_req.V_TRIPPED], + } + if float(gateway.version) >= 1.5: + map_sv_types.update({ + pres.S_SPRINKLER: [set_req.V_TRIPPED], + pres.S_WATER_LEAK: [set_req.V_TRIPPED], + pres.S_SOUND: [set_req.V_TRIPPED], + pres.S_VIBRATION: [set_req.V_TRIPPED], + pres.S_MOISTURE: [set_req.V_TRIPPED], + }) + + devices = {} + gateway.platform_callbacks.append(mysensors.pf_callback_factory( + map_sv_types, devices, add_devices, MySensorsBinarySensor)) + + +class MySensorsBinarySensor(BinarySensorDevice): + """Represent the value of a MySensors child node.""" + + # pylint: disable=too-many-arguments,too-many-instance-attributes + + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): + """Setup class attributes on instantiation. + + Args: + gateway (GatewayWrapper): Gateway object. + node_id (str): Id of node. + child_id (str): Id of child. + name (str): Entity name. + value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. + + Attributes: + gateway (GatewayWrapper): Gateway object. + node_id (str): Id of node. + child_id (str): Id of child. + _name (str): Entity name. + value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. + battery_level (int): Node battery level. + _values (dict): Child values. Non state values set as state attributes. + """ + self.gateway = gateway + self.node_id = node_id + self.child_id = child_id + self._name = name + self.value_type = value_type + self.child_type = child_type + self.battery_level = 0 + self._values = {} + + @property + def should_poll(self): + """MySensor gateway pushes its state to HA.""" + return False + + @property + def name(self): + """The name of this entity.""" + return self._name + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + attr = { + mysensors.ATTR_PORT: self.gateway.port, + mysensors.ATTR_NODE_ID: self.node_id, + mysensors.ATTR_CHILD_ID: self.child_id, + ATTR_BATTERY_LEVEL: self.battery_level, + } + + set_req = self.gateway.const.SetReq + + for value_type, value in self._values.items(): + if value_type != self.value_type: + try: + attr[set_req(value_type).name] = value + except ValueError: + _LOGGER.error('value_type %s is not valid for mysensors ' + 'version %s', value_type, + self.gateway.version) + return attr + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + if self.value_type in self._values: + return self._values[self.value_type] == STATE_ON + return False + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CASSES.""" + pres = self.gateway.const.Presentation + class_map = { + pres.S_DOOR: 'opening', + pres.S_MOTION: 'motion', + pres.S_SMOKE: 'smoke', + } + if float(self.gateway.version) >= 1.5: + class_map.update({ + pres.S_SPRINKLER: 'sprinkler', + pres.S_WATER_LEAK: 'leak', + pres.S_SOUND: 'sound', + pres.S_VIBRATION: 'vibration', + pres.S_MOISTURE: 'moisture', + }) + if class_map.get(self.child_type) in SENSOR_CLASSES: + return class_map.get(self.child_type) + + @property + def available(self): + """Return True if entity is available.""" + return self.value_type in self._values + + def update(self): + """Update the controller with the latest values from a sensor.""" + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + for value_type, value in child.values.items(): + _LOGGER.debug( + "%s: value_type %s, value = %s", self._name, value_type, value) + if value_type == self.gateway.const.SetReq.V_TRIPPED: + self._values[value_type] = STATE_ON if int( + value) == 1 else STATE_OFF + else: + self._values[value_type] = value + + self.battery_level = node.battery_level diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 3d9d2c14b13..c0daca374cb 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -58,7 +58,8 @@ class MySensorsLight(Light): # pylint: disable=too-many-arguments,too-many-instance-attributes - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup instance attributes.""" self.gateway = gateway self.node_id = node_id diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index a05435b2e8c..0510dc6cece 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -42,12 +42,14 @@ GATEWAYS = None DISCOVER_SENSORS = 'mysensors.sensors' DISCOVER_SWITCHES = 'mysensors.switches' DISCOVER_LIGHTS = 'mysensors.lights' +DISCOVER_BINARY_SENSORS = 'mysensors.binary_sensor' # Maps discovered services to their platforms DISCOVERY_COMPONENTS = [ ('sensor', DISCOVER_SENSORS), ('switch', DISCOVER_SWITCHES), ('light', DISCOVER_LIGHTS), + ('binary_sensor', DISCOVER_BINARY_SENSORS), ] @@ -148,7 +150,7 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class): else: device_class = entity_class devices[key] = device_class( - gateway, node_id, child.id, name, value_type) + gateway, node_id, child.id, name, value_type, child.type) _LOGGER.info('Adding new devices: %s', devices[key]) add_devices([devices[key]]) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 7d70a532164..a55c7fd3335 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -28,9 +28,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres = gateway.const.Presentation set_req = gateway.const.SetReq map_sv_types = { - pres.S_DOOR: [set_req.V_TRIPPED], - pres.S_MOTION: [set_req.V_TRIPPED], - pres.S_SMOKE: [set_req.V_TRIPPED], pres.S_TEMP: [set_req.V_TEMP], pres.S_HUM: [set_req.V_HUM], pres.S_BARO: [set_req.V_PRESSURE, set_req.V_FORECAST], @@ -64,9 +61,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): set_req.V_IMPEDANCE], pres.S_SPRINKLER: [set_req.V_TRIPPED], pres.S_WATER_LEAK: [set_req.V_TRIPPED], - pres.S_SOUND: [set_req.V_TRIPPED, set_req.V_LEVEL], - pres.S_VIBRATION: [set_req.V_TRIPPED, set_req.V_LEVEL], - pres.S_MOISTURE: [set_req.V_TRIPPED, set_req.V_LEVEL], + pres.S_SOUND: [set_req.V_LEVEL], + pres.S_VIBRATION: [set_req.V_LEVEL], + pres.S_MOISTURE: [set_req.V_LEVEL], pres.S_AIR_QUALITY: [set_req.V_LEVEL], pres.S_DUST: [set_req.V_LEVEL], }) @@ -82,7 +79,8 @@ class MySensorsSensor(Entity): # pylint: disable=too-many-arguments - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup class attributes on instantiation. Args: @@ -91,6 +89,7 @@ class MySensorsSensor(Entity): child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. Attributes: gateway (GatewayWrapper): Gateway object. diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 4254158d745..e0797e42c54 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -52,7 +52,8 @@ class MySensorsSwitch(SwitchDevice): # pylint: disable=too-many-arguments - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup class attributes on instantiation. Args: @@ -61,6 +62,7 @@ class MySensorsSwitch(SwitchDevice): child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. Attributes: gateway (GatewayWrapper): Gateway object diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py index 9c3f7f99e0f..9079b208204 100644 --- a/tests/components/binary_sensor/test_binary_sensor.py +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -11,7 +11,10 @@ from homeassistant.const import STATE_ON, STATE_OFF class TestBinarySensor(unittest.TestCase): + """Test the binary_sensor base class.""" + def test_state(self): + """Test binary sensor state.""" sensor = binary_sensor.BinarySensorDevice() self.assertEqual(STATE_OFF, sensor.state) with mock.patch('homeassistant.components.binary_sensor.' @@ -26,11 +29,12 @@ class TestBinarySensor(unittest.TestCase): binary_sensor.BinarySensorDevice().state) def test_attributes(self): + """Test binary sensor attributes.""" sensor = binary_sensor.BinarySensorDevice() self.assertEqual({'sensor_class': None}, - sensor.device_state_attributes) + sensor.state_attributes) with mock.patch('homeassistant.components.binary_sensor.' 'BinarySensorDevice.sensor_class', new='motion'): self.assertEqual({'sensor_class': 'motion'}, - sensor.device_state_attributes) + sensor.state_attributes) From 63a27f1943f063bff77426a890859375a8e45e34 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 22:23:01 -0800 Subject: [PATCH 093/186] Add discover helper method to discovery component --- homeassistant/components/discovery.py | 29 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 09816d73f9a..f6678fb35e5 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -1,6 +1,4 @@ """ -homeassistant.components.discovery -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starts a service to scan in intervals for new devices. Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. @@ -39,24 +37,41 @@ SERVICE_HANDLERS = { def listen(hass, service, callback): - """ - Setup listener for discovery of specific service. + """Setup listener for discovery of specific service. + Service can be a string or a list/tuple. """ - if isinstance(service, str): service = (service,) else: service = tuple(service) def discovery_event_listener(event): - """ Listens for discovery events. """ + """Listen for discovery events.""" if event.data[ATTR_SERVICE] in service: - callback(event.data[ATTR_SERVICE], event.data[ATTR_DISCOVERED]) + callback(event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED)) hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener) +def discover(hass, service, discovered=None, component=None, hass_config=None): + """Fire discovery event. + + Can ensure a component is loaded. + """ + if component is not None: + bootstrap.setup_component(hass, component, hass_config) + + data = { + ATTR_SERVICE: service + } + + if discovered is not None: + data[ATTR_DISCOVERED] = discovered + + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data) + + def setup(hass, config): """ Starts a discovery service. """ logger = logging.getLogger(__name__) From 4c538c718ba90522e0600e4f9214d1873c69efae Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 22:23:25 -0800 Subject: [PATCH 094/186] Clean up binary_sensor --- homeassistant/components/binary_sensor/__init__.py | 14 ++++++-------- .../components/binary_sensor/test_binary_sensor.py | 3 +-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 3934feae179..0eefd853538 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -61,11 +61,6 @@ class BinarySensorDevice(Entity): """Return the state of the binary sensor.""" return STATE_ON if self.is_on else STATE_OFF - @property - def friendly_state(self): - """Return the friendly state of the binary sensor.""" - return None - @property def sensor_class(self): """Return the class of this sensor, from SENSOR_CASSES.""" @@ -74,6 +69,9 @@ class BinarySensorDevice(Entity): @property def state_attributes(self): """Return device specific state attributes.""" - return { - 'sensor_class': self.sensor_class, - } + attr = {} + + if self.sensor_class is not None: + attr['sensor_class'] = self.sensor_class + + return attr diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py index 9079b208204..db5f09fdf6d 100644 --- a/tests/components/binary_sensor/test_binary_sensor.py +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -31,8 +31,7 @@ class TestBinarySensor(unittest.TestCase): def test_attributes(self): """Test binary sensor attributes.""" sensor = binary_sensor.BinarySensorDevice() - self.assertEqual({'sensor_class': None}, - sensor.state_attributes) + self.assertEqual({}, sensor.state_attributes) with mock.patch('homeassistant.components.binary_sensor.' 'BinarySensorDevice.sensor_class', new='motion'): From 1bfea626ffba61838b6419f19a712a9ad3b226bd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 23:20:14 -0800 Subject: [PATCH 095/186] Handle circular setup dependency --- homeassistant/bootstrap.py | 83 +++++++++++++++++++++----------------- tests/common.py | 2 +- tests/test_bootstrap.py | 18 ++++++++- 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 802da19e938..9db3f79e498 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,13 +1,4 @@ -""" -homeassistant.bootstrap -~~~~~~~~~~~~~~~~~~~~~~~ -Provides methods to bootstrap a home assistant instance. - -Each method will return a tuple (bus, statemachine). - -After bootstrapping you can add your own components or -start by calling homeassistant.start_home_assistant(bus) -""" +"""Provides methods to bootstrap a home assistant instance.""" import logging import logging.handlers @@ -15,6 +6,7 @@ import os import shutil import sys from collections import defaultdict +from threading import RLock import homeassistant.components as core_components import homeassistant.components.group as group @@ -32,6 +24,8 @@ from homeassistant.helpers import event_decorators, service from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) +_SETUP_LOCK = RLock() +_CURRENT_SETUP = [] ATTR_COMPONENT = 'component' @@ -78,42 +72,57 @@ def _handle_requirements(hass, component, name): def _setup_component(hass, domain, config): - """ Setup a component for Home Assistant. """ + """Setup a component for Home Assistant.""" + # pylint: disable=too-many-return-statements if domain in hass.config.components: return True - component = loader.get_component(domain) - missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) - if dep not in hass.config.components] + with _SETUP_LOCK: + # It might have been loaded while waiting for lock + if domain in hass.config.components: + return True - if missing_deps: - _LOGGER.error( - 'Not initializing %s because not all dependencies loaded: %s', - domain, ", ".join(missing_deps)) - return False - - if not _handle_requirements(hass, component, domain): - return False - - try: - if not component.setup(hass, config): - _LOGGER.error('component %s failed to initialize', domain) + if domain in _CURRENT_SETUP: + _LOGGER.error('Attempt made to setup %s during setup of %s', + domain, domain) return False - except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error during setup of component %s', domain) - return False - hass.config.components.append(component.DOMAIN) + component = loader.get_component(domain) + missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) + if dep not in hass.config.components] - # Assumption: if a component does not depend on groups - # it communicates with devices - if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []): - hass.pool.add_worker() + if missing_deps: + _LOGGER.error( + 'Not initializing %s because not all dependencies loaded: %s', + domain, ", ".join(missing_deps)) + return False - hass.bus.fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + if not _handle_requirements(hass, component, domain): + return False - return True + _CURRENT_SETUP.append(domain) + + try: + if not component.setup(hass, config): + _LOGGER.error('component %s failed to initialize', domain) + return False + except Exception: # pylint: disable=broad-except + _LOGGER.exception('Error during setup of component %s', domain) + return False + finally: + _CURRENT_SETUP.remove(domain) + + hass.config.components.append(component.DOMAIN) + + # Assumption: if a component does not depend on groups + # it communicates with devices + if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []): + hass.pool.add_worker() + + hass.bus.fire( + EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + + return True def prepare_setup_platform(hass, config, domain, platform_name): diff --git a/tests/common.py b/tests/common.py index 32f64ba2949..806416a3f0d 100644 --- a/tests/common.py +++ b/tests/common.py @@ -146,7 +146,7 @@ class MockModule(object): self.DEPENDENCIES = dependencies # Setup a mock setup if none given. if setup is None: - self.setup = lambda hass, config: False + self.setup = lambda hass, config: True else: self.setup = setup diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 52d146aa8c9..07b49302b09 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -9,13 +9,13 @@ import os import tempfile import unittest -from homeassistant import bootstrap +from homeassistant import bootstrap, loader from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_CUSTOMIZE) import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, MockModule class TestBootstrap(unittest.TestCase): @@ -102,3 +102,17 @@ class TestBootstrap(unittest.TestCase): state = hass.states.get('test.test') self.assertTrue(state.attributes['hidden']) + + def test_handle_setup_circular_dependency(self): + hass = get_test_home_assistant() + + loader.set_component('comp_b', MockModule('comp_b', ['comp_a'])) + + def setup_a(hass, config): + bootstrap.setup_component(hass, 'comp_b') + return True + + loader.set_component('comp_a', MockModule('comp_a', setup=setup_a)) + + bootstrap.setup_component(hass, 'comp_a') + self.assertEqual(['comp_a'], hass.config.components) From f5f52010d137a2dc1fe0fe640cf0ae60c3bb5844 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 23:21:56 -0800 Subject: [PATCH 096/186] Extract bloomsky binary_sensor --- .../components/binary_sensor/bloomsky.py | 69 +++++++++++++++++++ homeassistant/components/sensor/bloomsky.py | 69 ++++++++----------- 2 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 homeassistant/components/binary_sensor/bloomsky.py diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py new file mode 100644 index 00000000000..3f18af03013 --- /dev/null +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -0,0 +1,69 @@ +""" +Support the binary sensors of a BloomSky weather station. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.bloomsky/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.loader import get_component + +DEPENDENCIES = ["bloomsky"] + +# These are the available sensors mapped to binary_sensor class +SENSOR_TYPES = { + "Rain": "moisture", + "Night": None, +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the available BloomSky weather binary sensors.""" + logger = logging.getLogger(__name__) + bloomsky = get_component('bloomsky') + sensors = config.get('monitored_conditions', SENSOR_TYPES) + + for device in bloomsky.BLOOMSKY.devices.values(): + for variable in sensors: + if variable in SENSOR_TYPES: + add_devices([BloomSkySensor(bloomsky.BLOOMSKY, + device, + variable)]) + else: + logger.error("Cannot find definition for device: %s", variable) + + +class BloomSkySensor(BinarySensorDevice): + """ Represents a single binary sensor in a BloomSky device. """ + + def __init__(self, bs, device, sensor_name): + """Initialize a bloomsky binary sensor.""" + self._bloomsky = bs + self._device_id = device["DeviceID"] + self._sensor_name = sensor_name + self._name = "{} {}".format(device["DeviceName"], sensor_name) + self._unique_id = "bloomsky_binary_sensor {}".format(self._name) + self.update() + + @property + def name(self): + """The name of the BloomSky device and this sensor.""" + return self._name + + @property + def unique_id(self): + """Unique ID for this sensor.""" + return self._unique_id + + @property + def is_on(self): + """If binary sensor is on.""" + return self._state + + def update(self): + """Request an update from the BloomSky API.""" + self._bloomsky.refresh_devices() + + self._state = \ + self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] diff --git a/homeassistant/components/sensor/bloomsky.py b/homeassistant/components/sensor/bloomsky.py index 902b42d6e3f..89aeeb9bb3f 100644 --- a/homeassistant/components/sensor/bloomsky.py +++ b/homeassistant/components/sensor/bloomsky.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.bloomsky -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support the sensor of a BloomSky weather station. For more details about this component, please refer to the documentation at @@ -8,6 +6,7 @@ https://home-assistant.io/components/sensor.bloomsky/ """ import logging +from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component @@ -16,14 +15,12 @@ DEPENDENCIES = ["bloomsky"] # These are the available sensors SENSOR_TYPES = ["Temperature", "Humidity", - "Rain", "Pressure", "Luminance", - "Night", "UVIndex"] # Sensor units - these do not currently align with the API documentation -SENSOR_UNITS = {"Temperature": "°F", +SENSOR_UNITS = {"Temperature": TEMP_FAHRENHEIT, "Humidity": "%", "Pressure": "inHg", "Luminance": "cd/m²"} @@ -34,14 +31,13 @@ FORMAT_NUMBERS = ["Temperature", "Pressure"] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Set up the available BloomSky weather sensors. """ - + """Set up the available BloomSky weather sensors.""" logger = logging.getLogger(__name__) bloomsky = get_component('bloomsky') + sensors = config.get('monitored_conditions', SENSOR_TYPES) - for device_key in bloomsky.BLOOMSKY.devices: - device = bloomsky.BLOOMSKY.devices[device_key] - for variable in config["monitored_conditions"]: + for device in bloomsky.BLOOMSKY.devices.values(): + for variable in sensors: if variable in SENSOR_TYPES: add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, @@ -51,56 +47,45 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BloomSkySensor(Entity): - """ Represents a single sensor in a BloomSky device. """ + """Represents a single sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): + """Initialize a bloomsky sensor.""" self._bloomsky = bs self._device_id = device["DeviceID"] - self._client_name = device["DeviceName"] self._sensor_name = sensor_name - self._state = self.process_state(device) - self._sensor_update = "" + self._name = "{} {}".format(device["DeviceName"], sensor_name) + self._unique_id = "bloomsky_sensor {}".format(self._name) + self.update() @property def name(self): - """ The name of the BloomSky device and this sensor. """ - return "{} {}".format(self._client_name, self._sensor_name) + """The name of the BloomSky device and this sensor.""" + return self._name + + @property + def unique_id(self): + """Unique ID for this sensor.""" + return self._unique_id @property def state(self): - """ The current state (i.e. value) of this sensor. """ + """The current state (i.e. value) of this sensor.""" return self._state @property def unit_of_measurement(self): - """ This sensor's units. """ + """Return the sensor units.""" return SENSOR_UNITS.get(self._sensor_name, None) def update(self): - """ Request an update from the BloomSky API. """ + """Request an update from the BloomSky API.""" self._bloomsky.refresh_devices() - # TS is a Unix epoch timestamp for the last time the BloomSky servers - # heard from this device. If that value hasn't changed, the value has - # not been updated. - last_ts = self._bloomsky.devices[self._device_id]["Data"]["TS"] - if last_ts != self._sensor_update: - self.process_state(self._bloomsky.devices[self._device_id]) - self._sensor_update = last_ts - def process_state(self, device): - """ Handle the response from the BloomSky API for this sensor. """ - data = device["Data"][self._sensor_name] - if self._sensor_name == "Rain": - if data: - self._state = "Raining" - else: - self._state = "Not raining" - elif self._sensor_name == "Night": - if data: - self._state = "Nighttime" - else: - self._state = "Daytime" - elif self._sensor_name in FORMAT_NUMBERS: - self._state = "{0:.2f}".format(data) + state = \ + self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] + + if self._sensor_name in FORMAT_NUMBERS: + self._state = "{0:.2f}".format(state) else: - self._state = data + self._state = state From fb9d8c79b50e495706f896c0e284d10522b96d1a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Feb 2016 23:22:23 -0800 Subject: [PATCH 097/186] Bloomsky main component to load its platforms --- homeassistant/components/binary_sensor/__init__.py | 3 ++- homeassistant/components/bloomsky.py | 11 +++++++++++ homeassistant/components/camera/__init__.py | 5 ++++- homeassistant/components/sensor/__init__.py | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 0eefd853538..844bc609bfd 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,7 +12,7 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import (mysensors, ) +from homeassistant.components import (bloomsky, mysensors) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -32,6 +32,7 @@ SENSOR_CLASSES = [ # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { + bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', } diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index f13b9ce85c9..44a90007725 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -11,6 +11,7 @@ from datetime import timedelta import requests +from homeassistant.components import discovery from homeassistant.const import CONF_API_KEY from homeassistant.helpers import validate_config from homeassistant.util import Throttle @@ -24,6 +25,10 @@ _LOGGER = logging.getLogger(__name__) # no point in polling the API more frequently MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) +DISCOVER_SENSORS = 'bloomsky.sensors' +DISCOVER_BINARY_SENSORS = 'bloomsky.binary_sensor' +DISCOVER_CAMERAS = 'bloomsky.camera' + # pylint: disable=unused-argument,too-few-public-methods def setup(hass, config): @@ -42,6 +47,12 @@ def setup(hass, config): except RuntimeError: return False + for component, discovery_service in ( + ('camera', DISCOVER_CAMERAS), ('sensor', DISCOVER_SENSORS), + ('binary_sensor', DISCOVER_BINARY_SENSORS)): + discovery.discover(hass, discovery_service, component=component, + hass_config=config) + return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 7fab1fe3ae6..615554ae56f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -13,6 +13,7 @@ import requests from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.components import bloomsky from homeassistant.const import ( ATTR_ENTITY_PICTURE, HTTP_NOT_FOUND, @@ -26,7 +27,9 @@ SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' # Maps discovered services to their platforms -DISCOVERY_PLATFORMS = {} +DISCOVERY_PLATFORMS = { + bloomsky.DISCOVER_CAMERAS: 'bloomsky', +} STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 88071c0b5fb..8dbaef0e118 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -10,7 +10,7 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.components import ( - wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors) + wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, bloomsky) DOMAIN = 'sensor' SCAN_INTERVAL = 30 @@ -19,6 +19,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { + bloomsky.DISCOVER_SENSORS: 'bloomsky', wink.DISCOVER_SENSORS: 'wink', zwave.DISCOVER_SENSORS: 'zwave', isy994.DISCOVER_SENSORS: 'isy994', From 8a1fa822055253c8a6224de74df345bb062eff8d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 00:08:02 -0800 Subject: [PATCH 098/186] Add sensor class to bloomsky binary_sensor --- homeassistant/components/binary_sensor/__init__.py | 2 +- homeassistant/components/binary_sensor/bloomsky.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 844bc609bfd..c51b30d8b7c 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -64,7 +64,7 @@ class BinarySensorDevice(Entity): @property def sensor_class(self): - """Return the class of this sensor, from SENSOR_CASSES.""" + """Return the class of this sensor, from SENSOR_CLASSES.""" return None @property diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py index 3f18af03013..ce24a0c1015 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -56,6 +56,11 @@ class BloomSkySensor(BinarySensorDevice): """Unique ID for this sensor.""" return self._unique_id + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return SENSOR_TYPES.get(self._sensor_name) + @property def is_on(self): """If binary sensor is on.""" From f4e0f1d8954b8a49950bb82d5056712b876132b0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 00:11:07 -0800 Subject: [PATCH 099/186] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 20 +++++++++++-------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index ea3e441f6c5..255f032d803 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "4ae370eaaad6bc779d08713b79ce3cba" +VERSION = "72a593fc8887c08d6f4ad9bd483bd232" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 0013746b4d2..df86e85e4c0 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2860,6 +2860,7 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym font-size: 0px; border-radius: 2px; cursor: pointer; + min-height: 48px; } .camera-feed { width: 100%; @@ -2879,7 +2880,10 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym font-weight: 500; line-height: 16px; color: white; - } \ No newline at end of file +value:function(t,e){if(0===this.__batchDepth){if(h.getOption(this.reactorState,"throwOnDispatchInDispatch")&&this.__isDispatching)throw this.__isDispatching=!1,new Error("Dispatch may not be called while a dispatch is in progress");this.__isDispatching=!0}try{this.reactorState=h.dispatch(this.reactorState,t,e)}catch(n){throw this.__isDispatching=!1,n}try{this.__notify()}finally{this.__isDispatching=!1}}},{key:"batch",value:function(t){this.batchStart(),t(),this.batchEnd()}},{key:"registerStore",value:function(t,e){console.warn("Deprecation warning: `registerStore` will no longer be supported in 1.1, use `registerStores` instead"),this.registerStores(o({},t,e))}},{key:"registerStores",value:function(t){this.reactorState=h.registerStores(this.reactorState,t),this.__notify()}},{key:"replaceStores",value:function(t){this.reactorState=h.replaceStores(this.reactorState,t)}},{key:"serialize",value:function(){return h.serialize(this.reactorState)}},{key:"loadState",value:function(t){this.reactorState=h.loadState(this.reactorState,t),this.__notify()}},{key:"reset",value:function(){var t=h.reset(this.reactorState);this.reactorState=t,this.prevReactorState=t,this.observerState=new m.ObserverState}},{key:"__notify",value:function(){var t=this;if(!(this.__batchDepth>0)){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=h.evaluate(t.prevReactorState,r),a=h.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=a.reactorState;var u=o.result,s=a.result;c["default"].is(u,s)||i.call(null,s)}});var r=h.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){"use strict";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(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}},t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return new A({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,P.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&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(l(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 O(t,[n])})}),w(t)})}function a(t,e){return t.withMutations(function(t){(0,P.each)(e,function(e,n){t.update("stores",function(t){return t.set(n,e)})})})}function u(t,e,n){if(void 0===e&&l(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){T["default"].dispatchStart(t,e,n),t.get("stores").forEach(function(o,a){var u=r.get(a),s=void 0;try{s=o.handle(u,e,n)}catch(c){throw T["default"].dispatchError(t,c.message),c}if(void 0===s&&l(t,"throwOnUndefinedStoreReturnValue")){var f="Store handler must return a value, did you forget a return statement";throw T["default"].dispatchError(t,f),new Error(f)}r.set(a,s),u!==s&&(i=i.add(a))}),T["default"].dispatchEnd(t,r,i)}),a=t.set("state",o).set("dirtyStores",i).update("storeStates",function(t){return O(t,i)});return w(a)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations(function(r){(0,P.each)(e,function(e,i){var o=t.getIn(["stores",i]);if(o){var a=o.deserialize(e);void 0!==a&&(r.set(i,a),n.push(i))}})}),i=I["default"].Set(n);return t.update("state",function(t){return t.merge(r)}).update("dirtyStores",function(t){return t.union(i)}).update("storeStates",function(t){return O(t,n)})}function c(t,e,n){var r=e;(0,j.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),a=I["default"].Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),u=void 0;return u=0===o.size?t.update("any",function(t){return t.add(i)}):t.withMutations(function(t){o.forEach(function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,I["default"].Set()),t.updateIn(["stores",e],function(t){return t.add(i)})})}),u=u.set("nextId",i+1).setIn(["observersMap",i],a),{observerState:u,entry:a}}function l(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function f(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,j.isKeyPath)(e)&&(0,j.isKeyPath)(r)?(0,j.isEqual)(e,r):e===r:!1});return t.withMutations(function(t){r.forEach(function(e){return d(t,e)})})}function d(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 h(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&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(l(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 O(t,r)}),v(t)})}function p(t,e){var n=t.get("state");if((0,j.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(S(t,e),t);var r=(0,C.getDeps)(e).map(function(e){return p(t,e).result}),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,b(t,e,o))}function _(t){var e={};return t.get("stores").forEach(function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)}),e}function v(t){return t.set("dirtyStores",I["default"].Set())}function y(t){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?!1:r.every(function(e,n){return t.getIn(["storeStates",n])===e})}function b(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),a=(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],I["default"].Map({value:n,storeStates:a,dispatchId:i}))}function S(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function w(t){return t.update("dispatchId",function(t){return t+1})}function O(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=a,e.dispatch=u,e.loadState=s,e.addObserver=c,e.getOption=l,e.removeObserver=f,e.removeObserverByEntry=d,e.reset=h,e.evaluate=p,e.serialize=_,e.resetDirtyStores=v;var M=n(3),I=r(M),E=n(9),T=r(E),D=n(5),C=n(10),j=n(11),P=n(4),A=I["default"].Record({result:null,reactorState:null})},function(t,e,n){"use strict";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){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,d.isArray)(t)&&(0,d.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function a(t){return t.slice(0,t.length-1)}function u(t,e){e||(e=f["default"].Set());var n=f["default"].Set().withMutations(function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");a(t).forEach(function(t){if((0,h.isKeyPath)(t))e.add((0,l.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(u(t))}})});return e.union(n)}function s(t){if(!(0,h.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,p]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=u(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 l=n(3),f=r(l),d=n(4),h=n(11),p=function(t){return t};e["default"]={isGetter:i,getComputeFn:o,getFlattenedDeps:u,getStoreDeps:c,getDeps:a,fromKeyPath:s},t.exports=e["default"]},function(t,e,n){"use strict";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=u["default"].List(t),r=u["default"].List(e);return u["default"].is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var a=n(3),u=r(a),s=n(4)},function(t,e,n){"use strict";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 a=(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=a;var u=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=u}])})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(127),u=r(a);e["default"]=(0,u["default"])(o["default"].reactor)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.callApi=void 0;var i=n(130),o=r(i);e.callApi=o["default"]},function(t,e){"use strict";var n=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};t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(80),n(37),e["default"]=new o["default"]({is:"state-info",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"partial-base",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1}},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(146),a=i(o),u=n(147),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){t.registerStores({restApiCache:l["default"]})}function o(t){return[["restApiCache",t.entity],function(t){return!!t}]}function a(t){return[["restApiCache",t.entity],function(t){return t||(0,s.toImmutable)({})}]}function u(t){return function(e){return["restApiCache",t.entity,e]}}Object.defineProperty(e,"__esModule",{value:!0}),e.createApiActions=void 0,e.register=i,e.createHasDataGetter=o,e.createEntityMapGetter=a,e.createByIdGetter=u;var s=n(3),c=n(172),l=r(c),f=n(171),d=r(f);e.createApiActions=d["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({ENTITY_HISTORY_DATE_SELECTED:null,ENTITY_HISTORY_FETCH_START:null,ENTITY_HISTORY_FETCH_ERROR:null,ENTITY_HISTORY_FETCH_SUCCESS:null,RECENT_ENTITY_HISTORY_FETCH_START:null,RECENT_ENTITY_HISTORY_FETCH_ERROR:null,RECENT_ENTITY_HISTORY_FETCH_SUCCESS:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({LOGBOOK_DATE_SELECTED:null,LOGBOOK_ENTRIES_FETCH_START:null,LOGBOOK_ENTRIES_FETCH_ERROR:null,LOGBOOK_ENTRIES_FETCH_SUCCESS:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(173),a=i(o),u=n(54),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({authAttempt:u["default"],authCurrent:c["default"],rememberAuth:f["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(133),u=i(a),s=n(134),c=i(s),l=n(135),f=i(l),d=n(131),h=r(d),p=n(132),_=r(p);e.actions=h,e.getters=_},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var u=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),s=function(){function t(t,e){for(var n=0;n4?"value big":"value"},computeHideIcon:function(t,e,n){return!t||e||n},computeHideValue:function(t,e){return!t||e},imageChanged:function(t){this.$.badge.style.backgroundImage=t?"url("+t+")":""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"loading-box"})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(128),u=r(a);n(119),n(39),n(120),n(121),n(123),n(122),n(124),n(125),n(126),e["default"]=new o["default"]({is:"state-card-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=(0,u["default"])(t);if(e&&(0,u["default"])(e)===r)n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("state-card-"+r);i.stateObj=t,n.appendChild(i)}}})},function(t,e){"use strict";function n(t,e){return t?e.map(function(e){return e in t.attributes?"has-"+e:""}).join(" "):""}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u.evaluate(s.canToggleEntity(t))}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(2),a=r(o),u=a["default"].reactor,s=a["default"].serviceGetters},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){switch(t){case"alarm_control_panel":return e&&"disarmed"===e?"mdi:bell-outline":"mdi:bell";case"automation":return"mdi:playlist-play";case"binary_sensor":return e&&"off"===e?"mdi:radiobox-blank":"mdi:checkbox-marked-circle";case"camera":return"mdi:video";case"configurator":return"mdi:settings";case"conversation":return"mdi:text-to-speech";case"device_tracker":return"mdi:account";case"garage_door":return"mdi:glassdoor";case"group":return"mdi:google-circles-communities";case"homeassistant":return"mdi:home";case"input_boolean":return"mdi:drawing";case"light":return"mdi:lightbulb";case"lock":return e&&"unlocked"===e?"mdi:lock-open":"mdi:lock";case"media_player":return e&&"off"!==e&&"idle"!==e?"mdi:cast-connected":"mdi:cast";case"notify":return"mdi:comment-alert";case"proximity":return"mdi:apple-safari";case"rollershutter":return e&&"open"===e?"mdi:window-open":"mdi:window-closed";case"scene":return"mdi:google-pages";case"script":return"mdi:file-document";case"sensor":return"mdi:eye";case"simple_alarm":return"mdi:bell";case"sun":return"mdi:white-balance-sunny";case"switch":return"mdi:flash";case"thermostat":return"mdi:nest-thermostat";case"updater":return"mdi:cloud-upload";case"weblink":return"mdi:open-in-new";default:return console.warn("Unable to find icon for domain "+t+" ("+e+")"),a["default"]}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(40),a=r(o)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({SERVER_CONFIG_LOADED:null,COMPONENT_LOADED:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({serverComponent:u["default"],serverConfig:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(138),u=i(a),s=n(139),c=i(s),l=n(136),f=r(l),d=n(137),h=r(d);e.actions=f,e.getters=h},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(150),a=i(o),u=n(151),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({NAVIGATE:null,SHOW_SIDEBAR:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({notifications:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(168),u=i(a),s=n(166),c=r(s),l=n(167),f=r(l);e.actions=c,e.getters=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({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})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({streamStatus:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(180),u=i(a),s=n(176),c=r(s),l=n(177),f=r(l);e.actions=c,e.getters=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({isFetchingData:u["default"],isSyncScheduled:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(182),u=i(a),s=n(183),c=i(s),l=n(181),f=r(l),d=n(57),h=r(d);e.actions=f,e.getters=h},function(t,e){"use strict";function n(t){return t.getFullYear()+"-"+(t.getMonth()+1)+"-"+t.getDate()}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],a=i.split(":"),u=r(a,3),s=u[0],c=u[1],l=u[2],f=o.split("-"),d=r(f,3),h=d[0],p=d[1],_=d[2];return new Date(Date.UTC(_,parseInt(p,10)-1,h,s,c,l))}var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(22),u=r(a);e["default"]=new o["default"]({is:"domain-icon",properties:{domain:{type:String,value:""},state:{type:String,value:""}},computeIcon:function(t,e){return(0,u["default"])(t,e)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"ha-entity-toggle",properties:{stateObj:{type:Object},toggleChecked:{type:Boolean,value:!1},isOn:{type:Boolean,computed:"computeIsOn(stateObj)",observer:"isOnChanged"}},ready:function(){this.forceStateChange()},toggleChanged:function(t){var e=t.target.checked;e&&!this.isOn?this._call_service(!0):!e&&this.isOn&&this._call_service(!1)},isOnChanged:function(t){this.toggleChecked=t},forceStateChange:function(){this.toggleChecked===this.isOn&&(this.toggleChecked=!this.toggleChecked),this.toggleChecked=this.isOn},turnOn:function(){this._call_service(!0)},turnOff:function(){this._call_service(!1)},computeIsOn:function(t){return t&&"off"!==t.state&&"unlocked"!==t.state&&"closed"!==t.state},_call_service:function(t){var e=this,n=void 0,r=void 0;"lock"===this.stateObj.domain?(n="lock",r=t?"lock":"unlock"):"garage_door"===this.stateObj.domain?(n="garage_door",r=t?"open":"close"):(n="homeassistant",r=t?"turn_on":"turn_off");var i=s.callService(n,r,{entity_id:this.stateObj.entityId});this.stateObj.attributes.assumed_state||i.then(function(){return e.forceStateChange()})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-card",properties:{header:{type:String},elevation:{type:Number,value:1,reflectToAttribute:!0}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(67),o=r(i),a=n(2),u=r(a),s=n(1),c=r(s),l=6e4,f=u["default"].util.parseDateTime;e["default"]=new c["default"]({is:"relative-ha-datetime",properties:{datetime:{type:String,observer:"datetimeChanged"},datetimeObj:{type:Object,observer:"datetimeObjChanged"},parsedDateTime:{type:Object},relativeTime:{type:String,value:"not set"}},created:function(){this.updateRelative=this.updateRelative.bind(this)},attached:function(){this._interval=setInterval(this.updateRelative,l)},detached:function(){clearInterval(this._interval)},datetimeChanged:function(t){this.parsedDateTime=t?f(t):null,this.updateRelative()},datetimeObjChanged:function(t){this.parsedDateTime=t,this.updateRelative()},updateRelative:function(){this.relativeTime=this.parsedDateTime?(0,o["default"])(this.parsedDateTime).fromNow():""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(18),n(90),n(89),e["default"]=new o["default"]({is:"state-history-charts",properties:{stateHistory:{type:Object},isLoadingData:{type:Boolean,value:!1},apiLoaded:{type:Boolean,value:!1},isLoading:{type:Boolean,computed:"computeIsLoading(isLoadingData, apiLoaded)"},groupedStateHistory:{type:Object,computed:"computeGroupedStateHistory(isLoading, stateHistory)"},isSingleDevice:{type:Boolean,computed:"computeIsSingleDevice(stateHistory)"}},computeIsSingleDevice:function(t){return t&&1===t.size},computeGroupedStateHistory:function(t,e){if(t||!e)return{line:[],timeline:[]};var n={},r=[];e.forEach(function(t){if(t&&0!==t.size){var e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=e?e.attributes.unit_of_measurement:!1;i?i in n?n[i].push(t.toArray()):n[i]=[t.toArray()]:r.push(t.toArray())}}),r=r.length>0&&r;var i=Object.keys(n).map(function(t){return[t,n[t]]});return{line:i,timeline:r}},googleApiLoaded:function(){var t=this;window.google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){return t.apiLoaded=!0}})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size},extractUnit:function(t){return t[0]},extractData:function(t){return t[1]}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-display",properties:{stateObj:{type:Object}}})},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e["default"]="bookmark"},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,a["default"])(t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(67),a=r(o)},function(t,e){"use strict";function n(){var t=document.getElementById("ha-init-skeleton");t&&t.parentElement.removeChild(t)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){var e=t.state&&"off"===t.state;switch(t.attributes.sensor_class){case"opening":return e?"mdi:crop-square":"mdi:exit-to-app";case"moisture":return e?"mdi:water-off":"mdi:water";case"safety":case"gas":case"smoke":case"power":return e?"mdi:verified":"mdi:alert";case"motion":return e?"mdi:walk":"mdi:run";case"digital":default:return e?"mdi:radiobox-blank":"mdi:checkbox-marked-circle"}}function o(t){if(!t)return u["default"];if(t.attributes.icon)return t.attributes.icon;var e=t.attributes.unit_of_measurement;if(e&&"sensor"===t.domain){if(e===d.UNIT_TEMP_C||e===d.UNIT_TEMP_F)return"mdi:thermometer";if("Mice"===e)return"mdi:mouse-variant"}else if("binary_sensor"===t.domain)return i(t); +return(0,c["default"])(t.domain,t.state)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=o;var a=n(40),u=r(a),s=n(22),c=r(s),l=n(2),f=r(l),d=f["default"].util.temperatureUnits},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t,e){a.validate(t,{rememberAuth:e,useStreaming:u.useStreaming})};var i=n(2),o=r(i),a=o["default"].authActions,u=o["default"].localStoragePreferences},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.recentEntityHistoryUpdatedMap=e.recentEntityHistoryMap=e.hasDataForCurrentDate=e.entityHistoryForCurrentDate=e.entityHistoryMap=e.currentDate=e.isLoadingEntityHistory=void 0;var r=n(3),i=(e.isLoadingEntityHistory=["isLoadingEntityHistory"],e.currentDate=["currentEntityHistoryDate"]),o=e.entityHistoryMap=["entityHistory"];e.entityHistoryForCurrentDate=[i,o,function(t,e){return e.get(t)||(0,r.toImmutable)({})}],e.hasDataForCurrentDate=[i,o,function(t,e){return!!e.get(t)}],e.recentEntityHistoryMap=["recentEntityHistory"],e.recentEntityHistoryUpdatedMap=["recentEntityHistory"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentEntityHistoryDate:u["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:h["default"],recentEntityHistoryUpdated:_["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(141),u=i(a),s=n(142),c=i(s),l=n(143),f=i(l),d=n(144),h=i(d),p=n(145),_=i(p),v=n(140),y=r(v),m=n(45),g=r(m);e.actions=y,e.getters=g},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var a=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){function r(t,e,n){function r(){y&&clearTimeout(y),h&&clearTimeout(h),g=0,h=y=m=void 0}function s(e,n){n&&clearTimeout(n),h=y=m=void 0,e&&(g=o(),p=t.apply(v,d),y||h||(d=v=void 0))}function c(){var t=e-(o()-_);0>=t||t>e?s(m,h):y=setTimeout(c,t)}function l(){s(S,y)}function f(){if(d=arguments,_=o(),v=this,m=S&&(y||!w),b===!1)var n=w&&!y;else{h||w||(g=_);var r=b-(_-g),i=0>=r||r>b;i?(h&&(h=clearTimeout(h)),g=_,p=t.apply(v,d)):h||(h=setTimeout(l,r))}return i&&y?y=clearTimeout(y):y||e===b||(y=setTimeout(c,e)),n&&(i=!0,p=t.apply(v,d)),!i||y||h||(d=v=void 0),p}var d,h,p,_,v,y,m,g=0,b=!1,S=!0;if("function"!=typeof t)throw new TypeError(a);if(e=0>e?0:+e||0,n===!0){var w=!0;S=!1}else i(n)&&(w=!!n.leading,b="maxWait"in n&&u(+n.maxWait||0,e),S="trailing"in n?!!n.trailing:S);return f.cancel=r,f}var i=n(66),o=n(200),a="Expected a function",u=Math.max;t.exports=r},function(t,e,n){function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(203);t.exports=r},function(t,e){function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){function r(t){return i(t)&&u.call(t)==o}var i=n(66),o="[object Function]",a=Object.prototype,u=a.toString;t.exports=r},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){(function(t){!function(e,n){t.exports=n()}(this,function(){"use strict";function e(){return Kn.apply(null,arguments)}function n(t){Kn=t}function r(t){return"[object Array]"===Object.prototype.toString.call(t)}function i(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function o(t,e){var n,r=[];for(n=0;n0)for(n in $n)r=$n[n],i=e[r],h(i)||(t[r]=i);return t}function _(t){p(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),Zn===!1&&(Zn=!0,e.updateOffset(this),Zn=!1)}function v(t){return t instanceof _||null!=t&&null!=t._isAMomentObject}function y(t){return 0>t?Math.ceil(t):Math.floor(t)}function m(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=y(e)),n}function g(t,e,n){var r,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),a=0;for(r=0;i>r;r++)(n&&t[r]!==e[r]||!n&&m(t[r])!==m(e[r]))&&a++;return a+o}function b(){}function S(t){return t?t.toLowerCase().replace("_","-"):t}function w(t){for(var e,n,r,i,o=0;o0;){if(r=O(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&g(i,n,!0)>=e-1)break;e--}o++}return null}function O(e){var n=null;if(!Xn[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=Jn._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),M(n)}catch(r){}return Xn[e]}function M(t,e){var n;return t&&(n=h(e)?E(t):I(t,e),n&&(Jn=n)),Jn._abbr}function I(t,e){return null!==e?(e.abbr=t,Xn[t]=Xn[t]||new b,Xn[t].set(e),M(t),Xn[t]):(delete Xn[t],null)}function E(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Jn;if(!r(t)){if(e=O(t))return e;t=[t]}return w(t)}function T(t,e){var n=t.toLowerCase();Qn[n]=Qn[n+"s"]=Qn[e]=t}function D(t){return"string"==typeof t?Qn[t]||Qn[t.toLowerCase()]:void 0}function C(t){var e,n,r={};for(n in t)a(t,n)&&(e=D(n),e&&(r[e]=t[n]));return r}function j(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function P(t,n){return function(r){return null!=r?(k(this,t,r),e.updateOffset(this,n),this):A(this,t)}}function A(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function k(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function L(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=D(t),j(this[t]))return this[t](e);return this}function N(t,e,n){var r=""+Math.abs(t),i=e-r.length,o=t>=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+r}function R(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(rr[t]=i),e&&(rr[e[0]]=function(){return N(i.apply(this,arguments),e[1],e[2])}),n&&(rr[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function x(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function H(t){var e,n,r=t.match(tr);for(e=0,n=r.length;n>e;e++)rr[r[e]]?r[e]=rr[r[e]]:r[e]=x(r[e]);return function(i){var o="";for(e=0;n>e;e++)o+=r[e]instanceof Function?r[e].call(i,t):r[e];return o}}function Y(t,e){return t.isValid()?(e=z(e,t.localeData()),nr[e]=nr[e]||H(e),nr[e](t)):t.localeData().invalidDate()}function z(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(er.lastIndex=0;r>=0&&er.test(t);)t=t.replace(er,n),er.lastIndex=0,r-=1;return t}function U(t,e,n){Sr[t]=j(e)?e:function(t,r){return t&&n?n:e}}function V(t,e){return a(Sr,t)?Sr[t](e._strict,e._locale):new RegExp(F(t))}function F(t){return G(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}))}function G(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function B(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(r=function(t,n){n[e]=m(t)}),n=0;nr;r++){if(i=s([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(o="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[r]=new RegExp(o.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[r].test(t))return r;if(n&&"MMM"===e&&this._shortMonthsParse[r].test(t))return r;if(!n&&this._monthsParse[r].test(t))return r}}function X(t,e){var n;return t.isValid()?"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(n=Math.min(t.date(),K(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t):t}function Q(t){return null!=t?(X(this,t),e.updateOffset(this,!0),this):A(this,"Month")}function tt(){return K(this.year(),this.month())}function et(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function nt(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function rt(){function t(t,e){return e.length-t.length}var e,n,r=[],i=[],o=[];for(e=0;12>e;e++)n=s([2e3,e]),r.push(this.monthsShort(n,"")),i.push(this.months(n,"")),o.push(this.months(n,"")),o.push(this.monthsShort(n,""));for(r.sort(t),i.sort(t),o.sort(t),e=0;12>e;e++)r[e]=G(r[e]),i[e]=G(i[e]),o[e]=G(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+r.join("|")+")$","i")}function it(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[Mr]<0||n[Mr]>11?Mr:n[Ir]<1||n[Ir]>K(n[Or],n[Mr])?Ir:n[Er]<0||n[Er]>24||24===n[Er]&&(0!==n[Tr]||0!==n[Dr]||0!==n[Cr])?Er:n[Tr]<0||n[Tr]>59?Tr:n[Dr]<0||n[Dr]>59?Dr:n[Cr]<0||n[Cr]>999?Cr:-1,l(t)._overflowDayOfYear&&(Or>e||e>Ir)&&(e=Ir),l(t)._overflowWeeks&&-1===e&&(e=jr),l(t)._overflowWeekday&&-1===e&&(e=Pr),l(t).overflow=e),t}function ot(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function at(t,e){var n=!0;return u(function(){return n&&(ot(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function ut(t,e){xr[t]||(ot(e),xr[t]=!0)}function st(t){var e,n,r,i,o,a,u=t._i,s=Hr.exec(u)||Yr.exec(u);if(s){for(l(t).iso=!0,e=0,n=Ur.length;n>e;e++)if(Ur[e][1].exec(s[1])){i=Ur[e][0],r=Ur[e][2]!==!1;break}if(null==i)return void(t._isValid=!1);if(s[3]){for(e=0,n=Vr.length;n>e;e++)if(Vr[e][1].exec(s[3])){o=(s[2]||" ")+Vr[e][0];break}if(null==o)return void(t._isValid=!1)}if(!r&&null!=o)return void(t._isValid=!1);if(s[4]){if(!zr.exec(s[4]))return void(t._isValid=!1);a="Z"}t._f=i+(o||"")+(a||""),Ot(t)}else t._isValid=!1}function ct(t){var n=Fr.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(st(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function lt(t,e,n,r,i,o,a){var u=new Date(t,e,n,r,i,o,a);return 100>t&&t>=0&&isFinite(u.getFullYear())&&u.setFullYear(t),u}function ft(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function dt(t){return ht(t)?366:365}function ht(t){return t%4===0&&t%100!==0||t%400===0}function pt(){return ht(this.year())}function _t(t,e,n){var r=7+e-n,i=(7+ft(t,0,r).getUTCDay()-e)%7;return-i+r-1}function vt(t,e,n,r,i){var o,a,u=(7+n-r)%7,s=_t(t,r,i),c=1+7*(e-1)+u+s;return 0>=c?(o=t-1,a=dt(o)+c):c>dt(t)?(o=t+1,a=c-dt(t)):(o=t,a=c),{year:o,dayOfYear:a}}function yt(t,e,n){var r,i,o=_t(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>a?(i=t.year()-1,r=a+mt(i,e,n)):a>mt(t.year(),e,n)?(r=a-mt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function mt(t,e,n){var r=_t(t,e,n),i=_t(t+1,e,n);return(dt(t)-r+i)/7}function gt(t,e,n){return null!=t?t:null!=e?e:n}function bt(t){var n=new Date(e.now());return t._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function St(t){var e,n,r,i,o=[];if(!t._d){for(r=bt(t),t._w&&null==t._a[Ir]&&null==t._a[Mr]&&wt(t),t._dayOfYear&&(i=gt(t._a[Or],r[Or]),t._dayOfYear>dt(i)&&(l(t)._overflowDayOfYear=!0),n=ft(i,0,t._dayOfYear),t._a[Mr]=n.getUTCMonth(),t._a[Ir]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=r[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[Er]&&0===t._a[Tr]&&0===t._a[Dr]&&0===t._a[Cr]&&(t._nextDay=!0,t._a[Er]=0),t._d=(t._useUTC?ft:lt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Er]=24)}}function wt(t){var e,n,r,i,o,a,u,s;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,a=4,n=gt(e.GG,t._a[Or],yt(Pt(),1,4).year),r=gt(e.W,1),i=gt(e.E,1),(1>i||i>7)&&(s=!0)):(o=t._locale._week.dow,a=t._locale._week.doy,n=gt(e.gg,t._a[Or],yt(Pt(),o,a).year),r=gt(e.w,1),null!=e.d?(i=e.d,(0>i||i>6)&&(s=!0)):null!=e.e?(i=e.e+o,(e.e<0||e.e>6)&&(s=!0)):i=o),1>r||r>mt(n,o,a)?l(t)._overflowWeeks=!0:null!=s?l(t)._overflowWeekday=!0:(u=vt(n,r,i,o,a),t._a[Or]=u.year,t._dayOfYear=u.dayOfYear)}function Ot(t){if(t._f===e.ISO_8601)return void st(t);t._a=[],l(t).empty=!0;var n,r,i,o,a,u=""+t._i,s=u.length,c=0;for(i=z(t._f,t._locale).match(tr)||[],n=0;n0&&l(t).unusedInput.push(a),u=u.slice(u.indexOf(r)+r.length),c+=r.length),rr[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),q(o,r,t)):t._strict&&!r&&l(t).unusedTokens.push(o);l(t).charsLeftOver=s-c,u.length>0&&l(t).unusedInput.push(u),l(t).bigHour===!0&&t._a[Er]<=12&&t._a[Er]>0&&(l(t).bigHour=void 0),t._a[Er]=Mt(t._locale,t._a[Er],t._meridiem),St(t),it(t)}function Mt(t,e,n){var r;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(r=t.isPM(n),r&&12>e&&(e+=12),r||12!==e||(e=0),e):e}function It(t){var e,n,r,i,o;if(0===t._f.length)return l(t).invalidFormat=!0,void(t._d=new Date(NaN));for(i=0;io)&&(r=o,n=e));u(t,n||e)}function Et(t){if(!t._d){var e=C(t._i);t._a=o([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),St(t)}}function Tt(t){var e=new _(it(Dt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Dt(t){var e=t._i,n=t._f;return t._locale=t._locale||E(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new _(it(e)):(r(n)?It(t):n?Ot(t):i(e)?t._d=e:Ct(t),f(t)||(t._d=null),t))}function Ct(t){var n=t._i;void 0===n?t._d=new Date(e.now()):i(n)?t._d=new Date(+n):"string"==typeof n?ct(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),St(t)):"object"==typeof n?Et(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function jt(t,e,n,r,i){var o={};return"boolean"==typeof n&&(r=n,n=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=i,o._l=n,o._i=t,o._f=e,o._strict=r,Tt(o)}function Pt(t,e,n,r){return jt(t,e,n,r,!1)}function At(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Pt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+N(~~(t/60),2)+e+N(~~t%60,2)})}function Ht(t,e){var n=(e||"").match(t)||[],r=n[n.length-1]||[],i=(r+"").match(Kr)||["-",0,0],o=+(60*i[1])+m(i[2]);return"+"===i[0]?o:-o}function Yt(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Pt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Pt(t).local()}function zt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Ut(t,n){var r,i=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=Ht(mr,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=zt(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?re(this,Xt(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:zt(this):null!=t?this:NaN}function Vt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Ft(t){return this.utcOffset(0,t)}function Gt(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(zt(this),"m")),this}function Bt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ht(yr,this._i)),this}function Wt(t){return this.isValid()?(t=t?Pt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function qt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kt(){if(!h(this._isDSTShifted))return this._isDSTShifted;var t={};if(p(t,this),t=Dt(t),t._a){var e=t._isUTC?s(t._a):Pt(t._a);this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Jt(){return this.isValid()?!this._isUTC:!1}function $t(){return this.isValid()?this._isUTC:!1}function Zt(){return this.isValid()?this._isUTC&&0===this._offset:!1}function Xt(t,e){var n,r,i,o=t,u=null;return Rt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(u=Jr.exec(t))?(n="-"===u[1]?-1:1,o={y:0,d:m(u[Ir])*n,h:m(u[Er])*n,m:m(u[Tr])*n,s:m(u[Dr])*n,ms:m(u[Cr])*n}):(u=$r.exec(t))?(n="-"===u[1]?-1:1,o={y:Qt(u[2],n),M:Qt(u[3],n),d:Qt(u[4],n),h:Qt(u[5],n),m:Qt(u[6],n),s:Qt(u[7],n),w:Qt(u[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=ee(Pt(o.from),Pt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new Nt(o),Rt(t)&&a(t,"_locale")&&(r._locale=t._locale),r}function Qt(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function te(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function ee(t,e){var n;return t.isValid()&&e.isValid()?(e=Yt(e,t),t.isBefore(e)?n=te(t,e):(n=te(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function ne(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(ut(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=n,n=r,r=o),n="string"==typeof n?+n:n,i=Xt(n,r),re(this,i,t),this}}function re(t,n,r,i){var o=n._milliseconds,a=n._days,u=n._months;t.isValid()&&(i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),a&&k(t,"Date",A(t,"Date")+a*r),u&&X(t,A(t,"Month")+u*r),i&&e.updateOffset(t,a||u))}function ie(t,e){var n=t||Pt(),r=Yt(n,this).startOf("day"),i=this.diff(r,"days",!0),o=-6>i?"sameElse":-1>i?"lastWeek":0>i?"lastDay":1>i?"sameDay":2>i?"nextDay":7>i?"nextWeek":"sameElse",a=e&&(j(e[o])?e[o]():e[o]);return this.format(a||this.localeData().calendar(o,this,Pt(n)))}function oe(){return new _(this)}function ae(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e)):!1}function ue(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n):!1}function se(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function ce(t,e){var n,r=v(t)?t:Pt(t);return this.isValid()&&r.isValid()?(e=D(e||"millisecond"),"millisecond"===e?+this===+r:(n=+r,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e))):!1}function le(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function fe(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function de(t,e,n){var r,i,o,a;return this.isValid()?(r=Yt(t,this),r.isValid()?(i=6e4*(r.utcOffset()-this.utcOffset()),e=D(e),"year"===e||"month"===e||"quarter"===e?(a=he(this,r),"quarter"===e?a/=3:"year"===e&&(a/=12)):(o=this-r,a="second"===e?o/1e3:"minute"===e?o/6e4:"hour"===e?o/36e5:"day"===e?(o-i)/864e5:"week"===e?(o-i)/6048e5:o),n?a:y(a)):NaN):NaN}function he(t,e){var n,r,i=12*(e.year()-t.year())+(e.month()-t.month()),o=t.clone().add(i,"months");return 0>e-o?(n=t.clone().add(i-1,"months"),r=(e-o)/(o-n)):(n=t.clone().add(i+1,"months"),r=(e-o)/(n-o)),-(i+r)}function pe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function _e(){var t=this.clone().utc();return 0o&&(e=o),Ue.call(this,t,e,n,r,i))}function Ue(t,e,n,r,i){var o=vt(t,e,n,r,i),a=ft(o.year,0,o.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ve(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Fe(t){return yt(t,this._week.dow,this._week.doy).week}function Ge(){return this._week.dow}function Be(){return this._week.doy}function We(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function qe(t){var e=yt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function Ke(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Je(t,e){return r(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]; +}function $e(t){return this._weekdaysShort[t.day()]}function Ze(t){return this._weekdaysMin[t.day()]}function Xe(t,e,n){var r,i,o;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;7>r;r++){if(i=Pt([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(i,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(i,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(i,"").replace(".",".?")+"$","i")),this._weekdaysParse[r]||(o="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[r].test(t))return r;if(n&&"ddd"===e&&this._shortWeekdaysParse[r].test(t))return r;if(n&&"dd"===e&&this._minWeekdaysParse[r].test(t))return r;if(!n&&this._weekdaysParse[r].test(t))return r}}function Qe(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Ke(t,this.localeData()),this.add(t-e,"d")):e}function tn(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function en(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function nn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function rn(){return this.hours()%12||12}function on(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function an(t,e){return e._meridiemParse}function un(t){return"p"===(t+"").toLowerCase().charAt(0)}function sn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function cn(t,e){e[Cr]=m(1e3*("0."+t))}function ln(){return this._isUTC?"UTC":""}function fn(){return this._isUTC?"Coordinated Universal Time":""}function dn(t){return Pt(1e3*t)}function hn(){return Pt.apply(null,arguments).parseZone()}function pn(t,e,n){var r=this._calendar[t];return j(r)?r.call(e,n):r}function _n(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function vn(){return this._invalidDate}function yn(t){return this._ordinal.replace("%d",t)}function mn(t){return t}function gn(t,e,n,r){var i=this._relativeTime[n];return j(i)?i(t,e,n,r):i.replace(/%d/i,t)}function bn(t,e){var n=this._relativeTime[t>0?"future":"past"];return j(n)?n(e):n.replace(/%s/i,e)}function Sn(t){var e,n;for(n in t)e=t[n],j(e)?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function wn(t,e,n,r){var i=E(),o=s().set(r,e);return i[n](o,t)}function On(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return wn(t,e,n,i);var o,a=[];for(o=0;r>o;o++)a[o]=wn(t,o,n,i);return a}function Mn(t,e){return On(t,e,"months",12,"month")}function In(t,e){return On(t,e,"monthsShort",12,"month")}function En(t,e){return On(t,e,"weekdays",7,"day")}function Tn(t,e){return On(t,e,"weekdaysShort",7,"day")}function Dn(t,e){return On(t,e,"weekdaysMin",7,"day")}function Cn(){var t=this._data;return this._milliseconds=bi(this._milliseconds),this._days=bi(this._days),this._months=bi(this._months),t.milliseconds=bi(t.milliseconds),t.seconds=bi(t.seconds),t.minutes=bi(t.minutes),t.hours=bi(t.hours),t.months=bi(t.months),t.years=bi(t.years),this}function jn(t,e,n,r){var i=Xt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function Pn(t,e){return jn(this,t,e,1)}function An(t,e){return jn(this,t,e,-1)}function kn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,r,i,o=this._milliseconds,a=this._days,u=this._months,s=this._data;return o>=0&&a>=0&&u>=0||0>=o&&0>=a&&0>=u||(o+=864e5*kn(Rn(u)+a),a=0,u=0),s.milliseconds=o%1e3,t=y(o/1e3),s.seconds=t%60,e=y(t/60),s.minutes=e%60,n=y(e/60),s.hours=n%24,a+=y(n/24),i=y(Nn(a)),u+=i,a-=kn(Rn(i)),r=y(u/12),u%=12,s.days=a,s.months=u,s.years=r,this}function Nn(t){return 4800*t/146097}function Rn(t){return 146097*t/4800}function xn(t){var e,n,r=this._milliseconds;if(t=D(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+Nn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Rn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}}function Hn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12)}function Yn(t){return function(){return this.as(t)}}function zn(t){return t=D(t),this[t+"s"]()}function Un(t){return function(){return this._data[t]}}function Vn(){return y(this.days()/7)}function Fn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Gn(t,e,n){var r=Xt(t).abs(),i=Ri(r.as("s")),o=Ri(r.as("m")),a=Ri(r.as("h")),u=Ri(r.as("d")),s=Ri(r.as("M")),c=Ri(r.as("y")),l=i=o&&["m"]||o=a&&["h"]||a=u&&["d"]||u=s&&["M"]||s=c&&["y"]||["yy",c];return l[2]=e,l[3]=+t>0,l[4]=n,Fn.apply(null,l)}function Bn(t,e){return void 0===xi[t]?!1:void 0===e?xi[t]:(xi[t]=e,!0)}function Wn(t){var e=this.localeData(),n=Gn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function qn(){var t,e,n,r=Hi(this._milliseconds)/1e3,i=Hi(this._days),o=Hi(this._months);t=y(r/60),e=y(t/60),r%=60,t%=60,n=y(o/12),o%=12;var a=n,u=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(a?a+"Y":"")+(u?u+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Kn,Jn,$n=e.momentProperties=[],Zn=!1,Xn={},Qn={},tr=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,er=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,nr={},rr={},ir=/\d/,or=/\d\d/,ar=/\d{3}/,ur=/\d{4}/,sr=/[+-]?\d{6}/,cr=/\d\d?/,lr=/\d\d\d\d?/,fr=/\d\d\d\d\d\d?/,dr=/\d{1,3}/,hr=/\d{1,4}/,pr=/[+-]?\d{1,6}/,_r=/\d+/,vr=/[+-]?\d+/,yr=/Z|[+-]\d\d:?\d\d/gi,mr=/Z|[+-]\d\d(?::?\d\d)?/gi,gr=/[+-]?\d+(\.\d{1,3})?/,br=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Sr={},wr={},Or=0,Mr=1,Ir=2,Er=3,Tr=4,Dr=5,Cr=6,jr=7,Pr=8;R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),T("month","M"),U("M",cr),U("MM",cr,or),U("MMM",function(t,e){return e.monthsShortRegex(t)}),U("MMMM",function(t,e){return e.monthsRegex(t)}),B(["M","MM"],function(t,e){e[Mr]=m(t)-1}),B(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[Mr]=i:l(n).invalidMonth=t});var Ar=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,kr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Lr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Nr=br,Rr=br,xr={};e.suppressDeprecationWarnings=!1;var Hr=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Yr=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,zr=/Z|[+-]\d\d(?::?\d\d)?/,Ur=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Vr=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Fr=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=at("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),R("Y",0,0,function(){var t=this.year();return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),T("year","y"),U("Y",vr),U("YY",cr,or),U("YYYY",hr,ur),U("YYYYY",pr,sr),U("YYYYYY",pr,sr),B(["YYYYY","YYYYYY"],Or),B("YYYY",function(t,n){n[Or]=2===t.length?e.parseTwoDigitYear(t):m(t)}),B("YY",function(t,n){n[Or]=e.parseTwoDigitYear(t)}),B("Y",function(t,e){e[Or]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return m(t)+(m(t)>68?1900:2e3)};var Gr=P("FullYear",!1);e.ISO_8601=function(){};var Br=at("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:d()}),Wr=at("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:d()}),qr=function(){return Date.now?Date.now():+new Date};xt("Z",":"),xt("ZZ",""),U("Z",mr),U("ZZ",mr),B(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Ht(mr,t)});var Kr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Jr=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,$r=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Xt.fn=Nt.prototype;var Zr=ne(1,"add"),Xr=ne(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Qr=at("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),T("weekYear","gg"),T("isoWeekYear","GG"),U("G",vr),U("g",vr),U("GG",cr,or),U("gg",cr,or),U("GGGG",hr,ur),U("gggg",hr,ur),U("GGGGG",pr,sr),U("ggggg",pr,sr),W(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=m(t)}),W(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),R("Q",0,"Qo","quarter"),T("quarter","Q"),U("Q",ir),B("Q",function(t,e){e[Mr]=3*(m(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),T("week","w"),T("isoWeek","W"),U("w",cr),U("ww",cr,or),U("W",cr),U("WW",cr,or),W(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=m(t)});var ti={dow:0,doy:6};R("D",["DD",2],"Do","date"),T("date","D"),U("D",cr),U("DD",cr,or),U("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),B(["D","DD"],Ir),B("Do",function(t,e){e[Ir]=m(t.match(cr)[0],10)});var ei=P("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),T("day","d"),T("weekday","e"),T("isoWeekday","E"),U("d",cr),U("e",cr),U("E",cr),U("dd",br),U("ddd",br),U("dddd",br),W(["dd","ddd","dddd"],function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:l(n).invalidWeekday=t}),W(["d","e","E"],function(t,e,n,r){e[r]=m(t)});var ni="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ri="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ii="Su_Mo_Tu_We_Th_Fr_Sa".split("_");R("DDD",["DDDD",3],"DDDo","dayOfYear"),T("dayOfYear","DDD"),U("DDD",dr),U("DDDD",ar),B(["DDD","DDDD"],function(t,e,n){n._dayOfYear=m(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,rn),R("hmm",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)}),R("hmmss",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),on("a",!0),on("A",!1),T("hour","h"),U("a",an),U("A",an),U("H",cr),U("h",cr),U("HH",cr,or),U("hh",cr,or),U("hmm",lr),U("hmmss",fr),U("Hmm",lr),U("Hmmss",fr),B(["H","HH"],Er),B(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),B(["h","hh"],function(t,e,n){e[Er]=m(t),l(n).bigHour=!0}),B("hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r)),l(n).bigHour=!0}),B("hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i)),l(n).bigHour=!0}),B("Hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r))}),B("Hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i))});var oi=/[ap]\.?m?\.?/i,ai=P("Hours",!0);R("m",["mm",2],0,"minute"),T("minute","m"),U("m",cr),U("mm",cr,or),B(["m","mm"],Tr);var ui=P("Minutes",!1);R("s",["ss",2],0,"second"),T("second","s"),U("s",cr),U("ss",cr,or),B(["s","ss"],Dr);var si=P("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),T("millisecond","ms"),U("S",dr,ir),U("SS",dr,or),U("SSS",dr,ar);var ci;for(ci="SSSS";ci.length<=9;ci+="S")U(ci,_r);for(ci="S";ci.length<=9;ci+="S")B(ci,cn);var li=P("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var fi=_.prototype;fi.add=Zr,fi.calendar=ie,fi.clone=oe,fi.diff=de,fi.endOf=Me,fi.format=ve,fi.from=ye,fi.fromNow=me,fi.to=ge,fi.toNow=be,fi.get=L,fi.invalidAt=ke,fi.isAfter=ae,fi.isBefore=ue,fi.isBetween=se,fi.isSame=ce,fi.isSameOrAfter=le,fi.isSameOrBefore=fe,fi.isValid=Pe,fi.lang=Qr,fi.locale=Se,fi.localeData=we,fi.max=Wr,fi.min=Br,fi.parsingFlags=Ae,fi.set=L,fi.startOf=Oe,fi.subtract=Xr,fi.toArray=De,fi.toObject=Ce,fi.toDate=Te,fi.toISOString=_e,fi.toJSON=je,fi.toString=pe,fi.unix=Ee,fi.valueOf=Ie,fi.creationData=Le,fi.year=Gr,fi.isLeapYear=pt,fi.weekYear=Re,fi.isoWeekYear=xe,fi.quarter=fi.quarters=Ve,fi.month=Q,fi.daysInMonth=tt,fi.week=fi.weeks=We,fi.isoWeek=fi.isoWeeks=qe,fi.weeksInYear=Ye,fi.isoWeeksInYear=He,fi.date=ei,fi.day=fi.days=Qe,fi.weekday=tn,fi.isoWeekday=en,fi.dayOfYear=nn,fi.hour=fi.hours=ai,fi.minute=fi.minutes=ui,fi.second=fi.seconds=si,fi.millisecond=fi.milliseconds=li,fi.utcOffset=Ut,fi.utc=Ft,fi.local=Gt,fi.parseZone=Bt,fi.hasAlignedHourOffset=Wt,fi.isDST=qt,fi.isDSTShifted=Kt,fi.isLocal=Jt,fi.isUtcOffset=$t,fi.isUtc=Zt,fi.isUTC=Zt,fi.zoneAbbr=ln,fi.zoneName=fn,fi.dates=at("dates accessor is deprecated. Use date instead.",ei),fi.months=at("months accessor is deprecated. Use month instead",Q),fi.years=at("years accessor is deprecated. Use year instead",Gr),fi.zone=at("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Vt);var di=fi,hi={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},pi={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},_i="Invalid date",vi="%d",yi=/\d{1,2}/,mi={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},gi=b.prototype;gi._calendar=hi,gi.calendar=pn,gi._longDateFormat=pi,gi.longDateFormat=_n,gi._invalidDate=_i,gi.invalidDate=vn,gi._ordinal=vi,gi.ordinal=yn,gi._ordinalParse=yi,gi.preparse=mn,gi.postformat=mn,gi._relativeTime=mi,gi.relativeTime=gn,gi.pastFuture=bn,gi.set=Sn,gi.months=J,gi._months=kr,gi.monthsShort=$,gi._monthsShort=Lr,gi.monthsParse=Z,gi._monthsRegex=Rr,gi.monthsRegex=nt,gi._monthsShortRegex=Nr,gi.monthsShortRegex=et,gi.week=Fe,gi._week=ti,gi.firstDayOfYear=Be,gi.firstDayOfWeek=Ge,gi.weekdays=Je,gi._weekdays=ni,gi.weekdaysMin=Ze,gi._weekdaysMin=ii,gi.weekdaysShort=$e,gi._weekdaysShort=ri,gi.weekdaysParse=Xe,gi.isPM=un,gi._meridiemParse=oi,gi.meridiem=sn,M("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===m(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=at("moment.lang is deprecated. Use moment.locale instead.",M),e.langData=at("moment.langData is deprecated. Use moment.localeData instead.",E);var bi=Math.abs,Si=Yn("ms"),wi=Yn("s"),Oi=Yn("m"),Mi=Yn("h"),Ii=Yn("d"),Ei=Yn("w"),Ti=Yn("M"),Di=Yn("y"),Ci=Un("milliseconds"),ji=Un("seconds"),Pi=Un("minutes"),Ai=Un("hours"),ki=Un("days"),Li=Un("months"),Ni=Un("years"),Ri=Math.round,xi={s:45,m:45,h:22,d:26,M:11},Hi=Math.abs,Yi=Nt.prototype;Yi.abs=Cn,Yi.add=Pn,Yi.subtract=An,Yi.as=xn,Yi.asMilliseconds=Si,Yi.asSeconds=wi,Yi.asMinutes=Oi,Yi.asHours=Mi,Yi.asDays=Ii,Yi.asWeeks=Ei,Yi.asMonths=Ti,Yi.asYears=Di,Yi.valueOf=Hn,Yi._bubble=Ln,Yi.get=zn,Yi.milliseconds=Ci,Yi.seconds=ji,Yi.minutes=Pi,Yi.hours=Ai,Yi.days=ki,Yi.weeks=Vn,Yi.months=Li,Yi.years=Ni,Yi.humanize=Wn,Yi.toISOString=qn,Yi.toString=qn,Yi.toJSON=qn,Yi.locale=Se,Yi.localeData=we,Yi.toIsoString=at("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",qn),Yi.lang=Qr,R("X",0,0,"unix"),R("x",0,0,"valueOf"),U("x",vr),U("X",gr),B("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),B("x",function(t,e,n){n._d=new Date(m(t))}),e.version="2.11.2",n(Pt),e.fn=di,e.min=kt,e.max=Lt,e.now=qr,e.utc=s,e.unix=dn,e.months=Mn,e.isDate=i,e.locale=M,e.invalid=d,e.duration=Xt,e.isMoment=v,e.weekdays=En,e.parseZone=hn,e.localeData=E,e.isDuration=Rt,e.monthsShort=In,e.weekdaysMin=Dn,e.defineLocale=I,e.weekdaysShort=Tn,e.normalizeUnits=D,e.relativeTimeThreshold=Bn,e.prototype=di;var zi=e;return zi})}).call(e,n(68)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var a=n(169),u=n(194),s=i(u),c=n(196),l=i(c),f=n(198),d=i(f),h=n(15),p=r(h),_=n(24),v=r(_),y=n(9),m=r(y),g=n(46),b=r(g),S=n(149),w=r(S),O=n(25),M=r(O),I=n(154),E=r(I),T=n(49),D=r(T),C=n(52),j=r(C),P=n(27),A=r(P),k=n(59),L=r(k),N=n(13),R=r(N),x=n(29),H=r(x),Y=n(31),z=r(Y),U=n(185),V=r(U),F=n(191),G=r(F),B=n(10),W=r(B),q=function K(){o(this,K);var t=(0,s["default"])();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:a.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:a.localStoragePreferences.startSync.bind(a.localStoragePreferences,t)},startUrlSync:{value:j.urlSync.startSync.bind(null,t)},stopUrlSync:{value:j.urlSync.stopSync.bind(null,t)}}),(0,l["default"])(this,t,{auth:p,config:v,entity:m,entityHistory:b,errorLog:w,event:M,logbook:E,moreInfo:D,navigation:j,notification:A,view:L,service:R,stream:H,sync:z,template:V,voice:G,restApi:W})};e["default"]=q},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(79),e["default"]=new o["default"]({is:"ha-badges-card",properties:{states:{type:Array}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=u["default"].moreInfoActions,c=1e4;e["default"]=new o["default"]({is:"ha-camera-card",properties:{stateObj:{type:Object,observer:"updateCameraFeedSrc"},cameraFeedSrc:{type:String},imageLoaded:{type:Boolean,value:!0},elevation:{type:Number,value:1,reflectToAttribute:!0}},listeners:{tap:"cardTapped"},attached:function(){var t=this;this.timer=setInterval(function(){return t.updateCameraFeedSrc(t.stateObj)},c)},detached:function(){clearInterval(this.timer)},cardTapped:function(){var t=this;this.async(function(){return s.selectEntity(t.stateObj.entityId)},1)},updateCameraFeedSrc:function(t){var e=(new Date).getTime();this.cameraFeedSrc=t.attributes.entity_picture+"?time="+e},imageLoadSuccess:function(){this.imageLoaded=!0},imageLoadFail:function(){this.imageLoaded=!1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(71),n(73),n(74),e["default"]=new o["default"]({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=!e||e.cardType!==t.cardType,i=void 0;r?(n.lastChild&&n.removeChild(n.lastChild),i=document.createElement("ha-"+t.cardType+"-card")):i=n.lastChild,Object.keys(t).forEach(function(e){return i[e]=t[e]}),e&&Object.keys(e).forEach(function(e){e in t||(i[e]=void 0)}),r&&n.appendChild(i)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(21),c=r(s);n(36),n(35),n(19);var l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entities-card",properties:{states:{type:Array},groupEntity:{type:Object}},computeTitle:function(t,e){return e?e.entityDisplay:t[0].domain.replace(/_/g," ")},entityTapped:function(t){if(!t.target.classList.contains("paper-toggle-button")&&!t.target.classList.contains("paper-icon-button")){t.stopPropagation();var e=t.model.item.entityId;this.async(function(){return l.selectEntity(e)},1)}},showGroupToggle:function(t,e){return!t||!e||"on"!==t.state&&"off"!==t.state?!1:e.reduce(function(t,e){return t+(0,c["default"])(e.entityId)})>1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(36),e["default"]=new o["default"]({is:"ha-introduction-card",properties:{showInstallInstruction:{type:Boolean,value:!1},showHideInstruction:{type:Boolean,value:!0}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(41),u=r(a);e["default"]=new o["default"]({is:"display-time",properties:{dateObj:{type:Object}},computeTime:function(t){return t?(0,u["default"])(t):""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].entityGetters;e["default"]=new u["default"]({is:"entity-list",behaviors:[c["default"]],properties:{entities:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.entityId}).toArray()}]}},entitySelected:function(t){t.preventDefault(),this.fire("entity-selected",{entityId:t.model.entity.entityId})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a);n(17);var s=u["default"].reactor,c=u["default"].entityGetters,l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entity-marker",properties:{entityId:{type:String,value:""},state:{type:Object,computed:"computeState(entityId)"},icon:{type:Object,computed:"computeIcon(state)"},image:{type:Object,computed:"computeImage(state)"},value:{type:String,computed:"computeValue(state)"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;t.stopPropagation(),this.entityId&&this.async(function(){return l.selectEntity(e.entityId)},1)},computeState:function(t){return t&&s.evaluate(c.byId(t))},computeIcon:function(t){return!t&&"home"},computeImage:function(t){return t&&t.attributes.entity_picture},computeValue:function(t){return t&&t.entityDisplay.split(" ").map(function(t){return t.substr(0,1)}).join("")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(43),u=r(a);e["default"]=new o["default"]({is:"ha-state-icon",properties:{stateObj:{type:Object}},computeIcon:function(t){return(0,u["default"])(t)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(22),c=r(s),l=n(21),f=r(l),d=n(43),h=r(d);n(17);var p=u["default"].moreInfoActions,_=u["default"].serviceActions;e["default"]=new o["default"]({is:"ha-state-label-badge",properties:{state:{type:Object,observer:"stateChanged"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;return t.stopPropagation(),(0,f["default"])(this.state.entityId)?void("scene"===this.state.domain||"off"===this.state.state?_.callTurnOn(this.state.entityId):_.callTurnOff(this.state.entityId)):void this.async(function(){return p.selectEntity(e.state.entityId)},1)},computeClasses:function(t){switch(t.domain){case"scene":return"green";case"binary_sensor":case"script":return"on"===t.state?"blue":"grey";case"updater":return"blue";default:return""}},computeValue:function(t){switch(t.domain){case"binary_sensor":case"device_tracker":case"updater":case"sun":case"scene":case"script":case"alarm_control_panel":return null;case"sensor":default:return"unknown"===t.state?"-":t.state}},computeIcon:function(t){switch(t.domain){case"alarm_control_panel":return"pending"===t.state?"mdi:clock-fast":"armed_away"===t.state?"mdi:nature":"armed_home"===t.state?"mdi:home-variant":(0,c["default"])(t.domain,t.state);case"binary_sensor":case"device_tracker":case"scene":case"updater":case"script":return(0,h["default"])(t);case"sun":return"above_horizon"===t.state?(0,c["default"])(t.domain):"mdi:brightness-3";default:return null}},computeImage:function(t){return t.attributes.entity_picture||null},computeLabel:function(t){switch(t.domain){case"scene":case"script":return t.domain;case"device_tracker":return"not_home"===t.state?"Away":t.state;case"alarm_control_panel":return"pending"===t.state?"pend":"armed_away"===t.state||"armed_home"===t.state?"armed":"disarm";default:return t.attributes.unit_of_measurement||null}},computeDescription:function(t){return t.entityDisplay},stateChanged:function(){this.updateStyles()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(78),e["default"]=new o["default"]({is:"state-badge",properties:{stateObj:{type:Object,observer:"updateIconColor"}},updateIconColor:function(t){return t.attributes.entity_picture?(this.style.backgroundImage="url("+t.attributes.entity_picture+")",void(this.$.icon.style.display="none")):(this.style.backgroundImage="",this.$.icon.style.display="inline",void("light"===t.domain&&"on"===t.state&&t.attributes.rgb_color&&t.attributes.rgb_color.reduce(function(t,e){return t+e},0)<730?this.$.icon.style.color="rgb("+t.attributes.rgb_color.join(",")+")":this.$.icon.style.color=null))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].eventGetters;e["default"]=new u["default"]({is:"events-list",behaviors:[c["default"]],properties:{events:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.event}).toArray()}]}},eventSelected:function(t){t.preventDefault(),this.fire("event-selected",{eventType:t.model.event.event})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t in d?d[t]:30}function o(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(1),u=r(a),s=n(2),c=r(s);n(84),n(70),n(72);var l=c["default"].util,f=["camera"],d={configurator:-20,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,sensor:5,binary_sensor:6,scene:7,script:8};e["default"]=new u["default"]({is:"ha-cards",properties:{showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},cards:{type:Object,computed:"computeCards(columns, states, showIntroduction)"}},computeCards:function(t,e,n){function r(t){return t.filter(function(t){return!(t.entityId in c)})}function a(){var e=p;return p=(p+1)%t,e}function u(t,e){var n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2];if(0!==e.length){var r=[],i=[];e.forEach(function(t){-1===f.indexOf(t.domain)?i.push(t):r.push(t)});var o=a();i.length>0&&(d._columns[o].push(t),d[t]={cardType:"entities",states:i,groupEntity:n}),r.forEach(function(t){d._columns[o].push(t.entityId),d[t.entityId]={cardType:t.domain,stateObj:t}})}}for(var s=e.groupBy(function(t){return t.domain}),c={},d={_demo:!1,_badges:[],_columns:[]},h=0;t>h;h++)d._columns[h]=[];var p=0;return n&&(d._columns[a()].push("ha-introduction"),d["ha-introduction"]={cardType:"introduction",showHideInstruction:e.size>0&&!0}),s.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){if("a"===t)return void(d._demo=!0);var n=i(t);n>=0&&10>n?d._badges.push.apply(d._badges,r(s.get(t)).sortBy(o).toArray()):"group"===t?s.get(t).sortBy(o).forEach(function(t){var n=l.expandGroup(t,e);n.forEach(function(t){return c[t.entityId]=!0}),u(t.entityId,n.toArray(),t)}):u(t,r(s.get(t)).sortBy(o).toArray())}),d},computeCardDataOfCard:function(t,e){return t[e]}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-color-picker",properties:{color:{type:Object},width:{type:Number},height:{type:Number}},listeners:{mousedown:"onMouseDown",mouseup:"onMouseUp",touchstart:"onTouchStart",touchend:"onTouchEnd"},onMouseDown:function(t){this.onMouseMove(t),this.addEventListener("mousemove",this.onMouseMove)},onMouseUp:function(){this.removeEventListener("mousemove",this.onMouseMove)},onTouchStart:function(t){this.onTouchMove(t),this.addEventListener("touchmove",this.onTouchMove)},onTouchEnd:function(){this.removeEventListener("touchmove",this.onTouchMove)},onTouchMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},onMouseMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},processColorSelect:function(t){var e=this.canvas.getBoundingClientRect();t.clientX=e.left+e.width||t.clientY=e.top+e.height||this.onColorSelect(t.clientX-e.left,t.clientY-e.top)},onColorSelect:function(t,e){var n=this.context.getImageData(t,e,1,1).data;this.setColor({r:n[0],g:n[1],b:n[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{ +rgb:this.color})},ready:function(){var t=this;this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.debounce("drawGradient",function(){var e=void 0;t.width&&t.height||(e=getComputedStyle(t));var n=t.width||parseInt(e.width,10),r=t.height||parseInt(e.height,10),i=t.context.createLinearGradient(0,0,n,0);i.addColorStop(0,"rgb(255,0,0)"),i.addColorStop(.16,"rgb(255,0,255)"),i.addColorStop(.32,"rgb(0,0,255)"),i.addColorStop(.48,"rgb(0,255,255)"),i.addColorStop(.64,"rgb(0,255,0)"),i.addColorStop(.8,"rgb(255,255,0)"),i.addColorStop(1,"rgb(255,0,0)"),t.context.fillStyle=i,t.context.fillRect(0,0,n,r);var o=t.context.createLinearGradient(0,0,0,r);o.addColorStop(0,"rgba(255,255,255,1)"),o.addColorStop(.5,"rgba(255,255,255,0)"),o.addColorStop(.5,"rgba(0,0,0,0)"),o.addColorStop(1,"rgba(0,0,0,1)"),t.context.fillStyle=o,t.context.fillRect(0,0,n,r)},100)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"ha-demo-badge"})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(87),e["default"]=new o["default"]({is:"ha-logbook",properties:{entries:{type:Object,value:[]}},noEntries:function(t){return!t.length}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(91);var l=o["default"].configGetters,f=o["default"].navigationGetters,d=o["default"].authActions,h=o["default"].navigationActions;e["default"]=new u["default"]({is:"ha-sidebar",behaviors:[c["default"]],properties:{menuShown:{type:Boolean},menuSelected:{type:String},selected:{type:String,bindNuclear:f.activePane,observer:"selectedChanged"},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history")},hasLogbookComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("logbook")}},selectedChanged:function(t){document.activeElement&&document.activeElement.blur();for(var e=this.querySelectorAll(".menu [data-panel]"),n=0;nnew Date&&(o=new Date);var u=e.map(function(t){function e(t,e){c&&e&&s.push([t[0]].concat(c.slice(1).map(function(t,n){return e[n]?t:null}))),s.push(t),c=t}var n=t[t.length-1],r=n.domain,a=n.entityDisplay,u=new window.google.visualization.DataTable;u.addColumn({type:"datetime",id:"Time"});var s=[],c=void 0;if("thermostat"===r){var l=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1);u.addColumn("number",a+" current temperature");var f=void 0;l?!function(){u.addColumn("number",a+" target temperature high"),u.addColumn("number",a+" target temperature low");var t=[!1,!0,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.target_temp_high),a=i(n.attributes.target_temp_low);e([n.lastUpdatedAsDate,r,o,a],t)}}():!function(){u.addColumn("number",a+" target temperature");var t=[!1,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.temperature);e([n.lastUpdatedAsDate,r,o],t)}}(),t.forEach(f)}else!function(){u.addColumn("number",a);var n="sensor"!==r&&[!0];t.forEach(function(t){var r=i(t.state);e([t.lastChangedAsDate,r],n)})}();return e([o].concat(c.slice(1)),!1),u.addRows(s),u}),s=void 0;s=1===u.length?u[0]:u.slice(1).reduce(function(t,e){return window.google.visualization.data.join(t,e,"full",[[0,0]],(0,a["default"])(1,t.getNumberOfColumns()),(0,a["default"])(1,e.getNumberOfColumns()))},u[0]),this.chartEngine.draw(s,n)}}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"state-history-chart-timeline",properties:{data:{type:Object,observer:"dataChanged"},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){function t(t,e,n,r){var o=e.replace(/_/g," ");i.addRow([t,o,n,r])}if(this.isAttached){for(var e=o["default"].dom(this),n=this.data;e.node.lastChild;)e.node.removeChild(e.node.lastChild);if(n&&0!==n.length){var r=new window.google.visualization.Timeline(this),i=new window.google.visualization.DataTable;i.addColumn({type:"string",id:"Entity"}),i.addColumn({type:"string",id:"State"}),i.addColumn({type:"date",id:"Start"}),i.addColumn({type:"date",id:"End"});var a=new Date(n.reduce(function(t,e){return Math.min(t,e[0].lastChangedAsDate)},new Date)),u=new Date(a);u.setDate(u.getDate()+1),u>new Date&&(u=new Date);var s=0;n.forEach(function(e){if(0!==e.length){var n=e[0].entityDisplay,r=void 0,i=null,o=null;e.forEach(function(e){null!==i&&e.state!==i?(r=e.lastChangedAsDate,t(n,i,o,r),i=e.state,o=r):null===i&&(i=e.state,o=e.lastChangedAsDate)}),t(n,i,o,u),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].streamGetters,f=o["default"].streamActions;e["default"]=new u["default"]({is:"stream-status",behaviors:[c["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},hasError:{type:Boolean,bindNuclear:l.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?f.stop():f.start()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].voiceActions,f=o["default"].voiceGetters;e["default"]=new u["default"]({is:"ha-voice-command-dialog",behaviors:[c["default"]],properties:{dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},finalTranscript:{type:String,bindNuclear:f.finalTranscript},interimTranscript:{type:String,bindNuclear:f.extraInterimTranscript},isTransmitting:{type:Boolean,bindNuclear:f.isTransmitting},isListening:{type:Boolean,bindNuclear:f.isListening},showListenInterface:{type:Boolean,computed:"computeShowListenInterface(isListening, isTransmitting)",observer:"showListenInterfaceChanged"}},computeShowListenInterface:function(t,e){return t||e},dialogOpenChanged:function(t){!t&&this.isListening&&l.stop()},showListenInterfaceChanged:function(t){!t&&this.dialogOpen?this.dialogOpen=!1:t&&(this.dialogOpen=!0)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19),n(38),n(109);var l=o["default"].configGetters,f=o["default"].entityHistoryGetters,d=o["default"].entityHistoryActions,h=o["default"].moreInfoGetters,p=o["default"].moreInfoActions,_=["camera","configurator","scene"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[c["default"]],properties:{stateObj:{type:Object,bindNuclear:h.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[h.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(_delayedDialogOpen, _isLoadingHistoryData)"},_isLoadingHistoryData:{type:Boolean,bindNuclear:f.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:h.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1,computed:"computeShowHistoryComponent(hasHistoryComponent, stateObj)"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},_delayedDialogOpen:{type:Boolean,value:!1}},computeIsLoadingHistoryData:function(t,e){return!t||e},computeShowHistoryComponent:function(t,e){return this.hasHistoryComponent&&e&&-1===_.indexOf(e.domain)},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&d.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){var e=this;t?this.async(function(){return e._delayedDialogOpen=!0},10):!t&&this.stateObj&&(this.async(function(){return p.deselectEntity()},10),this._delayedDialogOpen=!1)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(4),c=r(s),l=n(42),f=r(l);n(86),n(96),n(103),n(102),n(104),n(97),n(98),n(100),n(101),n(99),n(105),n(93),n(92);var d=u["default"].navigationActions,h=u["default"].navigationGetters,p=u["default"].startUrlSync,_=u["default"].stopUrlSync;e["default"]=new o["default"]({is:"home-assistant-main",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},activePane:{type:String,bindNuclear:h.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:h.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:h.isActivePane("history")},isSelectedMap:{type:Boolean,bindNuclear:h.isActivePane("map")},isSelectedLogbook:{type:Boolean,bindNuclear:h.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:h.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:h.isActivePane("devState")},isSelectedDevTemplate:{type:Boolean,bindNuclear:h.isActivePane("devTemplate")},isSelectedDevService:{type:Boolean,bindNuclear:h.isActivePane("devService")},isSelectedDevInfo:{type:Boolean,bindNuclear:h.isActivePane("devInfo")},showSidebar:{type:Boolean,bindNuclear:h.showSidebar}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():d.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&d.showSidebar(!1)},activePaneChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){(0,f["default"])(),p()},computeForceNarrow:function(t,e){return t||!e},detached:function(){_()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(4),c=r(s),l=n(44),f=r(l),d=n(42),h=r(d),p=u["default"].authGetters;e["default"]=new o["default"]({is:"login-form",behaviors:[c["default"]],properties:{errorMessage:{type:String,bindNuclear:p.attemptErrorMessage},isInvalid:{type:Boolean,bindNuclear:p.isInvalidAttempt},isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:p.isValidating},loadingResources:{type:Boolean,value:!1},forceShowLoading:{type:Boolean,value:!1},showLoading:{type:Boolean,computed:"computeShowSpinner(forceShowLoading, isValidating)"}},listeners:{keydown:"passwordKeyDown","loginButton.tap":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],attached:function(){(0,h["default"])()},computeShowSpinner:function(t,e){return t||e},validatingChanged:function(t,e){t||e||(this.$.passwordInput.value="")},isValidatingChanged:function(t){var e=this;t||this.async(function(){return e.$.passwordInput.focus()},10)},passwordKeyDown:function(t){13===t.keyCode?(this.validatePassword(),t.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),(0,f["default"])(this.$.passwordInput.value,this.$.rememberLogin.checked)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(82);var l=o["default"].configGetters,f=o["default"].viewActions,d=o["default"].viewGetters,h=o["default"].voiceGetters,p=o["default"].streamGetters,_=o["default"].syncGetters,v=o["default"].syncActions,y=o["default"].voiceActions;e["default"]=new u["default"]({is:"partial-cards",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:_.isFetching},isStreaming:{type:Boolean,bindNuclear:p.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[h.isVoiceSupported,l.isComponentLoaded("conversation"),function(t,e){return t&&e}]},introductionLoaded:{type:Boolean,bindNuclear:l.isComponentLoaded("introduction")},locationName:{type:String,bindNuclear:l.locationName},showMenu:{type:Boolean,value:!1,observer:"windowChange"},currentView:{type:String,bindNuclear:[d.currentView,function(t){return t||""}],observer:"removeFocus"},views:{type:Array,bindNuclear:[d.views,function(t){return t.valueSeq().sortBy(function(t){return t.attributes.order}).toArray()}]},hasViews:{type:Boolean,computed:"computeHasViews(views)"},states:{type:Object,bindNuclear:d.currentViewEntities},columns:{type:Number,value:1}},created:function(){var t=this;this.windowChange=this.windowChange.bind(this);for(var e=[],n=0;5>n;n++)e.push(300+300*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n})},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){var t=this.mqls.reduce(function(t,e){return t+e.matches},0);this.columns=Math.max(1,t-this.showMenu)},removeFocus:function(){document.activeElement&&document.activeElement.blur()},handleRefresh:function(){v.fetchAll()},handleListenClick:function(){y.listen()},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},computeShowIntroduction:function(t,e,n){return""===t&&(e||0===n.size)},computeHasViews:function(t){return t.length>0},toggleMenu:function(){this.fire("open-menu")},viewSelected:function(t){var e=t.detail.item.getAttribute("data-entity")||null,n=this.currentView||null;e!==n&&this.async(function(){return f.selectView(e)},0)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(88);var s=o["default"].reactor,c=o["default"].serviceActions,l=o["default"].serviceGetters;e["default"]=new u["default"]({is:"partial-dev-call-service",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},domain:{type:String,value:""},service:{type:String,value:""},serviceData:{type:String,value:""},description:{type:String,computed:"computeDescription(domain, service)"}},computeDescription:function(t,e){return s.evaluate([l.entityMap,function(n){return n.has(t)&&n.get(t).get("services").has(e)?JSON.stringify(n.get(t).get("services").get(e).toJS(),null,2):"No description available"}])},serviceSelected:function(t){this.domain=t.detail.domain,this.service=t.detail.service},callService:function(){var t=void 0;try{t=this.serviceData?JSON.parse(this.serviceData):{}}catch(e){return void alert("Error parsing JSON: "+e)}c.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(81);var s=o["default"].eventActions;e["default"]=new u["default"]({is:"partial-dev-fire-event",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},eventType:{type:String,value:""},eventData:{type:String,value:""}},eventSelected:function(t){this.eventType=t.detail.eventType},fireEvent:function(){var t=void 0;try{t=this.eventData?JSON.parse(this.eventData):{}}catch(e){return void alert("Error parsing JSON: "+e)}s.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].configGetters,f=o["default"].errorLogActions;e["default"]=new u["default"]({is:"partial-dev-info",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:l.serverVersion},polymerVersion:{type:String,value:u["default"].version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(t){var e=this;t&&t.preventDefault(),this.errorLog="Loading error log…",f.fetchErrorLog().then(function(t){return e.errorLog=t||"No errors have been reported."})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(76);var s=o["default"].reactor,c=o["default"].entityGetters,l=o["default"].entityActions;e["default"]=new u["default"]({is:"partial-dev-set-state",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},entityId:{type:String,value:""},state:{type:String,value:""},stateAttributes:{type:String,value:""}},setStateData:function(t){var e=t?JSON.stringify(t,null," "):"";this.$.inputData.value=e,this.$.inputDataWrapper.update(this.$.inputData)},entitySelected:function(t){var e=s.evaluate(c.byId(t.detail.entityId));this.entityId=e.entityId,this.state=e.state,this.stateAttributes=JSON.stringify(e.attributes,null," ")},handleSetState:function(){var t=void 0;try{t=this.stateAttributes?JSON.parse(this.stateAttributes):{}}catch(e){return void alert("Error parsing JSON: "+e)}l.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].templateActions;e["default"]=new u["default"]({is:"partial-dev-template",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},error:{type:Boolean,value:!1},rendering:{type:Boolean,value:!1},template:{type:String,value:'{%- if is_state("device_tracker.paulus", "home") and \n is_state("device_tracker.anne_therese", "home") -%}\n\n You are both home, you silly\n\n{%- else -%}\n\n Anne Therese is at {{ states("device_tracker.anne_therese") }} and Paulus is at {{ states("device_tracker.paulus") }}\n\n{%- endif %}\n\nFor loop example:\n\n{% for state in states.sensor -%}\n {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}\n {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}\n{%- endfor -%}.',observer:"templateChanged"},processed:{type:String,value:""}},computeFormClasses:function(t){return"content fit layout "+(t?"vertical":"horizontal")},computeRenderedClasses:function(t){return t?"error rendered":"rendered"},templateChanged:function(){this.error&&(this.error=!1),this.debounce("render-template",this.renderTemplate,500)},renderTemplate:function(){var t=this;this.rendering=!0,l.render(this.template).then(function(e){t.processed=e,t.rendering=!1},function(e){t.processed=e.message,t.error=!0,t.rendering=!1})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(38);var l=o["default"].entityHistoryGetters,f=o["default"].entityHistoryActions;e["default"]=new u["default"]({is:"partial-history",behaviors:[c["default"]],properties:{narrow:{type:Boolean},showMenu:{type:Boolean,value:!1},isDataLoaded:{type:Boolean,bindNuclear:l.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:l.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:l.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:l.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return f.fetchSelectedDate()},1)},handleRefreshClick:function(){f.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(85),n(18);var l=o["default"].logbookGetters,f=o["default"].logbookActions;e["default"]=new u["default"]({is:"partial-logbook",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:l.currentDate},isLoading:{type:Boolean,bindNuclear:l.isLoadingEntries},isStale:{type:Boolean,bindNuclear:l.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[l.currentEntries,function(t){return t.reverse().toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return f.fetchDate(e.selectedDate)},1)},handleRefresh:function(){f.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(77);var l=o["default"].configGetters,f=o["default"].entityGetters;window.L.Icon.Default.imagePath="/static/images/leaflet",e["default"]=new u["default"]({is:"partial-map",behaviors:[c["default"]],properties:{locationGPS:{type:Number,bindNuclear:l.locationGPS},locationName:{type:String,bindNuclear:l.locationName},locationEntities:{type:Array,bindNuclear:[f.visibleEntityMap,function(t){return t.valueSeq().filter(function(t){return t.attributes.latitude&&"home"!==t.state}).toArray()}]},zoneEntities:{type:Array,bindNuclear:[f.entityMap,function(t){return t.valueSeq().filter(function(t){return"zone"===t.domain&&!t.attributes.passive}).toArray()}]},narrow:{type:Boolean},showMenu:{type:Boolean,value:!1}},attached:function(){var t=this;window.L.Browser.mobileWebkit&&this.async(function(){var e=t.$.map,n=e.style.display;e.style.display="none",t.async(function(){e.style.display=n},1)},1)},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].notificationGetters;e["default"]=new u["default"]({is:"notification-manager",behaviors:[c["default"]],properties:{text:{type:String,bindNuclear:l.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-alarm_control_panel",handleDisarmTap:function(){this.callService("alarm_disarm",{code:this.enteredCode})},handleHomeTap:function(){this.callService("alarm_arm_home",{code:this.enteredCode})},handleAwayTap:function(){this.callService("alarm_arm_away",{code:this.enteredCode})},properties:{stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""},disarmButtonVisible:{type:Boolean,value:!1},armHomeButtonVisible:{type:Boolean,value:!1},armAwayButtonVisible:{type:Boolean,value:!1},codeInputVisible:{type:Boolean,value:!1},codeInputEnabled:{type:Boolean,value:!1},codeFormat:{type:String,value:""},codeValid:{type:Boolean,computed:"validateCode(enteredCode, codeFormat)"}},validateCode:function(t,e){var n=new RegExp(e);return null===e?!0:n.test(t)},stateObjChanged:function(t){var e=this;t&&(this.codeFormat=t.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="armed_home"===t.state||"armed_away"===t.state||"disarmed"===t.state||"pending"===t.state||"triggered"===t.state,this.disarmButtonVisible="armed_home"===t.state||"armed_away"===t.state||"pending"===t.state||"triggered"===t.state,this.armHomeButtonVisible="disarmed"===t.state,this.armAwayButtonVisible="disarmed"===t.state),this.async(function(){return e.fire("iron-resize")},500)},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,s.callService("alarm_control_panel",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-camera",properties:{stateObj:{type:Object},dialogOpen:{type:Boolean}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(t){return t?"/api/camera_proxy_stream/"+this.stateObj.entityId:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(18);var l=o["default"].streamGetters,f=o["default"].syncActions,d=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[c["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"},fieldInput:{type:Object,value:{}}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},fieldChanged:function(t){var e=t.target;this.fieldInput[e.id]=e.value},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id,fields:this.fieldInput};d.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||f.fetchAll()},function(){t.isConfiguring=!1})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(129),u=r(a);n(110),n(111),n(116),n(108),n(117),n(115),n(112),n(114),n(107),n(118),n(106),n(113),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},dialogOpenChanged:function(t){var e=o["default"].dom(this);e.lastChild&&(e.lastChild.dialogOpen=t)},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=(0,u["default"])(t);if(e&&(0,u["default"])(e)===r)n.lastChild.dialogOpen=this.dialogOpen,n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("more-info-"+r);i.stateObj=t,i.dialogOpen=this.dialogOpen,n.appendChild(i)}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=["entity_picture","friendly_name","icon","unit_of_measurement"];e["default"]=new o["default"]({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return-1===a.indexOf(t)}):[]},getAttributeValue:function(t,e){var n=t.attributes[e];return Array.isArray(n)?n.join(", "):n}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19);var l=o["default"].entityGetters,f=o["default"].moreInfoGetters;e["default"]=new u["default"]({is:"more-info-group",behaviors:[c["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[f.currentEntity,l.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){f.callService("light","turn_on",{entity_id:t,rgb_color:[e.r,e.g,e.b]})}Object.defineProperty(e,"__esModule",{value:!0});var o=n(2),a=r(o),u=n(1),s=r(u),c=n(20),l=r(c);n(83);var f=a["default"].serviceActions,d=["brightness","rgb_color","color_temp"];e["default"]=new s["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0},ctSliderValue:{type:Number,value:0}},stateObjChanged:function(t){ +var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness,this.ctSliderValue=t.attributes.color_temp),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,l["default"])(t,d)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?f.callTurnOff(this.stateObj.entityId):f.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},ctSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||f.callService("light","turn_on",{entity_id:this.stateObj.entityId,color_temp:e})},colorPicked:function(t){var e=this;return this.skipColorPicked?void(this.colorChanged=!0):(this.color=t.detail.rgb,i(this.stateObj.entityId,this.color),this.colorChanged=!1,this.skipColorPicked=!0,void(this.colorDebounce=setTimeout(function(){e.colorChanged&&i(e.stateObj.entityId,e.color),e.skipColorPicked=!1},500)))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-lock",properties:{stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""},unlockButtonVisible:{type:Boolean,value:!1},lockButtonVisible:{type:Boolean,value:!1},codeInputVisible:{type:Boolean,value:!1},codeInputEnabled:{type:Boolean,value:!1},codeFormat:{type:String,value:""},codeValid:{type:Boolean,computed:"validateCode(enteredCode, codeFormat)"}},handleUnlockTap:function(){this.callService("unlock",{code:this.enteredCode})},handleLockTap:function(){this.callService("lock",{code:this.enteredCode})},validateCode:function(t,e){var n=new RegExp(e);return null===e?!0:n.test(t)},stateObjChanged:function(t){var e=this;t&&(this.codeFormat=t.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="locked"===t.state||"unlocked"===t.state,this.unlockButtonVisible="locked"===t.state,this.lockButtonVisible="unlocked"===t.state),this.async(function(){return e.fire("iron-resize")},500)},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,s.callService("lock",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["volume_level"];e["default"]=new u["default"]({is:"more-info-media_player",properties:{stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},volumeSliderValue:{type:Number,value:0},supportsPause:{type:Boolean,value:!1},supportsVolumeSet:{type:Boolean,value:!1},supportsVolumeMute:{type:Boolean,value:!1},supportsPreviousTrack:{type:Boolean,value:!1},supportsNextTrack:{type:Boolean,value:!1},supportsTurnOn:{type:Boolean,value:!1},supportsTurnOff:{type:Boolean,value:!1},supportsVolumeButtons:{type:Boolean,value:!1},hasMediaControl:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;if(t){var n=["playing","paused","unknown"];this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.hasMediaControl=-1!==n.indexOf(t.state),this.volumeSliderValue=100*t.attributes.volume_level,this.isMuted=t.attributes.is_volume_muted,this.supportsPause=0!==(1&t.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&t.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&t.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&t.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&t.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&t.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&t.attributes.supported_media_commands),this.supportsVolumeButtons=0!==(1024&t.attributes.supported_media_commands)}this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,c["default"])(t,f)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"mdi:volume-off":"mdi:volume-high"},computeHideVolumeButtons:function(t,e){return!e||t},computeShowPlaybackControls:function(t,e){return!t&&e},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"mdi:pause":"mdi:stop":"mdi:play"},computeHidePowerButton:function(t,e,n){return t?!e:!n},handleTogglePower:function(){this.callService(this.isOff?"turn_on":"turn_off")},handlePrevious:function(){this.callService("media_previous_track")},handlePlaybackControl:function(){this.callService("media_play_pause")},handleNext:function(){this.callService("media_next_track")},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},handleVolumeUp:function(){var t=this.$.volumeUp;this.handleVolumeWorker("volume_up",t,!0)},handleVolumeDown:function(){var t=this.$.volumeDown;this.handleVolumeWorker("volume_down",t,!0)},handleVolumeWorker:function(t,e,n){var r=this;(n||void 0!==e&&e.pointerDown)&&(this.callService(t),this.async(function(){return r.handleVolumeWorker(t,e,!1)},500))},volumeSliderChanged:function(t){var e=parseFloat(t.target.value),n=e>0?e/100:0;this.callService("volume_set",{volume_level:n})},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,l.callService("media_player",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-script",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(41),c=r(s),l=u["default"].util.parseDateTime;e["default"]=new o["default"]({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return l(t.attributes.next_rising)},computeSetting:function(t){return l(t.attributes.next_setting)},computeOrder:function(t,e){return t>e?["set","ris"]:["ris","set"]},itemCaption:function(t){return"ris"===t?"Rising ":"Setting "},itemDate:function(t){return"ris"===t?this.risingDate:this.settingDate},itemValue:function(t){return(0,c["default"])(this.itemDate(t))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["away_mode"];e["default"]=new u["default"]({is:"more-info-thermostat",properties:{stateObj:{type:Object,observer:"stateObjChanged"},tempMin:{type:Number},tempMax:{type:Number},targetTemperatureSliderValue:{type:Number},awayToggleChecked:{type:Boolean}},stateObjChanged:function(t){this.targetTemperatureSliderValue=t.attributes.temperature,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return(0,c["default"])(t,f)},targetTemperatureSliderChanged:function(t){l.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:t.target.value})},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.attributes.away_mode?this.service_set_away(!0):e||"on"!==this.stateObj.attributes.away_mode||this.service_set_away(!1)},service_set_away:function(t){var e=this;l.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-updater",properties:{}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),n(39),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(7);var s=o["default"].serviceActions;e["default"]=new u["default"]({is:"state-card-input_select",properties:{stateObj:{type:Object},selectedOption:{type:String,observer:"selectedOptionChanged"}},computeSelected:function(t){return t.attributes.options.indexOf(t.state)},selectedOptionChanged:function(t){""!==t&&t!==this.stateObj.state&&s.callService("input_select","select_option",{option:t,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7);var a=["playing","paused"];e["default"]=new o["default"]({is:"state-card-media_player",properties:{stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"}},computeIsPlaying:function(t){return-1!==a.indexOf(t.state)},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e=void 0;return"music"===t.attributes.media_content_type?t.attributes.media_artist:"tvshow"===t.attributes.media_content_type?(e=t.attributes.media_series_title,t.attributes.media_season&&t.attributes.media_episode&&(e+=" S"+t.attributes.media_season+"E"+t.attributes.media_episode),e):t.attributes.app_name?t.attributes.app_name:""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(7);var s=o["default"].serviceActions;e["default"]=new u["default"]({is:"state-card-rollershutter",properties:{stateObj:{type:Object}},computeIsFullyOpen:function(t){return 100===t.attributes.current_position},computeIsFullyClosed:function(t){return 0===t.attributes.current_position},onMoveUpTap:function(){s.callService("rollershutter","move_up",{entity_id:this.stateObj.entityId})},onMoveDownTap:function(){s.callService("rollershutter","move_down",{entity_id:this.stateObj.entityId})},onStopTap:function(){s.callService("rollershutter","stop",{entity_id:this.stateObj.entityId})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a);n(7);var s=u["default"].serviceActions;e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object}},activateScene:function(){s.callTurnOn(this.stateObj.entityId)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}},computeTargetTemperature:function(t){return t.attributes.temperature+" "+t.attributes.unit_of_measurement}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),n(35),e["default"]=new o["default"]({is:"state-card-toggle",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-weblink",properties:{stateObj:{type:Object}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation(),window.open(this.stateObj.state,"_blank")}})},function(t,e){"use strict";function n(t){return{attached:function(){var e=this;this.__unwatchFns=Object.keys(this.properties).reduce(function(n,r){if(!("bindNuclear"in e.properties[r]))return n;var i=e.properties[r].bindNuclear;if(!i)throw new Error("Undefined getter specified for key "+r);return e[r]=t.evaluate(i),n.concat(t.observe(i,function(t){e[r]=t}))},[])},detached:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==u.indexOf(t.domain)?t.domain:(0,a["default"])(t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(21),a=r(o),u=["configurator","input_select","media_player","rollershutter","scene","thermostat","weblink"]},function(t,e){"use strict";function n(t){return-1!==r.indexOf(t.domain)?t.domain:"default"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var r=["light","group","sun","configurator","thermostat","script","media_player","camera","updater","alarm_control_panel","lock"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(199),i=n(15),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],a=t.evaluate(i.getters.authInfo),u=a.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,u,!0),r.setRequestHeader("X-HA-access",a.authToken),r.onload=function(){var e=void 0;try{e="application/json"===r.getResponseHeader("content-type")?JSON.parse(r.responseText):r.responseText}catch(i){e=r.responseText}r.status>199&&r.status<300?t(e):n(e)},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],r=n.useStreaming,i=void 0===r?t.evaluate(c.getters.isSupported):r,o=n.rememberAuth,a=void 0===o?!1:o,s=n.host,d=void 0===s?"":s;t.dispatch(u["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),l.actions.fetchAll(t).then(function(){t.dispatch(u["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:a}),i?c.actions.start(t,{syncOnInitialConnect:!1}):l.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?f:n;t.dispatch(u["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){(0,s.callApi)(t,"POST","log_out"),t.dispatch(u["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var a=n(14),u=r(a),s=n(5),c=n(29),l=n(31),f="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=e.isValidating=["authAttempt","isValidating"],r=(e.isInvalidAttempt=["authAttempt","isInvalid"],e.attemptErrorMessage=["authAttempt","errorMessage"],e.rememberAuth=["rememberAuth"],e.attemptAuthInfo=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}]),i=e.currentAuthToken=["authCurrent","authToken"],o=e.currentAuthInfo=[i,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.authToken=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}],e.authInfo=[n,r,o,function(t,e,n){return t?e:n}]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(null==t)throw new TypeError("Cannot destructure undefined")}function o(t,e){var n=e.authToken,r=e.host;return(0,s.toImmutable)({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function a(t,e){return i(e),f.getInitialState()}function u(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var s=n(3),c=n(14),l=r(c),f=new s.Store({getInitialState:function(){return(0,s.toImmutable)({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(l["default"].VALIDATING_AUTH_TOKEN,o),this.on(l["default"].VALID_AUTH_TOKEN,a),this.on(l["default"].INVALID_AUTH_TOKEN,u)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.authToken,r=e.host;return(0,a.toImmutable)({authToken:n,host:r})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(14),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({authToken:null,host:""})},initialize:function(){this.on(s["default"].VALID_AUTH_TOKEN,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.rememberAuth;return n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(3),a=n(14),u=r(a),s=new o.Store({getInitialState:function(){return!0},initialize:function(){this.on(u["default"].VALID_AUTH_TOKEN,i)}});e["default"]=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(c["default"].SERVER_CONFIG_LOADED,e)}function o(t){(0,u.callApi)(t,"GET","config").then(function(e){return i(t,e)})}function a(t,e){t.dispatch(c["default"].COMPONENT_LOADED,{component:e})}Object.defineProperty(e,"__esModule",{value:!0}),e.configLoaded=i,e.fetchAll=o,e.componentLoaded=a;var u=n(5),s=n(23),c=r(s)},function(t,e){"use strict";function n(t){return[["serverComponent"],function(e){return e.contains(t)}]}Object.defineProperty(e,"__esModule",{value:!0}),e.isComponentLoaded=n,e.locationGPS=[["serverConfig","latitude"],["serverConfig","longitude"],function(t,e){return{latitude:t,longitude:e}}],e.locationName=["serverConfig","location_name"],e.serverVersion=["serverConfig","serverVersion"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.component;return t.push(n)}function o(t,e){var n=e.components;return(0,u.toImmutable)(n)}function a(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var u=n(3),s=n(23),c=r(s),l=new u.Store({getInitialState:function(){return(0,u.toImmutable)([])},initialize:function(){this.on(c["default"].COMPONENT_LOADED,i),this.on(c["default"].SERVER_CONFIG_LOADED,o),this.on(c["default"].LOG_OUT,a)}});e["default"]=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.temperature_unit,u=e.time_zone,s=e.version;return(0,a.toImmutable)({latitude:n,longitude:r,location_name:i,temperature_unit:o,time_zone:u,serverVersion:s})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(23),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({latitude:null,longitude:null,location_name:"Home",temperature_unit:"°C",time_zone:"UTC",serverVersion:"unknown"})},initialize:function(){this.on(s["default"].SERVER_CONFIG_LOADED,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){t.dispatch(f["default"].ENTITY_HISTORY_DATE_SELECTED,{date:e})}function a(t){var e=arguments.length<=1||void 0===arguments[1]?null:arguments[1];t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),(0,c.callApi)(t,"GET",n).then(function(e){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})},function(){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,{})})}function u(t,e){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_START,{date:e}),(0,c.callApi)(t,"GET","history/period/"+e).then(function(n){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})},function(){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_ERROR,{})})}function s(t){var e=t.evaluate(h.currentDate);return u(t,e)}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=o,e.fetchRecent=a,e.fetchDate=u,e.fetchSelectedDate=s;var c=n(5),l=n(11),f=i(l),d=n(45),h=r(d)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date;return(0,s["default"])(n)}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(32),s=r(u),c=n(11),l=r(c),f=new a.Store({getInitialState:function(){var t=new Date;return t.setDate(t.getDate()-1),(0,s["default"])(t)},initialize:function(){this.on(l["default"].ENTITY_HISTORY_DATE_SELECTED,i),this.on(l["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,(0,a.toImmutable)({})):t.withMutations(function(t){r.forEach(function(e){return t.setIn([n,e[0].entity_id],(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),o=n(11),a=r(o),u=new i.Store({getInitialState:function(){return!1},initialize:function(){this.on(a["default"].ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].LOG_OUT,function(){return!1})}});e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.stateHistory;return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(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(c,r)})}function o(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c="ALL_ENTRY_FETCH",l=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(10),o=n(16),a=r(o),u=(0,i.createApiActions)(a["default"]);e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.visibleEntityMap=e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(16),a=r(o),u=(e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]));e.byId=(0,i.createByIdGetter)(a["default"]),e.visibleEntityMap=[u,function(t){return t.filter(function(t){return!t.attributes.hidden})}]},function(t,e,n){"use strict";function r(t){return(0,i.callApi)(t,"GET","error_log")}Object.defineProperty(e,"__esModule",{value:!0}),e.fetchErrorLog=r;var i=n(5)},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}Object.defineProperty(e,"__esModule",{value:!0}),e.actions=void 0;var i=n(148),o=r(i);e.actions=o},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=n(10),a=n(27),u=n(47),s=r(u),c=(0,o.createApiActions)(s["default"]);c.fireEvent=function(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];return(0,i.callApi)(t,"POST","events/"+e,n).then(function(){a.actions.createNotification(t,"Event "+e+" successful fired!")})},e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(47),a=r(o);e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]),e.byId=(0,i.createByIdGetter)(a["default"])},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(s["default"].LOGBOOK_DATE_SELECTED,{date:e})}function o(t,e){t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_START,{date:e}),(0,a.callApi)(t,"GET","logbook/"+e).then(function(n){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_SUCCESS,{date:e,entries:n})},function(){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_ERROR,{})})}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=i,e.fetchDate=o;var a=n(5),u=n(12),s=r(u)},function(t,e,n){"use strict";function r(t){return!t||(new Date).getTime()-t>o}Object.defineProperty(e,"__esModule",{value:!0}),e.isLoadingEntries=e.currentEntries=e.isCurrentStale=e.currentDate=void 0;var i=n(3),o=6e4,a=e.currentDate=["currentLogbookDate"];e.isCurrentStale=[a,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}],e.currentEntries=[a,["logbookEntries"],function(t,e){return e.get(t)||(0,i.toImmutable)([])}],e.isLoadingEntries=["isLoadingLogbookEntries"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentLogbookDate:u["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:h["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(156),u=i(a),s=n(157),c=i(s),l=n(158),f=i(l),d=n(159),h=i(d),p=n(152),_=r(p),v=n(153),y=r(v);e.actions=_,e.getters=y},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var u=function(){function t(t,e){for(var n=0;nt;t+=2){var e=rt[t],n=rt[t+1];e(n),rt[t]=void 0,rt[t+1]=void 0}$=0}function y(){try{var t=n(215);return W=t.runOnLoop||t.runOnContext,d()}catch(e){return _()}}function m(){}function g(){return new TypeError("You cannot resolve a promise with itself")}function b(){return new TypeError("A promises callback cannot return that same promise.")}function S(t){try{return t.then}catch(e){return ut.error=e,ut}}function w(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function O(t,e,n){Z(function(t){var r=!1,i=w(n,e,function(n){r||(r=!0,e!==n?E(t,n):D(t,n))},function(e){r||(r=!0,C(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,C(t,i))},t)}function M(t,e){e._state===ot?D(t,e._result):e._state===at?C(t,e._result):j(e,void 0,function(e){E(t,e)},function(e){C(t,e)})}function I(t,e){if(e.constructor===t.constructor)M(t,e);else{var n=S(e);n===ut?C(t,ut.error):void 0===n?D(t,e):u(n)?O(t,e,n):D(t,e)}}function E(t,e){t===e?C(t,g()):a(e)?I(t,e):D(t,e)}function T(t){t._onerror&&t._onerror(t._result),P(t)}function D(t,e){t._state===it&&(t._result=e,t._state=ot,0!==t._subscribers.length&&Z(P,t))}function C(t,e){t._state===it&&(t._state=at,t._result=e,Z(T,t))}function j(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ot]=n,i[o+at]=r,0===o&&t._state&&Z(P,t)}function P(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,a=0;aa;a++)j(r.resolve(t[a]),void 0,e,n);return i}function Y(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(m);return E(n,t),n}function z(t){var e=this,n=new e(m);return C(n,t),n}function U(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function V(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function F(t){this._id=pt++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(u(t)||U(),this instanceof F||V(),N(this,t))}function G(){var t;if("undefined"!=typeof i)t=i;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=_t)}var B;B=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var W,q,K,J=B,$=0,Z=({}.toString,function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(q?q(v):K())}),X="undefined"!=typeof window?window:void 0,Q=X||{},tt=Q.MutationObserver||Q.WebKitMutationObserver,et="undefined"!=typeof t&&"[object process]"==={}.toString.call(t),nt="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,rt=new Array(1e3);K=et?f():tt?h():nt?p():void 0===X?y():_();var it=void 0,ot=1,at=2,ut=new A,st=new A;R.prototype._validateInput=function(t){return J(t)},R.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},R.prototype._init=function(){this._result=new Array(this.length)};var ct=R;R.prototype._enumerate=function(){for(var t=this,e=t.length,n=t.promise,r=t._input,i=0;n._state===it&&e>i;i++)t._eachEntry(r[i],i)},R.prototype._eachEntry=function(t,e){var n=this,r=n._instanceConstructor;s(t)?t.constructor===r&&t._state!==it?(t._onerror=null,n._settledAt(t._state,e,t._result)):n._willSettleAt(r.resolve(t),e):(n._remaining--,n._result[e]=t)},R.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===it&&(r._remaining--,t===at?C(i,n):r._result[e]=n),0===r._remaining&&D(i,r._result)},R.prototype._willSettleAt=function(t,e){var n=this;j(t,void 0,function(t){n._settledAt(ot,e,t)},function(t){n._settledAt(at,e,t)})};var lt=x,ft=H,dt=Y,ht=z,pt=0,_t=F;F.all=lt,F.race=ft,F.resolve=dt,F.reject=ht,F._setScheduler=c,F._setAsap=l,F._asap=Z,F.prototype={constructor:F,then:function(t,e){var n=this,r=n._state;if(r===ot&&!t||r===at&&!e)return this;var i=new this.constructor(m),o=n._result;if(r){var a=arguments[r-1];Z(function(){L(r,i,a,o)})}else j(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var vt=G,yt={Promise:_t,polyfill:vt};n(213).amd?(r=function(){return yt}.call(e,n,e,o),!(void 0!==r&&(o.exports=r))):"undefined"!=typeof o&&o.exports?o.exports=yt:"undefined"!=typeof this&&(this.ES6Promise=yt),vt()}).call(this)}).call(e,n(214),function(){return this}(),n(68)(t))},function(t,e,n){var r=n(63),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){var r=n(63),i=n(201),o=n(64),a="[object Array]",u=Object.prototype,s=u.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==a};t.exports=l},function(t,e,n){function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&a.test(t)}var i=n(65),o=n(64),a=/^\[object .+?Constructor\]$/,u=Object.prototype,s=Function.prototype.toString,c=u.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e){function n(t){return function(e){return null==e?void 0:e[t]}}t.exports=n},function(t,e,n){var r=n(204),i=r("length");t.exports=i},function(t,e,n){function r(t){return null!=t&&o(i(t))}var i=n(205),o=n(209);t.exports=r},function(t,e){function n(t,e){return t="number"==typeof t||r.test(t)?+t:-1,e=null==e?i:e,t>-1&&t%1==0&&e>t}var r=/^\d+$/,i=9007199254740991;t.exports=n},function(t,e,n){function r(t,e,n){if(!a(n))return!1;var r=typeof e;if("number"==r?i(n)&&o(e,n.length):"string"==r&&e in n){var u=n[e];return t===t?t===u:u!==u}return!1}var i=n(206),o=n(207),a=n(210);t.exports=r},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){function r(t,e,n){n&&i(t,e,n)&&(e=n=void 0),t=+t||0,n=null==n?1:+n||0,null==e?(e=t,t=0):e=+e||0;for(var r=-1,u=a(o((e-t)/(n||1)),0),s=Array(u);++r1)for(var n=1;n \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 4cdefac2fe6..40ff847f2d0 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 4cdefac2fe6f016fa09d872c8cb062ba01442b08 +Subproject commit 40ff847f2d0670367a2fd29e3340a4f94ab1ff49 From cd6d44ece391849d21f30dddb0de5482326e3a5d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 01:05:52 -0800 Subject: [PATCH 100/186] Properly clean up Home Assistant instances in tests --- tests/test_bootstrap.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 07b49302b09..5113301dee6 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -61,6 +61,7 @@ class TestBootstrap(unittest.TestCase): self.assertTrue(os.path.isfile(check_file)) bootstrap.process_ha_config_upgrade(hass) self.assertFalse(os.path.isfile(check_file)) + hass.stop() def test_not_remove_lib_if_not_upgrade(self): with tempfile.TemporaryDirectory() as config_dir: @@ -82,6 +83,7 @@ class TestBootstrap(unittest.TestCase): bootstrap.process_ha_config_upgrade(hass) self.assertTrue(os.path.isfile(check_file)) + hass.stop() def test_entity_customization(self): """ Test entity customization through config """ @@ -102,6 +104,7 @@ class TestBootstrap(unittest.TestCase): state = hass.states.get('test.test') self.assertTrue(state.attributes['hidden']) + hass.stop() def test_handle_setup_circular_dependency(self): hass = get_test_home_assistant() @@ -116,3 +119,4 @@ class TestBootstrap(unittest.TestCase): bootstrap.setup_component(hass, 'comp_a') self.assertEqual(['comp_a'], hass.config.components) + hass.stop() From ff6e071dff771a46ccd53c06faa809aadb164a47 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sat, 20 Feb 2016 12:48:05 +0100 Subject: [PATCH 101/186] added the for param to the conditions as well --- homeassistant/components/automation/state.py | 61 +++++++++++++------- tests/components/automation/test_state.py | 36 +++++++++++- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 2ddc0822cc9..31d84abbe2b 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -24,6 +24,31 @@ CONF_STATE = "state" CONF_FOR = "for" +def get_time_config(config): + """ Helper function to extract the time specified in the config """ + if CONF_FOR in config: + hours = config[CONF_FOR].get(CONF_HOURS) + minutes = config[CONF_FOR].get(CONF_MINUTES) + seconds = config[CONF_FOR].get(CONF_SECONDS) + + if hours is None and minutes is None and seconds is None: + logging.getLogger(__name__).error( + "Received invalid value for '%s': %s", + config[CONF_FOR], CONF_FOR) + return False + + if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: + logging.getLogger(__name__).error( + "For: requires a to: value'%s': %s", + config[CONF_FOR], CONF_FOR) + return False + + return timedelta(hours=(hours or 0.0), + minutes=(minutes or 0.0), + seconds=(seconds or 0.0)) + return False + + def trigger(hass, config, action): """ Listen for state changes based on `config`. """ entity_id = config.get(CONF_ENTITY_ID) @@ -42,20 +67,8 @@ def trigger(hass, config, action): return False if CONF_FOR in config: - hours = config[CONF_FOR].get(CONF_HOURS) - minutes = config[CONF_FOR].get(CONF_MINUTES) - seconds = config[CONF_FOR].get(CONF_SECONDS) - - if hours is None and minutes is None and seconds is None: - logging.getLogger(__name__).error( - "Received invalid value for '%s': %s", - config[CONF_FOR], CONF_FOR) - return False - - if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: - logging.getLogger(__name__).error( - "For: requires a to: value'%s': %s", - config[CONF_FOR], CONF_FOR) + time_delta = get_time_config(config) + if time_delta is False: return False def state_automation_listener(entity, from_s, to_s): @@ -77,11 +90,7 @@ def trigger(hass, config, action): EVENT_STATE_CHANGED, for_state_listener) if CONF_FOR in config: - now = dt_util.now() - target_tm = now + timedelta( - hours=(hours or 0.0), - minutes=(minutes or 0.0), - seconds=(seconds or 0.0)) + target_tm = dt_util.now() + time_delta for_time_listener = track_point_in_time( hass, state_for_listener, target_tm) for_state_listener = track_state_change( @@ -101,6 +110,11 @@ def if_action(hass, config): entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) + if CONF_FOR in config: + time_delta = get_time_config(config) + if time_delta is False: + return False + if entity_id is None or state is None: logging.getLogger(__name__).error( "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, @@ -111,6 +125,13 @@ def if_action(hass, config): def if_state(): """ Test if condition. """ - return hass.states.is_state(entity_id, state) + if hass.states.is_state(entity_id, state): + if CONF_FOR in config: + target_tm = dt_util.now() - time_delta + return target_tm > hass.states.get(entity_id).last_changed + else: + return True + else: + return False return if_state diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 8475d284d3e..308b10a02bf 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -6,7 +6,7 @@ Tests state automation. """ import unittest from datetime import timedelta - +import time import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation import homeassistant.components.automation.state as state @@ -423,3 +423,37 @@ class TestAutomationState(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_for_condition(self): + self.hass.states.set('test.entity', 'on') + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'state': 'on', + 'for': { + 'seconds': 3 + }, + }, + + 'action': { + 'service': 'test.automation' + } + } + })) + + # not enough time has passed + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + # wait until we have passed the condition + time.sleep(4) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) From f3c95adaca4dd7bccb6892759508c5930bc560bf Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sat, 20 Feb 2016 22:15:49 +0100 Subject: [PATCH 102/186] Fixed now => utcnow Fixed added time patch to unittest --- homeassistant/components/automation/state.py | 20 +++---- tests/components/automation/test_state.py | 59 +++++++++++--------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 31d84abbe2b..62822f3d7e8 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -90,7 +90,7 @@ def trigger(hass, config, action): EVENT_STATE_CHANGED, for_state_listener) if CONF_FOR in config: - target_tm = dt_util.now() + time_delta + target_tm = dt_util.utcnow() + time_delta for_time_listener = track_point_in_time( hass, state_for_listener, target_tm) for_state_listener = track_state_change( @@ -112,7 +112,7 @@ def if_action(hass, config): if CONF_FOR in config: time_delta = get_time_config(config) - if time_delta is False: + if not time_delta: return False if entity_id is None or state is None: @@ -125,13 +125,13 @@ def if_action(hass, config): def if_state(): """ Test if condition. """ - if hass.states.is_state(entity_id, state): - if CONF_FOR in config: - target_tm = dt_util.now() - time_delta - return target_tm > hass.states.get(entity_id).last_changed - else: - return True - else: - return False + is_state = hass.states.is_state(entity_id, state) + + if CONF_FOR not in config: + return is_state + + target_tm = dt_util.utcnow() - time_delta + return (is_state and + target_tm > hass.states.get(entity_id).last_changed) return if_state diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 308b10a02bf..cf1c49ba1ea 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -6,6 +6,7 @@ Tests state automation. """ import unittest from datetime import timedelta +from unittest.mock import patch import time import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation @@ -425,35 +426,39 @@ class TestAutomationState(unittest.TestCase): self.assertEqual(1, len(self.calls)) def test_if_fires_on_for_condition(self): - self.hass.states.set('test.entity', 'on') - self.assertTrue(automation.setup(self.hass, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'state': 'on', - 'for': { - 'seconds': 3 + point1 = dt_util.utcnow() + point2 = point1 + timedelta(seconds=10) + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = point1 + self.hass.states.set('test.entity', 'on') + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'state': 'on', + 'for': { + 'seconds': 5 + }, }, - }, - 'action': { - 'service': 'test.automation' + 'action': { + 'service': 'test.automation' + } } - } - })) + })) - # not enough time has passed - self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() - self.assertEqual(0, len(self.calls)) + # not enough time has passed + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) - # wait until we have passed the condition - time.sleep(4) - self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + # Time travel 10 secs into the future + mock_utcnow.return_value = point2 + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) From 6e7ca9505c500865dc8027206df5cad0cfbeb8b3 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sat, 20 Feb 2016 22:25:41 +0100 Subject: [PATCH 103/186] Removed unused import --- tests/components/automation/test_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index cf1c49ba1ea..b5f0e31ddc8 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -7,7 +7,7 @@ Tests state automation. import unittest from datetime import timedelta from unittest.mock import patch -import time + import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation import homeassistant.components.automation.state as state From 5053c807c0d4c4ce4c56f872258ff2dc3a09caac Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Sat, 20 Feb 2016 22:24:45 +0200 Subject: [PATCH 104/186] ZWave binary sensor support Treat ZWave binary sensors as binary_sensor components instead of regular sensors. --- .../components/binary_sensor/__init__.py | 3 +- .../components/binary_sensor/zwave.py | 69 +++++++++++++++++++ homeassistant/components/sensor/zwave.py | 15 +--- homeassistant/components/zwave.py | 11 ++- 4 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 homeassistant/components/binary_sensor/zwave.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index c51b30d8b7c..1be1c4423ee 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,7 +12,7 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import (bloomsky, mysensors) +from homeassistant.components import (bloomsky, mysensors, zwave) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -34,6 +34,7 @@ SENSOR_CLASSES = [ DISCOVERY_PLATFORMS = { bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', + zwave.DISCOVER_BINARY_SENSORS: 'zwave', } diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py new file mode 100644 index 00000000000..8803af3bdcd --- /dev/null +++ b/homeassistant/components/binary_sensor/zwave.py @@ -0,0 +1,69 @@ +""" +homeassistant.components.binary_sensor.zwave +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Interfaces with Z-Wave sensors. + +For more details about this platform, please refer to the documentation +at https://home-assistant.io/components/zwave/ +""" + + +import logging + +from homeassistant.components.zwave import ( + ATTR_NODE_ID, ATTR_VALUE_ID, + COMMAND_CLASS_SENSOR_BINARY, NETWORK, + ZWaveDeviceEntity) +from homeassistant.components.binary_sensor import ( + DOMAIN, + BinarySensorDevice) + +_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = [] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the mysensors platform for sensors.""" + + if discovery_info is None or NETWORK is None: + return + + node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] + value = node.values[discovery_info[ATTR_VALUE_ID]] + + value.set_change_verified(False) + if value.command_class == COMMAND_CLASS_SENSOR_BINARY: + add_devices([ZWaveBinarySensor(value, "opening")]) + + +class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): + """ Represents a binary sensor within Z-Wave. """ + + def __init__(self, value, sensor_class): + self._sensor_type = sensor_class + from openzwave.network import ZWaveNetwork + from pydispatch import dispatcher + + ZWaveDeviceEntity.__init__(self, value, DOMAIN) + + dispatcher.connect( + self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self._value.data + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return self._sensor_type + + @property + def should_poll(self): + return False + + def value_changed(self, value): + """ Called when a value has changed on the network. """ + if self._value.value_id == value.value_id: + self.update_ha_state() diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index f011b82b9f3..4159b915f26 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -14,7 +14,7 @@ import homeassistant.util.dt as dt_util from homeassistant.components.sensor import DOMAIN from homeassistant.components.zwave import ( ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_ALARM, COMMAND_CLASS_METER, - COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL, NETWORK, + COMMAND_CLASS_SENSOR_MULTILEVEL, NETWORK, TYPE_DECIMAL, ZWaveDeviceEntity, get_config_value) from homeassistant.const import ( STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) @@ -79,9 +79,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return # generic Device mappings - elif value.command_class == COMMAND_CLASS_SENSOR_BINARY: - add_devices([ZWaveBinarySensor(value)]) - elif value.command_class == COMMAND_CLASS_SENSOR_MULTILEVEL: add_devices([ZWaveMultilevelSensor(value)]) @@ -120,16 +117,6 @@ class ZWaveSensor(ZWaveDeviceEntity, Entity): self.update_ha_state() -# pylint: disable=too-few-public-methods -class ZWaveBinarySensor(ZWaveSensor): - """ Represents a binary sensor within Z-Wave. """ - - @property - def state(self): - """ Returns the state of the sensor. """ - return STATE_ON if self._value.data else STATE_OFF - - class ZWaveTriggerSensor(ZWaveSensor): """ Represents a stateless sensor which diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 49ab2bcaf40..80b56b1dac6 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -34,6 +34,7 @@ SERVICE_REMOVE_NODE = "remove_node" DISCOVER_SENSORS = "zwave.sensors" DISCOVER_SWITCHES = "zwave.switch" DISCOVER_LIGHTS = "zwave.light" +DISCOVER_BINARY_SENSORS = 'zwave.binary_sensor' EVENT_SCENE_ACTIVATED = "zwave.scene_activated" @@ -54,13 +55,13 @@ TYPE_BYTE = "Byte" TYPE_BOOL = "Bool" TYPE_DECIMAL = "Decimal" + # list of tuple (DOMAIN, discovered service, supported command # classes, value type) DISCOVERY_COMPONENTS = [ ('sensor', DISCOVER_SENSORS, - [COMMAND_CLASS_SENSOR_BINARY, - COMMAND_CLASS_SENSOR_MULTILEVEL, + [COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, COMMAND_CLASS_ALARM], TYPE_WHATEVER, @@ -75,8 +76,14 @@ DISCOVERY_COMPONENTS = [ [COMMAND_CLASS_SWITCH_BINARY], TYPE_BOOL, GENRE_USER), + ('binary_sensor', + DISCOVER_BINARY_SENSORS, + [COMMAND_CLASS_SENSOR_BINARY], + TYPE_BOOL, + GENRE_USER) ] + ATTR_NODE_ID = "node_id" ATTR_VALUE_ID = "value_id" From 8f70630790170dd357991484417eecc9e49b871e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 17:17:22 -0800 Subject: [PATCH 105/186] MQTT Light to expose assumed_state if optimistic --- homeassistant/components/light/mqtt.py | 5 +++++ tests/components/light/test_mqtt.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 3c860e074dd..2cdb9ee3fc4 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -134,6 +134,11 @@ class MqttLight(Light): """ True if device is on. """ return self._state + @property + def assumed_state(self): + """Return True if we do optimistic updates.""" + return self._optimistic + def turn_on(self, **kwargs): """ Turn the device on. """ should_update = False diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index f08e84677a5..139a0be8f20 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -45,7 +45,7 @@ light: """ import unittest -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE import homeassistant.components.light as light from tests.common import ( get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) @@ -115,6 +115,7 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) self.assertIsNone(state.attributes.get('rgb_color')) self.assertIsNone(state.attributes.get('brightness')) + self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') self.hass.pool.block_till_done() @@ -201,6 +202,7 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) + self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) light.turn_on(self.hass, 'light.test') self.hass.pool.block_till_done() From 443b39bccdbda059bda71b0f17d8abd555f8dc48 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 17:17:30 -0800 Subject: [PATCH 106/186] MQTT Switch to expose assumed_state if optimistic --- homeassistant/components/switch/mqtt.py | 5 +++++ tests/components/switch/test_mqtt.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 01360b02bec..6b8089daed3 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -97,6 +97,11 @@ class MqttSwitch(SwitchDevice): """ True if device is on. """ return self._state + @property + def assumed_state(self): + """Return True if we do optimistic updates.""" + return self._optimistic + def turn_on(self, **kwargs): """ Turn the device on. """ mqtt.publish(self.hass, self._command_topic, self._payload_on, diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index d412b5293a0..17bc65c7e5a 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -6,7 +6,7 @@ Tests MQTT switch. """ import unittest -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE import homeassistant.components.switch as switch from tests.common import ( mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) @@ -37,6 +37,7 @@ class TestSensorMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) + self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'state-topic', 'beer on') self.hass.pool.block_till_done() @@ -64,6 +65,7 @@ class TestSensorMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) + self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) switch.turn_on(self.hass, 'switch.test') self.hass.pool.block_till_done() From 1eae74be587c59b9020f3e3ecfe44a7bb74193b6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 19:11:02 -0800 Subject: [PATCH 107/186] Add assumed_state to group --- homeassistant/components/group.py | 93 ++++++++++++++++++-------- tests/components/test_group.py | 104 +++++++++++++++++++++++------- 2 files changed, 144 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index a851726e69d..3d157f32eea 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -9,7 +9,8 @@ https://home-assistant.io/components/group/ import homeassistant.core as ha from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, - STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_UNKNOWN) + STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_UNKNOWN, + ATTR_ASSUMED_STATE, ) from homeassistant.helpers.entity import ( Entity, generate_entity_id, split_entity_id) from homeassistant.helpers.event import track_state_change @@ -144,6 +145,7 @@ class Group(Entity): self.tracking = [] self.group_on = None self.group_off = None + self._assumed_state = False if entity_ids is not None: self.update_tracked_entity_ids(entity_ids) @@ -182,6 +184,11 @@ class Group(Entity): data[ATTR_VIEW] = True return data + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return self._assumed_state + def update_tracked_entity_ids(self, entity_ids): """ Update the tracked entity IDs. """ self.stop() @@ -207,47 +214,77 @@ class Group(Entity): def update(self): """ Query all the tracked states and determine current group state. """ self._state = STATE_UNKNOWN + self._update_group_state() + + def _state_changed_listener(self, entity_id, old_state, new_state): + """ Listener to receive state changes of tracked entities. """ + self._update_group_state(new_state) + self.update_ha_state() + + @property + def _tracking_states(self): + """States that the group is tracking.""" + states = [] for entity_id in self.tracking: state = self.hass.states.get(entity_id) if state is not None: - self._process_tracked_state(state) + states.append(state) - def _state_changed_listener(self, entity_id, old_state, new_state): - """ Listener to receive state changes of tracked entities. """ - self._process_tracked_state(new_state) - self.update_ha_state() + return states - def _process_tracked_state(self, tr_state): - """ Updates group state based on a new state of a tracked entity. """ + def _update_group_state(self, tr_state=None): + """Update group state. + + Optionally you can provide the only state changed since last update + allowing this method to take shortcuts. + """ + # pylint: disable=too-many-branches + # To store current states of group entities. Might not be needed. + states = None + gr_state, gr_on, gr_off = self._state, self.group_on, self.group_off # We have not determined type of group yet - if self.group_on is None: - self.group_on, self.group_off = _get_group_on_off(tr_state.state) + if gr_on is None: + if tr_state is None: + states = self._tracking_states - if self.group_on is not None: - # New state of the group is going to be based on the first - # state that we can recognize - self._state = tr_state.state + for state in states: + gr_on, gr_off = \ + _get_group_on_off(state.state) + if gr_on is not None: + break + else: + gr_on, gr_off = _get_group_on_off(tr_state.state) + if gr_on is not None: + self.group_on, self.group_off = gr_on, gr_off + + # We cannot determine state of the group + if gr_on is None: return - # There is already a group state - cur_gr_state = self._state - group_on, group_off = self.group_on, self.group_off + if tr_state is None or (gr_state == gr_on and + tr_state.state == gr_off): + if states is None: + states = self._tracking_states - # if cur_gr_state = OFF and tr_state = ON: set ON - # if cur_gr_state = ON and tr_state = OFF: research - # else: ignore + if any(state.state == gr_on for state in states): + self._state = gr_on + else: + self._state = gr_off - if cur_gr_state == group_off and tr_state.state == group_on: - self._state = group_on + elif tr_state.state in (gr_on, gr_off): + self._state = tr_state.state - elif cur_gr_state == group_on and tr_state.state == group_off: + if tr_state is None or self._assumed_state and \ + not tr_state.attributes.get(ATTR_ASSUMED_STATE): + if states is None: + states = self._tracking_states - # Set to off if no other states are on - if not any(self.hass.states.is_state(ent_id, group_on) - for ent_id in self.tracking - if tr_state.entity_id != ent_id): - self._state = group_off + self._assumed_state = any(state.attributes.get(ATTR_ASSUMED_STATE) + for state in states) + + elif tr_state.attributes.get(ATTR_ASSUMED_STATE): + self._assumed_state = True diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 8a3eeadcb14..8e5a5d8e0c8 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -8,7 +8,8 @@ Tests the group compoments. import unittest from homeassistant.const import ( - STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN) + STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN, + ATTR_ASSUMED_STATE, ) import homeassistant.components.group as group from tests.common import get_test_home_assistant @@ -21,19 +22,13 @@ class TestComponentsGroup(unittest.TestCase): """ Init needed objects. """ self.hass = get_test_home_assistant() - self.hass.states.set('light.Bowl', STATE_ON) - self.hass.states.set('light.Ceiling', STATE_OFF) - test_group = group.Group( - self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - - self.group_entity_id = test_group.entity_id - def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ self.hass.stop() def test_setup_group_with_mixed_groupable_states(self): """ Try to setup a group with mixed groupable states """ + self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('device_tracker.Paulus', STATE_HOME) group.Group( self.hass, 'person_and_light', @@ -46,6 +41,8 @@ class TestComponentsGroup(unittest.TestCase): def test_setup_group_with_a_non_existing_state(self): """ Try to setup a group with a non existing state """ + self.hass.states.set('light.Bowl', STATE_ON) + grp = group.Group( self.hass, 'light_and_nothing', ['light.Bowl', 'non.existing']) @@ -70,11 +67,15 @@ class TestComponentsGroup(unittest.TestCase): def test_monitor_group(self): """ Test if the group keeps track of states. """ + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) # Test if group setup in our init mode is ok - self.assertIn(self.group_entity_id, self.hass.states.entity_ids()) + self.assertIn(test_group.entity_id, self.hass.states.entity_ids()) - group_state = self.hass.states.get(self.group_entity_id) + group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_ON, group_state.state) self.assertTrue(group_state.attributes.get(group.ATTR_AUTO)) @@ -83,54 +84,73 @@ class TestComponentsGroup(unittest.TestCase): Test if the group turns off if the last device that was on turns off. """ self.hass.states.set('light.Bowl', STATE_OFF) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.hass.pool.block_till_done() - group_state = self.hass.states.get(self.group_entity_id) + group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_OFF, group_state.state) def test_group_turns_on_if_all_are_off_and_one_turns_on(self): """ Test if group turns on if all devices were turned off and one turns on. """ - # Make sure all are off. self.hass.states.set('light.Bowl', STATE_OFF) - self.hass.pool.block_till_done() + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) # Turn one on self.hass.states.set('light.Ceiling', STATE_ON) self.hass.pool.block_till_done() - group_state = self.hass.states.get(self.group_entity_id) + group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_ON, group_state.state) def test_is_on(self): """ Test is_on method. """ - self.assertTrue(group.is_on(self.hass, self.group_entity_id)) + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) + + self.assertTrue(group.is_on(self.hass, test_group.entity_id)) self.hass.states.set('light.Bowl', STATE_OFF) self.hass.pool.block_till_done() - self.assertFalse(group.is_on(self.hass, self.group_entity_id)) + self.assertFalse(group.is_on(self.hass, test_group.entity_id)) # Try on non existing state self.assertFalse(group.is_on(self.hass, 'non.existing')) def test_expand_entity_ids(self): """ Test expand_entity_ids method. """ + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) + self.assertEqual(sorted(['light.ceiling', 'light.bowl']), sorted(group.expand_entity_ids( - self.hass, [self.group_entity_id]))) + self.hass, [test_group.entity_id]))) def test_expand_entity_ids_does_not_return_duplicates(self): """ Test that expand_entity_ids does not return duplicates. """ - self.assertEqual( - ['light.bowl', 'light.ceiling'], - sorted(group.expand_entity_ids( - self.hass, [self.group_entity_id, 'light.Ceiling']))) + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertEqual( ['light.bowl', 'light.ceiling'], sorted(group.expand_entity_ids( - self.hass, ['light.bowl', self.group_entity_id]))) + self.hass, [test_group.entity_id, 'light.Ceiling']))) + + self.assertEqual( + ['light.bowl', 'light.ceiling'], + sorted(group.expand_entity_ids( + self.hass, ['light.bowl', test_group.entity_id]))) def test_expand_entity_ids_ignores_non_strings(self): """ Test that non string elements in lists are ignored. """ @@ -138,9 +158,14 @@ class TestComponentsGroup(unittest.TestCase): def test_get_entity_ids(self): """ Test get_entity_ids method. """ + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) + self.assertEqual( ['light.bowl', 'light.ceiling'], - sorted(group.get_entity_ids(self.hass, self.group_entity_id))) + sorted(group.get_entity_ids(self.hass, test_group.entity_id))) def test_get_entity_ids_with_domain_filter(self): """ Test if get_entity_ids works with a domain_filter. """ @@ -190,13 +215,18 @@ class TestComponentsGroup(unittest.TestCase): def test_setup(self): """ Test setup method. """ + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) + self.assertTrue( group.setup( self.hass, { group.DOMAIN: { 'second_group': { - 'entities': 'light.Bowl, ' + self.group_entity_id, + 'entities': 'light.Bowl, ' + test_group.entity_id, 'icon': 'mdi:work', 'view': True, }, @@ -207,7 +237,7 @@ class TestComponentsGroup(unittest.TestCase): group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) self.assertEqual(STATE_ON, group_state.state) - self.assertEqual(set((self.group_entity_id, 'light.bowl')), + self.assertEqual(set((test_group.entity_id, 'light.bowl')), set(group_state.attributes['entity_id'])) self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) self.assertEqual('mdi:work', @@ -242,3 +272,27 @@ class TestComponentsGroup(unittest.TestCase): ['light.test_1', 'light.test_2', 'switch.test_1', 'switch.test_2'], sorted(group.expand_entity_ids(self.hass, ['group.group_of_groups']))) + + def test_set_assumed_state_based_on_tracked(self): + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.states.set('light.Ceiling', STATE_OFF) + test_group = group.Group( + self.hass, 'init_group', + ['light.Bowl', 'light.Ceiling', 'sensor.no_exist']) + + state = self.hass.states.get(test_group.entity_id) + self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) + + self.hass.states.set('light.Bowl', STATE_ON, { + ATTR_ASSUMED_STATE: True + }) + self.hass.pool.block_till_done() + + state = self.hass.states.get(test_group.entity_id) + self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + + self.hass.states.set('light.Bowl', STATE_ON) + self.hass.pool.block_till_done() + + state = self.hass.states.get(test_group.entity_id) + self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) From 7f81122af650f9a9f3a2d60cb282876886fe8180 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 20:46:40 -0800 Subject: [PATCH 108/186] Update frontend with latest version --- homeassistant/components/frontend/version.py | 2 +- homeassistant/components/frontend/www_static/frontend.html | 2 +- .../components/frontend/www_static/home-assistant-polymer | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 255f032d803..199630bc46f 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "72a593fc8887c08d6f4ad9bd483bd232" +VERSION = "ab08c96ac9b571de5e8154326b4b2d21" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index df86e85e4c0..fec91554001 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -6450,7 +6450,7 @@ case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=ht(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?Pt(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(bn,e),r=e?Pt(this):0;return new S(function(){var i=n.next();return i.done?i:w(t,e?--r:r++,i.value,i)})},st.prototype[mn]=!0,t(ct,P),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(bn,e),r=0;return new S(function(){var e=n.next();return e.done?e:w(t,r++,e.value,e)})},t(lt,A),lt.prototype.has=function(t){return this._iter.includes(t)},lt.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){return t(e,e,n)},e)},lt.prototype.__iterator=function(t,e){var n=this._iter.__iterator(bn,e);return new S(function(){var e=n.next();return e.done?e:w(t,e.value,e.value,e)})},t(ft,j),ft.prototype.entrySeq=function(){return this._iter.toSeq()},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){if(e){jt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}},e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(bn,e);return new S(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){jt(r);var i=v(r);return w(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=lt.prototype.cacheResult=ft.prototype.cacheResult=Lt,t(xt,W),xt.prototype.toString=function(){return this.__toString("Map {","}")},xt.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},xt.prototype.set=function(t,e){return Jt(this,t,e)},xt.prototype.setIn=function(t,e){return this.updateIn(t,dn,function(){return e})},xt.prototype.remove=function(t){return Jt(this,t,dn)},xt.prototype.deleteIn=function(t){return this.updateIn(t,function(){return dn})},xt.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},xt.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,Rt(t),e,n);return r===dn?void 0:r},xt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Kt()},xt.prototype.merge=function(){return ne(this,void 0,arguments)},xt.prototype.mergeWith=function(t){var e=un.call(arguments,1);return ne(this,t,e)},xt.prototype.mergeIn=function(t){var e=un.call(arguments,1);return this.updateIn(t,Kt(),function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]})},xt.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},xt.prototype.mergeDeepWith=function(t){var e=un.call(arguments,1);return ne(this,re(t),e)},xt.prototype.mergeDeepIn=function(t){var e=un.call(arguments,1);return this.updateIn(t,Kt(),function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]})},xt.prototype.sort=function(t){return Oe(It(this,t))},xt.prototype.sortBy=function(t,e){return Oe(It(this,e,t))},xt.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},xt.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},xt.prototype.asImmutable=function(){return this.__ensureOwner()},xt.prototype.wasAltered=function(){return this.__altered},xt.prototype.__iterator=function(t,e){return new Gt(this,t,e)},xt.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},xt.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?qt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},xt.isMap=Ht;var Hn="@@__IMMUTABLE_MAP__@@",Yn=xt.prototype;Yn[Hn]=!0,Yn[sn]=Yn.remove,Yn.removeIn=Yn.deleteIn,Yt.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,a=i.length;a>o;o++)if(J(n,i[o][0]))return i[o][1];return r},Yt.prototype.update=function(t,e,r,o,a,u,s){for(var c=a===dn,l=this.entries,f=0,d=l.length;d>f&&!J(o,l[f][0]);f++);var h=d>f;if(h?l[f][1]===a:c)return this;if(n(s),(c||!h)&&n(u),!c||1!==l.length){if(!h&&!c&&l.length>=Un)return Qt(t,l,o,a);var p=t&&t===this.ownerID,_=p?l:i(l);return h?c?f===d-1?_.pop():_[f]=_.pop():_[f]=[o,a]:_.push([o,a]),p?(this.entries=_,this):new Yt(t,_)}},zt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&fn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ae(o&i-1)].get(t+cn,e,n,r)},zt.prototype.update=function(t,e,n,r,i,o,a){void 0===n&&(n=et(r));var u=(0===e?n:n>>>e)&fn,s=1<=Vn)return ee(t,d,c,u,p);if(l&&!p&&2===d.length&&Zt(d[1^f]))return d[1^f];if(l&&p&&1===d.length&&Zt(p))return p;var _=t&&t===this.ownerID,v=l?p?c:c^s:c|s,y=l?p?ue(d,f,p,_):ce(d,f,_):se(d,f,p,_);return _?(this.bitmap=v,this.nodes=y,this):new zt(t,v,y)},Ut.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&fn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Ut.prototype.update=function(t,e,n,r,i,o,a){void 0===n&&(n=et(r));var u=(0===e?n:n>>>e)&fn,s=i===dn,c=this.nodes,l=c[u];if(s&&!l)return this;var f=$t(l,t,e+cn,n,r,i,o,a);if(f===l)return this;var d=this.count;if(l){if(!f&&(d--,Fn>d))return te(t,c,d,u)}else d++;var h=t&&t===this.ownerID,p=ue(c,u,f,h);return h?(this.count=d,this.nodes=p,this):new Ut(t,d,p)},Vt.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,a=i.length;a>o;o++)if(J(n,i[o][0]))return i[o][1];return r},Vt.prototype.update=function(t,e,r,o,a,u,s){void 0===r&&(r=et(o));var c=a===dn;if(r!==this.keyHash)return c?this:(n(s),n(u),Xt(this,t,e,r,[o,a]));for(var l=this.entries,f=0,d=l.length;d>f&&!J(o,l[f][0]);f++);var h=d>f;if(h?l[f][1]===a:c)return this;if(n(s),(c||!h)&&n(u),c&&2===d)return new Ft(t,this.keyHash,l[1^f]);var p=t&&t===this.ownerID,_=p?l:i(l);return h?c?f===d-1?_.pop():_[f]=_.pop():_[f]=[o,a]:_.push([o,a]),p?(this.entries=_,this):new Vt(t,this.keyHash,_)},Ft.prototype.get=function(t,e,n,r){return J(n,this.entry[0])?this.entry[1]:r},Ft.prototype.update=function(t,e,r,i,o,a,u){var s=o===dn,c=J(i,this.entry[0]);return(c?o===this.entry[1]:s)?this:(n(u),s?void n(a):c?t&&t===this.ownerID?(this.entry[1]=o,this):new Ft(t,this.keyHash,[i,o]):(n(a),Xt(this,t,e,et(i),[i,o])))},Yt.prototype.iterate=Vt.prototype.iterate=function(t,e){for(var n=this.entries,r=0,i=n.length-1;i>=r;r++)if(t(n[e?i-r:r])===!1)return!1},zt.prototype.iterate=Ut.prototype.iterate=function(t,e){for(var n=this.nodes,r=0,i=n.length-1;i>=r;r++){var o=n[e?i-r:r];if(o&&o.iterate(t,e)===!1)return!1}},Ft.prototype.iterate=function(t,e){return t(this.entry)},t(Gt,S),Gt.prototype.next=function(){for(var t=this._type,e=this._stack;e;){var n,r=e.node,i=e.index++;if(r.entry){if(0===i)return Bt(t,r.entry)}else if(r.entries){if(n=r.entries.length-1,n>=i)return Bt(t,r.entries[this._reverse?n-i:i])}else if(n=r.nodes.length-1,n>=i){var o=r.nodes[this._reverse?n-i:i];if(o){if(o.entry)return Bt(t,o.entry);e=this._stack=Wt(o,e)}continue}e=this._stack=this._stack.__prev}return O()};var zn,Un=ln/4,Vn=ln/2,Fn=ln/4;t(le,q),le.of=function(){return this(arguments)},le.prototype.toString=function(){return this.__toString("List [","]")},le.prototype.get=function(t,e){if(t=a(this,t),t>=0&&t>>e&fn;if(r>=this.array.length)return new de([],t);var i,o=0===r;if(e>0){var a=this.array[r];if(i=a&&a.removeBefore(t,e-cn,n),i===a&&o)return this}if(o&&!i)return this;var u=me(this,t);if(!o)for(var s=0;r>s;s++)u.array[s]=void 0;return i&&(u.array[r]=i),u},de.prototype.removeAfter=function(t,e,n){if(n===(e?1<>>e&fn;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 a=me(this,t);return a.array.splice(r+1),i&&(a.array[r]=i),a};var Wn,qn={};t(Oe,xt),Oe.of=function(){return this(arguments)},Oe.prototype.toString=function(){return this.__toString("OrderedMap {","}")},Oe.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},Oe.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Ee()},Oe.prototype.set=function(t,e){return Te(this,t,e)},Oe.prototype.remove=function(t){return Te(this,t,dn)},Oe.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Oe.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate(function(e){return e&&t(e[1],e[0],n)},e)},Oe.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},Oe.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?Ie(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},Oe.isOrderedMap=Me,Oe.prototype[mn]=!0,Oe.prototype[sn]=Oe.prototype.remove;var Kn;t(De,q),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=a(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(){if(0===arguments.length)return this;for(var t=this.size+arguments.length,e=this._head,n=arguments.length-1;n>=0;n--)e={value:arguments[n],next:e};return this.__ownerID?(this.size=t,this._head=e,this.__hash=void 0,this.__altered=!0,this):je(t,e)},De.prototype.pushAll=function(t){if(t=p(t),0===t.size)return this;ut(t.size);var e=this.size,n=this._head;return t.reverse().forEach(function(t){e++,n={value:t,next:n}}),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):je(e,n)},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):Pe()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=l(e,this.size);if(r!==this.size)return q.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):je(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?je(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){if(e)return this.reverse().__iterate(t);for(var n=0,r=this._head;r&&t(r.value,n++,this)!==!1;)r=r.next;return n},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new S(function(){if(r){var e=r.value;return r=r.next,w(t,n++,e)}return O()})},De.isStack=Ce;var Jn="@@__IMMUTABLE_STACK__@@",$n=De.prototype;$n[Jn]=!0,$n.withMutations=Yn.withMutations,$n.asMutable=Yn.asMutable,$n.asImmutable=Yn.asImmutable,$n.wasAltered=Yn.wasAltered;var Zn;t(Ae,K),Ae.of=function(){return this(arguments)},Ae.fromKeys=function(t){return this(h(t).keySeq())},Ae.prototype.toString=function(){return this.__toString("Set {","}")},Ae.prototype.has=function(t){return this._map.has(t)},Ae.prototype.add=function(t){return Le(this,this._map.set(t,!0))},Ae.prototype.remove=function(t){return Le(this,this._map.remove(t))},Ae.prototype.clear=function(){return Le(this,this._map.clear())},Ae.prototype.union=function(){var t=un.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:"")+" ]"},qe.prototype.get=function(t,e){return this.has(t)?this._start+a(this,t)*this._step:e},qe.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=e?new qe(0,0):new qe(this.get(t,this._end),this.get(e,this._end),this._step))},qe.prototype.indexOf=function(t){var e=t-this._start;if(e%this._step===0){var n=e/this._step;if(n>=0&&n=o;o++){if(t(i,o,this)===!1)return o+1;i+=e?-r:r}return o},qe.prototype.__iterator=function(t,e){var n=this.size-1,r=this._step,i=e?this._start+n*r:this._start,o=0;return new S(function(){var a=i;return i+=e?-r:r,o>n?O():w(t,o++,a)})},qe.prototype.equals=function(t){return t instanceof qe?this._start===t._start&&this._end===t._end&&this._step===t._step:We(this,t)};var ir;t(Ke,P),Ke.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Ke.prototype.get=function(t,e){return this.has(t)?this._value:e},Ke.prototype.includes=function(t){return J(this._value,t)},Ke.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Ke(this._value,l(e,n)-c(t,n))},Ke.prototype.reverse=function(){return this},Ke.prototype.indexOf=function(t){return J(this._value,t)?0:-1},Ke.prototype.lastIndexOf=function(t){return J(this._value,t)?this.size:-1},Ke.prototype.__iterate=function(t,e){for(var n=0;nt?this.count():this.size);var r=this.slice(0,t);return Ct(this,1===n?r:r.concat(i(arguments,2),this.slice(t+e)))},findLastIndex:function(t,e){var n=this.toKeyedSeq().findLastKey(t,e);return void 0===n?-1:n},first:function(){return this.get(0)},flatten:function(t){return Ct(this,wt(this,t,!1))},get:function(t,e){return t=a(this,t),0>t||this.size===1/0||void 0!==this.size&&t>this.size?e:this.find(function(e,n){return n===t},void 0,e)},has:function(t){return t=a(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.length;if(!t||2>e)return t||{};for(var n=1;e>n;n++)for(var r=arguments[n],i=Object.keys(r),o=i.length,a=0;o>a;a++){var u=i[a];t[u]=r[u]}return t},e.clone=function(t){return e.isObject(t)?e.isArray(t)?t.slice():e.extend({},t):t},e.each=function(t,e,n){var i,o,a=t?t.length:0,u=-1;if(n&&(o=e,e=function(t,e,r){return o.call(n,t,e,r)}),r(a))for(;++ur;r++)n[r]=arguments[r];return new(i.apply(t,[null].concat(n)))};return e.__proto__=t,e.prototype=t.prototype,e}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return c["default"].Iterable.isIterable(t)}function o(t){return i(t)||!(0,l.isObject)(t)}function a(t){return i(t)?t.toJS():t}function u(t){return i(t)?t:c["default"].fromJS(t)}Object.defineProperty(e,"__esModule",{value:!0}),e.isImmutable=i,e.isImmutableValue=o,e.toJS=a,e.toImmutable=u;var s=n(3),c=r(s),l=n(4)},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){for(var n=0;n0)){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=h.evaluate(t.prevReactorState,r),a=h.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=a.reactorState;var u=o.result,s=a.result;c["default"].is(u,s)||i.call(null,s)}});var r=h.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){"use strict";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(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}},t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return new A({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,P.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&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(l(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 O(t,[n])})}),w(t)})}function a(t,e){return t.withMutations(function(t){(0,P.each)(e,function(e,n){t.update("stores",function(t){return t.set(n,e)})})})}function u(t,e,n){if(void 0===e&&l(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){T["default"].dispatchStart(t,e,n),t.get("stores").forEach(function(o,a){var u=r.get(a),s=void 0;try{s=o.handle(u,e,n)}catch(c){throw T["default"].dispatchError(t,c.message),c}if(void 0===s&&l(t,"throwOnUndefinedStoreReturnValue")){var f="Store handler must return a value, did you forget a return statement";throw T["default"].dispatchError(t,f),new Error(f)}r.set(a,s),u!==s&&(i=i.add(a))}),T["default"].dispatchEnd(t,r,i)}),a=t.set("state",o).set("dirtyStores",i).update("storeStates",function(t){return O(t,i)});return w(a)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations(function(r){(0,P.each)(e,function(e,i){var o=t.getIn(["stores",i]);if(o){var a=o.deserialize(e);void 0!==a&&(r.set(i,a),n.push(i))}})}),i=I["default"].Set(n);return t.update("state",function(t){return t.merge(r)}).update("dirtyStores",function(t){return t.union(i)}).update("storeStates",function(t){return O(t,n)})}function c(t,e,n){var r=e;(0,j.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),a=I["default"].Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),u=void 0;return u=0===o.size?t.update("any",function(t){return t.add(i)}):t.withMutations(function(t){o.forEach(function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,I["default"].Set()),t.updateIn(["stores",e],function(t){return t.add(i)})})}),u=u.set("nextId",i+1).setIn(["observersMap",i],a),{observerState:u,entry:a}}function l(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function f(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,j.isKeyPath)(e)&&(0,j.isKeyPath)(r)?(0,j.isEqual)(e,r):e===r:!1});return t.withMutations(function(t){r.forEach(function(e){return d(t,e)})})}function d(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 h(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&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(l(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 O(t,r)}),v(t)})}function p(t,e){var n=t.get("state");if((0,j.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(S(t,e),t);var r=(0,C.getDeps)(e).map(function(e){return p(t,e).result}),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,b(t,e,o))}function _(t){var e={};return t.get("stores").forEach(function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)}),e}function v(t){return t.set("dirtyStores",I["default"].Set())}function y(t){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?!1:r.every(function(e,n){return t.getIn(["storeStates",n])===e})}function b(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),a=(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],I["default"].Map({value:n,storeStates:a,dispatchId:i}))}function S(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function w(t){return t.update("dispatchId",function(t){return t+1})}function O(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=a,e.dispatch=u,e.loadState=s,e.addObserver=c,e.getOption=l,e.removeObserver=f,e.removeObserverByEntry=d,e.reset=h,e.evaluate=p,e.serialize=_,e.resetDirtyStores=v;var M=n(3),I=r(M),E=n(9),T=r(E),D=n(5),C=n(10),j=n(11),P=n(4),A=I["default"].Record({result:null,reactorState:null})},function(t,e,n){"use strict";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){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,d.isArray)(t)&&(0,d.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function a(t){return t.slice(0,t.length-1)}function u(t,e){e||(e=f["default"].Set());var n=f["default"].Set().withMutations(function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");a(t).forEach(function(t){if((0,h.isKeyPath)(t))e.add((0,l.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(u(t))}})});return e.union(n)}function s(t){if(!(0,h.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,p]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=u(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 l=n(3),f=r(l),d=n(4),h=n(11),p=function(t){return t};e["default"]={isGetter:i,getComputeFn:o,getFlattenedDeps:u,getStoreDeps:c,getDeps:a,fromKeyPath:s},t.exports=e["default"]},function(t,e,n){"use strict";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=u["default"].List(t),r=u["default"].List(e);return u["default"].is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var a=n(3),u=r(a),s=n(4)},function(t,e,n){"use strict";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 a=(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=a;var u=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=u}])})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(127),u=r(a);e["default"]=(0,u["default"])(o["default"].reactor)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.callApi=void 0;var i=n(130),o=r(i);e.callApi=o["default"]},function(t,e){"use strict";var n=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};t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(80),n(37),e["default"]=new o["default"]({is:"state-info",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"partial-base",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1}},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(146),a=i(o),u=n(147),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){t.registerStores({restApiCache:l["default"]})}function o(t){return[["restApiCache",t.entity],function(t){return!!t}]}function a(t){return[["restApiCache",t.entity],function(t){return t||(0,s.toImmutable)({})}]}function u(t){return function(e){return["restApiCache",t.entity,e]}}Object.defineProperty(e,"__esModule",{value:!0}),e.createApiActions=void 0,e.register=i,e.createHasDataGetter=o,e.createEntityMapGetter=a,e.createByIdGetter=u;var s=n(3),c=n(172),l=r(c),f=n(171),d=r(f);e.createApiActions=d["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({ENTITY_HISTORY_DATE_SELECTED:null,ENTITY_HISTORY_FETCH_START:null,ENTITY_HISTORY_FETCH_ERROR:null,ENTITY_HISTORY_FETCH_SUCCESS:null,RECENT_ENTITY_HISTORY_FETCH_START:null,RECENT_ENTITY_HISTORY_FETCH_ERROR:null,RECENT_ENTITY_HISTORY_FETCH_SUCCESS:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({LOGBOOK_DATE_SELECTED:null,LOGBOOK_ENTRIES_FETCH_START:null,LOGBOOK_ENTRIES_FETCH_ERROR:null,LOGBOOK_ENTRIES_FETCH_SUCCESS:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(173),a=i(o),u=n(54),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({authAttempt:u["default"],authCurrent:c["default"],rememberAuth:f["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(133),u=i(a),s=n(134),c=i(s),l=n(135),f=i(l),d=n(131),h=r(d),p=n(132),_=r(p);e.actions=h,e.getters=_},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var u=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),s=function(){function t(t,e){for(var n=0;n4?"value big":"value"},computeHideIcon:function(t,e,n){return!t||e||n},computeHideValue:function(t,e){return!t||e},imageChanged:function(t){this.$.badge.style.backgroundImage=t?"url("+t+")":""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"loading-box"})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(128),u=r(a);n(119),n(39),n(120),n(121),n(123),n(122),n(124),n(125),n(126),e["default"]=new o["default"]({is:"state-card-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=(0,u["default"])(t);if(e&&(0,u["default"])(e)===r)n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("state-card-"+r);i.stateObj=t,n.appendChild(i)}}})},function(t,e){"use strict";function n(t,e){return t?e.map(function(e){return e in t.attributes?"has-"+e:""}).join(" "):""}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u.evaluate(s.canToggleEntity(t))}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(2),a=r(o),u=a["default"].reactor,s=a["default"].serviceGetters},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){switch(t){case"alarm_control_panel":return e&&"disarmed"===e?"mdi:bell-outline":"mdi:bell";case"automation":return"mdi:playlist-play";case"binary_sensor":return e&&"off"===e?"mdi:radiobox-blank":"mdi:checkbox-marked-circle";case"camera":return"mdi:video";case"configurator":return"mdi:settings";case"conversation":return"mdi:text-to-speech";case"device_tracker":return"mdi:account";case"garage_door":return"mdi:glassdoor";case"group":return"mdi:google-circles-communities";case"homeassistant":return"mdi:home";case"input_boolean":return"mdi:drawing";case"light":return"mdi:lightbulb";case"lock":return e&&"unlocked"===e?"mdi:lock-open":"mdi:lock";case"media_player":return e&&"off"!==e&&"idle"!==e?"mdi:cast-connected":"mdi:cast";case"notify":return"mdi:comment-alert";case"proximity":return"mdi:apple-safari";case"rollershutter":return e&&"open"===e?"mdi:window-open":"mdi:window-closed";case"scene":return"mdi:google-pages";case"script":return"mdi:file-document";case"sensor":return"mdi:eye";case"simple_alarm":return"mdi:bell";case"sun":return"mdi:white-balance-sunny";case"switch":return"mdi:flash";case"thermostat":return"mdi:nest-thermostat";case"updater":return"mdi:cloud-upload";case"weblink":return"mdi:open-in-new";default:return console.warn("Unable to find icon for domain "+t+" ("+e+")"),a["default"]}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(40),a=r(o)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({SERVER_CONFIG_LOADED:null,COMPONENT_LOADED:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({serverComponent:u["default"],serverConfig:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(138),u=i(a),s=n(139),c=i(s),l=n(136),f=r(l),d=n(137),h=r(d);e.actions=f,e.getters=h},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0;var o=n(150),a=i(o),u=n(151),s=r(u);e.actions=a["default"],e.getters=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({NAVIGATE:null,SHOW_SIDEBAR:null,LOG_OUT:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({notifications:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(168),u=i(a),s=n(166),c=r(s),l=n(167),f=r(l);e.actions=c,e.getters=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({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})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({streamStatus:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(180),u=i(a),s=n(176),c=r(s),l=n(177),f=r(l);e.actions=c,e.getters=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(6),o=r(i);e["default"]=(0,o["default"])({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null})},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({isFetchingData:u["default"],isSyncScheduled:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(182),u=i(a),s=n(183),c=i(s),l=n(181),f=r(l),d=n(57),h=r(d);e.actions=f,e.getters=h},function(t,e){"use strict";function n(t){return t.getFullYear()+"-"+(t.getMonth()+1)+"-"+t.getDate()}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],a=i.split(":"),u=r(a,3),s=u[0],c=u[1],l=u[2],f=o.split("-"),d=r(f,3),h=d[0],p=d[1],_=d[2];return new Date(Date.UTC(_,parseInt(p,10)-1,h,s,c,l))}var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(22),u=r(a);e["default"]=new o["default"]({is:"domain-icon",properties:{domain:{type:String,value:""},state:{type:String,value:""}},computeIcon:function(t,e){return(0,u["default"])(t,e)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"ha-entity-toggle",properties:{stateObj:{type:Object},toggleChecked:{type:Boolean,value:!1},isOn:{type:Boolean,computed:"computeIsOn(stateObj)",observer:"isOnChanged"}},ready:function(){this.forceStateChange()},toggleChanged:function(t){var e=t.target.checked;e&&!this.isOn?this._call_service(!0):!e&&this.isOn&&this._call_service(!1)},isOnChanged:function(t){this.toggleChecked=t},forceStateChange:function(){this.toggleChecked===this.isOn&&(this.toggleChecked=!this.toggleChecked),this.toggleChecked=this.isOn},turnOn:function(){this._call_service(!0)},turnOff:function(){this._call_service(!1)},computeIsOn:function(t){return t&&"off"!==t.state&&"unlocked"!==t.state&&"closed"!==t.state},_call_service:function(t){var e=this,n=void 0,r=void 0;"lock"===this.stateObj.domain?(n="lock",r=t?"lock":"unlock"):"garage_door"===this.stateObj.domain?(n="garage_door",r=t?"open":"close"):(n="homeassistant",r=t?"turn_on":"turn_off");var i=s.callService(n,r,{entity_id:this.stateObj.entityId});this.stateObj.attributes.assumed_state||i.then(function(){return e.forceStateChange()})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-card",properties:{header:{type:String},elevation:{type:Number,value:1,reflectToAttribute:!0}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(67),o=r(i),a=n(2),u=r(a),s=n(1),c=r(s),l=6e4,f=u["default"].util.parseDateTime;e["default"]=new c["default"]({is:"relative-ha-datetime",properties:{datetime:{type:String,observer:"datetimeChanged"},datetimeObj:{type:Object,observer:"datetimeObjChanged"},parsedDateTime:{type:Object},relativeTime:{type:String,value:"not set"}},created:function(){this.updateRelative=this.updateRelative.bind(this)},attached:function(){this._interval=setInterval(this.updateRelative,l)},detached:function(){clearInterval(this._interval)},datetimeChanged:function(t){this.parsedDateTime=t?f(t):null,this.updateRelative()},datetimeObjChanged:function(t){this.parsedDateTime=t,this.updateRelative()},updateRelative:function(){this.relativeTime=this.parsedDateTime?(0,o["default"])(this.parsedDateTime).fromNow():""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(18),n(90),n(89),e["default"]=new o["default"]({is:"state-history-charts",properties:{stateHistory:{type:Object},isLoadingData:{type:Boolean,value:!1},apiLoaded:{type:Boolean,value:!1},isLoading:{type:Boolean,computed:"computeIsLoading(isLoadingData, apiLoaded)"},groupedStateHistory:{type:Object,computed:"computeGroupedStateHistory(isLoading, stateHistory)"},isSingleDevice:{type:Boolean,computed:"computeIsSingleDevice(stateHistory)"}},computeIsSingleDevice:function(t){return t&&1===t.size},computeGroupedStateHistory:function(t,e){if(t||!e)return{line:[],timeline:[]};var n={},r=[];e.forEach(function(t){if(t&&0!==t.size){var e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=e?e.attributes.unit_of_measurement:!1;i?i in n?n[i].push(t.toArray()):n[i]=[t.toArray()]:r.push(t.toArray())}}),r=r.length>0&&r;var i=Object.keys(n).map(function(t){return[t,n[t]]});return{line:i,timeline:r}},googleApiLoaded:function(){var t=this;window.google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){return t.apiLoaded=!0}})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size},extractUnit:function(t){return t[0]},extractData:function(t){return t[1]}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-display",properties:{stateObj:{type:Object}}})},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e["default"]="bookmark"},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,a["default"])(t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(67),a=r(o)},function(t,e){"use strict";function n(){var t=document.getElementById("ha-init-skeleton");t&&t.parentElement.removeChild(t)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){var e=t.state&&"off"===t.state;switch(t.attributes.sensor_class){case"opening":return e?"mdi:crop-square":"mdi:exit-to-app";case"moisture":return e?"mdi:water-off":"mdi:water";case"safety":case"gas":case"smoke":case"power":return e?"mdi:verified":"mdi:alert";case"motion":return e?"mdi:walk":"mdi:run";case"digital":default:return e?"mdi:radiobox-blank":"mdi:checkbox-marked-circle"}}function o(t){if(!t)return u["default"];if(t.attributes.icon)return t.attributes.icon;var e=t.attributes.unit_of_measurement;if(e&&"sensor"===t.domain){if(e===d.UNIT_TEMP_C||e===d.UNIT_TEMP_F)return"mdi:thermometer";if("Mice"===e)return"mdi:mouse-variant"}else if("binary_sensor"===t.domain)return i(t); return(0,c["default"])(t.domain,t.state)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=o;var a=n(40),u=r(a),s=n(22),c=r(s),l=n(2),f=r(l),d=f["default"].util.temperatureUnits},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t,e){a.validate(t,{rememberAuth:e,useStreaming:u.useStreaming})};var i=n(2),o=r(i),a=o["default"].authActions,u=o["default"].localStoragePreferences},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.recentEntityHistoryUpdatedMap=e.recentEntityHistoryMap=e.hasDataForCurrentDate=e.entityHistoryForCurrentDate=e.entityHistoryMap=e.currentDate=e.isLoadingEntityHistory=void 0;var r=n(3),i=(e.isLoadingEntityHistory=["isLoadingEntityHistory"],e.currentDate=["currentEntityHistoryDate"]),o=e.entityHistoryMap=["entityHistory"];e.entityHistoryForCurrentDate=[i,o,function(t,e){return e.get(t)||(0,r.toImmutable)({})}],e.hasDataForCurrentDate=[i,o,function(t,e){return!!e.get(t)}],e.recentEntityHistoryMap=["recentEntityHistory"],e.recentEntityHistoryUpdatedMap=["recentEntityHistory"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentEntityHistoryDate:u["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:h["default"],recentEntityHistoryUpdated:_["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(141),u=i(a),s=n(142),c=i(s),l=n(143),f=i(l),d=n(144),h=i(d),p=n(145),_=i(p),v=n(140),y=r(v),m=n(45),g=r(m);e.actions=y,e.getters=g},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var a=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){function r(t,e,n){function r(){y&&clearTimeout(y),h&&clearTimeout(h),g=0,h=y=m=void 0}function s(e,n){n&&clearTimeout(n),h=y=m=void 0,e&&(g=o(),p=t.apply(v,d),y||h||(d=v=void 0))}function c(){var t=e-(o()-_);0>=t||t>e?s(m,h):y=setTimeout(c,t)}function l(){s(S,y)}function f(){if(d=arguments,_=o(),v=this,m=S&&(y||!w),b===!1)var n=w&&!y;else{h||w||(g=_);var r=b-(_-g),i=0>=r||r>b;i?(h&&(h=clearTimeout(h)),g=_,p=t.apply(v,d)):h||(h=setTimeout(l,r))}return i&&y?y=clearTimeout(y):y||e===b||(y=setTimeout(c,e)),n&&(i=!0,p=t.apply(v,d)),!i||y||h||(d=v=void 0),p}var d,h,p,_,v,y,m,g=0,b=!1,S=!0;if("function"!=typeof t)throw new TypeError(a);if(e=0>e?0:+e||0,n===!0){var w=!0;S=!1}else i(n)&&(w=!!n.leading,b="maxWait"in n&&u(+n.maxWait||0,e),S="trailing"in n?!!n.trailing:S);return f.cancel=r,f}var i=n(66),o=n(200),a="Expected a function",u=Math.max;t.exports=r},function(t,e,n){function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(203);t.exports=r},function(t,e){function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){function r(t){return i(t)&&u.call(t)==o}var i=n(66),o="[object Function]",a=Object.prototype,u=a.toString;t.exports=r},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){(function(t){!function(e,n){t.exports=n()}(this,function(){"use strict";function e(){return Kn.apply(null,arguments)}function n(t){Kn=t}function r(t){return"[object Array]"===Object.prototype.toString.call(t)}function i(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function o(t,e){var n,r=[];for(n=0;n0)for(n in $n)r=$n[n],i=e[r],h(i)||(t[r]=i);return t}function _(t){p(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),Zn===!1&&(Zn=!0,e.updateOffset(this),Zn=!1)}function v(t){return t instanceof _||null!=t&&null!=t._isAMomentObject}function y(t){return 0>t?Math.ceil(t):Math.floor(t)}function m(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=y(e)),n}function g(t,e,n){var r,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),a=0;for(r=0;i>r;r++)(n&&t[r]!==e[r]||!n&&m(t[r])!==m(e[r]))&&a++;return a+o}function b(){}function S(t){return t?t.toLowerCase().replace("_","-"):t}function w(t){for(var e,n,r,i,o=0;o0;){if(r=O(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&g(i,n,!0)>=e-1)break;e--}o++}return null}function O(e){var n=null;if(!Xn[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=Jn._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),M(n)}catch(r){}return Xn[e]}function M(t,e){var n;return t&&(n=h(e)?E(t):I(t,e),n&&(Jn=n)),Jn._abbr}function I(t,e){return null!==e?(e.abbr=t,Xn[t]=Xn[t]||new b,Xn[t].set(e),M(t),Xn[t]):(delete Xn[t],null)}function E(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Jn;if(!r(t)){if(e=O(t))return e;t=[t]}return w(t)}function T(t,e){var n=t.toLowerCase();Qn[n]=Qn[n+"s"]=Qn[e]=t}function D(t){return"string"==typeof t?Qn[t]||Qn[t.toLowerCase()]:void 0}function C(t){var e,n,r={};for(n in t)a(t,n)&&(e=D(n),e&&(r[e]=t[n]));return r}function j(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function P(t,n){return function(r){return null!=r?(k(this,t,r),e.updateOffset(this,n),this):A(this,t)}}function A(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function k(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function L(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=D(t),j(this[t]))return this[t](e);return this}function N(t,e,n){var r=""+Math.abs(t),i=e-r.length,o=t>=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+r}function R(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(rr[t]=i),e&&(rr[e[0]]=function(){return N(i.apply(this,arguments),e[1],e[2])}),n&&(rr[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function x(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function H(t){var e,n,r=t.match(tr);for(e=0,n=r.length;n>e;e++)rr[r[e]]?r[e]=rr[r[e]]:r[e]=x(r[e]);return function(i){var o="";for(e=0;n>e;e++)o+=r[e]instanceof Function?r[e].call(i,t):r[e];return o}}function Y(t,e){return t.isValid()?(e=z(e,t.localeData()),nr[e]=nr[e]||H(e),nr[e](t)):t.localeData().invalidDate()}function z(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(er.lastIndex=0;r>=0&&er.test(t);)t=t.replace(er,n),er.lastIndex=0,r-=1;return t}function U(t,e,n){Sr[t]=j(e)?e:function(t,r){return t&&n?n:e}}function V(t,e){return a(Sr,t)?Sr[t](e._strict,e._locale):new RegExp(F(t))}function F(t){return G(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}))}function G(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function B(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(r=function(t,n){n[e]=m(t)}),n=0;nr;r++){if(i=s([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(o="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[r]=new RegExp(o.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[r].test(t))return r;if(n&&"MMM"===e&&this._shortMonthsParse[r].test(t))return r;if(!n&&this._monthsParse[r].test(t))return r}}function X(t,e){var n;return t.isValid()?"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(n=Math.min(t.date(),K(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t):t}function Q(t){return null!=t?(X(this,t),e.updateOffset(this,!0),this):A(this,"Month")}function tt(){return K(this.year(),this.month())}function et(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function nt(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function rt(){function t(t,e){return e.length-t.length}var e,n,r=[],i=[],o=[];for(e=0;12>e;e++)n=s([2e3,e]),r.push(this.monthsShort(n,"")),i.push(this.months(n,"")),o.push(this.months(n,"")),o.push(this.monthsShort(n,""));for(r.sort(t),i.sort(t),o.sort(t),e=0;12>e;e++)r[e]=G(r[e]),i[e]=G(i[e]),o[e]=G(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+r.join("|")+")$","i")}function it(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[Mr]<0||n[Mr]>11?Mr:n[Ir]<1||n[Ir]>K(n[Or],n[Mr])?Ir:n[Er]<0||n[Er]>24||24===n[Er]&&(0!==n[Tr]||0!==n[Dr]||0!==n[Cr])?Er:n[Tr]<0||n[Tr]>59?Tr:n[Dr]<0||n[Dr]>59?Dr:n[Cr]<0||n[Cr]>999?Cr:-1,l(t)._overflowDayOfYear&&(Or>e||e>Ir)&&(e=Ir),l(t)._overflowWeeks&&-1===e&&(e=jr),l(t)._overflowWeekday&&-1===e&&(e=Pr),l(t).overflow=e),t}function ot(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function at(t,e){var n=!0;return u(function(){return n&&(ot(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function ut(t,e){xr[t]||(ot(e),xr[t]=!0)}function st(t){var e,n,r,i,o,a,u=t._i,s=Hr.exec(u)||Yr.exec(u);if(s){for(l(t).iso=!0,e=0,n=Ur.length;n>e;e++)if(Ur[e][1].exec(s[1])){i=Ur[e][0],r=Ur[e][2]!==!1;break}if(null==i)return void(t._isValid=!1);if(s[3]){for(e=0,n=Vr.length;n>e;e++)if(Vr[e][1].exec(s[3])){o=(s[2]||" ")+Vr[e][0];break}if(null==o)return void(t._isValid=!1)}if(!r&&null!=o)return void(t._isValid=!1);if(s[4]){if(!zr.exec(s[4]))return void(t._isValid=!1);a="Z"}t._f=i+(o||"")+(a||""),Ot(t)}else t._isValid=!1}function ct(t){var n=Fr.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(st(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function lt(t,e,n,r,i,o,a){var u=new Date(t,e,n,r,i,o,a);return 100>t&&t>=0&&isFinite(u.getFullYear())&&u.setFullYear(t),u}function ft(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function dt(t){return ht(t)?366:365}function ht(t){return t%4===0&&t%100!==0||t%400===0}function pt(){return ht(this.year())}function _t(t,e,n){var r=7+e-n,i=(7+ft(t,0,r).getUTCDay()-e)%7;return-i+r-1}function vt(t,e,n,r,i){var o,a,u=(7+n-r)%7,s=_t(t,r,i),c=1+7*(e-1)+u+s;return 0>=c?(o=t-1,a=dt(o)+c):c>dt(t)?(o=t+1,a=c-dt(t)):(o=t,a=c),{year:o,dayOfYear:a}}function yt(t,e,n){var r,i,o=_t(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>a?(i=t.year()-1,r=a+mt(i,e,n)):a>mt(t.year(),e,n)?(r=a-mt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function mt(t,e,n){var r=_t(t,e,n),i=_t(t+1,e,n);return(dt(t)-r+i)/7}function gt(t,e,n){return null!=t?t:null!=e?e:n}function bt(t){var n=new Date(e.now());return t._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function St(t){var e,n,r,i,o=[];if(!t._d){for(r=bt(t),t._w&&null==t._a[Ir]&&null==t._a[Mr]&&wt(t),t._dayOfYear&&(i=gt(t._a[Or],r[Or]),t._dayOfYear>dt(i)&&(l(t)._overflowDayOfYear=!0),n=ft(i,0,t._dayOfYear),t._a[Mr]=n.getUTCMonth(),t._a[Ir]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=r[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[Er]&&0===t._a[Tr]&&0===t._a[Dr]&&0===t._a[Cr]&&(t._nextDay=!0,t._a[Er]=0),t._d=(t._useUTC?ft:lt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Er]=24)}}function wt(t){var e,n,r,i,o,a,u,s;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,a=4,n=gt(e.GG,t._a[Or],yt(Pt(),1,4).year),r=gt(e.W,1),i=gt(e.E,1),(1>i||i>7)&&(s=!0)):(o=t._locale._week.dow,a=t._locale._week.doy,n=gt(e.gg,t._a[Or],yt(Pt(),o,a).year),r=gt(e.w,1),null!=e.d?(i=e.d,(0>i||i>6)&&(s=!0)):null!=e.e?(i=e.e+o,(e.e<0||e.e>6)&&(s=!0)):i=o),1>r||r>mt(n,o,a)?l(t)._overflowWeeks=!0:null!=s?l(t)._overflowWeekday=!0:(u=vt(n,r,i,o,a),t._a[Or]=u.year,t._dayOfYear=u.dayOfYear)}function Ot(t){if(t._f===e.ISO_8601)return void st(t);t._a=[],l(t).empty=!0;var n,r,i,o,a,u=""+t._i,s=u.length,c=0;for(i=z(t._f,t._locale).match(tr)||[],n=0;n0&&l(t).unusedInput.push(a),u=u.slice(u.indexOf(r)+r.length),c+=r.length),rr[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),q(o,r,t)):t._strict&&!r&&l(t).unusedTokens.push(o);l(t).charsLeftOver=s-c,u.length>0&&l(t).unusedInput.push(u),l(t).bigHour===!0&&t._a[Er]<=12&&t._a[Er]>0&&(l(t).bigHour=void 0),t._a[Er]=Mt(t._locale,t._a[Er],t._meridiem),St(t),it(t)}function Mt(t,e,n){var r;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(r=t.isPM(n),r&&12>e&&(e+=12),r||12!==e||(e=0),e):e}function It(t){var e,n,r,i,o;if(0===t._f.length)return l(t).invalidFormat=!0,void(t._d=new Date(NaN));for(i=0;io)&&(r=o,n=e));u(t,n||e)}function Et(t){if(!t._d){var e=C(t._i);t._a=o([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),St(t)}}function Tt(t){var e=new _(it(Dt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Dt(t){var e=t._i,n=t._f;return t._locale=t._locale||E(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new _(it(e)):(r(n)?It(t):n?Ot(t):i(e)?t._d=e:Ct(t),f(t)||(t._d=null),t))}function Ct(t){var n=t._i;void 0===n?t._d=new Date(e.now()):i(n)?t._d=new Date(+n):"string"==typeof n?ct(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),St(t)):"object"==typeof n?Et(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function jt(t,e,n,r,i){var o={};return"boolean"==typeof n&&(r=n,n=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=i,o._l=n,o._i=t,o._f=e,o._strict=r,Tt(o)}function Pt(t,e,n,r){return jt(t,e,n,r,!1)}function At(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Pt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+N(~~(t/60),2)+e+N(~~t%60,2)})}function Ht(t,e){var n=(e||"").match(t)||[],r=n[n.length-1]||[],i=(r+"").match(Kr)||["-",0,0],o=+(60*i[1])+m(i[2]);return"+"===i[0]?o:-o}function Yt(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Pt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Pt(t).local()}function zt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Ut(t,n){var r,i=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=Ht(mr,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=zt(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?re(this,Xt(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:zt(this):null!=t?this:NaN}function Vt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Ft(t){return this.utcOffset(0,t)}function Gt(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(zt(this),"m")),this}function Bt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ht(yr,this._i)),this}function Wt(t){return this.isValid()?(t=t?Pt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function qt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kt(){if(!h(this._isDSTShifted))return this._isDSTShifted;var t={};if(p(t,this),t=Dt(t),t._a){var e=t._isUTC?s(t._a):Pt(t._a);this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Jt(){return this.isValid()?!this._isUTC:!1}function $t(){return this.isValid()?this._isUTC:!1}function Zt(){return this.isValid()?this._isUTC&&0===this._offset:!1}function Xt(t,e){var n,r,i,o=t,u=null;return Rt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(u=Jr.exec(t))?(n="-"===u[1]?-1:1,o={y:0,d:m(u[Ir])*n,h:m(u[Er])*n,m:m(u[Tr])*n,s:m(u[Dr])*n,ms:m(u[Cr])*n}):(u=$r.exec(t))?(n="-"===u[1]?-1:1,o={y:Qt(u[2],n),M:Qt(u[3],n),d:Qt(u[4],n),h:Qt(u[5],n),m:Qt(u[6],n),s:Qt(u[7],n),w:Qt(u[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=ee(Pt(o.from),Pt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new Nt(o),Rt(t)&&a(t,"_locale")&&(r._locale=t._locale),r}function Qt(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function te(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function ee(t,e){var n;return t.isValid()&&e.isValid()?(e=Yt(e,t),t.isBefore(e)?n=te(t,e):(n=te(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function ne(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(ut(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=n,n=r,r=o),n="string"==typeof n?+n:n,i=Xt(n,r),re(this,i,t),this}}function re(t,n,r,i){var o=n._milliseconds,a=n._days,u=n._months;t.isValid()&&(i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),a&&k(t,"Date",A(t,"Date")+a*r),u&&X(t,A(t,"Month")+u*r),i&&e.updateOffset(t,a||u))}function ie(t,e){var n=t||Pt(),r=Yt(n,this).startOf("day"),i=this.diff(r,"days",!0),o=-6>i?"sameElse":-1>i?"lastWeek":0>i?"lastDay":1>i?"sameDay":2>i?"nextDay":7>i?"nextWeek":"sameElse",a=e&&(j(e[o])?e[o]():e[o]);return this.format(a||this.localeData().calendar(o,this,Pt(n)))}function oe(){return new _(this)}function ae(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e)):!1}function ue(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n):!1}function se(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function ce(t,e){var n,r=v(t)?t:Pt(t);return this.isValid()&&r.isValid()?(e=D(e||"millisecond"),"millisecond"===e?+this===+r:(n=+r,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e))):!1}function le(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function fe(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function de(t,e,n){var r,i,o,a;return this.isValid()?(r=Yt(t,this),r.isValid()?(i=6e4*(r.utcOffset()-this.utcOffset()),e=D(e),"year"===e||"month"===e||"quarter"===e?(a=he(this,r),"quarter"===e?a/=3:"year"===e&&(a/=12)):(o=this-r,a="second"===e?o/1e3:"minute"===e?o/6e4:"hour"===e?o/36e5:"day"===e?(o-i)/864e5:"week"===e?(o-i)/6048e5:o),n?a:y(a)):NaN):NaN}function he(t,e){var n,r,i=12*(e.year()-t.year())+(e.month()-t.month()),o=t.clone().add(i,"months");return 0>e-o?(n=t.clone().add(i-1,"months"),r=(e-o)/(o-n)):(n=t.clone().add(i+1,"months"),r=(e-o)/(n-o)),-(i+r)}function pe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function _e(){var t=this.clone().utc();return 0o&&(e=o),Ue.call(this,t,e,n,r,i))}function Ue(t,e,n,r,i){var o=vt(t,e,n,r,i),a=ft(o.year,0,o.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ve(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Fe(t){return yt(t,this._week.dow,this._week.doy).week}function Ge(){return this._week.dow}function Be(){return this._week.doy}function We(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function qe(t){var e=yt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function Ke(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Je(t,e){return r(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]; -}function $e(t){return this._weekdaysShort[t.day()]}function Ze(t){return this._weekdaysMin[t.day()]}function Xe(t,e,n){var r,i,o;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;7>r;r++){if(i=Pt([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(i,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(i,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(i,"").replace(".",".?")+"$","i")),this._weekdaysParse[r]||(o="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[r].test(t))return r;if(n&&"ddd"===e&&this._shortWeekdaysParse[r].test(t))return r;if(n&&"dd"===e&&this._minWeekdaysParse[r].test(t))return r;if(!n&&this._weekdaysParse[r].test(t))return r}}function Qe(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Ke(t,this.localeData()),this.add(t-e,"d")):e}function tn(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function en(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function nn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function rn(){return this.hours()%12||12}function on(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function an(t,e){return e._meridiemParse}function un(t){return"p"===(t+"").toLowerCase().charAt(0)}function sn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function cn(t,e){e[Cr]=m(1e3*("0."+t))}function ln(){return this._isUTC?"UTC":""}function fn(){return this._isUTC?"Coordinated Universal Time":""}function dn(t){return Pt(1e3*t)}function hn(){return Pt.apply(null,arguments).parseZone()}function pn(t,e,n){var r=this._calendar[t];return j(r)?r.call(e,n):r}function _n(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function vn(){return this._invalidDate}function yn(t){return this._ordinal.replace("%d",t)}function mn(t){return t}function gn(t,e,n,r){var i=this._relativeTime[n];return j(i)?i(t,e,n,r):i.replace(/%d/i,t)}function bn(t,e){var n=this._relativeTime[t>0?"future":"past"];return j(n)?n(e):n.replace(/%s/i,e)}function Sn(t){var e,n;for(n in t)e=t[n],j(e)?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function wn(t,e,n,r){var i=E(),o=s().set(r,e);return i[n](o,t)}function On(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return wn(t,e,n,i);var o,a=[];for(o=0;r>o;o++)a[o]=wn(t,o,n,i);return a}function Mn(t,e){return On(t,e,"months",12,"month")}function In(t,e){return On(t,e,"monthsShort",12,"month")}function En(t,e){return On(t,e,"weekdays",7,"day")}function Tn(t,e){return On(t,e,"weekdaysShort",7,"day")}function Dn(t,e){return On(t,e,"weekdaysMin",7,"day")}function Cn(){var t=this._data;return this._milliseconds=bi(this._milliseconds),this._days=bi(this._days),this._months=bi(this._months),t.milliseconds=bi(t.milliseconds),t.seconds=bi(t.seconds),t.minutes=bi(t.minutes),t.hours=bi(t.hours),t.months=bi(t.months),t.years=bi(t.years),this}function jn(t,e,n,r){var i=Xt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function Pn(t,e){return jn(this,t,e,1)}function An(t,e){return jn(this,t,e,-1)}function kn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,r,i,o=this._milliseconds,a=this._days,u=this._months,s=this._data;return o>=0&&a>=0&&u>=0||0>=o&&0>=a&&0>=u||(o+=864e5*kn(Rn(u)+a),a=0,u=0),s.milliseconds=o%1e3,t=y(o/1e3),s.seconds=t%60,e=y(t/60),s.minutes=e%60,n=y(e/60),s.hours=n%24,a+=y(n/24),i=y(Nn(a)),u+=i,a-=kn(Rn(i)),r=y(u/12),u%=12,s.days=a,s.months=u,s.years=r,this}function Nn(t){return 4800*t/146097}function Rn(t){return 146097*t/4800}function xn(t){var e,n,r=this._milliseconds;if(t=D(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+Nn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Rn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}}function Hn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12)}function Yn(t){return function(){return this.as(t)}}function zn(t){return t=D(t),this[t+"s"]()}function Un(t){return function(){return this._data[t]}}function Vn(){return y(this.days()/7)}function Fn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Gn(t,e,n){var r=Xt(t).abs(),i=Ri(r.as("s")),o=Ri(r.as("m")),a=Ri(r.as("h")),u=Ri(r.as("d")),s=Ri(r.as("M")),c=Ri(r.as("y")),l=i=o&&["m"]||o=a&&["h"]||a=u&&["d"]||u=s&&["M"]||s=c&&["y"]||["yy",c];return l[2]=e,l[3]=+t>0,l[4]=n,Fn.apply(null,l)}function Bn(t,e){return void 0===xi[t]?!1:void 0===e?xi[t]:(xi[t]=e,!0)}function Wn(t){var e=this.localeData(),n=Gn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function qn(){var t,e,n,r=Hi(this._milliseconds)/1e3,i=Hi(this._days),o=Hi(this._months);t=y(r/60),e=y(t/60),r%=60,t%=60,n=y(o/12),o%=12;var a=n,u=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(a?a+"Y":"")+(u?u+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Kn,Jn,$n=e.momentProperties=[],Zn=!1,Xn={},Qn={},tr=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,er=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,nr={},rr={},ir=/\d/,or=/\d\d/,ar=/\d{3}/,ur=/\d{4}/,sr=/[+-]?\d{6}/,cr=/\d\d?/,lr=/\d\d\d\d?/,fr=/\d\d\d\d\d\d?/,dr=/\d{1,3}/,hr=/\d{1,4}/,pr=/[+-]?\d{1,6}/,_r=/\d+/,vr=/[+-]?\d+/,yr=/Z|[+-]\d\d:?\d\d/gi,mr=/Z|[+-]\d\d(?::?\d\d)?/gi,gr=/[+-]?\d+(\.\d{1,3})?/,br=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Sr={},wr={},Or=0,Mr=1,Ir=2,Er=3,Tr=4,Dr=5,Cr=6,jr=7,Pr=8;R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),T("month","M"),U("M",cr),U("MM",cr,or),U("MMM",function(t,e){return e.monthsShortRegex(t)}),U("MMMM",function(t,e){return e.monthsRegex(t)}),B(["M","MM"],function(t,e){e[Mr]=m(t)-1}),B(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[Mr]=i:l(n).invalidMonth=t});var Ar=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,kr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Lr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Nr=br,Rr=br,xr={};e.suppressDeprecationWarnings=!1;var Hr=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Yr=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,zr=/Z|[+-]\d\d(?::?\d\d)?/,Ur=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Vr=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Fr=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=at("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),R("Y",0,0,function(){var t=this.year();return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),T("year","y"),U("Y",vr),U("YY",cr,or),U("YYYY",hr,ur),U("YYYYY",pr,sr),U("YYYYYY",pr,sr),B(["YYYYY","YYYYYY"],Or),B("YYYY",function(t,n){n[Or]=2===t.length?e.parseTwoDigitYear(t):m(t)}),B("YY",function(t,n){n[Or]=e.parseTwoDigitYear(t)}),B("Y",function(t,e){e[Or]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return m(t)+(m(t)>68?1900:2e3)};var Gr=P("FullYear",!1);e.ISO_8601=function(){};var Br=at("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:d()}),Wr=at("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:d()}),qr=function(){return Date.now?Date.now():+new Date};xt("Z",":"),xt("ZZ",""),U("Z",mr),U("ZZ",mr),B(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Ht(mr,t)});var Kr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Jr=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,$r=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Xt.fn=Nt.prototype;var Zr=ne(1,"add"),Xr=ne(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Qr=at("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),T("weekYear","gg"),T("isoWeekYear","GG"),U("G",vr),U("g",vr),U("GG",cr,or),U("gg",cr,or),U("GGGG",hr,ur),U("gggg",hr,ur),U("GGGGG",pr,sr),U("ggggg",pr,sr),W(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=m(t)}),W(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),R("Q",0,"Qo","quarter"),T("quarter","Q"),U("Q",ir),B("Q",function(t,e){e[Mr]=3*(m(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),T("week","w"),T("isoWeek","W"),U("w",cr),U("ww",cr,or),U("W",cr),U("WW",cr,or),W(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=m(t)});var ti={dow:0,doy:6};R("D",["DD",2],"Do","date"),T("date","D"),U("D",cr),U("DD",cr,or),U("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),B(["D","DD"],Ir),B("Do",function(t,e){e[Ir]=m(t.match(cr)[0],10)});var ei=P("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),T("day","d"),T("weekday","e"),T("isoWeekday","E"),U("d",cr),U("e",cr),U("E",cr),U("dd",br),U("ddd",br),U("dddd",br),W(["dd","ddd","dddd"],function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:l(n).invalidWeekday=t}),W(["d","e","E"],function(t,e,n,r){e[r]=m(t)});var ni="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ri="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ii="Su_Mo_Tu_We_Th_Fr_Sa".split("_");R("DDD",["DDDD",3],"DDDo","dayOfYear"),T("dayOfYear","DDD"),U("DDD",dr),U("DDDD",ar),B(["DDD","DDDD"],function(t,e,n){n._dayOfYear=m(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,rn),R("hmm",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)}),R("hmmss",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),on("a",!0),on("A",!1),T("hour","h"),U("a",an),U("A",an),U("H",cr),U("h",cr),U("HH",cr,or),U("hh",cr,or),U("hmm",lr),U("hmmss",fr),U("Hmm",lr),U("Hmmss",fr),B(["H","HH"],Er),B(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),B(["h","hh"],function(t,e,n){e[Er]=m(t),l(n).bigHour=!0}),B("hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r)),l(n).bigHour=!0}),B("hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i)),l(n).bigHour=!0}),B("Hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r))}),B("Hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i))});var oi=/[ap]\.?m?\.?/i,ai=P("Hours",!0);R("m",["mm",2],0,"minute"),T("minute","m"),U("m",cr),U("mm",cr,or),B(["m","mm"],Tr);var ui=P("Minutes",!1);R("s",["ss",2],0,"second"),T("second","s"),U("s",cr),U("ss",cr,or),B(["s","ss"],Dr);var si=P("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),T("millisecond","ms"),U("S",dr,ir),U("SS",dr,or),U("SSS",dr,ar);var ci;for(ci="SSSS";ci.length<=9;ci+="S")U(ci,_r);for(ci="S";ci.length<=9;ci+="S")B(ci,cn);var li=P("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var fi=_.prototype;fi.add=Zr,fi.calendar=ie,fi.clone=oe,fi.diff=de,fi.endOf=Me,fi.format=ve,fi.from=ye,fi.fromNow=me,fi.to=ge,fi.toNow=be,fi.get=L,fi.invalidAt=ke,fi.isAfter=ae,fi.isBefore=ue,fi.isBetween=se,fi.isSame=ce,fi.isSameOrAfter=le,fi.isSameOrBefore=fe,fi.isValid=Pe,fi.lang=Qr,fi.locale=Se,fi.localeData=we,fi.max=Wr,fi.min=Br,fi.parsingFlags=Ae,fi.set=L,fi.startOf=Oe,fi.subtract=Xr,fi.toArray=De,fi.toObject=Ce,fi.toDate=Te,fi.toISOString=_e,fi.toJSON=je,fi.toString=pe,fi.unix=Ee,fi.valueOf=Ie,fi.creationData=Le,fi.year=Gr,fi.isLeapYear=pt,fi.weekYear=Re,fi.isoWeekYear=xe,fi.quarter=fi.quarters=Ve,fi.month=Q,fi.daysInMonth=tt,fi.week=fi.weeks=We,fi.isoWeek=fi.isoWeeks=qe,fi.weeksInYear=Ye,fi.isoWeeksInYear=He,fi.date=ei,fi.day=fi.days=Qe,fi.weekday=tn,fi.isoWeekday=en,fi.dayOfYear=nn,fi.hour=fi.hours=ai,fi.minute=fi.minutes=ui,fi.second=fi.seconds=si,fi.millisecond=fi.milliseconds=li,fi.utcOffset=Ut,fi.utc=Ft,fi.local=Gt,fi.parseZone=Bt,fi.hasAlignedHourOffset=Wt,fi.isDST=qt,fi.isDSTShifted=Kt,fi.isLocal=Jt,fi.isUtcOffset=$t,fi.isUtc=Zt,fi.isUTC=Zt,fi.zoneAbbr=ln,fi.zoneName=fn,fi.dates=at("dates accessor is deprecated. Use date instead.",ei),fi.months=at("months accessor is deprecated. Use month instead",Q),fi.years=at("years accessor is deprecated. Use year instead",Gr),fi.zone=at("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Vt);var di=fi,hi={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},pi={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},_i="Invalid date",vi="%d",yi=/\d{1,2}/,mi={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},gi=b.prototype;gi._calendar=hi,gi.calendar=pn,gi._longDateFormat=pi,gi.longDateFormat=_n,gi._invalidDate=_i,gi.invalidDate=vn,gi._ordinal=vi,gi.ordinal=yn,gi._ordinalParse=yi,gi.preparse=mn,gi.postformat=mn,gi._relativeTime=mi,gi.relativeTime=gn,gi.pastFuture=bn,gi.set=Sn,gi.months=J,gi._months=kr,gi.monthsShort=$,gi._monthsShort=Lr,gi.monthsParse=Z,gi._monthsRegex=Rr,gi.monthsRegex=nt,gi._monthsShortRegex=Nr,gi.monthsShortRegex=et,gi.week=Fe,gi._week=ti,gi.firstDayOfYear=Be,gi.firstDayOfWeek=Ge,gi.weekdays=Je,gi._weekdays=ni,gi.weekdaysMin=Ze,gi._weekdaysMin=ii,gi.weekdaysShort=$e,gi._weekdaysShort=ri,gi.weekdaysParse=Xe,gi.isPM=un,gi._meridiemParse=oi,gi.meridiem=sn,M("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===m(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=at("moment.lang is deprecated. Use moment.locale instead.",M),e.langData=at("moment.langData is deprecated. Use moment.localeData instead.",E);var bi=Math.abs,Si=Yn("ms"),wi=Yn("s"),Oi=Yn("m"),Mi=Yn("h"),Ii=Yn("d"),Ei=Yn("w"),Ti=Yn("M"),Di=Yn("y"),Ci=Un("milliseconds"),ji=Un("seconds"),Pi=Un("minutes"),Ai=Un("hours"),ki=Un("days"),Li=Un("months"),Ni=Un("years"),Ri=Math.round,xi={s:45,m:45,h:22,d:26,M:11},Hi=Math.abs,Yi=Nt.prototype;Yi.abs=Cn,Yi.add=Pn,Yi.subtract=An,Yi.as=xn,Yi.asMilliseconds=Si,Yi.asSeconds=wi,Yi.asMinutes=Oi,Yi.asHours=Mi,Yi.asDays=Ii,Yi.asWeeks=Ei,Yi.asMonths=Ti,Yi.asYears=Di,Yi.valueOf=Hn,Yi._bubble=Ln,Yi.get=zn,Yi.milliseconds=Ci,Yi.seconds=ji,Yi.minutes=Pi,Yi.hours=Ai,Yi.days=ki,Yi.weeks=Vn,Yi.months=Li,Yi.years=Ni,Yi.humanize=Wn,Yi.toISOString=qn,Yi.toString=qn,Yi.toJSON=qn,Yi.locale=Se,Yi.localeData=we,Yi.toIsoString=at("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",qn),Yi.lang=Qr,R("X",0,0,"unix"),R("x",0,0,"valueOf"),U("x",vr),U("X",gr),B("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),B("x",function(t,e,n){n._d=new Date(m(t))}),e.version="2.11.2",n(Pt),e.fn=di,e.min=kt,e.max=Lt,e.now=qr,e.utc=s,e.unix=dn,e.months=Mn,e.isDate=i,e.locale=M,e.invalid=d,e.duration=Xt,e.isMoment=v,e.weekdays=En,e.parseZone=hn,e.localeData=E,e.isDuration=Rt,e.monthsShort=In,e.weekdaysMin=Dn,e.defineLocale=I,e.weekdaysShort=Tn,e.normalizeUnits=D,e.relativeTimeThreshold=Bn,e.prototype=di;var zi=e;return zi})}).call(e,n(68)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var a=n(169),u=n(194),s=i(u),c=n(196),l=i(c),f=n(198),d=i(f),h=n(15),p=r(h),_=n(24),v=r(_),y=n(9),m=r(y),g=n(46),b=r(g),S=n(149),w=r(S),O=n(25),M=r(O),I=n(154),E=r(I),T=n(49),D=r(T),C=n(52),j=r(C),P=n(27),A=r(P),k=n(59),L=r(k),N=n(13),R=r(N),x=n(29),H=r(x),Y=n(31),z=r(Y),U=n(185),V=r(U),F=n(191),G=r(F),B=n(10),W=r(B),q=function K(){o(this,K);var t=(0,s["default"])();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:a.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:a.localStoragePreferences.startSync.bind(a.localStoragePreferences,t)},startUrlSync:{value:j.urlSync.startSync.bind(null,t)},stopUrlSync:{value:j.urlSync.stopSync.bind(null,t)}}),(0,l["default"])(this,t,{auth:p,config:v,entity:m,entityHistory:b,errorLog:w,event:M,logbook:E,moreInfo:D,navigation:j,notification:A,view:L,service:R,stream:H,sync:z,template:V,voice:G,restApi:W})};e["default"]=q},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(79),e["default"]=new o["default"]({is:"ha-badges-card",properties:{states:{type:Array}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=u["default"].moreInfoActions,c=1e4;e["default"]=new o["default"]({is:"ha-camera-card",properties:{stateObj:{type:Object,observer:"updateCameraFeedSrc"},cameraFeedSrc:{type:String},imageLoaded:{type:Boolean,value:!0},elevation:{type:Number,value:1,reflectToAttribute:!0}},listeners:{tap:"cardTapped"},attached:function(){var t=this;this.timer=setInterval(function(){return t.updateCameraFeedSrc(t.stateObj)},c)},detached:function(){clearInterval(this.timer)},cardTapped:function(){var t=this;this.async(function(){return s.selectEntity(t.stateObj.entityId)},1)},updateCameraFeedSrc:function(t){var e=(new Date).getTime();this.cameraFeedSrc=t.attributes.entity_picture+"?time="+e},imageLoadSuccess:function(){this.imageLoaded=!0},imageLoadFail:function(){this.imageLoaded=!1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(71),n(73),n(74),e["default"]=new o["default"]({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=!e||e.cardType!==t.cardType,i=void 0;r?(n.lastChild&&n.removeChild(n.lastChild),i=document.createElement("ha-"+t.cardType+"-card")):i=n.lastChild,Object.keys(t).forEach(function(e){return i[e]=t[e]}),e&&Object.keys(e).forEach(function(e){e in t||(i[e]=void 0)}),r&&n.appendChild(i)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(21),c=r(s);n(36),n(35),n(19);var l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entities-card",properties:{states:{type:Array},groupEntity:{type:Object}},computeTitle:function(t,e){return e?e.entityDisplay:t[0].domain.replace(/_/g," ")},entityTapped:function(t){if(!t.target.classList.contains("paper-toggle-button")&&!t.target.classList.contains("paper-icon-button")){t.stopPropagation();var e=t.model.item.entityId;this.async(function(){return l.selectEntity(e)},1)}},showGroupToggle:function(t,e){return!t||!e||"on"!==t.state&&"off"!==t.state?!1:e.reduce(function(t,e){return t+(0,c["default"])(e.entityId)})>1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(36),e["default"]=new o["default"]({is:"ha-introduction-card",properties:{showInstallInstruction:{type:Boolean,value:!1},showHideInstruction:{type:Boolean,value:!0}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(41),u=r(a);e["default"]=new o["default"]({is:"display-time",properties:{dateObj:{type:Object}},computeTime:function(t){return t?(0,u["default"])(t):""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].entityGetters;e["default"]=new u["default"]({is:"entity-list",behaviors:[c["default"]],properties:{entities:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.entityId}).toArray()}]}},entitySelected:function(t){t.preventDefault(),this.fire("entity-selected",{entityId:t.model.entity.entityId})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a);n(17);var s=u["default"].reactor,c=u["default"].entityGetters,l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entity-marker",properties:{entityId:{type:String,value:""},state:{type:Object,computed:"computeState(entityId)"},icon:{type:Object,computed:"computeIcon(state)"},image:{type:Object,computed:"computeImage(state)"},value:{type:String,computed:"computeValue(state)"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;t.stopPropagation(),this.entityId&&this.async(function(){return l.selectEntity(e.entityId)},1)},computeState:function(t){return t&&s.evaluate(c.byId(t))},computeIcon:function(t){return!t&&"home"},computeImage:function(t){return t&&t.attributes.entity_picture},computeValue:function(t){return t&&t.entityDisplay.split(" ").map(function(t){return t.substr(0,1)}).join("")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(43),u=r(a);e["default"]=new o["default"]({is:"ha-state-icon",properties:{stateObj:{type:Object}},computeIcon:function(t){return(0,u["default"])(t)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(22),c=r(s),l=n(21),f=r(l),d=n(43),h=r(d);n(17);var p=u["default"].moreInfoActions,_=u["default"].serviceActions;e["default"]=new o["default"]({is:"ha-state-label-badge",properties:{state:{type:Object,observer:"stateChanged"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;return t.stopPropagation(),(0,f["default"])(this.state.entityId)?void("scene"===this.state.domain||"off"===this.state.state?_.callTurnOn(this.state.entityId):_.callTurnOff(this.state.entityId)):void this.async(function(){return p.selectEntity(e.state.entityId)},1)},computeClasses:function(t){switch(t.domain){case"scene":return"green";case"binary_sensor":case"script":return"on"===t.state?"blue":"grey";case"updater":return"blue";default:return""}},computeValue:function(t){switch(t.domain){case"binary_sensor":case"device_tracker":case"updater":case"sun":case"scene":case"script":case"alarm_control_panel":return null;case"sensor":default:return"unknown"===t.state?"-":t.state}},computeIcon:function(t){switch(t.domain){case"alarm_control_panel":return"pending"===t.state?"mdi:clock-fast":"armed_away"===t.state?"mdi:nature":"armed_home"===t.state?"mdi:home-variant":(0,c["default"])(t.domain,t.state);case"binary_sensor":case"device_tracker":case"scene":case"updater":case"script":return(0,h["default"])(t);case"sun":return"above_horizon"===t.state?(0,c["default"])(t.domain):"mdi:brightness-3";default:return null}},computeImage:function(t){return t.attributes.entity_picture||null},computeLabel:function(t){switch(t.domain){case"scene":case"script":return t.domain;case"device_tracker":return"not_home"===t.state?"Away":t.state;case"alarm_control_panel":return"pending"===t.state?"pend":"armed_away"===t.state||"armed_home"===t.state?"armed":"disarm";default:return t.attributes.unit_of_measurement||null}},computeDescription:function(t){return t.entityDisplay},stateChanged:function(){this.updateStyles()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(78),e["default"]=new o["default"]({is:"state-badge",properties:{stateObj:{type:Object,observer:"updateIconColor"}},updateIconColor:function(t){return t.attributes.entity_picture?(this.style.backgroundImage="url("+t.attributes.entity_picture+")",void(this.$.icon.style.display="none")):(this.style.backgroundImage="",this.$.icon.style.display="inline",void("light"===t.domain&&"on"===t.state&&t.attributes.rgb_color&&t.attributes.rgb_color.reduce(function(t,e){return t+e},0)<730?this.$.icon.style.color="rgb("+t.attributes.rgb_color.join(",")+")":this.$.icon.style.color=null))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].eventGetters;e["default"]=new u["default"]({is:"events-list",behaviors:[c["default"]],properties:{events:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.event}).toArray()}]}},eventSelected:function(t){t.preventDefault(),this.fire("event-selected",{eventType:t.model.event.event})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t in d?d[t]:30}function o(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(1),u=r(a),s=n(2),c=r(s);n(84),n(70),n(72);var l=c["default"].util,f=["camera"],d={configurator:-20,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,sensor:5,binary_sensor:6,scene:7,script:8};e["default"]=new u["default"]({is:"ha-cards",properties:{showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},cards:{type:Object,computed:"computeCards(columns, states, showIntroduction)"}},computeCards:function(t,e,n){function r(t){return t.filter(function(t){return!(t.entityId in c)})}function a(){var e=p;return p=(p+1)%t,e}function u(t,e){var n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2];if(0!==e.length){var r=[],i=[];e.forEach(function(t){-1===f.indexOf(t.domain)?i.push(t):r.push(t)});var o=a();i.length>0&&(d._columns[o].push(t),d[t]={cardType:"entities",states:i,groupEntity:n}),r.forEach(function(t){d._columns[o].push(t.entityId),d[t.entityId]={cardType:t.domain,stateObj:t}})}}for(var s=e.groupBy(function(t){return t.domain}),c={},d={_demo:!1,_badges:[],_columns:[]},h=0;t>h;h++)d._columns[h]=[];var p=0;return n&&(d._columns[a()].push("ha-introduction"),d["ha-introduction"]={cardType:"introduction",showHideInstruction:e.size>0&&!0}),s.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){if("a"===t)return void(d._demo=!0);var n=i(t);n>=0&&10>n?d._badges.push.apply(d._badges,r(s.get(t)).sortBy(o).toArray()):"group"===t?s.get(t).sortBy(o).forEach(function(t){var n=l.expandGroup(t,e);n.forEach(function(t){return c[t.entityId]=!0}),u(t.entityId,n.toArray(),t)}):u(t,r(s.get(t)).sortBy(o).toArray())}),d},computeCardDataOfCard:function(t,e){return t[e]}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-color-picker",properties:{color:{type:Object},width:{type:Number},height:{type:Number}},listeners:{mousedown:"onMouseDown",mouseup:"onMouseUp",touchstart:"onTouchStart",touchend:"onTouchEnd"},onMouseDown:function(t){this.onMouseMove(t),this.addEventListener("mousemove",this.onMouseMove)},onMouseUp:function(){this.removeEventListener("mousemove",this.onMouseMove)},onTouchStart:function(t){this.onTouchMove(t),this.addEventListener("touchmove",this.onTouchMove)},onTouchEnd:function(){this.removeEventListener("touchmove",this.onTouchMove)},onTouchMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},onMouseMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},processColorSelect:function(t){var e=this.canvas.getBoundingClientRect();t.clientX=e.left+e.width||t.clientY=e.top+e.height||this.onColorSelect(t.clientX-e.left,t.clientY-e.top)},onColorSelect:function(t,e){var n=this.context.getImageData(t,e,1,1).data;this.setColor({r:n[0],g:n[1],b:n[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{ +}function $e(t){return this._weekdaysShort[t.day()]}function Ze(t){return this._weekdaysMin[t.day()]}function Xe(t,e,n){var r,i,o;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;7>r;r++){if(i=Pt([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(i,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(i,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(i,"").replace(".",".?")+"$","i")),this._weekdaysParse[r]||(o="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[r].test(t))return r;if(n&&"ddd"===e&&this._shortWeekdaysParse[r].test(t))return r;if(n&&"dd"===e&&this._minWeekdaysParse[r].test(t))return r;if(!n&&this._weekdaysParse[r].test(t))return r}}function Qe(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Ke(t,this.localeData()),this.add(t-e,"d")):e}function tn(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function en(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function nn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function rn(){return this.hours()%12||12}function on(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function an(t,e){return e._meridiemParse}function un(t){return"p"===(t+"").toLowerCase().charAt(0)}function sn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function cn(t,e){e[Cr]=m(1e3*("0."+t))}function ln(){return this._isUTC?"UTC":""}function fn(){return this._isUTC?"Coordinated Universal Time":""}function dn(t){return Pt(1e3*t)}function hn(){return Pt.apply(null,arguments).parseZone()}function pn(t,e,n){var r=this._calendar[t];return j(r)?r.call(e,n):r}function _n(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function vn(){return this._invalidDate}function yn(t){return this._ordinal.replace("%d",t)}function mn(t){return t}function gn(t,e,n,r){var i=this._relativeTime[n];return j(i)?i(t,e,n,r):i.replace(/%d/i,t)}function bn(t,e){var n=this._relativeTime[t>0?"future":"past"];return j(n)?n(e):n.replace(/%s/i,e)}function Sn(t){var e,n;for(n in t)e=t[n],j(e)?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function wn(t,e,n,r){var i=E(),o=s().set(r,e);return i[n](o,t)}function On(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return wn(t,e,n,i);var o,a=[];for(o=0;r>o;o++)a[o]=wn(t,o,n,i);return a}function Mn(t,e){return On(t,e,"months",12,"month")}function In(t,e){return On(t,e,"monthsShort",12,"month")}function En(t,e){return On(t,e,"weekdays",7,"day")}function Tn(t,e){return On(t,e,"weekdaysShort",7,"day")}function Dn(t,e){return On(t,e,"weekdaysMin",7,"day")}function Cn(){var t=this._data;return this._milliseconds=bi(this._milliseconds),this._days=bi(this._days),this._months=bi(this._months),t.milliseconds=bi(t.milliseconds),t.seconds=bi(t.seconds),t.minutes=bi(t.minutes),t.hours=bi(t.hours),t.months=bi(t.months),t.years=bi(t.years),this}function jn(t,e,n,r){var i=Xt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function Pn(t,e){return jn(this,t,e,1)}function An(t,e){return jn(this,t,e,-1)}function kn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,r,i,o=this._milliseconds,a=this._days,u=this._months,s=this._data;return o>=0&&a>=0&&u>=0||0>=o&&0>=a&&0>=u||(o+=864e5*kn(Rn(u)+a),a=0,u=0),s.milliseconds=o%1e3,t=y(o/1e3),s.seconds=t%60,e=y(t/60),s.minutes=e%60,n=y(e/60),s.hours=n%24,a+=y(n/24),i=y(Nn(a)),u+=i,a-=kn(Rn(i)),r=y(u/12),u%=12,s.days=a,s.months=u,s.years=r,this}function Nn(t){return 4800*t/146097}function Rn(t){return 146097*t/4800}function xn(t){var e,n,r=this._milliseconds;if(t=D(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+Nn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Rn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}}function Hn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12)}function Yn(t){return function(){return this.as(t)}}function zn(t){return t=D(t),this[t+"s"]()}function Un(t){return function(){return this._data[t]}}function Vn(){return y(this.days()/7)}function Fn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Gn(t,e,n){var r=Xt(t).abs(),i=Ri(r.as("s")),o=Ri(r.as("m")),a=Ri(r.as("h")),u=Ri(r.as("d")),s=Ri(r.as("M")),c=Ri(r.as("y")),l=i=o&&["m"]||o=a&&["h"]||a=u&&["d"]||u=s&&["M"]||s=c&&["y"]||["yy",c];return l[2]=e,l[3]=+t>0,l[4]=n,Fn.apply(null,l)}function Bn(t,e){return void 0===xi[t]?!1:void 0===e?xi[t]:(xi[t]=e,!0)}function Wn(t){var e=this.localeData(),n=Gn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function qn(){var t,e,n,r=Hi(this._milliseconds)/1e3,i=Hi(this._days),o=Hi(this._months);t=y(r/60),e=y(t/60),r%=60,t%=60,n=y(o/12),o%=12;var a=n,u=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(a?a+"Y":"")+(u?u+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Kn,Jn,$n=e.momentProperties=[],Zn=!1,Xn={},Qn={},tr=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,er=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,nr={},rr={},ir=/\d/,or=/\d\d/,ar=/\d{3}/,ur=/\d{4}/,sr=/[+-]?\d{6}/,cr=/\d\d?/,lr=/\d\d\d\d?/,fr=/\d\d\d\d\d\d?/,dr=/\d{1,3}/,hr=/\d{1,4}/,pr=/[+-]?\d{1,6}/,_r=/\d+/,vr=/[+-]?\d+/,yr=/Z|[+-]\d\d:?\d\d/gi,mr=/Z|[+-]\d\d(?::?\d\d)?/gi,gr=/[+-]?\d+(\.\d{1,3})?/,br=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Sr={},wr={},Or=0,Mr=1,Ir=2,Er=3,Tr=4,Dr=5,Cr=6,jr=7,Pr=8;R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),T("month","M"),U("M",cr),U("MM",cr,or),U("MMM",function(t,e){return e.monthsShortRegex(t)}),U("MMMM",function(t,e){return e.monthsRegex(t)}),B(["M","MM"],function(t,e){e[Mr]=m(t)-1}),B(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[Mr]=i:l(n).invalidMonth=t});var Ar=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,kr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Lr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Nr=br,Rr=br,xr={};e.suppressDeprecationWarnings=!1;var Hr=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Yr=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,zr=/Z|[+-]\d\d(?::?\d\d)?/,Ur=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Vr=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Fr=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=at("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),R("Y",0,0,function(){var t=this.year();return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),T("year","y"),U("Y",vr),U("YY",cr,or),U("YYYY",hr,ur),U("YYYYY",pr,sr),U("YYYYYY",pr,sr),B(["YYYYY","YYYYYY"],Or),B("YYYY",function(t,n){n[Or]=2===t.length?e.parseTwoDigitYear(t):m(t)}),B("YY",function(t,n){n[Or]=e.parseTwoDigitYear(t)}),B("Y",function(t,e){e[Or]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return m(t)+(m(t)>68?1900:2e3)};var Gr=P("FullYear",!1);e.ISO_8601=function(){};var Br=at("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:d()}),Wr=at("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:d()}),qr=function(){return Date.now?Date.now():+new Date};xt("Z",":"),xt("ZZ",""),U("Z",mr),U("ZZ",mr),B(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Ht(mr,t)});var Kr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Jr=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,$r=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Xt.fn=Nt.prototype;var Zr=ne(1,"add"),Xr=ne(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Qr=at("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),T("weekYear","gg"),T("isoWeekYear","GG"),U("G",vr),U("g",vr),U("GG",cr,or),U("gg",cr,or),U("GGGG",hr,ur),U("gggg",hr,ur),U("GGGGG",pr,sr),U("ggggg",pr,sr),W(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=m(t)}),W(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),R("Q",0,"Qo","quarter"),T("quarter","Q"),U("Q",ir),B("Q",function(t,e){e[Mr]=3*(m(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),T("week","w"),T("isoWeek","W"),U("w",cr),U("ww",cr,or),U("W",cr),U("WW",cr,or),W(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=m(t)});var ti={dow:0,doy:6};R("D",["DD",2],"Do","date"),T("date","D"),U("D",cr),U("DD",cr,or),U("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),B(["D","DD"],Ir),B("Do",function(t,e){e[Ir]=m(t.match(cr)[0],10)});var ei=P("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),T("day","d"),T("weekday","e"),T("isoWeekday","E"),U("d",cr),U("e",cr),U("E",cr),U("dd",br),U("ddd",br),U("dddd",br),W(["dd","ddd","dddd"],function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:l(n).invalidWeekday=t}),W(["d","e","E"],function(t,e,n,r){e[r]=m(t)});var ni="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ri="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ii="Su_Mo_Tu_We_Th_Fr_Sa".split("_");R("DDD",["DDDD",3],"DDDo","dayOfYear"),T("dayOfYear","DDD"),U("DDD",dr),U("DDDD",ar),B(["DDD","DDDD"],function(t,e,n){n._dayOfYear=m(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,rn),R("hmm",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)}),R("hmmss",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),on("a",!0),on("A",!1),T("hour","h"),U("a",an),U("A",an),U("H",cr),U("h",cr),U("HH",cr,or),U("hh",cr,or),U("hmm",lr),U("hmmss",fr),U("Hmm",lr),U("Hmmss",fr),B(["H","HH"],Er),B(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),B(["h","hh"],function(t,e,n){e[Er]=m(t),l(n).bigHour=!0}),B("hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r)),l(n).bigHour=!0}),B("hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i)),l(n).bigHour=!0}),B("Hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r))}),B("Hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i))});var oi=/[ap]\.?m?\.?/i,ai=P("Hours",!0);R("m",["mm",2],0,"minute"),T("minute","m"),U("m",cr),U("mm",cr,or),B(["m","mm"],Tr);var ui=P("Minutes",!1);R("s",["ss",2],0,"second"),T("second","s"),U("s",cr),U("ss",cr,or),B(["s","ss"],Dr);var si=P("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),T("millisecond","ms"),U("S",dr,ir),U("SS",dr,or),U("SSS",dr,ar);var ci;for(ci="SSSS";ci.length<=9;ci+="S")U(ci,_r);for(ci="S";ci.length<=9;ci+="S")B(ci,cn);var li=P("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var fi=_.prototype;fi.add=Zr,fi.calendar=ie,fi.clone=oe,fi.diff=de,fi.endOf=Me,fi.format=ve,fi.from=ye,fi.fromNow=me,fi.to=ge,fi.toNow=be,fi.get=L,fi.invalidAt=ke,fi.isAfter=ae,fi.isBefore=ue,fi.isBetween=se,fi.isSame=ce,fi.isSameOrAfter=le,fi.isSameOrBefore=fe,fi.isValid=Pe,fi.lang=Qr,fi.locale=Se,fi.localeData=we,fi.max=Wr,fi.min=Br,fi.parsingFlags=Ae,fi.set=L,fi.startOf=Oe,fi.subtract=Xr,fi.toArray=De,fi.toObject=Ce,fi.toDate=Te,fi.toISOString=_e,fi.toJSON=je,fi.toString=pe,fi.unix=Ee,fi.valueOf=Ie,fi.creationData=Le,fi.year=Gr,fi.isLeapYear=pt,fi.weekYear=Re,fi.isoWeekYear=xe,fi.quarter=fi.quarters=Ve,fi.month=Q,fi.daysInMonth=tt,fi.week=fi.weeks=We,fi.isoWeek=fi.isoWeeks=qe,fi.weeksInYear=Ye,fi.isoWeeksInYear=He,fi.date=ei,fi.day=fi.days=Qe,fi.weekday=tn,fi.isoWeekday=en,fi.dayOfYear=nn,fi.hour=fi.hours=ai,fi.minute=fi.minutes=ui,fi.second=fi.seconds=si,fi.millisecond=fi.milliseconds=li,fi.utcOffset=Ut,fi.utc=Ft,fi.local=Gt,fi.parseZone=Bt,fi.hasAlignedHourOffset=Wt,fi.isDST=qt,fi.isDSTShifted=Kt,fi.isLocal=Jt,fi.isUtcOffset=$t,fi.isUtc=Zt,fi.isUTC=Zt,fi.zoneAbbr=ln,fi.zoneName=fn,fi.dates=at("dates accessor is deprecated. Use date instead.",ei),fi.months=at("months accessor is deprecated. Use month instead",Q),fi.years=at("years accessor is deprecated. Use year instead",Gr),fi.zone=at("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Vt);var di=fi,hi={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},pi={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},_i="Invalid date",vi="%d",yi=/\d{1,2}/,mi={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},gi=b.prototype;gi._calendar=hi,gi.calendar=pn,gi._longDateFormat=pi,gi.longDateFormat=_n,gi._invalidDate=_i,gi.invalidDate=vn,gi._ordinal=vi,gi.ordinal=yn,gi._ordinalParse=yi,gi.preparse=mn,gi.postformat=mn,gi._relativeTime=mi,gi.relativeTime=gn,gi.pastFuture=bn,gi.set=Sn,gi.months=J,gi._months=kr,gi.monthsShort=$,gi._monthsShort=Lr,gi.monthsParse=Z,gi._monthsRegex=Rr,gi.monthsRegex=nt,gi._monthsShortRegex=Nr,gi.monthsShortRegex=et,gi.week=Fe,gi._week=ti,gi.firstDayOfYear=Be,gi.firstDayOfWeek=Ge,gi.weekdays=Je,gi._weekdays=ni,gi.weekdaysMin=Ze,gi._weekdaysMin=ii,gi.weekdaysShort=$e,gi._weekdaysShort=ri,gi.weekdaysParse=Xe,gi.isPM=un,gi._meridiemParse=oi,gi.meridiem=sn,M("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===m(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=at("moment.lang is deprecated. Use moment.locale instead.",M),e.langData=at("moment.langData is deprecated. Use moment.localeData instead.",E);var bi=Math.abs,Si=Yn("ms"),wi=Yn("s"),Oi=Yn("m"),Mi=Yn("h"),Ii=Yn("d"),Ei=Yn("w"),Ti=Yn("M"),Di=Yn("y"),Ci=Un("milliseconds"),ji=Un("seconds"),Pi=Un("minutes"),Ai=Un("hours"),ki=Un("days"),Li=Un("months"),Ni=Un("years"),Ri=Math.round,xi={s:45,m:45,h:22,d:26,M:11},Hi=Math.abs,Yi=Nt.prototype;Yi.abs=Cn,Yi.add=Pn,Yi.subtract=An,Yi.as=xn,Yi.asMilliseconds=Si,Yi.asSeconds=wi,Yi.asMinutes=Oi,Yi.asHours=Mi,Yi.asDays=Ii,Yi.asWeeks=Ei,Yi.asMonths=Ti,Yi.asYears=Di,Yi.valueOf=Hn,Yi._bubble=Ln,Yi.get=zn,Yi.milliseconds=Ci,Yi.seconds=ji,Yi.minutes=Pi,Yi.hours=Ai,Yi.days=ki,Yi.weeks=Vn,Yi.months=Li,Yi.years=Ni,Yi.humanize=Wn,Yi.toISOString=qn,Yi.toString=qn,Yi.toJSON=qn,Yi.locale=Se,Yi.localeData=we,Yi.toIsoString=at("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",qn),Yi.lang=Qr,R("X",0,0,"unix"),R("x",0,0,"valueOf"),U("x",vr),U("X",gr),B("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),B("x",function(t,e,n){n._d=new Date(m(t))}),e.version="2.11.2",n(Pt),e.fn=di,e.min=kt,e.max=Lt,e.now=qr,e.utc=s,e.unix=dn,e.months=Mn,e.isDate=i,e.locale=M,e.invalid=d,e.duration=Xt,e.isMoment=v,e.weekdays=En,e.parseZone=hn,e.localeData=E,e.isDuration=Rt,e.monthsShort=In,e.weekdaysMin=Dn,e.defineLocale=I,e.weekdaysShort=Tn,e.normalizeUnits=D,e.relativeTimeThreshold=Bn,e.prototype=di;var zi=e;return zi})}).call(e,n(68)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var a=n(169),u=n(194),s=i(u),c=n(196),l=i(c),f=n(198),d=i(f),h=n(15),p=r(h),_=n(24),v=r(_),y=n(9),m=r(y),g=n(46),b=r(g),S=n(149),w=r(S),O=n(25),M=r(O),I=n(154),E=r(I),T=n(49),D=r(T),C=n(52),j=r(C),P=n(27),A=r(P),k=n(59),L=r(k),N=n(13),R=r(N),x=n(29),H=r(x),Y=n(31),z=r(Y),U=n(185),V=r(U),F=n(191),G=r(F),B=n(10),W=r(B),q=function K(){o(this,K);var t=(0,s["default"])();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:a.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:a.localStoragePreferences.startSync.bind(a.localStoragePreferences,t)},startUrlSync:{value:j.urlSync.startSync.bind(null,t)},stopUrlSync:{value:j.urlSync.stopSync.bind(null,t)}}),(0,l["default"])(this,t,{auth:p,config:v,entity:m,entityHistory:b,errorLog:w,event:M,logbook:E,moreInfo:D,navigation:j,notification:A,view:L,service:R,stream:H,sync:z,template:V,voice:G,restApi:W})};e["default"]=q},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(79),e["default"]=new o["default"]({is:"ha-badges-card",properties:{states:{type:Array}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=u["default"].moreInfoActions,c=1e4;e["default"]=new o["default"]({is:"ha-camera-card",properties:{stateObj:{type:Object,observer:"updateCameraFeedSrc"},cameraFeedSrc:{type:String},imageLoaded:{type:Boolean,value:!0},elevation:{type:Number,value:1,reflectToAttribute:!0}},listeners:{tap:"cardTapped"},attached:function(){var t=this;this.timer=setInterval(function(){return t.updateCameraFeedSrc(t.stateObj)},c)},detached:function(){clearInterval(this.timer)},cardTapped:function(){var t=this;this.async(function(){return s.selectEntity(t.stateObj.entityId)},1)},updateCameraFeedSrc:function(t){var e=(new Date).getTime();this.cameraFeedSrc=t.attributes.entity_picture+"?time="+e},imageLoadSuccess:function(){this.imageLoaded=!0},imageLoadFail:function(){this.imageLoaded=!1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(71),n(73),n(74),e["default"]=new o["default"]({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=!e||e.cardType!==t.cardType,i=void 0;r?(n.lastChild&&n.removeChild(n.lastChild),i=document.createElement("ha-"+t.cardType+"-card")):i=n.lastChild,Object.keys(t).forEach(function(e){return i[e]=t[e]}),e&&Object.keys(e).forEach(function(e){e in t||(i[e]=void 0)}),r&&n.appendChild(i)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(21),c=r(s);n(36),n(35),n(19);var l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entities-card",properties:{states:{type:Array},groupEntity:{type:Object}},computeTitle:function(t,e){return e?e.entityDisplay:t[0].domain.replace(/_/g," ")},entityTapped:function(t){if(!t.target.classList.contains("paper-toggle-button")&&!t.target.classList.contains("paper-icon-button")){t.stopPropagation();var e=t.model.item.entityId;this.async(function(){return l.selectEntity(e)},1)}},showGroupToggle:function(t,e){return!t||!e||"on"!==t.state&&"off"!==t.state?!1:e.reduce(function(t,e){return t+(0,c["default"])(e.entityId)},0)>1}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(36),e["default"]=new o["default"]({is:"ha-introduction-card",properties:{showInstallInstruction:{type:Boolean,value:!1},showHideInstruction:{type:Boolean,value:!0}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(41),u=r(a);e["default"]=new o["default"]({is:"display-time",properties:{dateObj:{type:Object}},computeTime:function(t){return t?(0,u["default"])(t):""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].entityGetters;e["default"]=new u["default"]({is:"entity-list",behaviors:[c["default"]],properties:{entities:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.entityId}).toArray()}]}},entitySelected:function(t){t.preventDefault(),this.fire("entity-selected",{entityId:t.model.entity.entityId})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a);n(17);var s=u["default"].reactor,c=u["default"].entityGetters,l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entity-marker",properties:{entityId:{type:String,value:""},state:{type:Object,computed:"computeState(entityId)"},icon:{type:Object,computed:"computeIcon(state)"},image:{type:Object,computed:"computeImage(state)"},value:{type:String,computed:"computeValue(state)"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;t.stopPropagation(),this.entityId&&this.async(function(){return l.selectEntity(e.entityId)},1)},computeState:function(t){return t&&s.evaluate(c.byId(t))},computeIcon:function(t){return!t&&"home"},computeImage:function(t){return t&&t.attributes.entity_picture},computeValue:function(t){return t&&t.entityDisplay.split(" ").map(function(t){return t.substr(0,1)}).join("")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(43),u=r(a);e["default"]=new o["default"]({is:"ha-state-icon",properties:{stateObj:{type:Object}},computeIcon:function(t){return(0,u["default"])(t)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(22),c=r(s),l=n(21),f=r(l),d=n(43),h=r(d);n(17);var p=u["default"].moreInfoActions,_=u["default"].serviceActions;e["default"]=new o["default"]({is:"ha-state-label-badge",properties:{state:{type:Object,observer:"stateChanged"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;return t.stopPropagation(),(0,f["default"])(this.state.entityId)?void("scene"===this.state.domain||"off"===this.state.state?_.callTurnOn(this.state.entityId):_.callTurnOff(this.state.entityId)):void this.async(function(){return p.selectEntity(e.state.entityId)},1)},computeClasses:function(t){switch(t.domain){case"scene":return"green";case"binary_sensor":case"script":return"on"===t.state?"blue":"grey";case"updater":return"blue";default:return""}},computeValue:function(t){switch(t.domain){case"binary_sensor":case"device_tracker":case"updater":case"sun":case"scene":case"script":case"alarm_control_panel":return null;case"sensor":default:return"unknown"===t.state?"-":t.state}},computeIcon:function(t){switch(t.domain){case"alarm_control_panel":return"pending"===t.state?"mdi:clock-fast":"armed_away"===t.state?"mdi:nature":"armed_home"===t.state?"mdi:home-variant":(0,c["default"])(t.domain,t.state);case"binary_sensor":case"device_tracker":case"scene":case"updater":case"script":return(0,h["default"])(t);case"sun":return"above_horizon"===t.state?(0,c["default"])(t.domain):"mdi:brightness-3";default:return null}},computeImage:function(t){return t.attributes.entity_picture||null},computeLabel:function(t){switch(t.domain){case"scene":case"script":return t.domain;case"device_tracker":return"not_home"===t.state?"Away":t.state;case"alarm_control_panel":return"pending"===t.state?"pend":"armed_away"===t.state||"armed_home"===t.state?"armed":"disarm";default:return t.attributes.unit_of_measurement||null}},computeDescription:function(t){return t.entityDisplay},stateChanged:function(){this.updateStyles()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(78),e["default"]=new o["default"]({is:"state-badge",properties:{stateObj:{type:Object,observer:"updateIconColor"}},updateIconColor:function(t){return t.attributes.entity_picture?(this.style.backgroundImage="url("+t.attributes.entity_picture+")",void(this.$.icon.style.display="none")):(this.style.backgroundImage="",this.$.icon.style.display="inline",void("light"===t.domain&&"on"===t.state&&t.attributes.rgb_color&&t.attributes.rgb_color.reduce(function(t,e){return t+e},0)<730?this.$.icon.style.color="rgb("+t.attributes.rgb_color.join(",")+")":this.$.icon.style.color=null))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].eventGetters;e["default"]=new u["default"]({is:"events-list",behaviors:[c["default"]],properties:{events:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.event}).toArray()}]}},eventSelected:function(t){t.preventDefault(),this.fire("event-selected",{eventType:t.model.event.event})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t in d?d[t]:30}function o(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(1),u=r(a),s=n(2),c=r(s);n(84),n(70),n(72);var l=c["default"].util,f=["camera"],d={configurator:-20,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,sensor:5,binary_sensor:6,scene:7,script:8};e["default"]=new u["default"]({is:"ha-cards",properties:{showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},cards:{type:Object,computed:"computeCards(columns, states, showIntroduction)"}},computeCards:function(t,e,n){function r(t){return t.filter(function(t){return!(t.entityId in c)})}function a(){var e=p;return p=(p+1)%t,e}function u(t,e){var n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2];if(0!==e.length){var r=[],i=[];e.forEach(function(t){-1===f.indexOf(t.domain)?i.push(t):r.push(t)});var o=a();i.length>0&&(d._columns[o].push(t),d[t]={cardType:"entities",states:i,groupEntity:n}),r.forEach(function(t){d._columns[o].push(t.entityId),d[t.entityId]={cardType:t.domain,stateObj:t}})}}for(var s=e.groupBy(function(t){return t.domain}),c={},d={_demo:!1,_badges:[],_columns:[]},h=0;t>h;h++)d._columns[h]=[];var p=0;return n&&(d._columns[a()].push("ha-introduction"),d["ha-introduction"]={cardType:"introduction",showHideInstruction:e.size>0&&!0}),s.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){if("a"===t)return void(d._demo=!0);var n=i(t);n>=0&&10>n?d._badges.push.apply(d._badges,r(s.get(t)).sortBy(o).toArray()):"group"===t?s.get(t).sortBy(o).forEach(function(t){var n=l.expandGroup(t,e);n.forEach(function(t){return c[t.entityId]=!0}),u(t.entityId,n.toArray(),t)}):u(t,r(s.get(t)).sortBy(o).toArray())}),d},computeCardDataOfCard:function(t,e){return t[e]}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-color-picker",properties:{color:{type:Object},width:{type:Number},height:{type:Number}},listeners:{mousedown:"onMouseDown",mouseup:"onMouseUp",touchstart:"onTouchStart",touchend:"onTouchEnd"},onMouseDown:function(t){this.onMouseMove(t),this.addEventListener("mousemove",this.onMouseMove)},onMouseUp:function(){this.removeEventListener("mousemove",this.onMouseMove)},onTouchStart:function(t){this.onTouchMove(t),this.addEventListener("touchmove",this.onTouchMove)},onTouchEnd:function(){this.removeEventListener("touchmove",this.onTouchMove)},onTouchMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},onMouseMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},processColorSelect:function(t){var e=this.canvas.getBoundingClientRect();t.clientX=e.left+e.width||t.clientY=e.top+e.height||this.onColorSelect(t.clientX-e.left,t.clientY-e.top)},onColorSelect:function(t,e){var n=this.context.getImageData(t,e,1,1).data;this.setColor({r:n[0],g:n[1],b:n[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{ rgb:this.color})},ready:function(){var t=this;this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.debounce("drawGradient",function(){var e=void 0;t.width&&t.height||(e=getComputedStyle(t));var n=t.width||parseInt(e.width,10),r=t.height||parseInt(e.height,10),i=t.context.createLinearGradient(0,0,n,0);i.addColorStop(0,"rgb(255,0,0)"),i.addColorStop(.16,"rgb(255,0,255)"),i.addColorStop(.32,"rgb(0,0,255)"),i.addColorStop(.48,"rgb(0,255,255)"),i.addColorStop(.64,"rgb(0,255,0)"),i.addColorStop(.8,"rgb(255,255,0)"),i.addColorStop(1,"rgb(255,0,0)"),t.context.fillStyle=i,t.context.fillRect(0,0,n,r);var o=t.context.createLinearGradient(0,0,0,r);o.addColorStop(0,"rgba(255,255,255,1)"),o.addColorStop(.5,"rgba(255,255,255,0)"),o.addColorStop(.5,"rgba(0,0,0,0)"),o.addColorStop(1,"rgba(0,0,0,1)"),t.context.fillStyle=o,t.context.fillRect(0,0,n,r)},100)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"ha-demo-badge"})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(87),e["default"]=new o["default"]({is:"ha-logbook",properties:{entries:{type:Object,value:[]}},noEntries:function(t){return!t.length}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(91);var l=o["default"].configGetters,f=o["default"].navigationGetters,d=o["default"].authActions,h=o["default"].navigationActions;e["default"]=new u["default"]({is:"ha-sidebar",behaviors:[c["default"]],properties:{menuShown:{type:Boolean},menuSelected:{type:String},selected:{type:String,bindNuclear:f.activePane,observer:"selectedChanged"},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history")},hasLogbookComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("logbook")}},selectedChanged:function(t){document.activeElement&&document.activeElement.blur();for(var e=this.querySelectorAll(".menu [data-panel]"),n=0;nnew Date&&(o=new Date);var u=e.map(function(t){function e(t,e){c&&e&&s.push([t[0]].concat(c.slice(1).map(function(t,n){return e[n]?t:null}))),s.push(t),c=t}var n=t[t.length-1],r=n.domain,a=n.entityDisplay,u=new window.google.visualization.DataTable;u.addColumn({type:"datetime",id:"Time"});var s=[],c=void 0;if("thermostat"===r){var l=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1);u.addColumn("number",a+" current temperature");var f=void 0;l?!function(){u.addColumn("number",a+" target temperature high"),u.addColumn("number",a+" target temperature low");var t=[!1,!0,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.target_temp_high),a=i(n.attributes.target_temp_low);e([n.lastUpdatedAsDate,r,o,a],t)}}():!function(){u.addColumn("number",a+" target temperature");var t=[!1,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.temperature);e([n.lastUpdatedAsDate,r,o],t)}}(),t.forEach(f)}else!function(){u.addColumn("number",a);var n="sensor"!==r&&[!0];t.forEach(function(t){var r=i(t.state);e([t.lastChangedAsDate,r],n)})}();return e([o].concat(c.slice(1)),!1),u.addRows(s),u}),s=void 0;s=1===u.length?u[0]:u.slice(1).reduce(function(t,e){return window.google.visualization.data.join(t,e,"full",[[0,0]],(0,a["default"])(1,t.getNumberOfColumns()),(0,a["default"])(1,e.getNumberOfColumns()))},u[0]),this.chartEngine.draw(s,n)}}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"state-history-chart-timeline",properties:{data:{type:Object,observer:"dataChanged"},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){function t(t,e,n,r){var o=e.replace(/_/g," ");i.addRow([t,o,n,r])}if(this.isAttached){for(var e=o["default"].dom(this),n=this.data;e.node.lastChild;)e.node.removeChild(e.node.lastChild);if(n&&0!==n.length){var r=new window.google.visualization.Timeline(this),i=new window.google.visualization.DataTable;i.addColumn({type:"string",id:"Entity"}),i.addColumn({type:"string",id:"State"}),i.addColumn({type:"date",id:"Start"}),i.addColumn({type:"date",id:"End"});var a=new Date(n.reduce(function(t,e){return Math.min(t,e[0].lastChangedAsDate)},new Date)),u=new Date(a);u.setDate(u.getDate()+1),u>new Date&&(u=new Date);var s=0;n.forEach(function(e){if(0!==e.length){var n=e[0].entityDisplay,r=void 0,i=null,o=null;e.forEach(function(e){null!==i&&e.state!==i?(r=e.lastChangedAsDate,t(n,i,o,r),i=e.state,o=r):null===i&&(i=e.state,o=e.lastChangedAsDate)}),t(n,i,o,u),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].streamGetters,f=o["default"].streamActions;e["default"]=new u["default"]({is:"stream-status",behaviors:[c["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},hasError:{type:Boolean,bindNuclear:l.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?f.stop():f.start()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].voiceActions,f=o["default"].voiceGetters;e["default"]=new u["default"]({is:"ha-voice-command-dialog",behaviors:[c["default"]],properties:{dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},finalTranscript:{type:String,bindNuclear:f.finalTranscript},interimTranscript:{type:String,bindNuclear:f.extraInterimTranscript},isTransmitting:{type:Boolean,bindNuclear:f.isTransmitting},isListening:{type:Boolean,bindNuclear:f.isListening},showListenInterface:{type:Boolean,computed:"computeShowListenInterface(isListening, isTransmitting)",observer:"showListenInterfaceChanged"}},computeShowListenInterface:function(t,e){return t||e},dialogOpenChanged:function(t){!t&&this.isListening&&l.stop()},showListenInterfaceChanged:function(t){!t&&this.dialogOpen?this.dialogOpen=!1:t&&(this.dialogOpen=!0)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19),n(38),n(109);var l=o["default"].configGetters,f=o["default"].entityHistoryGetters,d=o["default"].entityHistoryActions,h=o["default"].moreInfoGetters,p=o["default"].moreInfoActions,_=["camera","configurator","scene"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[c["default"]],properties:{stateObj:{type:Object,bindNuclear:h.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[h.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(_delayedDialogOpen, _isLoadingHistoryData)"},_isLoadingHistoryData:{type:Boolean,bindNuclear:f.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:h.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1,computed:"computeShowHistoryComponent(hasHistoryComponent, stateObj)"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},_delayedDialogOpen:{type:Boolean,value:!1}},computeIsLoadingHistoryData:function(t,e){return!t||e},computeShowHistoryComponent:function(t,e){return this.hasHistoryComponent&&e&&-1===_.indexOf(e.domain)},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&d.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){var e=this;t?this.async(function(){return e._delayedDialogOpen=!0},10):!t&&this.stateObj&&(this.async(function(){return p.deselectEntity()},10),this._delayedDialogOpen=!1)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(4),c=r(s),l=n(42),f=r(l);n(86),n(96),n(103),n(102),n(104),n(97),n(98),n(100),n(101),n(99),n(105),n(93),n(92);var d=u["default"].navigationActions,h=u["default"].navigationGetters,p=u["default"].startUrlSync,_=u["default"].stopUrlSync;e["default"]=new o["default"]({is:"home-assistant-main",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},activePane:{type:String,bindNuclear:h.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:h.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:h.isActivePane("history")},isSelectedMap:{type:Boolean,bindNuclear:h.isActivePane("map")},isSelectedLogbook:{type:Boolean,bindNuclear:h.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:h.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:h.isActivePane("devState")},isSelectedDevTemplate:{type:Boolean,bindNuclear:h.isActivePane("devTemplate")},isSelectedDevService:{type:Boolean,bindNuclear:h.isActivePane("devService")},isSelectedDevInfo:{type:Boolean,bindNuclear:h.isActivePane("devInfo")},showSidebar:{type:Boolean,bindNuclear:h.showSidebar}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():d.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&d.showSidebar(!1)},activePaneChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){(0,f["default"])(),p()},computeForceNarrow:function(t,e){return t||!e},detached:function(){_()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(4),c=r(s),l=n(44),f=r(l),d=n(42),h=r(d),p=u["default"].authGetters;e["default"]=new o["default"]({is:"login-form",behaviors:[c["default"]],properties:{errorMessage:{type:String,bindNuclear:p.attemptErrorMessage},isInvalid:{type:Boolean,bindNuclear:p.isInvalidAttempt},isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:p.isValidating},loadingResources:{type:Boolean,value:!1},forceShowLoading:{type:Boolean,value:!1},showLoading:{type:Boolean,computed:"computeShowSpinner(forceShowLoading, isValidating)"}},listeners:{keydown:"passwordKeyDown","loginButton.tap":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],attached:function(){(0,h["default"])()},computeShowSpinner:function(t,e){return t||e},validatingChanged:function(t,e){t||e||(this.$.passwordInput.value="")},isValidatingChanged:function(t){var e=this;t||this.async(function(){return e.$.passwordInput.focus()},10)},passwordKeyDown:function(t){13===t.keyCode?(this.validatePassword(),t.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),(0,f["default"])(this.$.passwordInput.value,this.$.rememberLogin.checked)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(82);var l=o["default"].configGetters,f=o["default"].viewActions,d=o["default"].viewGetters,h=o["default"].voiceGetters,p=o["default"].streamGetters,_=o["default"].syncGetters,v=o["default"].syncActions,y=o["default"].voiceActions;e["default"]=new u["default"]({is:"partial-cards",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:_.isFetching},isStreaming:{type:Boolean,bindNuclear:p.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[h.isVoiceSupported,l.isComponentLoaded("conversation"),function(t,e){return t&&e}]},introductionLoaded:{type:Boolean,bindNuclear:l.isComponentLoaded("introduction")},locationName:{type:String,bindNuclear:l.locationName},showMenu:{type:Boolean,value:!1,observer:"windowChange"},currentView:{type:String,bindNuclear:[d.currentView,function(t){return t||""}],observer:"removeFocus"},views:{type:Array,bindNuclear:[d.views,function(t){return t.valueSeq().sortBy(function(t){return t.attributes.order}).toArray()}]},hasViews:{type:Boolean,computed:"computeHasViews(views)"},states:{type:Object,bindNuclear:d.currentViewEntities},columns:{type:Number,value:1}},created:function(){var t=this;this.windowChange=this.windowChange.bind(this);for(var e=[],n=0;5>n;n++)e.push(300+300*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n})},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){var t=this.mqls.reduce(function(t,e){return t+e.matches},0);this.columns=Math.max(1,t-this.showMenu)},removeFocus:function(){document.activeElement&&document.activeElement.blur()},handleRefresh:function(){v.fetchAll()},handleListenClick:function(){y.listen()},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},computeShowIntroduction:function(t,e,n){return""===t&&(e||0===n.size)},computeHasViews:function(t){return t.length>0},toggleMenu:function(){this.fire("open-menu")},viewSelected:function(t){var e=t.detail.item.getAttribute("data-entity")||null,n=this.currentView||null;e!==n&&this.async(function(){return f.selectView(e)},0)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(88);var s=o["default"].reactor,c=o["default"].serviceActions,l=o["default"].serviceGetters;e["default"]=new u["default"]({is:"partial-dev-call-service",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},domain:{type:String,value:""},service:{type:String,value:""},serviceData:{type:String,value:""},description:{type:String,computed:"computeDescription(domain, service)"}},computeDescription:function(t,e){return s.evaluate([l.entityMap,function(n){return n.has(t)&&n.get(t).get("services").has(e)?JSON.stringify(n.get(t).get("services").get(e).toJS(),null,2):"No description available"}])},serviceSelected:function(t){this.domain=t.detail.domain,this.service=t.detail.service},callService:function(){var t=void 0;try{t=this.serviceData?JSON.parse(this.serviceData):{}}catch(e){return void alert("Error parsing JSON: "+e)}c.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(81);var s=o["default"].eventActions;e["default"]=new u["default"]({is:"partial-dev-fire-event",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},eventType:{type:String,value:""},eventData:{type:String,value:""}},eventSelected:function(t){this.eventType=t.detail.eventType},fireEvent:function(){var t=void 0;try{t=this.eventData?JSON.parse(this.eventData):{}}catch(e){return void alert("Error parsing JSON: "+e)}s.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].configGetters,f=o["default"].errorLogActions;e["default"]=new u["default"]({is:"partial-dev-info",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:l.serverVersion},polymerVersion:{type:String,value:u["default"].version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(t){var e=this;t&&t.preventDefault(),this.errorLog="Loading error log…",f.fetchErrorLog().then(function(t){return e.errorLog=t||"No errors have been reported."})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(8),n(76);var s=o["default"].reactor,c=o["default"].entityGetters,l=o["default"].entityActions;e["default"]=new u["default"]({is:"partial-dev-set-state",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},entityId:{type:String,value:""},state:{type:String,value:""},stateAttributes:{type:String,value:""}},setStateData:function(t){var e=t?JSON.stringify(t,null," "):"";this.$.inputData.value=e,this.$.inputDataWrapper.update(this.$.inputData)},entitySelected:function(t){var e=s.evaluate(c.byId(t.detail.entityId));this.entityId=e.entityId,this.state=e.state,this.stateAttributes=JSON.stringify(e.attributes,null," ")},handleSetState:function(){var t=void 0;try{t=this.stateAttributes?JSON.parse(this.stateAttributes):{}}catch(e){return void alert("Error parsing JSON: "+e)}l.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].templateActions;e["default"]=new u["default"]({is:"partial-dev-template",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},error:{type:Boolean,value:!1},rendering:{type:Boolean,value:!1},template:{type:String,value:'{%- if is_state("device_tracker.paulus", "home") and \n is_state("device_tracker.anne_therese", "home") -%}\n\n You are both home, you silly\n\n{%- else -%}\n\n Anne Therese is at {{ states("device_tracker.anne_therese") }} and Paulus is at {{ states("device_tracker.paulus") }}\n\n{%- endif %}\n\nFor loop example:\n\n{% for state in states.sensor -%}\n {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}\n {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}\n{%- endfor -%}.',observer:"templateChanged"},processed:{type:String,value:""}},computeFormClasses:function(t){return"content fit layout "+(t?"vertical":"horizontal")},computeRenderedClasses:function(t){return t?"error rendered":"rendered"},templateChanged:function(){this.error&&(this.error=!1),this.debounce("render-template",this.renderTemplate,500)},renderTemplate:function(){var t=this;this.rendering=!0,l.render(this.template).then(function(e){t.processed=e,t.rendering=!1},function(e){t.processed=e.message,t.error=!0,t.rendering=!1})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(38);var l=o["default"].entityHistoryGetters,f=o["default"].entityHistoryActions;e["default"]=new u["default"]({is:"partial-history",behaviors:[c["default"]],properties:{narrow:{type:Boolean},showMenu:{type:Boolean,value:!1},isDataLoaded:{type:Boolean,bindNuclear:l.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:l.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:l.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:l.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return f.fetchSelectedDate()},1)},handleRefreshClick:function(){f.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(85),n(18);var l=o["default"].logbookGetters,f=o["default"].logbookActions;e["default"]=new u["default"]({is:"partial-logbook",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:l.currentDate},isLoading:{type:Boolean,bindNuclear:l.isLoadingEntries},isStale:{type:Boolean,bindNuclear:l.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[l.currentEntries,function(t){return t.reverse().toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return f.fetchDate(e.selectedDate)},1)},handleRefresh:function(){f.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(77);var l=o["default"].configGetters,f=o["default"].entityGetters;window.L.Icon.Default.imagePath="/static/images/leaflet",e["default"]=new u["default"]({is:"partial-map",behaviors:[c["default"]],properties:{locationGPS:{type:Number,bindNuclear:l.locationGPS},locationName:{type:String,bindNuclear:l.locationName},locationEntities:{type:Array,bindNuclear:[f.visibleEntityMap,function(t){return t.valueSeq().filter(function(t){return t.attributes.latitude&&"home"!==t.state}).toArray()}]},zoneEntities:{type:Array,bindNuclear:[f.entityMap,function(t){return t.valueSeq().filter(function(t){return"zone"===t.domain&&!t.attributes.passive}).toArray()}]},narrow:{type:Boolean},showMenu:{type:Boolean,value:!1}},attached:function(){var t=this;window.L.Browser.mobileWebkit&&this.async(function(){var e=t.$.map,n=e.style.display;e.style.display="none",t.async(function(){e.style.display=n},1)},1)},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].notificationGetters;e["default"]=new u["default"]({is:"notification-manager",behaviors:[c["default"]],properties:{text:{type:String,bindNuclear:l.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-alarm_control_panel",handleDisarmTap:function(){this.callService("alarm_disarm",{code:this.enteredCode})},handleHomeTap:function(){this.callService("alarm_arm_home",{code:this.enteredCode})},handleAwayTap:function(){this.callService("alarm_arm_away",{code:this.enteredCode})},properties:{stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""},disarmButtonVisible:{type:Boolean,value:!1},armHomeButtonVisible:{type:Boolean,value:!1},armAwayButtonVisible:{type:Boolean,value:!1},codeInputVisible:{type:Boolean,value:!1},codeInputEnabled:{type:Boolean,value:!1},codeFormat:{type:String,value:""},codeValid:{type:Boolean,computed:"validateCode(enteredCode, codeFormat)"}},validateCode:function(t,e){var n=new RegExp(e);return null===e?!0:n.test(t)},stateObjChanged:function(t){var e=this;t&&(this.codeFormat=t.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="armed_home"===t.state||"armed_away"===t.state||"disarmed"===t.state||"pending"===t.state||"triggered"===t.state,this.disarmButtonVisible="armed_home"===t.state||"armed_away"===t.state||"pending"===t.state||"triggered"===t.state,this.armHomeButtonVisible="disarmed"===t.state,this.armAwayButtonVisible="disarmed"===t.state),this.async(function(){return e.fire("iron-resize")},500)},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,s.callService("alarm_control_panel",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-camera",properties:{stateObj:{type:Object},dialogOpen:{type:Boolean}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(t){return t?"/api/camera_proxy_stream/"+this.stateObj.entityId:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(18);var l=o["default"].streamGetters,f=o["default"].syncActions,d=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[c["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"},fieldInput:{type:Object,value:{}}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},fieldChanged:function(t){var e=t.target;this.fieldInput[e.id]=e.value},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id,fields:this.fieldInput};d.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||f.fetchAll()},function(){t.isConfiguring=!1})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(129),u=r(a);n(110),n(111),n(116),n(108),n(117),n(115),n(112),n(114),n(107),n(118),n(106),n(113),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},dialogOpenChanged:function(t){var e=o["default"].dom(this);e.lastChild&&(e.lastChild.dialogOpen=t)},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=(0,u["default"])(t);if(e&&(0,u["default"])(e)===r)n.lastChild.dialogOpen=this.dialogOpen,n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("more-info-"+r);i.stateObj=t,i.dialogOpen=this.dialogOpen,n.appendChild(i)}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=["entity_picture","friendly_name","icon","unit_of_measurement"];e["default"]=new o["default"]({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return-1===a.indexOf(t)}):[]},getAttributeValue:function(t,e){var n=t.attributes[e];return Array.isArray(n)?n.join(", "):n}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19);var l=o["default"].entityGetters,f=o["default"].moreInfoGetters;e["default"]=new u["default"]({is:"more-info-group",behaviors:[c["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[f.currentEntity,l.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){f.callService("light","turn_on",{entity_id:t,rgb_color:[e.r,e.g,e.b]})}Object.defineProperty(e,"__esModule",{value:!0});var o=n(2),a=r(o),u=n(1),s=r(u),c=n(20),l=r(c);n(83);var f=a["default"].serviceActions,d=["brightness","rgb_color","color_temp"];e["default"]=new s["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0},ctSliderValue:{type:Number,value:0}},stateObjChanged:function(t){ var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness,this.ctSliderValue=t.attributes.color_temp),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,l["default"])(t,d)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?f.callTurnOff(this.stateObj.entityId):f.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},ctSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||f.callService("light","turn_on",{entity_id:this.stateObj.entityId,color_temp:e})},colorPicked:function(t){var e=this;return this.skipColorPicked?void(this.colorChanged=!0):(this.color=t.detail.rgb,i(this.stateObj.entityId,this.color),this.colorChanged=!1,this.skipColorPicked=!0,void(this.colorDebounce=setTimeout(function(){e.colorChanged&&i(e.stateObj.entityId,e.color),e.skipColorPicked=!1},500)))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-lock",properties:{stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""},unlockButtonVisible:{type:Boolean,value:!1},lockButtonVisible:{type:Boolean,value:!1},codeInputVisible:{type:Boolean,value:!1},codeInputEnabled:{type:Boolean,value:!1},codeFormat:{type:String,value:""},codeValid:{type:Boolean,computed:"validateCode(enteredCode, codeFormat)"}},handleUnlockTap:function(){this.callService("unlock",{code:this.enteredCode})},handleLockTap:function(){this.callService("lock",{code:this.enteredCode})},validateCode:function(t,e){var n=new RegExp(e);return null===e?!0:n.test(t)},stateObjChanged:function(t){var e=this;t&&(this.codeFormat=t.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="locked"===t.state||"unlocked"===t.state,this.unlockButtonVisible="locked"===t.state,this.lockButtonVisible="unlocked"===t.state),this.async(function(){return e.fire("iron-resize")},500)},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,s.callService("lock",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["volume_level"];e["default"]=new u["default"]({is:"more-info-media_player",properties:{stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},volumeSliderValue:{type:Number,value:0},supportsPause:{type:Boolean,value:!1},supportsVolumeSet:{type:Boolean,value:!1},supportsVolumeMute:{type:Boolean,value:!1},supportsPreviousTrack:{type:Boolean,value:!1},supportsNextTrack:{type:Boolean,value:!1},supportsTurnOn:{type:Boolean,value:!1},supportsTurnOff:{type:Boolean,value:!1},supportsVolumeButtons:{type:Boolean,value:!1},hasMediaControl:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;if(t){var n=["playing","paused","unknown"];this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.hasMediaControl=-1!==n.indexOf(t.state),this.volumeSliderValue=100*t.attributes.volume_level,this.isMuted=t.attributes.is_volume_muted,this.supportsPause=0!==(1&t.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&t.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&t.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&t.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&t.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&t.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&t.attributes.supported_media_commands),this.supportsVolumeButtons=0!==(1024&t.attributes.supported_media_commands)}this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,c["default"])(t,f)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"mdi:volume-off":"mdi:volume-high"},computeHideVolumeButtons:function(t,e){return!e||t},computeShowPlaybackControls:function(t,e){return!t&&e},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"mdi:pause":"mdi:stop":"mdi:play"},computeHidePowerButton:function(t,e,n){return t?!e:!n},handleTogglePower:function(){this.callService(this.isOff?"turn_on":"turn_off")},handlePrevious:function(){this.callService("media_previous_track")},handlePlaybackControl:function(){this.callService("media_play_pause")},handleNext:function(){this.callService("media_next_track")},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},handleVolumeUp:function(){var t=this.$.volumeUp;this.handleVolumeWorker("volume_up",t,!0)},handleVolumeDown:function(){var t=this.$.volumeDown;this.handleVolumeWorker("volume_down",t,!0)},handleVolumeWorker:function(t,e,n){var r=this;(n||void 0!==e&&e.pointerDown)&&(this.callService(t),this.async(function(){return r.handleVolumeWorker(t,e,!1)},500))},volumeSliderChanged:function(t){var e=parseFloat(t.target.value),n=e>0?e/100:0;this.callService("volume_set",{volume_level:n})},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,l.callService("media_player",t,n)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-script",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a),s=n(41),c=r(s),l=u["default"].util.parseDateTime;e["default"]=new o["default"]({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return l(t.attributes.next_rising)},computeSetting:function(t){return l(t.attributes.next_setting)},computeOrder:function(t,e){return t>e?["set","ris"]:["ris","set"]},itemCaption:function(t){return"ris"===t?"Rising ":"Setting "},itemDate:function(t){return"ris"===t?this.risingDate:this.settingDate},itemValue:function(t){return(0,c["default"])(this.itemDate(t))}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["away_mode"];e["default"]=new u["default"]({is:"more-info-thermostat",properties:{stateObj:{type:Object,observer:"stateObjChanged"},tempMin:{type:Number},tempMax:{type:Number},targetTemperatureSliderValue:{type:Number},awayToggleChecked:{type:Boolean}},stateObjChanged:function(t){this.targetTemperatureSliderValue=t.attributes.temperature,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return(0,c["default"])(t,f)},targetTemperatureSliderChanged:function(t){l.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:t.target.value})},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.attributes.away_mode?this.service_set_away(!0):e||"on"!==this.stateObj.attributes.away_mode||this.service_set_away(!1)},service_set_away:function(t){var e=this;l.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-updater",properties:{}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),n(39),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(7);var s=o["default"].serviceActions;e["default"]=new u["default"]({is:"state-card-input_select",properties:{stateObj:{type:Object},selectedOption:{type:String,observer:"selectedOptionChanged"}},computeSelected:function(t){return t.attributes.options.indexOf(t.state)},selectedOptionChanged:function(t){""!==t&&t!==this.stateObj.state&&s.callService("input_select","select_option",{option:t,entity_id:this.stateObj.entityId})},stopPropagation:function(t){t.stopPropagation()}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7);var a=["playing","paused"];e["default"]=new o["default"]({is:"state-card-media_player",properties:{stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"}},computeIsPlaying:function(t){return-1!==a.indexOf(t.state)},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e=void 0;return"music"===t.attributes.media_content_type?t.attributes.media_artist:"tvshow"===t.attributes.media_content_type?(e=t.attributes.media_series_title,t.attributes.media_season&&t.attributes.media_episode&&(e+=" S"+t.attributes.media_season+"E"+t.attributes.media_episode),e):t.attributes.app_name?t.attributes.app_name:""}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=r(i),a=n(1),u=r(a);n(7);var s=o["default"].serviceActions;e["default"]=new u["default"]({is:"state-card-rollershutter",properties:{stateObj:{type:Object}},computeIsFullyOpen:function(t){return 100===t.attributes.current_position},computeIsFullyClosed:function(t){return 0===t.attributes.current_position},onMoveUpTap:function(){s.callService("rollershutter","move_up",{entity_id:this.stateObj.entityId})},onMoveDownTap:function(){s.callService("rollershutter","move_down",{entity_id:this.stateObj.entityId})},onStopTap:function(){s.callService("rollershutter","stop",{entity_id:this.stateObj.entityId})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),a=n(2),u=r(a);n(7);var s=u["default"].serviceActions;e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object}},activateScene:function(){s.callTurnOn(this.stateObj.entityId)}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}},computeTargetTemperature:function(t){return t.attributes.temperature+" "+t.attributes.unit_of_measurement}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),n(35),e["default"]=new o["default"]({is:"state-card-toggle",properties:{stateObj:{type:Object}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(7),e["default"]=new o["default"]({is:"state-card-weblink",properties:{stateObj:{type:Object}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation(),window.open(this.stateObj.state,"_blank")}})},function(t,e){"use strict";function n(t){return{attached:function(){var e=this;this.__unwatchFns=Object.keys(this.properties).reduce(function(n,r){if(!("bindNuclear"in e.properties[r]))return n;var i=e.properties[r].bindNuclear;if(!i)throw new Error("Undefined getter specified for key "+r);return e[r]=t.evaluate(i),n.concat(t.observe(i,function(t){e[r]=t}))},[])},detached:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==u.indexOf(t.domain)?t.domain:(0,a["default"])(t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(21),a=r(o),u=["configurator","input_select","media_player","rollershutter","scene","thermostat","weblink"]},function(t,e){"use strict";function n(t){return-1!==r.indexOf(t.domain)?t.domain:"default"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var r=["light","group","sun","configurator","thermostat","script","media_player","camera","updater","alarm_control_panel","lock"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(199),i=n(15),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],a=t.evaluate(i.getters.authInfo),u=a.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,u,!0),r.setRequestHeader("X-HA-access",a.authToken),r.onload=function(){var e=void 0;try{e="application/json"===r.getResponseHeader("content-type")?JSON.parse(r.responseText):r.responseText}catch(i){e=r.responseText}r.status>199&&r.status<300?t(e):n(e)},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],r=n.useStreaming,i=void 0===r?t.evaluate(c.getters.isSupported):r,o=n.rememberAuth,a=void 0===o?!1:o,s=n.host,d=void 0===s?"":s;t.dispatch(u["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),l.actions.fetchAll(t).then(function(){t.dispatch(u["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:a}),i?c.actions.start(t,{syncOnInitialConnect:!1}):l.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?f:n;t.dispatch(u["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){(0,s.callApi)(t,"POST","log_out"),t.dispatch(u["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var a=n(14),u=r(a),s=n(5),c=n(29),l=n(31),f="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=e.isValidating=["authAttempt","isValidating"],r=(e.isInvalidAttempt=["authAttempt","isInvalid"],e.attemptErrorMessage=["authAttempt","errorMessage"],e.rememberAuth=["rememberAuth"],e.attemptAuthInfo=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}]),i=e.currentAuthToken=["authCurrent","authToken"],o=e.currentAuthInfo=[i,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.authToken=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}],e.authInfo=[n,r,o,function(t,e,n){return t?e:n}]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(null==t)throw new TypeError("Cannot destructure undefined")}function o(t,e){var n=e.authToken,r=e.host;return(0,s.toImmutable)({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function a(t,e){return i(e),f.getInitialState()}function u(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var s=n(3),c=n(14),l=r(c),f=new s.Store({getInitialState:function(){return(0,s.toImmutable)({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(l["default"].VALIDATING_AUTH_TOKEN,o),this.on(l["default"].VALID_AUTH_TOKEN,a),this.on(l["default"].INVALID_AUTH_TOKEN,u)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.authToken,r=e.host;return(0,a.toImmutable)({authToken:n,host:r})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(14),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({authToken:null,host:""})},initialize:function(){this.on(s["default"].VALID_AUTH_TOKEN,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.rememberAuth;return n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(3),a=n(14),u=r(a),s=new o.Store({getInitialState:function(){return!0},initialize:function(){this.on(u["default"].VALID_AUTH_TOKEN,i)}});e["default"]=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(c["default"].SERVER_CONFIG_LOADED,e)}function o(t){(0,u.callApi)(t,"GET","config").then(function(e){return i(t,e)})}function a(t,e){t.dispatch(c["default"].COMPONENT_LOADED,{component:e})}Object.defineProperty(e,"__esModule",{value:!0}),e.configLoaded=i,e.fetchAll=o,e.componentLoaded=a;var u=n(5),s=n(23),c=r(s)},function(t,e){"use strict";function n(t){return[["serverComponent"],function(e){return e.contains(t)}]}Object.defineProperty(e,"__esModule",{value:!0}),e.isComponentLoaded=n,e.locationGPS=[["serverConfig","latitude"],["serverConfig","longitude"],function(t,e){return{latitude:t,longitude:e}}],e.locationName=["serverConfig","location_name"],e.serverVersion=["serverConfig","serverVersion"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.component;return t.push(n)}function o(t,e){var n=e.components;return(0,u.toImmutable)(n)}function a(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var u=n(3),s=n(23),c=r(s),l=new u.Store({getInitialState:function(){return(0,u.toImmutable)([])},initialize:function(){this.on(c["default"].COMPONENT_LOADED,i),this.on(c["default"].SERVER_CONFIG_LOADED,o),this.on(c["default"].LOG_OUT,a)}});e["default"]=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.temperature_unit,u=e.time_zone,s=e.version;return(0,a.toImmutable)({latitude:n,longitude:r,location_name:i,temperature_unit:o,time_zone:u,serverVersion:s})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(23),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({latitude:null,longitude:null,location_name:"Home",temperature_unit:"°C",time_zone:"UTC",serverVersion:"unknown"})},initialize:function(){this.on(s["default"].SERVER_CONFIG_LOADED,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){t.dispatch(f["default"].ENTITY_HISTORY_DATE_SELECTED,{date:e})}function a(t){var e=arguments.length<=1||void 0===arguments[1]?null:arguments[1];t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),(0,c.callApi)(t,"GET",n).then(function(e){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})},function(){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,{})})}function u(t,e){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_START,{date:e}),(0,c.callApi)(t,"GET","history/period/"+e).then(function(n){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})},function(){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_ERROR,{})})}function s(t){var e=t.evaluate(h.currentDate);return u(t,e)}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=o,e.fetchRecent=a,e.fetchDate=u,e.fetchSelectedDate=s;var c=n(5),l=n(11),f=i(l),d=n(45),h=r(d)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date;return(0,s["default"])(n)}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(32),s=r(u),c=n(11),l=r(c),f=new a.Store({getInitialState:function(){var t=new Date;return t.setDate(t.getDate()-1),(0,s["default"])(t)},initialize:function(){this.on(l["default"].ENTITY_HISTORY_DATE_SELECTED,i),this.on(l["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,(0,a.toImmutable)({})):t.withMutations(function(t){r.forEach(function(e){return t.setIn([n,e[0].entity_id],(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),o=n(11),a=r(o),u=new i.Store({getInitialState:function(){return!1},initialize:function(){this.on(a["default"].ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].LOG_OUT,function(){return!1})}});e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.stateHistory;return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(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(c,r)})}function o(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c="ALL_ENTRY_FETCH",l=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(10),o=n(16),a=r(o),u=(0,i.createApiActions)(a["default"]);e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.visibleEntityMap=e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(16),a=r(o),u=(e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]));e.byId=(0,i.createByIdGetter)(a["default"]),e.visibleEntityMap=[u,function(t){return t.filter(function(t){return!t.attributes.hidden})}]},function(t,e,n){"use strict";function r(t){return(0,i.callApi)(t,"GET","error_log")}Object.defineProperty(e,"__esModule",{value:!0}),e.fetchErrorLog=r;var i=n(5)},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}Object.defineProperty(e,"__esModule",{value:!0}),e.actions=void 0;var i=n(148),o=r(i);e.actions=o},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=n(10),a=n(27),u=n(47),s=r(u),c=(0,o.createApiActions)(s["default"]);c.fireEvent=function(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];return(0,i.callApi)(t,"POST","events/"+e,n).then(function(){a.actions.createNotification(t,"Event "+e+" successful fired!")})},e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(47),a=r(o);e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]),e.byId=(0,i.createByIdGetter)(a["default"])},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(s["default"].LOGBOOK_DATE_SELECTED,{date:e})}function o(t,e){t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_START,{date:e}),(0,a.callApi)(t,"GET","logbook/"+e).then(function(n){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_SUCCESS,{date:e,entries:n})},function(){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_ERROR,{})})}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=i,e.fetchDate=o;var a=n(5),u=n(12),s=r(u)},function(t,e,n){"use strict";function r(t){return!t||(new Date).getTime()-t>o}Object.defineProperty(e,"__esModule",{value:!0}),e.isLoadingEntries=e.currentEntries=e.isCurrentStale=e.currentDate=void 0;var i=n(3),o=6e4,a=e.currentDate=["currentLogbookDate"];e.isCurrentStale=[a,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}],e.currentEntries=[a,["logbookEntries"],function(t,e){return e.get(t)||(0,i.toImmutable)([])}],e.isLoadingEntries=["isLoadingLogbookEntries"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentLogbookDate:u["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:h["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(156),u=i(a),s=n(157),c=i(s),l=n(158),f=i(l),d=n(159),h=i(d),p=n(152),_=r(p),v=n(153),y=r(v);e.actions=_,e.getters=y},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var u=function(){function t(t,e){for(var n=0;nt;t+=2){var e=rt[t],n=rt[t+1];e(n),rt[t]=void 0,rt[t+1]=void 0}$=0}function y(){try{var t=n(215);return W=t.runOnLoop||t.runOnContext,d()}catch(e){return _()}}function m(){}function g(){return new TypeError("You cannot resolve a promise with itself")}function b(){return new TypeError("A promises callback cannot return that same promise.")}function S(t){try{return t.then}catch(e){return ut.error=e,ut}}function w(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function O(t,e,n){Z(function(t){var r=!1,i=w(n,e,function(n){r||(r=!0,e!==n?E(t,n):D(t,n))},function(e){r||(r=!0,C(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,C(t,i))},t)}function M(t,e){e._state===ot?D(t,e._result):e._state===at?C(t,e._result):j(e,void 0,function(e){E(t,e)},function(e){C(t,e)})}function I(t,e){if(e.constructor===t.constructor)M(t,e);else{var n=S(e);n===ut?C(t,ut.error):void 0===n?D(t,e):u(n)?O(t,e,n):D(t,e)}}function E(t,e){t===e?C(t,g()):a(e)?I(t,e):D(t,e)}function T(t){t._onerror&&t._onerror(t._result),P(t)}function D(t,e){t._state===it&&(t._result=e,t._state=ot,0!==t._subscribers.length&&Z(P,t))}function C(t,e){t._state===it&&(t._state=at,t._result=e,Z(T,t))}function j(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ot]=n,i[o+at]=r,0===o&&t._state&&Z(P,t)}function P(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,a=0;aa;a++)j(r.resolve(t[a]),void 0,e,n);return i}function Y(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(m);return E(n,t),n}function z(t){var e=this,n=new e(m);return C(n,t),n}function U(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function V(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function F(t){this._id=pt++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(u(t)||U(),this instanceof F||V(),N(this,t))}function G(){var t;if("undefined"!=typeof i)t=i;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=_t)}var B;B=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var W,q,K,J=B,$=0,Z=({}.toString,function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(q?q(v):K())}),X="undefined"!=typeof window?window:void 0,Q=X||{},tt=Q.MutationObserver||Q.WebKitMutationObserver,et="undefined"!=typeof t&&"[object process]"==={}.toString.call(t),nt="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,rt=new Array(1e3);K=et?f():tt?h():nt?p():void 0===X?y():_();var it=void 0,ot=1,at=2,ut=new A,st=new A;R.prototype._validateInput=function(t){return J(t)},R.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},R.prototype._init=function(){this._result=new Array(this.length)};var ct=R;R.prototype._enumerate=function(){for(var t=this,e=t.length,n=t.promise,r=t._input,i=0;n._state===it&&e>i;i++)t._eachEntry(r[i],i)},R.prototype._eachEntry=function(t,e){var n=this,r=n._instanceConstructor;s(t)?t.constructor===r&&t._state!==it?(t._onerror=null,n._settledAt(t._state,e,t._result)):n._willSettleAt(r.resolve(t),e):(n._remaining--,n._result[e]=t)},R.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===it&&(r._remaining--,t===at?C(i,n):r._result[e]=n),0===r._remaining&&D(i,r._result)},R.prototype._willSettleAt=function(t,e){var n=this;j(t,void 0,function(t){n._settledAt(ot,e,t)},function(t){n._settledAt(at,e,t)})};var lt=x,ft=H,dt=Y,ht=z,pt=0,_t=F;F.all=lt,F.race=ft,F.resolve=dt,F.reject=ht,F._setScheduler=c,F._setAsap=l,F._asap=Z,F.prototype={constructor:F,then:function(t,e){var n=this,r=n._state;if(r===ot&&!t||r===at&&!e)return this;var i=new this.constructor(m),o=n._result;if(r){var a=arguments[r-1];Z(function(){L(r,i,a,o)})}else j(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var vt=G,yt={Promise:_t,polyfill:vt};n(213).amd?(r=function(){return yt}.call(e,n,e,o),!(void 0!==r&&(o.exports=r))):"undefined"!=typeof o&&o.exports?o.exports=yt:"undefined"!=typeof this&&(this.ES6Promise=yt),vt()}).call(this)}).call(e,n(214),function(){return this}(),n(68)(t))},function(t,e,n){var r=n(63),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){var r=n(63),i=n(201),o=n(64),a="[object Array]",u=Object.prototype,s=u.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==a};t.exports=l},function(t,e,n){function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&a.test(t)}var i=n(65),o=n(64),a=/^\[object .+?Constructor\]$/,u=Object.prototype,s=Function.prototype.toString,c=u.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e){function n(t){return function(e){return null==e?void 0:e[t]}}t.exports=n},function(t,e,n){var r=n(204),i=r("length");t.exports=i},function(t,e,n){function r(t){return null!=t&&o(i(t))}var i=n(205),o=n(209);t.exports=r},function(t,e){function n(t,e){return t="number"==typeof t||r.test(t)?+t:-1,e=null==e?i:e,t>-1&&t%1==0&&e>t}var r=/^\d+$/,i=9007199254740991;t.exports=n},function(t,e,n){function r(t,e,n){if(!a(n))return!1;var r=typeof e;if("number"==r?i(n)&&o(e,n.length):"string"==r&&e in n){var u=n[e];return t===t?t===u:u!==u}return!1}var i=n(206),o=n(207),a=n(210);t.exports=r},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){function r(t,e,n){n&&i(t,e,n)&&(e=n=void 0),t=+t||0,n=null==n?1:+n||0,null==e?(e=t,t=0):e=+e||0;for(var r=-1,u=a(o((e-t)/(n||1)),0),s=Array(u);++r1)for(var n=1;n \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 40ff847f2d0..4a85f5fa63a 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 40ff847f2d0670367a2fd29e3340a4f94ab1ff49 +Subproject commit 4a85f5fa63a1809990b91f77bb64a1fd6a363e04 From 6847dac582eb6c5f6b5363a69a00d641e54df698 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 20:58:01 -0800 Subject: [PATCH 109/186] Expose current time in templates Fixes #1282 --- homeassistant/util/template.py | 5 ++++- tests/util/test_template.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index d9b1990a252..3774e9bb898 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -12,6 +12,7 @@ from jinja2.sandbox import ImmutableSandboxedEnvironment from homeassistant.const import STATE_UNKNOWN from homeassistant.exceptions import TemplateError +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) _SENTINEL = object() @@ -45,7 +46,9 @@ def render(hass, template, variables=None, **kwargs): return ENV.from_string(template, { 'states': AllStates(hass), 'is_state': hass.states.is_state, - 'is_state_attr': hass.states.is_state_attr + 'is_state_attr': hass.states.is_state_attr, + 'now': dt_util.now, + 'utcnow': dt_util.utcnow, }).render(kwargs).strip() except jinja2.TemplateError as err: raise TemplateError(err) diff --git a/tests/util/test_template.py b/tests/util/test_template.py index 09e0e154888..a3b680c71eb 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -6,8 +6,11 @@ Tests Home Assistant template util methods. """ # pylint: disable=too-many-public-methods import unittest +from unittest.mock import patch + from homeassistant.exceptions import TemplateError from homeassistant.util import template +import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -143,3 +146,19 @@ class TestUtilTemplate(unittest.TestCase): self.assertEqual( 'unknown', template.render(self.hass, '{{ states("test.object2") }}')) + + @patch('homeassistant.core.dt_util.now', return_value=dt_util.now()) + @patch('homeassistant.util.template.TemplateEnvironment.is_safe_callable', + return_value=True) + def test_now_function(self, mock_is_safe, mock_now): + self.assertEqual( + dt_util.now().isoformat(), + template.render(self.hass, '{{ now().isoformat() }}')) + + @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) + @patch('homeassistant.util.template.TemplateEnvironment.is_safe_callable', + return_value=True) + def test_utcnow_function(self, mock_is_safe, mock_utcnow): + self.assertEqual( + dt_util.utcnow().isoformat(), + template.render(self.hass, '{{ utcnow().isoformat() }}')) From 6ac54b20c7b36a50eec0bea432f9329ea32d5c77 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 21:58:53 -0800 Subject: [PATCH 110/186] Add template distance helper --- homeassistant/util/template.py | 65 +++++++++++++++++++++++++-- tests/util/test_template.py | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index 3774e9bb898..3006a426d43 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -10,9 +10,10 @@ import logging import jinja2 from jinja2.sandbox import ImmutableSandboxedEnvironment -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.core import State from homeassistant.exceptions import TemplateError -import homeassistant.util.dt as dt_util +from homeassistant.util import convert, dt as dt_util, location _LOGGER = logging.getLogger(__name__) _SENTINEL = object() @@ -42,11 +43,14 @@ def render(hass, template, variables=None, **kwargs): if variables is not None: kwargs.update(variables) + location_helper = LocationHelpers(hass) + try: return ENV.from_string(template, { - 'states': AllStates(hass), + 'distance': location_helper.distance, 'is_state': hass.states.is_state, 'is_state_attr': hass.states.is_state_attr, + 'states': AllStates(hass), 'now': dt_util.now, 'utcnow': dt_util.utcnow, }).render(kwargs).strip() @@ -88,6 +92,61 @@ class DomainStates(object): key=lambda state: state.entity_id)) +class LocationHelpers(object): + """Class to expose distance helpers to templates.""" + + def __init__(self, hass): + """Initialize distance helpers.""" + self._hass = hass + + def distance(self, *args): + """Calculate distance. + + Will calculate distance from home to a point or between points. + Points can be passed in using state objects or lat/lng coordinates. + """ + locations = [] + + to_process = list(args) + + while to_process: + value = to_process.pop(0) + + if isinstance(value, State): + latitude = value.attributes.get(ATTR_LATITUDE) + longitude = value.attributes.get(ATTR_LONGITUDE) + + if latitude is None or longitude is None: + _LOGGER.warning( + 'Distance:State does not contains a location: %s', + value) + return None + + else: + # We expect this and next value to be lat&lng + if not to_process: + _LOGGER.warning( + 'Distance:Expected latitude and longitude, got %s', + value) + return None + + value_2 = to_process.pop(0) + latitude = convert(value, float) + longitude = convert(value_2, float) + + if latitude is None or longitude is None: + _LOGGER.warning('Distance:Unable to process latitude and ' + 'longitude: %s, %s', value, value_2) + return None + + locations.append((latitude, longitude)) + + if len(locations) == 1: + return self._hass.config.distance(*locations[0]) + + return location.distance(*locations[0] + locations[1]) + + def forgiving_round(value, precision=0): """ Rounding method that accepts strings. """ try: diff --git a/tests/util/test_template.py b/tests/util/test_template.py index a3b680c71eb..cac5978393d 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -162,3 +162,84 @@ class TestUtilTemplate(unittest.TestCase): self.assertEqual( dt_util.utcnow().isoformat(), template.render(self.hass, '{{ utcnow().isoformat() }}')) + + def test_distance_function_with_1_state(self): + self.hass.states.set('test.object', 'happy', { + 'latitude': 32.87336, + 'longitude': -117.22943, + }) + + self.assertEqual( + '187', + template.render( + self.hass, '{{ distance(states.test.object) | round }}')) + + def test_distance_function_with_2_states(self): + self.hass.states.set('test.object', 'happy', { + 'latitude': 32.87336, + 'longitude': -117.22943, + }) + + self.hass.states.set('test.object_2', 'happy', { + 'latitude': self.hass.config.latitude, + 'longitude': self.hass.config.longitude, + }) + + self.assertEqual( + '187', + template.render( + self.hass, + '{{ distance(states.test.object, states.test.object_2)' + '| round }}')) + + def test_distance_function_with_1_coord(self): + self.assertEqual( + '187', + template.render( + self.hass, '{{ distance("32.87336", "-117.22943") | round }}')) + + def test_distance_function_with_2_coords(self): + self.assertEqual( + '187', + template.render( + self.hass, + '{{ distance("32.87336", "-117.22943", %s, %s) | round }}' + % (self.hass.config.latitude, self.hass.config.longitude))) + + def test_distance_function_with_1_state_1_coord(self): + self.hass.states.set('test.object_2', 'happy', { + 'latitude': self.hass.config.latitude, + 'longitude': self.hass.config.longitude, + }) + + self.assertEqual( + '187', + template.render( + self.hass, + '{{ distance("32.87336", "-117.22943", states.test.object_2) ' + '| round }}')) + + self.assertEqual( + '187', + template.render( + self.hass, + '{{ distance(states.test.object_2, "32.87336", "-117.22943") ' + '| round }}')) + + def test_distance_function_return_None_if_invalid_state(self): + self.hass.states.set('test.object_2', 'happy', { + 'latitude': 10, + }) + + self.assertEqual( + 'None', + template.render( + self.hass, + '{{ distance(states.test.object_2) | round }}')) + + def test_distance_function_return_None_if_invalid_coord(self): + self.assertEqual( + 'None', + template.render( + self.hass, + '{{ distance("123", "abc") | round }}')) From 9ad2cf7b7a470ae724a0b97ee8b04b36f96c128d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Feb 2016 21:59:16 -0800 Subject: [PATCH 111/186] Adjust template rounding tests --- homeassistant/util/template.py | 9 +++++---- tests/util/test_template.py | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/util/template.py b/homeassistant/util/template.py index 3006a426d43..0cbddb2aa16 100644 --- a/homeassistant/util/template.py +++ b/homeassistant/util/template.py @@ -148,17 +148,17 @@ class LocationHelpers(object): def forgiving_round(value, precision=0): - """ Rounding method that accepts strings. """ + """Rounding filter that accepts strings.""" try: value = round(float(value), precision) return int(value) if precision == 0 else value - except ValueError: + except (ValueError, TypeError): # If value can't be converted to float return value def multiply(value, amount): - """ Converts to float and multiplies value. """ + """Filter to convert value to float and multiply it.""" try: return float(value) * amount except ValueError: @@ -167,9 +167,10 @@ def multiply(value, amount): class TemplateEnvironment(ImmutableSandboxedEnvironment): - """ Home Assistant template environment. """ + """Home Assistant template environment.""" def is_safe_callable(self, obj): + """Test if callback is safe.""" return isinstance(obj, AllStates) or super().is_safe_callable(obj) ENV = TemplateEnvironment() diff --git a/tests/util/test_template.py b/tests/util/test_template.py index cac5978393d..737bb87fce9 100644 --- a/tests/util/test_template.py +++ b/tests/util/test_template.py @@ -62,9 +62,6 @@ class TestUtilTemplate(unittest.TestCase): self.hass, '{{ states.sensor.temperature.state | round(1) }}')) - def test_rounding_value2(self): - self.hass.states.set('sensor.temperature', 12.78) - self.assertEqual( '128', template.render( @@ -72,6 +69,21 @@ class TestUtilTemplate(unittest.TestCase): '{{ states.sensor.temperature.state | multiply(10) | round }}' )) + def test_rounding_value_get_original_value_on_error(self): + self.assertEqual( + 'None', + template.render( + self.hass, + '{{ None | round }}' + )) + + self.assertEqual( + 'no_number', + template.render( + self.hass, + '{{ "no_number" | round }}' + )) + def test_passing_vars_as_keywords(self): self.assertEqual( '127', template.render(self.hass, '{{ hello }}', hello=127)) From 2f665285955ebd4a254d542e95cb15c3c4260d64 Mon Sep 17 00:00:00 2001 From: Christian Fredborg Braedstrup Date: Sat, 20 Feb 2016 21:17:48 +0100 Subject: [PATCH 112/186] Added support for d-link W215 smart plug --- .coveragerc | 1 + homeassistant/components/switch/dlink.py | 79 ++++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 83 insertions(+) create mode 100644 homeassistant/components/switch/dlink.py diff --git a/.coveragerc b/.coveragerc index 376648c36f0..aa010d67b2b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -142,6 +142,7 @@ omit = homeassistant/components/sensor/worldclock.py homeassistant/components/switch/arest.py homeassistant/components/switch/edimax.py + homeassistant/components/switch/dlink.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/mystrom.py homeassistant/components/switch/orvibo.py diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/switch/dlink.py new file mode 100644 index 00000000000..00b8ac0dab1 --- /dev/null +++ b/homeassistant/components/switch/dlink.py @@ -0,0 +1,79 @@ +""" +homeassistant.components.switch.dlink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for D-link W215 smart switch. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.dlink/ +""" +import logging + +from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.helpers import validate_config + +# constants +DEFAULT_USERNAME = 'admin' +DEFAULT_PASSWORD = '' +DEVICE_DEFAULT_NAME = 'D-link Smart Plug W215' +REQUIREMENTS = ['https://github.com/LinuxChristian/pyW215/archive/' + 'v0.1.1.zip#pyW215==0.1.1'] + +# setup logger +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return D-Link Smart Plugs. """ + from pyW215.pyW215 import SmartPlug + + # check for required values in configuration file + if not validate_config({DOMAIN: config}, + {DOMAIN: [CONF_HOST]}, + _LOGGER): + return False + + host = config.get(CONF_HOST) + username = config.get(CONF_USERNAME, DEFAULT_USERNAME) + password = str(config.get(CONF_PASSWORD, DEFAULT_PASSWORD)) + name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME) + + add_devices_callback([SmartPlugSwitch(SmartPlug(host, + password, + username), + name)]) + + +class SmartPlugSwitch(SwitchDevice): + """ Represents an d-link Smart Plug switch. """ + def __init__(self, smartplug, name): + self.smartplug = smartplug + self._name = name + + @property + def name(self): + """ Returns the name of the Smart Plug, if any. """ + return self._name + + @property + def current_power_watt(self): + """ Current power usage in watt. """ + try: + return float(self.smartplug.current_consumption) + except ValueError: + return None + + @property + def is_on(self): + """ True if switch is on. """ + return self.smartplug.state == 'ON' + + def turn_on(self, **kwargs): + """ Turns the switch on. """ + self.smartplug.state = 'ON' + + def turn_off(self): + """ Turns the switch off. """ + self.smartplug.state = 'OFF' diff --git a/requirements_all.txt b/requirements_all.txt index f251d900deb..c3e56d357f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -69,6 +69,9 @@ https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip#RFXtrx==0.4 # homeassistant.components.sensor.netatmo https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0 +# homeassistant.components.switch.dlink +https://github.com/LinuxChristian/pyW215/archive/v0.1.1.zip#pyW215==0.1.1 + # homeassistant.components.alarm_control_panel.alarmdotcom https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1 From d1a4dc77d1742e31b379b865a8da97e7f317b881 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Sun, 21 Feb 2016 10:01:03 +0200 Subject: [PATCH 113/186] Disable pylint warning --- homeassistant/components/binary_sensor/zwave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 8803af3bdcd..eb9f446febe 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -41,6 +41,7 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): def __init__(self, value, sensor_class): self._sensor_type = sensor_class + # pylint: disable=import-error from openzwave.network import ZWaveNetwork from pydispatch import dispatcher From 3da554a198aad1b7dffbdaabe72678d3555d29fd Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Sun, 21 Feb 2016 12:01:32 -0500 Subject: [PATCH 114/186] Update wink version --- homeassistant/components/garage_door/wink.py | 2 +- homeassistant/components/light/wink.py | 2 +- homeassistant/components/lock/wink.py | 2 +- homeassistant/components/sensor/wink.py | 2 +- homeassistant/components/switch/wink.py | 2 +- homeassistant/components/wink.py | 2 +- requirements_all.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/garage_door/wink.py b/homeassistant/components/garage_door/wink.py index b32b20dde0a..c863be2c513 100644 --- a/homeassistant/components/garage_door/wink.py +++ b/homeassistant/components/garage_door/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 2fed8904762..07f18b0d612 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 1900a864877..675b138d2c8 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.lock import LockDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index 3eca0858433..8f56742419d 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.const import CONF_ACCESS_TOKEN, STATE_CLOSED, STATE_OPEN from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 23ba434ad2b..35e1fabc9a0 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.wink import WinkToggleDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 85a7e995f98..5e1572a90d9 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.loader import get_component DOMAIN = "wink" -REQUIREMENTS = ['python-wink==0.6.0'] +REQUIREMENTS = ['python-wink==0.6.1'] DISCOVER_LIGHTS = "wink.lights" DISCOVER_SWITCHES = "wink.switches" diff --git a/requirements_all.txt b/requirements_all.txt index c3e56d357f7..26ca2b92072 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -206,7 +206,7 @@ python-twitch==1.2.0 # homeassistant.components.lock.wink # homeassistant.components.sensor.wink # homeassistant.components.switch.wink -python-wink==0.6.0 +python-wink==0.6.1 # homeassistant.components.keyboard pyuserinput==0.1.9 From b961b5037fa4c671299eb6d5ea5b250a1bc69c03 Mon Sep 17 00:00:00 2001 From: Andrew Thigpen Date: Sun, 21 Feb 2016 11:11:35 -0600 Subject: [PATCH 115/186] Only turn on scripts that are not currently running. Prevents an errant API call from advancing a currently executing delay step before it should. --- homeassistant/components/script.py | 8 +++++-- tests/components/test_script.py | 37 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 59b55a1289b..dbb145ebb1d 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -70,8 +70,12 @@ def setup(hass, config): """ Execute a service call to script. \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 4a85f5fa63a..bee75620fec 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 4a85f5fa63a1809990b91f77bb64a1fd6a363e04 +Subproject commit bee75620fec43bad4f44574e6e9550da7ff1d2ec From e4485dcf3d63e18c9f188644781483487afc0203 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Mon, 22 Feb 2016 09:40:27 +0100 Subject: [PATCH 126/186] Updated structure, added more tests --- homeassistant/components/automation/state.py | 70 +++++++++----------- tests/components/automation/test_state.py | 19 ++++++ 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 62822f3d7e8..b9c4164e584 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -26,27 +26,28 @@ CONF_FOR = "for" def get_time_config(config): """ Helper function to extract the time specified in the config """ - if CONF_FOR in config: - hours = config[CONF_FOR].get(CONF_HOURS) - minutes = config[CONF_FOR].get(CONF_MINUTES) - seconds = config[CONF_FOR].get(CONF_SECONDS) + if CONF_FOR not in config: + return None - if hours is None and minutes is None and seconds is None: - logging.getLogger(__name__).error( - "Received invalid value for '%s': %s", - config[CONF_FOR], CONF_FOR) - return False + hours = config[CONF_FOR].get(CONF_HOURS) + minutes = config[CONF_FOR].get(CONF_MINUTES) + seconds = config[CONF_FOR].get(CONF_SECONDS) - if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: - logging.getLogger(__name__).error( - "For: requires a to: value'%s': %s", - config[CONF_FOR], CONF_FOR) - return False + if hours is None and minutes is None and seconds is None: + logging.getLogger(__name__).error( + "Received invalid value for '%s': %s", + config[CONF_FOR], CONF_FOR) + return None - return timedelta(hours=(hours or 0.0), - minutes=(minutes or 0.0), - seconds=(seconds or 0.0)) - return False + if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: + logging.getLogger(__name__).error( + "For: requires a to: value'%s': %s", + config[CONF_FOR], CONF_FOR) + return None + + return timedelta(hours=(hours or 0.0), + minutes=(minutes or 0.0), + seconds=(seconds or 0.0)) def trigger(hass, config, action): @@ -56,20 +57,19 @@ def trigger(hass, config, action): if entity_id is None: logging.getLogger(__name__).error( "Missing trigger configuration key %s", CONF_ENTITY_ID) - return False + return None from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL + time_delta = get_time_config(config) if isinstance(from_state, bool) or isinstance(to_state, bool): logging.getLogger(__name__).error( 'Config error. Surround to/from values with quotes.') - return False + return None - if CONF_FOR in config: - time_delta = get_time_config(config) - if time_delta is False: - return False + if CONF_FOR in config and time_delta is None: + return None def state_automation_listener(entity, from_s, to_s): """ Listens for state changes and calls action. """ @@ -89,7 +89,7 @@ def trigger(hass, config, action): hass.bus.remove_listener( EVENT_STATE_CHANGED, for_state_listener) - if CONF_FOR in config: + if time_delta is not None: target_tm = dt_util.utcnow() + time_delta for_time_listener = track_point_in_time( hass, state_for_listener, target_tm) @@ -110,28 +110,24 @@ def if_action(hass, config): entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) - if CONF_FOR in config: - time_delta = get_time_config(config) - if not time_delta: - return False - if entity_id is None or state is None: logging.getLogger(__name__).error( "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, CONF_STATE) return None + time_delta = get_time_config(config) + if CONF_FOR in config and time_delta is None: + return None + state = str(state) def if_state(): """ Test if condition. """ is_state = hass.states.is_state(entity_id, state) - - if CONF_FOR not in config: - return is_state - - target_tm = dt_util.utcnow() - time_delta - return (is_state and - target_tm > hass.states.get(entity_id).last_changed) + return (time_delta is None and is_state or + time_delta is not None and + dt_util.utcnow() - time_delta > + hass.states.get(entity_id).last_changed) return if_state diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index b5f0e31ddc8..d9c458a151d 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -462,3 +462,22 @@ class TestAutomationState(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + + def test_if_fails_setup_for_without_time(self): + self.assertIsNone(state.if_action( + self.hass, { + 'platform': 'state', + 'entity_id': 'test.entity', + 'state': 'on', + 'for': {}, + })) + + def test_if_fails_setup_for_without_entity(self): + self.assertIsNone(state.if_action( + self.hass, { + 'platform': 'state', + 'state': 'on', + 'for': { + 'seconds': 5 + }, + })) From fd3ea95b8221ea95d67959e0787b434499923f48 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Feb 2016 10:11:46 +0100 Subject: [PATCH 127/186] Add docstrings, fix typos, and update docsstring for PEP257 --- .../components/binary_sensor/__init__.py | 2 -- .../components/binary_sensor/apcupsd.py | 8 +++----- .../components/binary_sensor/arest.py | 18 +++++++---------- .../components/binary_sensor/bloomsky.py | 2 +- .../binary_sensor/command_sensor.py | 20 +++++++++---------- .../components/binary_sensor/demo.py | 14 ++++++------- .../components/binary_sensor/mqtt.py | 14 ++++++------- .../components/binary_sensor/mysensors.py | 5 +++-- .../components/binary_sensor/nest.py | 8 +++----- .../components/binary_sensor/nx584.py | 6 ++++-- .../components/binary_sensor/rest.py | 14 ++++++------- .../components/binary_sensor/rpi_gpio.py | 15 ++++++-------- homeassistant/components/binary_sensor/tcp.py | 13 ++++++------ .../components/binary_sensor/zigbee.py | 4 +--- .../components/binary_sensor/zwave.py | 13 +++++------- 15 files changed, 67 insertions(+), 89 deletions(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 1be1c4423ee..c3e13047aad 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to interface with binary sensors (sensors which only know two states) that can be monitored. diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index 6d3ddb2c2fa..8a08ef3a6ac 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.apcupsd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a binary sensor to track online status of a UPS. For more details about this platform, please refer to the documentation at @@ -14,12 +12,12 @@ DEFAULT_NAME = "UPS Online Status" def setup_platform(hass, config, add_entities, discovery_info=None): - """ Instantiate an OnlineStatus binary sensor entity and add it to HA. """ + """Instantiate an OnlineStatus binary sensor entity.""" add_entities((OnlineStatus(config, apcupsd.DATA),)) class OnlineStatus(BinarySensorDevice): - """ Binary sensor to represent UPS online status. """ + """Binary sensor to represent UPS online status.""" def __init__(self, config, data): self._config = config self._data = data @@ -33,7 +31,7 @@ class OnlineStatus(BinarySensorDevice): @property def is_on(self): - """ True if the UPS is online, else False. """ + """True if the UPS is online, else False.""" return self._state == apcupsd.VALUE_ONLINE def update(self): diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 3a7eac35bb5..b56d906b2e6 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.arest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The arest sensor will consume an exposed aREST API of a device. For more details about this platform, please refer to the documentation at @@ -24,8 +22,7 @@ CONF_PIN = 'pin' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the aREST binary sensor. """ - + """Get the aREST binary sensor.""" resource = config.get(CONF_RESOURCE) pin = config.get(CONF_PIN) @@ -56,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes, too-many-arguments class ArestBinarySensor(BinarySensorDevice): - """ Implements an aREST binary sensor for a pin. """ + """Implements an aREST binary sensor for a pin.""" def __init__(self, arest, resource, name, pin): self.arest = arest @@ -73,23 +70,22 @@ class ArestBinarySensor(BinarySensorDevice): @property def name(self): - """ The name of the binary sensor. """ + """The name of the binary sensor.""" return self._name @property def is_on(self): - """ True if the binary sensor is on. """ + """True if the binary sensor is on.""" return bool(self.arest.data.get('state')) def update(self): - """ Gets the latest data from aREST API. """ + """Gets the latest data from aREST API.""" self.arest.update() # pylint: disable=too-few-public-methods class ArestData(object): - """ Class for handling the data retrieval for pins. """ - + """Class for handling the data retrieval for pins.""" def __init__(self, resource, pin): self._resource = resource self._pin = pin @@ -97,7 +93,7 @@ class ArestData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from aREST device. """ + """Gets the latest data from aREST device.""" try: response = requests.get('{}/digital/{}'.format( self._resource, self._pin), timeout=10) diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py index ce24a0c1015..32ccad6df91 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -38,7 +38,7 @@ class BloomSkySensor(BinarySensorDevice): """ Represents a single binary sensor in a BloomSky device. """ def __init__(self, bs, device, sensor_name): - """Initialize a bloomsky binary sensor.""" + """Initialize a BloomSky binary sensor.""" self._bloomsky = bs self._device_id = device["DeviceID"] self._sensor_name = sensor_name diff --git a/homeassistant/components/binary_sensor/command_sensor.py b/homeassistant/components/binary_sensor/command_sensor.py index 45b950cec8f..677a4f2b95c 100644 --- a/homeassistant/components/binary_sensor/command_sensor.py +++ b/homeassistant/components/binary_sensor/command_sensor.py @@ -1,8 +1,6 @@ """ -homeassistant.components.binary_sensor.command_sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure custom shell commands to turn a value -into a logical value for a binary sensor. +Allows to configure custom shell commands to turn a value into a logical value +for a binary sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.command/ @@ -27,8 +25,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Add the Command Sensor. """ - + """Add the Command Sensor.""" if config.get('command') is None: _LOGGER.error('Missing required variable: "command"') return False @@ -47,8 +44,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments class CommandBinarySensor(BinarySensorDevice): - """ Represents a binary sensor that is returning - a value of a shell commands. """ + """ + Represents a binary sensor that is returning a value of a shell commands. + """ def __init__(self, hass, data, name, payload_on, payload_off, value_template): self._hass = hass @@ -62,16 +60,16 @@ class CommandBinarySensor(BinarySensorDevice): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def is_on(self): - """ True if the binary sensor is on. """ + """True if the binary sensor is on.""" return self._state def update(self): - """ Gets the latest data and updates the state. """ + """Gets the latest data and updates the state.""" self.data.update() value = self.data.value diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index 90c7accf512..3759ccb7662 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -1,13 +1,11 @@ """ -homeassistant.components.binary_sensor.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo platform that has two fake binary sensors. """ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo binary sensors. """ + """Sets up the Demo binary sensors """ add_devices([ DemoBinarySensor('Basement Floor Wet', False, 'moisture'), DemoBinarySensor('Movement Backyard', True, 'motion'), @@ -15,7 +13,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoBinarySensor(BinarySensorDevice): - """ A Demo binary sensor. """ + """A Demo binary sensor.""" def __init__(self, name, state, sensor_class): self._name = name @@ -24,20 +22,20 @@ class DemoBinarySensor(BinarySensorDevice): @property def sensor_class(self): - """ Return our class. """ + """Return the class of this sensor.""" return self._sensor_type @property def should_poll(self): - """ No polling needed for a demo binary sensor. """ + """No polling needed for a demo binary sensor.""" return False @property def name(self): - """ Returns the name of the binary sensor. """ + """Returns the name of the binary sensor.""" return self._name @property def is_on(self): - """ True if the binary sensor is on. """ + """True if the binary sensor is on.""" return self._state diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index e8f09e80ae2..139720bb117 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to configure a MQTT binary sensor. For more details about this platform, please refer to the documentation at @@ -25,7 +23,7 @@ DEPENDENCIES = ['mqtt'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Add MQTT binary sensor. """ + """Add MQTT binary sensor.""" if config.get('state_topic') is None: _LOGGER.error('Missing required variable: state_topic') @@ -43,7 +41,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttBinarySensor(BinarySensorDevice): - """ Represents a binary sensor that is updated by MQTT. """ + """Represents a binary sensor that is updated by MQTT.""" def __init__(self, hass, name, state_topic, qos, payload_on, payload_off, value_template): self._hass = hass @@ -55,7 +53,7 @@ class MqttBinarySensor(BinarySensorDevice): self._qos = qos def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" if value_template is not None: payload = template.render_with_possible_json_value( hass, value_template, payload) @@ -70,15 +68,15 @@ class MqttBinarySensor(BinarySensorDevice): @property def should_poll(self): - """ No polling needed. """ + """No polling needed.""" return False @property def name(self): - """ The name of the binary sensor. """ + """The name of the binary sensor.""" return self._name @property def is_on(self): - """ True if the binary sensor is on. """ + """True if the binary sensor is on.""" return self._state diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index ffa6742697a..77b4432e000 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -54,7 +54,8 @@ class MySensorsBinarySensor(BinarySensorDevice): def __init__( self, gateway, node_id, child_id, name, value_type, child_type): - """Setup class attributes on instantiation. + """ + Setup class attributes on instantiation. Args: gateway (GatewayWrapper): Gateway object. @@ -124,7 +125,7 @@ class MySensorsBinarySensor(BinarySensorDevice): @property def sensor_class(self): - """Return the class of this sensor, from SENSOR_CASSES.""" + """Return the class of this sensor, from SENSOR_CLASSES.""" pres = self.gateway.const.Presentation class_map = { pres.S_DOOR: 'opening', diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index a3c2b252b15..f686a368100 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.nest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Nest Thermostat Binary Sensors. For more details about this platform, please refer to the documentation at @@ -27,7 +25,7 @@ BINARY_TYPES = ['fan', def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup Nest binary sensors. """ + """Setup Nest binary sensors.""" logger = logging.getLogger(__name__) try: @@ -48,9 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NestBinarySensor(NestSensor, BinarySensorDevice): - """ Represents a Nest binary sensor. """ + """Represents a Nest binary sensor.""" @property def is_on(self): - """ True if the binary sensor is on. """ + """True if the binary sensor is on.""" return bool(getattr(self.device, self.variable)) diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index f77de7693ce..b5e36395a7a 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.nx584 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for exposing nx584 elements as sensors. For more details about this platform, please refer to the documentation at @@ -73,18 +71,22 @@ class NX584ZoneSensor(BinarySensorDevice): @property def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" return self._zone_type @property def should_poll(self): + """No polling needed.""" return False @property def name(self): + """Name of the binary sensor.""" return self._zone['name'] @property def is_on(self): + """Return true if the binary sensor is on.""" # True means "faulted" or "open" or "abnormal state" return self._zone['state'] diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index a7f0ad72604..63c7f7a3e19 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.rest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The rest binary sensor will consume responses sent by an exposed REST API. For more details about this platform, please refer to the documentation at @@ -21,7 +19,7 @@ DEFAULT_METHOD = 'GET' # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup REST binary sensors. """ + """Setup REST binary sensors.""" resource = config.get('resource', None) method = config.get('method', DEFAULT_METHOD) payload = config.get('payload', None) @@ -41,10 +39,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments class RestBinarySensor(BinarySensorDevice): - """ A REST binary sensor. """ + """A REST binary sensor.""" def __init__(self, hass, rest, name, value_template): - """ Initialize a REST binary sensor. """ + """Initialize a REST binary sensor.""" self._hass = hass self.rest = rest self._name = name @@ -54,12 +52,12 @@ class RestBinarySensor(BinarySensorDevice): @property def name(self): - """ Name of the binary sensor. """ + """Name of the binary sensor.""" return self._name @property def is_on(self): - """ Return if the binary sensor is on. """ + """Return true if the binary sensor is on.""" if self.rest.data is None: return False @@ -69,5 +67,5 @@ class RestBinarySensor(BinarySensorDevice): return bool(int(self.rest.data)) def update(self): - """ Get the latest data from REST API and updates the state. """ + """Get the latest data from REST API and updates the state.""" self.rest.update() diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index a43d009e25f..22df2795076 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -1,12 +1,9 @@ """ -homeassistant.components.binary_sensor.rpi_gpio -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to configure a binary sensor using RPi GPIO. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.rpi_gpio/ """ - import logging import homeassistant.components.rpi_gpio as rpi_gpio @@ -23,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Raspberry PI GPIO devices. """ + """Sets up the Raspberry PI GPIO devices.""" pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE) bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME) @@ -39,7 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class RPiGPIOBinarySensor(BinarySensorDevice): - """ Represents a binary sensor that uses Raspberry Pi GPIO. """ + """Represents a binary sensor that uses Raspberry Pi GPIO.""" def __init__(self, name, port, pull_mode, bouncetime, invert_logic): # pylint: disable=no-member @@ -53,22 +50,22 @@ class RPiGPIOBinarySensor(BinarySensorDevice): self._state = rpi_gpio.read_input(self._port) def read_gpio(port): - """ Reads state from GPIO. """ + """Reads state from GPIO.""" self._state = rpi_gpio.read_input(self._port) self.update_ha_state() rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime) @property def should_poll(self): - """ No polling needed. """ + """No polling needed.""" return False @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def is_on(self): - """ Returns the state of the entity. """ + """Returns the state of the entity.""" return self._state != self._invert_logic diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 1f8bf2387c9..33d26f76333 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -1,30 +1,31 @@ """ -homeassistant.components.binary_sensor.tcp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a binary_sensor which gets its values from a TCP socket. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.tcp/ """ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.tcp import Sensor, DOMAIN, CONF_VALUE_ON - DEPENDENCIES = [DOMAIN] - _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): - """ Create the BinarySensor. """ + """Create the binary sensor.""" if not BinarySensor.validate_config(config): return False + add_entities((BinarySensor(hass, config),)) class BinarySensor(BinarySensorDevice, Sensor): - """ A binary sensor which is on when its state == CONF_VALUE_ON. """ + """A binary sensor which is on when its state == CONF_VALUE_ON.""" required = (CONF_VALUE_ON,) @property def is_on(self): + """True if the binary sensor is on.""" return self._state == self._config[CONF_VALUE_ON] diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 6954ed33a82..49b6f12ed5c 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -1,6 +1,4 @@ """ -homeassistant.components.binary_sensor.zigbee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Contains functionality to use a ZigBee device as a binary sensor. For more details about this platform, please refer to the documentation at @@ -14,7 +12,7 @@ DEPENDENCIES = ["zigbee"] def setup_platform(hass, config, add_entities, discovery_info=None): - """ Create and add an entity based on the configuration. """ + """Create and add an entity based on the configuration.""" add_entities([ ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config)) ]) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index eb9f446febe..8b21500cc85 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -1,13 +1,9 @@ """ -homeassistant.components.binary_sensor.zwave -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interfaces with Z-Wave sensors. For more details about this platform, please refer to the documentation -at https://home-assistant.io/components/zwave/ +https://home-assistant.io/components/binary_sensor.zwave/ """ - - import logging from homeassistant.components.zwave import ( @@ -23,7 +19,7 @@ DEPENDENCIES = [] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for sensors.""" + """Setup the Z-Wave platform for sensors.""" if discovery_info is None or NETWORK is None: return @@ -37,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): - """ Represents a binary sensor within Z-Wave. """ + """Represents a binary sensor within Z-Wave.""" def __init__(self, value, sensor_class): self._sensor_type = sensor_class @@ -62,9 +58,10 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): @property def should_poll(self): + """No polling needed.""" return False def value_changed(self, value): - """ Called when a value has changed on the network. """ + """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: self.update_ha_state() From 066b569cfef72950cd940688995d2800bca96f2f Mon Sep 17 00:00:00 2001 From: t30 Date: Mon, 22 Feb 2016 10:43:29 +0000 Subject: [PATCH 128/186] Added command_rollershutter component --- .../rollershutter/command_rollershutter.py | 122 ++++++++++++++++++ .../test_command_rollershutter.py | 82 ++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 homeassistant/components/rollershutter/command_rollershutter.py create mode 100644 tests/components/rollershutter/test_command_rollershutter.py diff --git a/homeassistant/components/rollershutter/command_rollershutter.py b/homeassistant/components/rollershutter/command_rollershutter.py new file mode 100644 index 00000000000..bd6241f4648 --- /dev/null +++ b/homeassistant/components/rollershutter/command_rollershutter.py @@ -0,0 +1,122 @@ +""" +homeassistant.components.rollershutter.command_rollershutter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allows to configure a command rollershutter. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/rollershutter.command_rollershutter/ +""" +import logging +import subprocess + +from homeassistant.components.rollershutter import RollershutterDevice +from homeassistant.const import CONF_VALUE_TEMPLATE +from homeassistant.util import template + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return rollershutter controlled by shell commands. """ + + rollershutters = config.get('rollershutters', {}) + devices = [] + + for dev_name, properties in rollershutters.items(): + devices.append( + CommandRollershutter( + hass, + properties.get('name', dev_name), + properties.get('upcmd', 'true'), + properties.get('downcmd', 'true'), + properties.get('stopcmd', 'true'), + properties.get('statecmd', False), + properties.get(CONF_VALUE_TEMPLATE, '{{ value }}'))) + add_devices_callback(devices) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class CommandRollershutter(RollershutterDevice): + """ Represents a rollershutter - can be controlled using shell cmd. """ + + # pylint: disable=too-many-arguments + def __init__(self, hass, name, command_up, command_down, command_stop, + command_state, value_template): + + self._hass = hass + self._name = name + self._state = None # Unknown + self._command_up = command_up + self._command_down = command_down + self._command_stop = command_stop + self._command_state = command_state + self._value_template = value_template + + @staticmethod + def _move_rollershutter(command): + """ Execute the actual commands. """ + _LOGGER.info('Running command: %s', command) + + success = (subprocess.call(command, shell=True) == 0) + + if not success: + _LOGGER.error('Command failed: %s', command) + + return success + + @staticmethod + def _query_state_value(command): + """ Execute state command for return value. """ + _LOGGER.info('Running state command: %s', command) + + try: + return_value = subprocess.check_output(command, shell=True) + return return_value.strip().decode('utf-8') + except subprocess.CalledProcessError: + _LOGGER.error('Command failed: %s', command) + + @property + def should_poll(self): + """ Only poll if we have statecmd. """ + return self._command_state is not None + + @property + def name(self): + """ The name of the rollershutter. """ + return self._name + + @property + def current_position(self): + """ + Return current position of rollershutter. + None is unknown, 0 is closed, 100 is fully open. + """ + return self._state + + def _query_state(self): + """ Query for state. """ + if not self._command_state: + _LOGGER.error('No state command specified') + return + return self._query_state_value(self._command_state) + + def update(self): + """ Update device state. """ + if self._command_state: + payload = str(self._query_state()) + if self._value_template: + payload = template.render_with_possible_json_value( + self._hass, self._value_template, payload) + self._state = int(payload) + + def move_up(self, **kwargs): + """ Move the rollershutter up. """ + self._move_rollershutter(self._command_up) + + def move_down(self, **kwargs): + """ Move the rollershutter down. """ + self._move_rollershutter(self._command_down) + + def stop(self, **kwargs): + """ Stop the device. """ + self._move_rollershutter(self._command_stop) diff --git a/tests/components/rollershutter/test_command_rollershutter.py b/tests/components/rollershutter/test_command_rollershutter.py new file mode 100644 index 00000000000..0fe74ee25ec --- /dev/null +++ b/tests/components/rollershutter/test_command_rollershutter.py @@ -0,0 +1,82 @@ +""" +tests.components.rollershutter.command_rollershutter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests the command_rollershutter component +""" + +import os +import tempfile +import unittest +from unittest import mock + +import homeassistant.core as ha +import homeassistant.components.rollershutter as rollershutter +from homeassistant.components.rollershutter import ( + command_rollershutter as cmd_rs) + + +class TestCommandRollerShutter(unittest.TestCase): + def setup_method(self, method): + self.hass = ha.HomeAssistant() + self.hass.config.latitude = 32.87336 + self.hass.config.longitude = 117.22743 + self.rs = cmd_rs.CommandRollershutter(self.hass, 'foo', + 'cmd_up', 'cmd_dn', + 'cmd_stop', 'cmd_state', + None) # FIXME + + def teardown_method(self, method): + """ Stop down stuff we started. """ + self.hass.stop() + + def test_should_poll(self): + self.assertTrue(self.rs.should_poll) + self.rs._command_state = None + self.assertFalse(self.rs.should_poll) + + def test_query_state_value(self): + with mock.patch('subprocess.check_output') as mock_run: + mock_run.return_value = b' foo bar ' + result = self.rs._query_state_value('runme') + self.assertEqual('foo bar', result) + mock_run.assert_called_once_with('runme', shell=True) + + def test_state_value(self): + with tempfile.TemporaryDirectory() as tempdirname: + path = os.path.join(tempdirname, 'rollershutter_status') + test_rollershutter = { + 'statecmd': 'cat {}'.format(path), + 'upcmd': 'echo 1 > {}'.format(path), + 'downcmd': 'echo 1 > {}'.format(path), + 'stopcmd': 'echo 0 > {}'.format(path), + 'value_template': '{{ value }}' + } + self.assertTrue(rollershutter.setup(self.hass, { + 'rollershutter': { + 'platform': 'command_rollershutter', + 'rollershutters': { + 'test': test_rollershutter + } + } + })) + + state = self.hass.states.get('rollershutter.test') + self.assertEqual('unknown', state.state) + + rollershutter.move_up(self.hass, 'rollershutter.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('rollershutter.test') + self.assertEqual('open', state.state) + + rollershutter.move_down(self.hass, 'rollershutter.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('rollershutter.test') + self.assertEqual('open', state.state) + + rollershutter.stop(self.hass, 'rollershutter.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('rollershutter.test') + self.assertEqual('closed', state.state) From cbe27d8f1a1cd1b8b67204fdfd5fde599ac94323 Mon Sep 17 00:00:00 2001 From: Gert Date: Mon, 22 Feb 2016 11:44:00 +0100 Subject: [PATCH 129/186] Don't set wallet if not setup in configuration Because of problems with the Wallet part of python blockchain library (see #1242 ) , the entire Bitcoin module isn't working currently. This change does not fix those problems but at least makes the sensor work again for people who don't need Wallet-related functionality. It also just seems better practice to not set a wallet and call "wallet.get_balance()" when not wallet is set in configuration. --- homeassistant/components/sensor/bitcoin.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index fe17ff574b1..62d1622aff4 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -59,12 +59,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Currency "%s" is not available. Using "USD".', currency) currency = 'USD' - wallet = Wallet(wallet_id, password) - - try: - wallet.get_balance() - except exceptions.APIException as error: - _LOGGER.error(error) + if wallet_id is not None and password is not None: + wallet = Wallet(wallet_id, password) + try: + wallet.get_balance() + except exceptions.APIException as error: + _LOGGER.error(error) + wallet = None + else: wallet = None data = BitcoinData() From 8d5a16434603f3eeb1dff3c71f5ba9c96e36dd48 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 22 Feb 2016 11:13:57 +0000 Subject: [PATCH 130/186] Fix service documentation appearing for notifications We want the descriptions for notify.notify regardless of the name of the exact notify service being called. --- homeassistant/components/notify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 245b9c6fde3..a44dd638479 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -89,7 +89,7 @@ def setup(hass, config): service_call_handler = partial(notify_message, notify_service) service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY) hass.services.register(DOMAIN, service_notify, service_call_handler, - descriptions.get(service_notify)) + descriptions.get(SERVICE_NOTIFY)) success = True return success From 1f842140efd132bf7e567346b140bbf36b29470c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 22 Feb 2016 13:45:01 +0100 Subject: [PATCH 131/186] Fix issue #1301 --- homeassistant/components/rfxtrx.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index 4789348c69a..fdefce5470b 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -11,7 +11,7 @@ import logging from homeassistant.util import slugify REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip' + - '#RFXtrx==0.4'] + '#pyRFXtrx==0.4'] DOMAIN = "rfxtrx" diff --git a/requirements_all.txt b/requirements_all.txt index b5736ef3801..b6f47d96131 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -64,7 +64,7 @@ hikvision==0.4 # http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0 # homeassistant.components.rfxtrx -https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip#RFXtrx==0.4 +https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip#pyRFXtrx==0.4 # homeassistant.components.sensor.netatmo https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0 From 7d9e882d5245cf64bbb1473ad9d3bb173d239630 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Feb 2016 14:42:11 +0100 Subject: [PATCH 132/186] Update docstrings to match PEP257 --- homeassistant/components/sensor/tcp.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index a2e14f12c39..8c021474753 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -1,7 +1,8 @@ """ -homeassistant.components.sensor.tcp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a sensor which gets its values from a TCP socket. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.tcp/ """ import logging import socket @@ -12,9 +13,6 @@ from homeassistant.util import template from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity - -# DEPENDENCIES = [DOMAIN] - DOMAIN = "tcp" CONF_PORT = "port" @@ -32,18 +30,18 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): - """ Create the Sensor. """ + """Create the TCP Sensor.""" if not Sensor.validate_config(config): return False add_entities((Sensor(hass, config),)) class Sensor(Entity): - """ Sensor Entity which gets its value from a TCP socket. """ + """Sensor entity which gets its value from a TCP socket.""" required = tuple() def __init__(self, hass, config): - """ Set all the config values if they exist and get initial state. """ + """Set all the config values if they exist and get initial state.""" self._hass = hass self._config = { CONF_NAME: config.get(CONF_NAME), @@ -62,7 +60,7 @@ class Sensor(Entity): @classmethod def validate_config(cls, config): - """ Ensure the config has all of the necessary values. """ + """Ensure the configuration has all of the necessary values.""" always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) for key in always_required + tuple(cls.required): if key not in config: @@ -73,6 +71,7 @@ class Sensor(Entity): @property def name(self): + """The name of this sensor.""" name = self._config[CONF_NAME] if name is not None: return name @@ -80,14 +79,16 @@ class Sensor(Entity): @property def state(self): + """Return the state of the device.""" return self._state @property def unit_of_measurement(self): + """Unit of measurement of this entity.""" return self._config[CONF_UNIT] def update(self): - """ Get the latest value for this sensor. """ + """Get the latest value for this sensor.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: try: sock.connect( From 5bd0b5ab99a60f281f0ef59fee62807950161563 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Feb 2016 14:43:12 +0100 Subject: [PATCH 133/186] Update docstring --- homeassistant/components/binary_sensor/tcp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 33d26f76333..125e0f12b91 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -1,5 +1,5 @@ """ -Provides a binary_sensor which gets its values from a TCP socket. +Provides a binary sensor which gets its values from a TCP socket. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.tcp/ From d39883211277b09be1f547690ba2d2a178a8e570 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 22 Feb 2016 13:17:53 -0800 Subject: [PATCH 134/186] Make UVC cameras honor the local camera password store, if set The NVR tells us the admin username, but not the password for the camera. Right now, we assume the default password, which obviously doesn't work for people that have changed it. The uvcclient library provides a way to set the cached admin password for a camera, which is stored in a client-specific location. We can utilize that to grab the password, falling back to the default if it's unset. With this, people just need to run a command per camera to set the admin password on their systems, if it has changed. --- homeassistant/components/camera/uvc.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index a1ae128f4da..c69f1f18e1f 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -14,7 +14,7 @@ import requests from homeassistant.components.camera import DOMAIN, Camera from homeassistant.helpers import validate_config -REQUIREMENTS = ['uvcclient==0.6'] +REQUIREMENTS = ['uvcclient==0.8'] _LOGGER = logging.getLogger(__name__) @@ -81,18 +81,27 @@ class UnifiVideoCamera(Camera): def _login(self): from uvcclient import camera as uvc_camera + from uvcclient import store as uvc_store + caminfo = self._nvr.get_camera(self._uuid) if self._connect_addr: addrs = [self._connect_addr] else: addrs = [caminfo['host'], caminfo['internalHost']] + store = uvc_store.get_info_store() + password = store.get_camera_password(self._uuid) + if password is None: + _LOGGER.debug('Logging into camera %(name)s with default password', + dict(name=self._name)) + password = 'ubnt' + camera = None for addr in addrs: try: camera = uvc_camera.UVCCameraClient(addr, caminfo['username'], - 'ubnt') + password) camera.login() _LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s', dict(name=self._name, addr=addr)) From 590512916a4f202d432cfa979384541653e960ea Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 22 Feb 2016 14:06:06 -0800 Subject: [PATCH 135/186] Add tests for camera.uvc and fix bugs found in the process This adds tests for the uvc camera module. It's a good thing too, because I found a few bugs which are fixed here as well: - Graceful handling of non-integer port - Failure to take the first host that works when probing host,internalHost - Failure to detect if neither of them actually work This also converts the code to only call add_devices once with a listcomp. --- .coveragerc | 5 +- homeassistant/components/camera/uvc.py | 20 ++- requirements_all.txt | 2 +- tests/components/camera/test_uvc.py | 194 +++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 tests/components/camera/test_uvc.py diff --git a/.coveragerc b/.coveragerc index ba936e424e1..9eb9ee2f5b8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -69,7 +69,10 @@ omit = homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py - homeassistant/components/camera/* + homeassistant/components/camera/bloomsky.py + homeassistant/components/camera/foscam.py + homeassistant/components/camera/generic.py + homeassistant/components/camera/mjpeg.py homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index c69f1f18e1f..5a84c535540 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -26,8 +26,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return None addr = config.get('nvr') - port = int(config.get('port', 7080)) key = config.get('key') + try: + port = int(config.get('port', 7080)) + except ValueError: + _LOGGER.error('Invalid port number provided') + return False from uvcclient import nvr nvrconn = nvr.UVCRemote(addr, port, key) @@ -43,10 +47,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Unable to connect to NVR: %s', str(ex)) return False - for camera in cameras: - add_devices([UnifiVideoCamera(nvrconn, - camera['uuid'], - camera['name'])]) + add_devices([UnifiVideoCamera(nvrconn, + camera['uuid'], + camera['name']) + for camera in cameras]) + return True class UnifiVideoCamera(Camera): @@ -93,7 +98,7 @@ class UnifiVideoCamera(Camera): password = store.get_camera_password(self._uuid) if password is None: _LOGGER.debug('Logging into camera %(name)s with default password', - dict(name=self._name)) + dict(name=self._name)) password = 'ubnt' camera = None @@ -106,13 +111,14 @@ class UnifiVideoCamera(Camera): _LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s', dict(name=self._name, addr=addr)) self._connect_addr = addr + break except socket.error: pass except uvc_camera.CameraConnectError: pass except uvc_camera.CameraAuthError: pass - if not camera: + if not self._connect_addr: _LOGGER.error('Unable to login to camera') return None diff --git a/requirements_all.txt b/requirements_all.txt index b5736ef3801..c93dc535451 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,7 +262,7 @@ unifi==1.2.4 urllib3 # homeassistant.components.camera.uvc -uvcclient==0.6 +uvcclient==0.8 # homeassistant.components.verisure vsure==0.5.1 diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py new file mode 100644 index 00000000000..1c87945cff2 --- /dev/null +++ b/tests/components/camera/test_uvc.py @@ -0,0 +1,194 @@ +""" +tests.components.camera.test_uvc +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests for uvc camera module. +""" + +import socket +import unittest +from unittest import mock + +import requests +from uvcclient import camera +from uvcclient import nvr + +from homeassistant.components.camera import uvc + + +class TestUVCSetup(unittest.TestCase): + @mock.patch('uvcclient.nvr.UVCRemote') + @mock.patch.object(uvc, 'UnifiVideoCamera') + def test_setup_full_config(self, mock_uvc, mock_remote): + config = { + 'nvr': 'foo', + 'port': 123, + 'key': 'secret', + } + fake_cameras = [ + {'uuid': 'one', 'name': 'Front'}, + {'uuid': 'two', 'name': 'Back'}, + ] + hass = mock.MagicMock() + add_devices = mock.MagicMock() + mock_remote.return_value.index.return_value = fake_cameras + self.assertTrue(uvc.setup_platform(hass, config, add_devices)) + mock_remote.assert_called_once_with('foo', 123, 'secret') + add_devices.assert_called_once_with([ + mock_uvc.return_value, mock_uvc.return_value]) + mock_uvc.assert_has_calls([ + mock.call(mock_remote.return_value, 'one', 'Front'), + mock.call(mock_remote.return_value, 'two', 'Back'), + ]) + + @mock.patch('uvcclient.nvr.UVCRemote') + @mock.patch.object(uvc, 'UnifiVideoCamera') + def test_setup_partial_config(self, mock_uvc, mock_remote): + config = { + 'nvr': 'foo', + 'key': 'secret', + } + fake_cameras = [ + {'uuid': 'one', 'name': 'Front'}, + {'uuid': 'two', 'name': 'Back'}, + ] + hass = mock.MagicMock() + add_devices = mock.MagicMock() + mock_remote.return_value.index.return_value = fake_cameras + self.assertTrue(uvc.setup_platform(hass, config, add_devices)) + mock_remote.assert_called_once_with('foo', 7080, 'secret') + add_devices.assert_called_once_with([ + mock_uvc.return_value, mock_uvc.return_value]) + mock_uvc.assert_has_calls([ + mock.call(mock_remote.return_value, 'one', 'Front'), + mock.call(mock_remote.return_value, 'two', 'Back'), + ]) + + def test_setup_incomplete_config(self): + self.assertFalse(uvc.setup_platform( + None, {'nvr': 'foo'}, None)) + self.assertFalse(uvc.setup_platform( + None, {'key': 'secret'}, None)) + self.assertFalse(uvc.setup_platform( + None, {'port': 'invalid'}, None)) + + @mock.patch('uvcclient.nvr.UVCRemote') + def test_setup_nvr_errors(self, mock_remote): + errors = [nvr.NotAuthorized, nvr.NvrError, + requests.exceptions.ConnectionError] + config = { + 'nvr': 'foo', + 'key': 'secret', + } + for error in errors: + mock_remote.return_value.index.side_effect = error + self.assertFalse(uvc.setup_platform(None, config, None)) + + +class TestUVC(unittest.TestCase): + def setup_method(self, method): + self.nvr = mock.MagicMock() + self.uuid = 'uuid' + self.name = 'name' + self.uvc = uvc.UnifiVideoCamera(self.nvr, self.uuid, self.name) + self.nvr.get_camera.return_value = { + 'model': 'UVC Fake', + 'recordingSettings': { + 'fullTimeRecordEnabled': True, + }, + 'host': 'host-a', + 'internalHost': 'host-b', + 'username': 'admin', + } + + def test_properties(self): + self.assertEqual(self.name, self.uvc.name) + self.assertTrue(self.uvc.is_recording) + self.assertEqual('Ubiquiti', self.uvc.brand) + self.assertEqual('UVC Fake', self.uvc.model) + + @mock.patch('uvcclient.store.get_info_store') + @mock.patch('uvcclient.camera.UVCCameraClient') + def test_login(self, mock_camera, mock_store): + mock_store.return_value.get_camera_password.return_value = 'seekret' + self.uvc._login() + mock_camera.assert_called_once_with('host-a', 'admin', 'seekret') + mock_camera.return_value.login.assert_called_once_with() + + @mock.patch('uvcclient.store.get_info_store') + @mock.patch('uvcclient.camera.UVCCameraClient') + def test_login_no_password(self, mock_camera, mock_store): + mock_store.return_value.get_camera_password.return_value = None + self.uvc._login() + mock_camera.assert_called_once_with('host-a', 'admin', 'ubnt') + mock_camera.return_value.login.assert_called_once_with() + + @mock.patch('uvcclient.store.get_info_store') + @mock.patch('uvcclient.camera.UVCCameraClient') + def test_login_tries_both_addrs_and_caches(self, mock_camera, mock_store): + responses = [0] + + def fake_login(*a): + try: + responses.pop(0) + raise socket.error + except IndexError: + pass + + mock_store.return_value.get_camera_password.return_value = None + mock_camera.return_value.login.side_effect = fake_login + self.uvc._login() + self.assertEqual(2, mock_camera.call_count) + self.assertEqual('host-b', self.uvc._connect_addr) + + mock_camera.reset_mock() + self.uvc._login() + mock_camera.assert_called_once_with('host-b', 'admin', 'ubnt') + mock_camera.return_value.login.assert_called_once_with() + + @mock.patch('uvcclient.store.get_info_store') + @mock.patch('uvcclient.camera.UVCCameraClient') + def test_login_fails_both_properly(self, mock_camera, mock_store): + mock_camera.return_value.login.side_effect = socket.error + self.assertEqual(None, self.uvc._login()) + self.assertEqual(None, self.uvc._connect_addr) + + def test_camera_image_tries_login_bails_on_failure(self): + with mock.patch.object(self.uvc, '_login') as mock_login: + mock_login.return_value = False + self.assertEqual(None, self.uvc.camera_image()) + mock_login.assert_called_once_with() + + def test_camera_image_logged_in(self): + self.uvc._camera = mock.MagicMock() + self.assertEqual(self.uvc._camera.get_snapshot.return_value, + self.uvc.camera_image()) + + def test_camera_image_error(self): + self.uvc._camera = mock.MagicMock() + self.uvc._camera.get_snapshot.side_effect = camera.CameraConnectError + self.assertEqual(None, self.uvc.camera_image()) + + def test_camera_image_reauths(self): + responses = [0] + + def fake_snapshot(): + try: + responses.pop() + raise camera.CameraAuthError() + except IndexError: + pass + return 'image' + + self.uvc._camera = mock.MagicMock() + self.uvc._camera.get_snapshot.side_effect = fake_snapshot + with mock.patch.object(self.uvc, '_login') as mock_login: + self.assertEqual('image', self.uvc.camera_image()) + mock_login.assert_called_once_with() + self.assertEqual([], responses) + + def test_camera_image_reauths_only_once(self): + self.uvc._camera = mock.MagicMock() + self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError + with mock.patch.object(self.uvc, '_login') as mock_login: + self.assertRaises(camera.CameraAuthError, self.uvc.camera_image) + mock_login.assert_called_once_with() From 60d579af8422e0d67b9eb3bacde42b806942758e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Feb 2016 06:21:49 +0100 Subject: [PATCH 136/186] Update/add docstrings (PEP257) --- homeassistant/components/sensor/__init__.py | 4 +- homeassistant/components/sensor/apcupsd.py | 12 ++--- homeassistant/components/sensor/arduino.py | 14 ++---- homeassistant/components/sensor/arest.py | 21 ++++---- homeassistant/components/sensor/bitcoin.py | 24 ++++----- .../components/sensor/command_sensor.py | 18 +++---- homeassistant/components/sensor/cpuspeed.py | 23 ++++----- homeassistant/components/sensor/demo.py | 17 +++---- homeassistant/components/sensor/dht.py | 25 ++++------ homeassistant/components/sensor/dweet.py | 24 ++++----- homeassistant/components/sensor/ecobee.py | 16 +++--- homeassistant/components/sensor/efergy.py | 14 +++--- homeassistant/components/sensor/eliqonline.py | 18 +++---- homeassistant/components/sensor/forecast.py | 23 ++++----- homeassistant/components/sensor/glances.py | 23 ++++----- homeassistant/components/sensor/isy994.py | 17 +++---- homeassistant/components/sensor/mfi.py | 11 ++-- homeassistant/components/sensor/modbus.py | 22 ++++---- homeassistant/components/sensor/mqtt.py | 18 +++---- homeassistant/components/sensor/mysensors.py | 2 +- homeassistant/components/sensor/nest.py | 31 +++++------- homeassistant/components/sensor/netatmo.py | 25 ++++------ .../components/sensor/neurio_energy.py | 19 ++++--- homeassistant/components/sensor/onewire.py | 16 +++--- .../components/sensor/openweathermap.py | 23 ++++----- homeassistant/components/sensor/rest.py | 18 +++---- homeassistant/components/sensor/rfxtrx.py | 18 +++---- homeassistant/components/sensor/sabnzbd.py | 17 +++---- homeassistant/components/sensor/speedtest.py | 21 ++++---- .../sensor/swiss_public_transport.py | 26 ++++------ .../components/sensor/systemmonitor.py | 19 +++---- .../components/sensor/tellduslive.py | 27 ++++++---- homeassistant/components/sensor/tellstick.py | 13 +++-- homeassistant/components/sensor/temper.py | 15 +++--- homeassistant/components/sensor/template.py | 15 +++--- homeassistant/components/sensor/time_date.py | 16 +++--- homeassistant/components/sensor/torque.py | 25 ++++------ .../components/sensor/transmission.py | 18 +++---- homeassistant/components/sensor/twitch.py | 20 ++++---- homeassistant/components/sensor/vera.py | 23 +++++---- homeassistant/components/sensor/verisure.py | 35 ++++++------- homeassistant/components/sensor/wink.py | 26 +++++----- homeassistant/components/sensor/worldclock.py | 18 +++---- homeassistant/components/sensor/yr.py | 29 +++++------ homeassistant/components/sensor/zigbee.py | 8 +-- homeassistant/components/sensor/zwave.py | 50 ++++++++----------- 46 files changed, 407 insertions(+), 510 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 8dbaef0e118..67bcfb6f701 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to interface with various sensors that can be monitored. For more details about this component, please refer to the documentation at @@ -31,7 +29,7 @@ DISCOVERY_PLATFORMS = { def setup(hass, config): - """ Track states and offer events for sensors. """ + """Track states and offer events for sensors.""" component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS) diff --git a/homeassistant/components/sensor/apcupsd.py b/homeassistant/components/sensor/apcupsd.py index bed8740a55e..75db04467c2 100644 --- a/homeassistant/components/sensor/apcupsd.py +++ b/homeassistant/components/sensor/apcupsd.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.apcupsd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a sensor to track various status aspects of a UPS. For more details about this platform, please refer to the documentation at @@ -59,7 +57,7 @@ def infer_unit(value): class Sensor(Entity): - """ Generic sensor entity for APCUPSd status values. """ + """Generic sensor entity for APCUPSd status values.""" def __init__(self, config, data, unit=None): self._config = config self._unit = unit @@ -69,22 +67,22 @@ class Sensor(Entity): @property def name(self): - """ The name of the UPS sensor. """ + """The name of the UPS sensor.""" return self._config.get("name", DEFAULT_NAME) @property def state(self): - """ True if the UPS is online, else False. """ + """True if the UPS is online, else False.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" if self._unit is None: return self._inferred_unit return self._unit def update(self): - """ Get the latest status and use it to update our sensor state. """ + """Get the latest status and use it to update our sensor state.""" key = self._config[apcupsd.CONF_TYPE].upper() self._state, self._inferred_unit = infer_unit(self._data.status[key]) diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py index a99765808f8..f8b62fe1322 100644 --- a/homeassistant/components/sensor/arduino.py +++ b/homeassistant/components/sensor/arduino.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.arduino -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for getting information from Arduino pins. Only analog pins are supported. @@ -14,13 +12,11 @@ from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.helpers.entity import Entity DEPENDENCIES = ['arduino'] - _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Arduino platform. """ - + """Sets up the Arduino platform.""" # Verify that the Arduino board is present if arduino.BOARD is None: _LOGGER.error('A connection has not been made to the Arduino board.') @@ -37,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ArduinoSensor(Entity): - """ Represents an Arduino Sensor. """ + """Represents an Arduino Sensor.""" def __init__(self, name, pin, pin_type): self._pin = pin self._name = name or DEVICE_DEFAULT_NAME @@ -49,14 +45,14 @@ class ArduinoSensor(Entity): @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" return self._value @property def name(self): - """ Get the name of the sensor. """ + """Get the name of the sensor.""" return self._name def update(self): - """ Get the latest value from the pin. """ + """Get the latest value from the pin.""" self._value = arduino.BOARD.get_analog_inputs()[self._pin][1] diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index 0a1680c2aa2..c731060d6dc 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.arest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The arest sensor will consume an exposed aREST API of a device. For more details about this platform, please refer to the documentation at @@ -27,8 +25,7 @@ CONF_MONITORED_VARIABLES = 'monitored_variables' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the aREST sensor. """ - + """Get the aREST sensor.""" resource = config.get(CONF_RESOURCE) var_conf = config.get(CONF_MONITORED_VARIABLES) pins = config.get('pins', None) @@ -53,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): arest = ArestData(resource) def make_renderer(value_template): - """ Creates renderer based on variable_template value """ + """Creates renderer based on variable_template value.""" if value_template is None: return lambda value: value @@ -102,7 +99,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes, too-many-arguments class ArestSensor(Entity): - """ Implements an aREST sensor for exposed variables. """ + """Implements an aREST sensor for exposed variables.""" def __init__(self, arest, resource, location, name, variable=None, pin=None, unit_of_measurement=None, renderer=None): @@ -125,17 +122,17 @@ class ArestSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" values = self.arest.data if 'error' in values: @@ -147,13 +144,13 @@ class ArestSensor(Entity): return value def update(self): - """ Gets the latest data from aREST API. """ + """Gets the latest data from aREST API.""" self.arest.update() # pylint: disable=too-few-public-methods class ArestData(object): - """ Class for handling the data retrieval for variables. """ + """Class for handling the data retrieval for variables.""" def __init__(self, resource, pin=None): self._resource = resource @@ -162,7 +159,7 @@ class ArestData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from aREST device. """ + """Gets the latest data from aREST device.""" try: if self._pin is None: response = requests.get(self._resource, timeout=10) diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index a92b20a4cee..d0b518f6779 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.bitcoin -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Bitcoin information service that uses blockchain.info and its online wallet. For more details about this platform, please refer to the documentation at @@ -45,8 +43,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Bitcoin sensor. """ - + """Get the Bitcoin sensor.""" from blockchain.wallet import Wallet from blockchain import exchangerates, exceptions @@ -84,8 +81,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class BitcoinSensor(Entity): - """ Implements a Bitcoin sensor. """ - + """Implements a Bitcoin sensor.""" def __init__(self, data, option_type, currency, wallet=''): self.data = data self._name = OPTION_TYPES[option_type][0] @@ -98,27 +94,27 @@ class BitcoinSensor(Entity): @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): + """Unit the value is expressed in.""" return self._unit_of_measurement @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON # pylint: disable=too-many-branches def update(self): - """ Gets the latest data and updates the states. """ - + """Gets the latest data and updates the states.""" self.data.update() stats = self.data.stats ticker = self.data.ticker @@ -176,16 +172,14 @@ class BitcoinSensor(Entity): class BitcoinData(object): - """ Gets the latest data and updates the states. """ - + """Gets the latest data and updates the states.""" def __init__(self): self.stats = None self.ticker = None @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from blockchain.info. """ - + """Gets the latest data from blockchain.info.""" from blockchain import statistics, exchangerates self.stats = statistics.get() diff --git a/homeassistant/components/sensor/command_sensor.py b/homeassistant/components/sensor/command_sensor.py index a0877ac6888..d87010a5525 100644 --- a/homeassistant/components/sensor/command_sensor.py +++ b/homeassistant/components/sensor/command_sensor.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.command_sensor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to configure custom shell commands to turn a value for a sensor. For more details about this platform, please refer to the documentation at @@ -24,7 +22,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add the Command Sensor. """ + """Add the Command Sensor.""" if config.get('command') is None: _LOGGER.error('Missing required variable: "command"') @@ -43,7 +41,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments class CommandSensor(Entity): - """ Represents a sensor that is returning a value of a shell commands. """ + """Represents a sensor that is returning a value of a shell commands.""" def __init__(self, hass, data, name, unit_of_measurement, value_template): self._hass = hass self.data = data @@ -55,21 +53,21 @@ class CommandSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state def update(self): - """ Gets the latest data and updates the state. """ + """Gets the latest data and updates the state.""" self.data.update() value = self.data.value @@ -82,7 +80,7 @@ class CommandSensor(Entity): # pylint: disable=too-few-public-methods class CommandSensorData(object): - """ Class for handling the data retrieval. """ + """Class for handling the data retrieval.""" def __init__(self, command): self.command = command @@ -90,7 +88,7 @@ class CommandSensorData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data with a shell command. """ + """Gets the latest data with a shell command.""" _LOGGER.info('Running command: %s', self.command) try: diff --git a/homeassistant/components/sensor/cpuspeed.py b/homeassistant/components/sensor/cpuspeed.py index 68d1857e2b8..76f4b750e75 100644 --- a/homeassistant/components/sensor/cpuspeed.py +++ b/homeassistant/components/sensor/cpuspeed.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.cpuspeed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Shows the current CPU speed. +Support for displaying the current CPU speed. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.cpuspeed/ @@ -15,7 +13,6 @@ REQUIREMENTS = ['py-cpuinfo==0.1.8'] _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "CPU speed" - ATTR_VENDOR = 'Vendor ID' ATTR_BRAND = 'Brand' ATTR_HZ = 'GHz Advertised' @@ -24,14 +21,12 @@ ICON = 'mdi:pulse' # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the CPU speed sensor. """ - + """Sets up the CPU speed sensor.""" add_devices([CpuSpeedSensor(config.get('name', DEFAULT_NAME))]) class CpuSpeedSensor(Entity): - """ A CPU info sensor. """ - + """A CPU info sensor.""" def __init__(self, name): self._name = name self._state = None @@ -40,22 +35,22 @@ class CpuSpeedSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement @property def device_state_attributes(self): - """ Returns the state attributes. """ + """Returns the state attributes.""" if self.info is not None: return { ATTR_VENDOR: self.info['vendor_id'], @@ -65,11 +60,11 @@ class CpuSpeedSensor(Entity): @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON def update(self): - """ Gets the latest data and updates the state. """ + """Gets the latest data and updates the state.""" from cpuinfo import cpuinfo self.info = cpuinfo.get_cpu_info() diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index f0ddbd4a2a0..04541db15a3 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo platform that has a couple of fake sensors. """ from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELCIUS @@ -9,7 +7,7 @@ from homeassistant.helpers.entity import Entity # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo sensors. """ + """Sets up the Demo sensors.""" add_devices([ DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12), DemoSensor('Outside Humidity', 54, '%', None), @@ -17,8 +15,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoSensor(Entity): - """ A Demo sensor. """ - + """A Demo sensor.""" def __init__(self, name, state, unit_of_measurement, battery): self._name = name self._state = state @@ -27,27 +24,27 @@ class DemoSensor(Entity): @property def should_poll(self): - """ No polling needed for a demo sensor. """ + """No polling needed for a demo sensor.""" return False @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit this state is expressed in. """ + """Unit this state is expressed in.""" return self._unit_of_measurement @property def device_state_attributes(self): - """ Returns the state attributes. """ + """Returns the state attributes.""" if self._battery: return { ATTR_BATTERY_LEVEL: self._battery, diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index a3019f2b84d..14999229542 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.dht -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Adafruit DHT temperature and humidity sensor. +Support for Adafruit DHT temperature and humidity sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.dht/ @@ -13,7 +11,7 @@ from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -# update this requirement to upstream as soon as it supports python3 +# Update this requirement to upstream as soon as it supports Python 3. REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' '4101340de8d2457dd194bca1e8d11cbfc237e919.zip' '#Adafruit_DHT==1.1.0'] @@ -30,8 +28,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the DHT sensor. """ - + """Get the DHT sensor.""" # pylint: disable=import-error import Adafruit_DHT @@ -70,8 +67,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class DHTSensor(Entity): - """ Implements an DHT sensor. """ - + """Implements an DHT sensor.""" def __init__(self, dht_client, sensor_type, temp_unit, name): self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] @@ -84,21 +80,21 @@ class DHTSensor(Entity): @property def name(self): + """Returns the name of the sensor.""" return '{} {}'.format(self.client_name, self._name) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): - """ Gets the latest data from the DHT and updates the states. """ - + """Gets the latest data from the DHT and updates the states.""" self.dht_client.update() data = self.dht_client.data @@ -111,8 +107,7 @@ class DHTSensor(Entity): class DHTClient(object): - """ Gets the latest data from the DHT sensor. """ - + """Gets the latest data from the DHT sensor.""" def __init__(self, adafruit_dht, sensor, pin): self.adafruit_dht = adafruit_dht self.sensor = sensor @@ -121,7 +116,7 @@ class DHTClient(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data the DHT sensor. """ + """Gets the latest data the DHT sensor.""" humidity, temperature = self.adafruit_dht.read_retry(self.sensor, self.pin) if temperature: diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py index f1b90d59f47..a618594b1f9 100644 --- a/homeassistant/components/sensor/dweet.py +++ b/homeassistant/components/sensor/dweet.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.dweet -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Displays values from Dweet.io. +Support for showing values from Dweet.io. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.dweet/ @@ -20,13 +18,13 @@ REQUIREMENTS = ['dweepy==0.2.0'] DEFAULT_NAME = 'Dweet.io Sensor' CONF_DEVICE = 'device' -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # pylint: disable=unused-variable, too-many-function-args def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the Dweet sensor. """ + """Setup the Dweet sensor.""" import dweepy device = config.get('device') @@ -60,8 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments class DweetSensor(Entity): - """ Implements a Dweet sensor. """ - + """Implements a Dweet sensor.""" def __init__(self, hass, dweet, name, value_template, unit_of_measurement): self.hass = hass self.dweet = dweet @@ -73,17 +70,17 @@ class DweetSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement @property def state(self): - """ Returns the state. """ + """Returns the state.""" if self.dweet.data is None: return STATE_UNKNOWN else: @@ -93,21 +90,20 @@ class DweetSensor(Entity): return value def update(self): - """ Gets the latest data from REST API. """ + """Gets the latest data from REST API.""" self.dweet.update() # pylint: disable=too-few-public-methods class DweetData(object): - """ Class for handling the data retrieval. """ - + """Class for handling the data retrieval.""" def __init__(self, device): self._device = device self.data = None @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from Dweet.io. """ + """Gets the latest data from Dweet.io.""" import dweepy try: diff --git a/homeassistant/components/sensor/ecobee.py b/homeassistant/components/sensor/ecobee.py index 39ba1d1ef50..47dd3eb4bfd 100644 --- a/homeassistant/components/sensor/ecobee.py +++ b/homeassistant/components/sensor/ecobee.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.ecobee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sensor platform for Ecobee sensors. +Support for Ecobee sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.ecobee/ @@ -24,7 +22,7 @@ ECOBEE_CONFIG_FILE = 'ecobee.conf' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Ecobee sensors. """ + """Sets up the Ecobee sensors.""" if discovery_info is None: return data = ecobee.NETWORK @@ -42,7 +40,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class EcobeeSensor(Entity): - """ An Ecobee sensor. """ + """An Ecobee sensor.""" def __init__(self, sensor_name, sensor_type, sensor_index): self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0] @@ -55,12 +53,12 @@ class EcobeeSensor(Entity): @property def name(self): - """ Returns the name of the Ecobee sensor.. """ + """Returns the name of the Ecobee sensor.""" return self._name.rstrip() @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property @@ -70,11 +68,11 @@ class EcobeeSensor(Entity): @property def unit_of_measurement(self): - """ Unit of measurement this sensor expresses itself in. """ + """Unit of measurement this sensor expresses itself in.""" return self._unit_of_measurement def update(self): - """ Get the latest state of the sensor. """ + """Get the latest state of the sensor.""" data = ecobee.NETWORK data.update() for sensor in data.ecobee.get_remote_sensors(self.index): diff --git a/homeassistant/components/sensor/efergy.py b/homeassistant/components/sensor/efergy.py index 71c8f8ed789..689e457a9a8 100644 --- a/homeassistant/components/sensor/efergy.py +++ b/homeassistant/components/sensor/efergy.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.efergy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Monitors home energy use as measured by an efergy engage hub using its (unofficial, undocumented) API. @@ -23,7 +21,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Efergy sensor. """ + """Sets up the Efergy sensor.""" app_token = config.get("app_token") if not app_token: _LOGGER.error( @@ -48,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes class EfergySensor(Entity): - """ Implements an Efergy sensor. """ + """Implements an Efergy sensor.""" # pylint: disable=too-many-arguments def __init__(self, sensor_type, app_token, utc_offset, period, currency): @@ -66,21 +64,21 @@ class EfergySensor(Entity): @property def name(self): - """ Returns the name. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): - """ Gets the Efergy monitor data from the web service. """ + """Gets the Efergy monitor data from the web service.""" try: if self.type == 'instant_readings': url_string = _RESOURCE + 'getInstant?token=' + self.app_token diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index fb5cd0f1211..74e7e7b837e 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.eliqonline -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Monitors home energy use for the eliq online service. For more details about this platform, please refer to the documentation at @@ -19,7 +17,7 @@ DEFAULT_NAME = "ELIQ Energy Usage" def setup_platform(hass, config, add_devices, discovery_info=None): - """ Set up the Eliq sensor. """ + """Set up the Eliq sensor.""" import eliqonline @@ -39,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class EliqSensor(Entity): - """ Implements a Eliq sensor. """ + """Implements a Eliq sensor.""" def __init__(self, api, channel_id, name): self._name = name @@ -52,28 +50,28 @@ class EliqSensor(Entity): @property def name(self): - """ Returns the name. """ + """Returns the name of the sensor.""" return self._name @property def icon(self): - """ Returns icon. """ + """Returns icon.""" return "mdi:speedometer" @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state def update(self): - """ Gets the latest data. """ + """Gets the latest data.""" try: response = self.api.get_data_now(channelid=self.channel_id) self._state = int(response.power) except (TypeError, URLError): - _LOGGER.error("could not connect to the eliqonline servers") + _LOGGER.error("Could not connect to the eliqonline servers") diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 5e080a341c0..b383eb6f297 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.forecast -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Forecast.io weather service. +Support for Forecast.io weather service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.forecast/ @@ -41,12 +39,12 @@ SENSOR_TYPES = { 'ozone': ['Ozone', 'DU', 'DU', 'DU', 'DU', 'DU'], } -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Forecast.io sensor. """ + """Get the Forecast.io sensor.""" import forecastio if None in (hass.config.latitude, hass.config.longitude): @@ -88,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class ForeCastSensor(Entity): - """ Implements an Forecast.io sensor. """ + """Implements an Forecast.io sensor.""" def __init__(self, weather_data, sensor_type): self.client_name = 'Weather' @@ -111,26 +109,27 @@ class ForeCastSensor(Entity): @property def name(self): + """The name of the sensor.""" return '{} {}'.format(self.client_name, self._name) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement @property def unit_system(self): - """ Unit system of this entity. """ + """Unit system of this entity.""" return self._unit_system # pylint: disable=too-many-branches def update(self): - """ Gets the latest data from Forecast.io and updates the states. """ + """Gets the latest data from Forecast.io and updates the states.""" import forecastio self.forecast_client.update() @@ -177,7 +176,7 @@ class ForeCastSensor(Entity): class ForeCastData(object): - """ Gets the latest data from Forecast.io. """ + """Gets the latest data from Forecast.io.""" def __init__(self, api_key, latitude, longitude, units): self._api_key = api_key @@ -190,7 +189,7 @@ class ForeCastData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from Forecast.io. """ + """Gets the latest data from Forecast.io.""" import forecastio forecast = forecastio.load_forecast(self._api_key, diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index be46c20ba77..a3160da7c7c 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.glances -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Gathers system information of hosts which running glances. +Support gahtering system information of hosts which are running glances. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.glances/ @@ -39,13 +37,13 @@ SENSOR_TYPES = { } _LOGGER = logging.getLogger(__name__) -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the Glances sensor. """ + """Setup the Glances sensor.""" host = config.get(CONF_HOST) port = config.get('port', CONF_PORT) @@ -85,7 +83,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class GlancesSensor(Entity): - """ Implements a Glances sensor. """ + """Implements a Glances sensor.""" def __init__(self, rest, name, sensor_type): self.rest = rest @@ -97,7 +95,7 @@ class GlancesSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" if self._name is None: return SENSOR_TYPES[self.type][0] else: @@ -105,13 +103,13 @@ class GlancesSensor(Entity): @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement # pylint: disable=too-many-branches, too-many-return-statements @property def state(self): - """ Returns the state of the resources. """ + """Returns the state of the resources.""" value = self.rest.data if value is not None: @@ -149,21 +147,20 @@ class GlancesSensor(Entity): return value['processcount']['sleeping'] def update(self): - """ Gets the latest data from REST API. """ + """Gets the latest data from REST API.""" self.rest.update() # pylint: disable=too-few-public-methods class GlancesData(object): - """ Class for handling the data retrieval. """ - + """Class for handling the data retrieval.""" def __init__(self, resource): self._resource = resource self.data = dict() @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from the Glances REST API. """ + """Gets the latest data from the Glances REST API.""" try: response = requests.get(self._resource, timeout=10) self.data = response.json() diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 8ef42ab5b2d..38b0241e4a2 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.isy994 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for ISY994 sensors. For more details about this platform, please refer to the documentation at @@ -29,16 +27,16 @@ DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like', def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the ISY994 platform. """ + """Sets up the ISY994 platform.""" # pylint: disable=protected-access logger = logging.getLogger(__name__) devs = [] - # verify connection + # Verify connection if ISY is None or not ISY.connected: logger.error('A connection has not been made to the ISY controller.') return False - # import weather + # Import weather if ISY.climate is not None: for prop in ISY.climate._id2name: if prop is not None: @@ -49,14 +47,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): getattr(ISY.climate, prop + '_units')) devs.append(ISYSensorDevice(node)) - # import sensor nodes + # Import sensor nodes for (path, node) in ISY.nodes: if SENSOR_STRING in node.name: if HIDDEN_STRING in path: node.name += HIDDEN_STRING devs.append(ISYSensorDevice(node, [STATE_ON, STATE_OFF])) - # import sensor programs + # Import sensor programs for (folder_name, states) in ( ('HA.locations', [STATE_HOME, STATE_NOT_HOME]), ('HA.sensors', [STATE_OPEN, STATE_CLOSED]), @@ -75,7 +73,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WeatherPseudoNode(object): - """ This class allows weather variable to act as regular nodes. """ + """This class allows weather variable to act as regular nodes.""" # pylint: disable=too-few-public-methods def __init__(self, device_id, name, status, units=None): @@ -86,8 +84,7 @@ class WeatherPseudoNode(object): class ISYSensorDevice(ISYDeviceABC): - """ Represents a ISY sensor. """ - + """Represents a ISY sensor.""" _domain = 'sensor' def __init__(self, node, states=None): diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index c29d5cea7b8..53b1726a210 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.mfi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Ubiquiti mFi sensors. For more details about this platform, please refer to the documentation at @@ -36,8 +34,7 @@ SENSOR_MODELS = [ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up mFi sensors. """ - + """Sets up mFi sensors.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['host', CONF_USERNAME, @@ -66,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MfiSensor(Entity): - """ An mFi sensor that exposes tag=value. """ + """An mFi sensor that exposes tag=value.""" def __init__(self, port, hass): self._port = port @@ -74,10 +71,12 @@ class MfiSensor(Entity): @property def name(self): + """Returns the name of th sensor.""" return self._port.label @property def state(self): + """Returns the state of the sensor.""" if self._port.model == 'Input Digital': return self._port.value > 0 and STATE_ON or STATE_OFF else: @@ -86,6 +85,7 @@ class MfiSensor(Entity): @property def unit_of_measurement(self): + """Unit of measurement of this entity, if any.""" if self._port.tag == 'temperature': return TEMP_CELCIUS elif self._port.tag == 'active_pwr': @@ -95,4 +95,5 @@ class MfiSensor(Entity): return self._port.tag def update(self): + """Gets the latest data.""" self._port.refresh() diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 2483855c589..96830fda250 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -1,6 +1,4 @@ """ -homeassistant.components.modbus -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Modbus sensors. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ DEPENDENCIES = ['modbus'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Read config and create Modbus devices. """ + """Create Modbus devices.""" sensors = [] slave = config.get("slave", None) if modbus.TYPE == "serial" and not slave: @@ -54,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ModbusSensor(Entity): # pylint: disable=too-many-arguments - """ Represents a Modbus Sensor. """ + """Represents a Modbus Sensor.""" def __init__(self, name, slave, register, bit=None, unit=None, coil=False): self._name = name @@ -66,26 +64,24 @@ class ModbusSensor(Entity): self._coil = coil def __str__(self): + """Returns the name and the state of the sensor.""" return "%s: %s" % (self.name, self.state) @property def should_poll(self): - """ - We should poll, because slaves are not allowed to - initiate communication on Modbus networks. - """ + """ Polling needed.""" return True @property def unique_id(self): - """ Returns a unique id. """ + """Returns a unique id.""" return "MODBUS-SENSOR-{}-{}-{}".format(self.slave, self.register, self.bit) @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" if self.bit: return STATE_ON if self._value else STATE_OFF else: @@ -93,12 +89,12 @@ class ModbusSensor(Entity): @property def name(self): - """ Get the name of the sensor. """ + """Get the name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" if self._unit == "C": return TEMP_CELCIUS elif self._unit == "F": @@ -107,7 +103,7 @@ class ModbusSensor(Entity): return self._unit def update(self): - """ Update the state of the sensor. """ + """Update the state of the sensor.""" if self._coil: result = modbus.NETWORK.read_coils(self.register, 1) self._value = result.bits[0] diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 1e6a49ad9c4..1c7c549144c 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a MQTT sensor. +Support for MQTT sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mqtt/ @@ -23,7 +21,7 @@ DEPENDENCIES = ['mqtt'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add MQTT Sensor. """ + """Add MQTT Sensor.""" if config.get('state_topic') is None: _LOGGER.error("Missing required variable: state_topic") @@ -40,7 +38,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttSensor(Entity): - """ Represents a sensor that can be updated using MQTT. """ + """Represents a sensor that can be updated using MQTT.""" def __init__(self, hass, name, state_topic, qos, unit_of_measurement, value_template): self._state = STATE_UNKNOWN @@ -51,7 +49,7 @@ class MqttSensor(Entity): self._unit_of_measurement = unit_of_measurement def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" if value_template is not None: payload = template.render_with_possible_json_value( hass, value_template, payload) @@ -62,20 +60,20 @@ class MqttSensor(Entity): @property def should_poll(self): - """ No polling needed """ + """No polling needed.""" return False @property def name(self): - """ The name of the sensor """ + """The name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit this state is expressed in. """ + """Unit this state is expressed in.""" return self._unit_of_measurement @property def state(self): - """ Returns the state of the entity. """ + """Returns the state of the entity.""" return self._state diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index a55c7fd3335..ffea4892988 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -110,7 +110,7 @@ class MySensorsSensor(Entity): @property def should_poll(self): - """MySensor gateway pushes its state to HA.""" + """No polling needed.""" return False @property diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index 465dfd28415..004779fb3b0 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.nest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Nest Thermostat Sensors. For more details about this platform, please refer to the documentation at @@ -41,8 +39,7 @@ SENSOR_TEMP_TYPES = ['temperature', def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup Nest Sensor. """ - + """Setup Nest Sensor.""" logger = logging.getLogger(__name__) try: for structure in nest.NEST.structures: @@ -71,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NestSensor(Entity): - """ Represents a Nest sensor. """ + """Represents a Nest sensor.""" def __init__(self, structure, device, variable): self.structure = structure @@ -80,8 +77,7 @@ class NestSensor(Entity): @property def name(self): - """ Returns the name of the nest, if any. """ - + """Returns the name of the nest, if any.""" location = self.device.where name = self.device.name if location is None: @@ -96,30 +92,28 @@ class NestSensor(Entity): class NestBasicSensor(NestSensor): - """ Represents a basic Nest sensor with state. """ - + """Represents a basic Nest sensor with state.""" @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" return getattr(self.device, self.variable) @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return SENSOR_UNITS.get(self.variable, None) class NestTempSensor(NestSensor): - """ Represents a Nest Temperature sensor. """ - + """Represents a Nest Temperature sensor.""" @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return TEMP_CELCIUS @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" temp = getattr(self.device, self.variable) if temp is None: return None @@ -128,11 +122,10 @@ class NestTempSensor(NestSensor): class NestWeatherSensor(NestSensor): - """ Represents a basic Nest Weather Conditions sensor. """ - + """Represents a basic Nest Weather Conditions sensor.""" @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" if self.variable == 'kph' or self.variable == 'direction': return getattr(self.structure.weather.current.wind, self.variable) else: @@ -140,5 +133,5 @@ class NestWeatherSensor(NestSensor): @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return SENSOR_UNITS.get(self.variable, None) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 29271b82ea9..0d50c0be106 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.netatmo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -NetAtmo Weather Service service. +Support for the NetAtmo Weather Service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.netatmo/ @@ -41,8 +39,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the NetAtmo sensor. """ - + """Get the NetAtmo sensor.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_API_KEY, CONF_USERNAME, @@ -89,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class NetAtmoSensor(Entity): - """ Implements a NetAtmo sensor. """ + """Implements a NetAtmo sensor.""" def __init__(self, netatmo_data, module_name, sensor_type): self._name = "NetAtmo {} {}".format(module_name, @@ -103,26 +100,27 @@ class NetAtmoSensor(Entity): @property def name(self): + """The name of the sensor.""" return self._name @property def icon(self): + """Icon to use in the frontend, if any.""" return SENSOR_TYPES[self.type][2] @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement # pylint: disable=too-many-branches def update(self): - """ Gets the latest data from NetAtmo API and updates the states. """ - + """Gets the latest data from NetAtmo API and updates the states.""" self.netatmo_data.update() data = self.netatmo_data.data[self.module_name] @@ -139,21 +137,20 @@ class NetAtmoSensor(Entity): class NetAtmoData(object): - """ Gets the latest data from NetAtmo. """ + """Gets the latest data from NetAtmo.""" def __init__(self, auth): self.auth = auth self.data = None def get_module_names(self): - """ Return all module available on the API as a list. """ + """Return all module available on the API as a list.""" self.update() return self.data.keys() @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Call the NetAtmo API to update the data. """ + """Call the NetAtmo API to update the data.""" import lnetatmo - # Gets the latest data from NetAtmo. """ dev_list = lnetatmo.DeviceList(self.auth) self.data = dev_list.lastData(exclude=3600) diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py index f3b22c0383c..fd335b663f3 100644 --- a/homeassistant/components/sensor/neurio_energy.py +++ b/homeassistant/components/sensor/neurio_energy.py @@ -1,7 +1,6 @@ """ -homeassistant.components.sensor.neurio_energy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Monitors home energy use as measured by an neurio hub using its official API. +Support for monitoring an neurio hub. + For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.neurio_energy/ """ @@ -19,7 +18,7 @@ ICON = 'mdi:flash' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Neurio sensor. """ + """Sets up the Neurio sensor.""" api_key = config.get("api_key") api_secret = config.get("api_secret") sensor_id = config.get("sensor_id") @@ -43,7 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes class NeurioEnergy(Entity): - """ Implements an Neurio energy. """ + """Implements an Neurio energy.""" # pylint: disable=too-many-arguments def __init__(self, api_key, api_secret, sensor_id): @@ -56,26 +55,26 @@ class NeurioEnergy(Entity): @property def name(self): - """ Returns the name. """ + """Returns the name of th sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON def update(self): - """ Gets the Neurio monitor data from the web service. """ + """Gets the Neurio monitor data from the web service.""" import neurio try: neurio_tp = neurio.TokenProvider(key=self.api_key, diff --git a/homeassistant/components/sensor/onewire.py b/homeassistant/components/sensor/onewire.py index 5a198864c38..404587d3dac 100644 --- a/homeassistant/components/sensor/onewire.py +++ b/homeassistant/components/sensor/onewire.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.onewire -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for DS18B20 One Wire Sensors. For more details about this platform, please refer to the documentation at @@ -27,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the one wire Sensors. """ + """Sets up the one wire Sensors.""" if DEVICE_FILES == []: _LOGGER.error('No onewire sensor found.') @@ -58,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class OneWire(Entity): - """ An One wire Sensor. """ + """An One wire Sensor.""" def __init__(self, name, device_file): self._name = name @@ -67,7 +65,7 @@ class OneWire(Entity): self.update() def _read_temp_raw(self): - """ Read the temperature as it is returned by the sensor. """ + """Read the temperature as it is returned by the sensor.""" ds_device_file = open(self._device_file, 'r') lines = ds_device_file.readlines() ds_device_file.close() @@ -75,21 +73,21 @@ class OneWire(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return TEMP_CELCIUS def update(self): - """ Gets the latest data from the device. """ + """Gets the latest data from the device.""" lines = self._read_temp_raw() while lines[0].strip()[-3:] != 'YES': time.sleep(0.2) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 49c1eadf36c..38a3dfd68e2 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.openweathermap -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -OpenWeatherMap (OWM) service. +Support for the OpenWeatherMap (OWM) service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.openweathermap/ @@ -26,13 +24,12 @@ SENSOR_TYPES = { 'snow': ['Snow', 'mm'] } -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the OpenWeatherMap sensor. """ - + """Get the OpenWeatherMap sensor.""" if None in (hass.config.latitude, hass.config.longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False @@ -71,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class OpenWeatherMapSensor(Entity): - """ Implements an OpenWeatherMap sensor. """ + """Implements an OpenWeatherMap sensor.""" def __init__(self, weather_data, sensor_type, temp_unit): self.client_name = 'Weather' @@ -85,22 +82,22 @@ class OpenWeatherMapSensor(Entity): @property def name(self): + """The name of the sensor.""" return '{} {}'.format(self.client_name, self._name) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement # pylint: disable=too-many-branches def update(self): - """ Gets the latest data from OWM and updates the states. """ - + """Gets the latest data from OWM and updates the states.""" self.owa_client.update() data = self.owa_client.data fc_data = self.owa_client.fc_data @@ -143,7 +140,7 @@ class OpenWeatherMapSensor(Entity): class WeatherData(object): - """ Gets the latest data from OpenWeatherMap. """ + """Gets the latest data from OpenWeatherMap.""" def __init__(self, owm, forecast, latitude, longitude): self.owm = owm @@ -155,7 +152,7 @@ class WeatherData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from OpenWeatherMap. """ + """Gets the latest data from OpenWeatherMap.""" obs = self.owm.weather_at_coords(self.latitude, self.longitude) if obs is None: _LOGGER.warning('Failed to fetch data from OWM') diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py index fb6499366f1..126ac4265cb 100644 --- a/homeassistant/components/sensor/rest.py +++ b/homeassistant/components/sensor/rest.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.rest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The rest sensor will consume responses sent by an exposed REST API. +Support for REST API sensors.. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.rest/ @@ -26,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the REST sensor. """ + """Get the REST sensor.""" resource = config.get('resource', None) method = config.get('method', DEFAULT_METHOD) payload = config.get('payload', None) @@ -46,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments class RestSensor(Entity): - """ Implements a REST sensor. """ + """Implements a REST sensor.""" def __init__(self, hass, rest, name, unit_of_measurement, value_template): self._hass = hass @@ -59,21 +57,21 @@ class RestSensor(Entity): @property def name(self): - """ The name of the sensor. """ + """The name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit the value is expressed in. """ + """Unit the value is expressed in.""" return self._unit_of_measurement @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state def update(self): - """ Gets the latest data from REST API and updates the state. """ + """Gets the latest data from REST API and updates the state.""" self.rest.update() value = self.rest.data @@ -97,7 +95,7 @@ class RestData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from REST service with GET method. """ + """Gets the latest data from REST service with GET method.""" try: with requests.Session() as sess: response = sess.send(self._request, timeout=10, diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 66e8c9f218e..d424d899bcc 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.rfxtrx -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Shows sensor values from RFXtrx sensors. +Support for RFXtrx sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.rfxtrx/ @@ -28,11 +26,11 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Setup the RFXtrx platform. """ + """Setup the RFXtrx platform.""" from RFXtrx import SensorEvent def sensor_update(event): - """ Callback for sensor updates from the RFXtrx gateway. """ + """Callback for sensor updates from the RFXtrx gateway.""" if isinstance(event, SensorEvent): entity_id = slugify(event.device.id_string.lower()) @@ -56,7 +54,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class RfxtrxSensor(Entity): - """ Represents a RFXtrx sensor. """ + """Represents a RFXtrx sensor.""" def __init__(self, event): self.event = event @@ -74,25 +72,27 @@ class RfxtrxSensor(Entity): id_string) def __str__(self): + """Returns the name.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" if self._data_type: return self.event.values[self._data_type] return None @property def name(self): - """ Get the name of the sensor. """ + """Get the name of the sensor.""" return self._name @property def device_state_attributes(self): + """Returns the state attributes.""" return self.event.values @property def unit_of_measurement(self): - """ Unit this state is expressed in. """ + """Unit this state is expressed in.""" return self._unit_of_measurement diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index 5d8de104a5a..8d4c14984f0 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.sabnzbd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Monitors SABnzbd NZB client API. +Support for monitoring an SABnzbd NZB client. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sabnzbd/ @@ -26,13 +24,12 @@ SENSOR_TYPES = { } _LOGGER = logging.getLogger(__name__) - _THROTTLED_REFRESH = None # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the SABnzbd sensors. """ + """Sets up the SABnzbd sensors.""" from pysabnzbd import SabnzbdApi, SabnzbdApiException api_key = config.get("api_key") @@ -68,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SabnzbdSensor(Entity): - """ Represents an SABnzbd sensor. """ + """Represents an SABnzbd sensor.""" def __init__(self, sensor_type, sabnzb_client, client_name): self._name = SENSOR_TYPES[sensor_type][0] @@ -80,20 +77,21 @@ class SabnzbdSensor(Entity): @property def name(self): + """Returns the name of the sensor.""" return self.client_name + ' ' + self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement def refresh_sabnzbd_data(self): - """ Calls the throttled SABnzbd refresh method. """ + """Calls the throttled SABnzbd refresh method.""" if _THROTTLED_REFRESH is not None: from pysabnzbd import SabnzbdApiException try: @@ -104,6 +102,7 @@ class SabnzbdSensor(Entity): ) def update(self): + """Gets the latest data and updates the states.""" self.refresh_sabnzbd_data() if self.sabnzb_client.queue: if self.type == 'current_status': diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py index 629495f2be0..67891f1f4d5 100644 --- a/homeassistant/components/sensor/speedtest.py +++ b/homeassistant/components/sensor/speedtest.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.speedtest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Speedtest.net sensor based on speedtest-cli. +Support for Speedtest.net based on speedtest-cli. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.speedtest/ @@ -40,7 +38,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the Speedtest sensor. """ + """Setup the Speedtest sensor.""" data = SpeedtestData(hass, config) dev = [] @@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(dev) def update(call=None): - """ Update service for manual updates. """ + """Update service for manual updates.""" data.update(dt_util.now()) for sensor in dev: sensor.update() @@ -63,7 +61,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class SpeedtestSensor(Entity): - """ Implements a speedtest.net sensor. """ + """Implements a speedtest.net sensor.""" def __init__(self, speedtest_data, sensor_type): self._name = SENSOR_TYPES[sensor_type][0] @@ -74,20 +72,21 @@ class SpeedtestSensor(Entity): @property def name(self): + """The name of the sensor.""" return '{} {}'.format('Speedtest', self._name) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): - """ Gets the latest data from Forecast.io and updates the states. """ + """Gets the latest data and updates the states.""" data = self.speedtest_client.data if data is not None: if self.type == 'ping': @@ -99,7 +98,7 @@ class SpeedtestSensor(Entity): class SpeedtestData(object): - """ Gets the latest data from speedtest.net. """ + """Gets the latest data from speedtest.net.""" def __init__(self, hass, config): self.data = None @@ -112,7 +111,7 @@ class SpeedtestData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self, now): - """ Gets the latest data from speedtest.net. """ + """Gets the latest data from speedtest.net.""" _LOGGER.info('Executing speedtest') re_output = _SPEEDTEST_REGEX.split( check_output([sys.executable, self.path( diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 4d44e58619f..f3e3407fd12 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -1,8 +1,5 @@ """ -homeassistant.components.sensor.swiss_public_transport -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Swiss public transport sensor will give you the next two departure times -from a given location to another one. This sensor is limited to Switzerland. +Support for transport.opendata.ch For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.swiss_public_transport/ @@ -26,12 +23,12 @@ ATTR_TARGET = 'Destination' ATTR_REMAINING_TIME = 'Remaining time' ICON = 'mdi:bus' -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Swiss public transport sensor. """ + """Get the Swiss public transport sensor.""" # journal contains [0] Station ID start, [1] Station ID destination # [2] Station name start, and [3] Station name destination @@ -56,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class SwissPublicTransportSensor(Entity): - """ Implements an Swiss public transport sensor. """ + """Implements an Swiss public transport sensor.""" def __init__(self, data, journey): self.data = data @@ -67,17 +64,17 @@ class SwissPublicTransportSensor(Entity): @property def name(self): - """ Returns the name. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def device_state_attributes(self): - """ Returns the state attributes. """ + """Returns the state attributes.""" if self._times is not None: return { ATTR_DEPARTURE_TIME1: self._times[0], @@ -90,12 +87,12 @@ class SwissPublicTransportSensor(Entity): @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON # pylint: disable=too-many-branches def update(self): - """ Gets the latest data from opendata.ch and updates the states. """ + """Gets the latest data from opendata.ch and updates the states.""" self.data.update() self._times = self.data.times try: @@ -106,7 +103,7 @@ class SwissPublicTransportSensor(Entity): # pylint: disable=too-few-public-methods class PublicTransportData(object): - """ Class for handling the data retrieval. """ + """Class for handling the data retrieval.""" def __init__(self, journey): self.start = journey[0] @@ -115,8 +112,7 @@ class PublicTransportData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from opendata.ch. """ - + """Gets the latest data from opendata.ch.""" response = requests.get( _RESOURCE + 'connections?' + diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index 9e191a112b0..b6286943a2c 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.systemmonitor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Shows system monitor values such as: disk, memory, and processor use. +Support for monitoring the local system.. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.systemmonitor/ @@ -40,8 +38,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the sensors. """ - + """Sets up the sensors.""" dev = [] for resource in config['resources']: if 'arg' not in resource: @@ -55,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SystemMonitorSensor(Entity): - """ A system monitor sensor. """ + """A system monitor sensor.""" def __init__(self, sensor_type, argument=''): self._name = SENSOR_TYPES[sensor_type][0] + ' ' + argument @@ -67,27 +64,27 @@ class SystemMonitorSensor(Entity): @property def name(self): - """ Returns the name of the sensor. """ + """Returns the name of the sensor.""" return self._name.rstrip() @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return SENSOR_TYPES[self.type][2] @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement # pylint: disable=too-many-branches def update(self): - """ Get the latest system informations. """ + """Get the latest system information.""" import psutil if self.type == 'disk_use_percent': self._state = psutil.disk_usage(self.argument).percent diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index 001d20ee792..b7e4a8b65ba 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.tellduslive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Shows sensor values from Tellstick Net/Telstick Live. +Support for Tellstick Net/Telstick Live. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.tellduslive/ @@ -41,14 +39,14 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up Tellstick sensors. """ + """Sets up Tellstick sensors.""" if discovery_info is None: return add_devices(TelldusLiveSensor(sensor) for sensor in discovery_info) class TelldusLiveSensor(Entity): - """ Represents a Telldus Live sensor. """ + """ Represents a Telldus Live sensor.""" def __init__(self, sensor_id): self._id = sensor_id @@ -56,55 +54,63 @@ class TelldusLiveSensor(Entity): _LOGGER.debug("created sensor %s", self) def update(self): - """ update sensor values """ + """Update sensor values.""" tellduslive.NETWORK.update_sensors() self._sensor = tellduslive.NETWORK.get_sensor(self._id) @property def _sensor_name(self): + return self._sensor["name"] @property def _sensor_value(self): + return self._sensor["data"]["value"] @property def _sensor_type(self): + return self._sensor["data"]["name"] @property def _battery_level(self): + sensor_battery_level = self._sensor.get("battery") return round(sensor_battery_level * 100 / 255) \ if sensor_battery_level else None @property def _last_updated(self): + sensor_last_updated = self._sensor.get("lastUpdated") return str(datetime.fromtimestamp(sensor_last_updated)) \ if sensor_last_updated else None @property def _value_as_temperature(self): + return round(float(self._sensor_value), 1) @property def _value_as_humidity(self): + return int(round(float(self._sensor_value))) @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME, self.quantity_name) @property def available(self): + return not self._sensor.get("offline", False) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" if self._sensor_type == SENSOR_TYPE_TEMP: return self._value_as_temperature elif self._sensor_type == SENSOR_TYPE_HUMIDITY: @@ -112,6 +118,7 @@ class TelldusLiveSensor(Entity): @property def device_state_attributes(self): + """Returns the state attributes.""" attrs = {} if self._battery_level is not None: attrs[ATTR_BATTERY_LEVEL] = self._battery_level @@ -121,13 +128,15 @@ class TelldusLiveSensor(Entity): @property def quantity_name(self): - """ name of quantity """ + """Name of quantity.""" return SENSOR_TYPES[self._sensor_type][0] @property def unit_of_measurement(self): + return SENSOR_TYPES[self._sensor_type][1] @property def icon(self): + return SENSOR_TYPES[self._sensor_type][2] diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index 0b8b80c3388..6efdb737536 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.tellstick -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Shows sensor values from Tellstick sensors. +Support for Tellstick sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.tellstick/ @@ -20,7 +18,7 @@ REQUIREMENTS = ['tellcore-py==1.1.2'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up Tellstick sensors. """ + """Sets up Tellstick sensors.""" import tellcore.telldus as telldus import tellcore.constants as tellcore_constants @@ -79,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TellstickSensor(Entity): - """ Represents a Tellstick sensor. """ + """Represents a Tellstick sensor.""" def __init__(self, name, sensor, datatype, sensor_info): self.datatype = datatype @@ -90,14 +88,15 @@ class TellstickSensor(Entity): @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self.sensor.value(self.datatype).value @property def unit_of_measurement(self): + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index a330ef064c0..8fd97bf2224 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.temper -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for getting temperature from TEMPer devices. For more details about this platform, please refer to the documentation at @@ -20,7 +18,7 @@ REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return Temper sensors. """ + """Find and return Temper sensors.""" from temperusb.temper import TemperHandler temp_unit = hass.config.temperature_unit @@ -31,7 +29,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class TemperSensor(Entity): - """ Represents an Temper temperature sensor. """ + """Represents an Temper temperature sensor.""" + def __init__(self, temper_device, temp_unit, name): self.temper_device = temper_device self.temp_unit = temp_unit @@ -40,21 +39,21 @@ class TemperSensor(Entity): @property def name(self): - """ Returns the name of the temperature sensor. """ + """Returns the name of the temperature sensor.""" return self._name @property def state(self): - """ Returns the state of the entity. """ + """Returns the state of the entity.""" return self.current_value @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self.temp_unit def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" try: self.current_value = self.temper_device.get_temperature() except IOError: diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 0b58c075893..bd4ece7d450 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows the creation of a sensor that breaks out state_attributes from other entities. @@ -26,7 +24,7 @@ STATE_ERROR = 'error' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the sensors. """ + """Sets up the sensors.""" sensors = [] if config.get(CONF_SENSORS) is None: @@ -68,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SensorTemplate(Entity): - """ Represents a Template Sensor. """ + """Represents a Template Sensor.""" # pylint: disable=too-many-arguments def __init__(self, @@ -96,25 +94,26 @@ class SensorTemplate(Entity): @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Returns the unit_of_measurement of the device. """ + """Returns the unit_of_measurement of the device.""" return self._unit_of_measurement @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False def update(self): + """Gets the latest data and updates the states.""" try: self._state = template.render(self.hass, self._template) except TemplateError as ex: diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py index 4e63384a8ac..1d48b15b5b9 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/sensor/time_date.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.time_date -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Date and Time service. +Support for showing the date and the time. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.time_date/ @@ -23,7 +21,7 @@ OPTION_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Time and Date sensor. """ + """Get the Time and Date sensor.""" if hass.config.time_zone is None: _LOGGER.error("Timezone is not set in Home Assistant config") @@ -41,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods class TimeDateSensor(Entity): - """ Implements a Time and Date sensor. """ + """Implements a Time and Date sensor.""" def __init__(self, option_type): self._name = OPTION_TYPES[option_type] @@ -51,16 +49,17 @@ class TimeDateSensor(Entity): @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def icon(self): + """Icon to use in the frontend, if any.""" if "date" in self.type and "time" in self.type: return "mdi:calendar-clock" elif "date" in self.type: @@ -69,8 +68,7 @@ class TimeDateSensor(Entity): return "mdi:clock" def update(self): - """ Gets the latest data and updates the states. """ - + """Gets the latest data and updates the states.""" time_date = dt_util.utcnow() time = dt_util.datetime_to_time_str(dt_util.as_local(time_date)) time_utc = dt_util.datetime_to_time_str(time_date) diff --git a/homeassistant/components/sensor/torque.py b/homeassistant/components/sensor/torque.py index c3e5b8541ad..9507bbb0de5 100644 --- a/homeassistant/components/sensor/torque.py +++ b/homeassistant/components/sensor/torque.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.torque -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Get data from the Torque OBD application. +Support for the Torque OBD application. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.torque/ @@ -29,25 +27,24 @@ VALUE_KEY = re.compile(SENSOR_VALUE_KEY) def decode(value): - """ Double-decode required. """ + """Double-decode required.""" return value.encode('raw_unicode_escape').decode('utf-8') def convert_pid(value): - """ Convert pid from hex string to integer. """ + """Convert pid from hex string to integer.""" return int(value, 16) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Set up Torque platform. """ - + """Set up Torque platform.""" vehicle = config.get('name', DEFAULT_NAME) email = config.get('email', None) sensors = {} def _receive_data(handler, path_match, data): - """ Received data from Torque. """ + """Received data from Torque.""" handler.send_response(HTTP_OK) handler.end_headers() @@ -84,7 +81,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TorqueSensor(Entity): - """ Represents a Torque sensor. """ + """Represents a Torque sensor.""" def __init__(self, name, unit): self._name = name @@ -93,25 +90,25 @@ class TorqueSensor(Entity): @property def name(self): - """ Returns the name of the sensor. """ + """Returns the name of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Returns the unit of measurement. """ + """Returns the unit of measurement.""" return self._unit @property def state(self): - """ State of the sensor. """ + """State of the sensor.""" return self._state @property def icon(self): - """ Sensor default icon. """ + """Sensor default icon.""" return 'mdi:car' def on_update(self, value): - """ Receive an update. """ + """Receive an update.""" self._state = value self.update_ha_state() diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index 662394f3b18..cf772fc62c2 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.transmission -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Monitors Transmission BitTorrent client API. +Support for monitoring the Transmission BitTorrent client API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.transmission/ @@ -27,7 +25,7 @@ _THROTTLED_REFRESH = None # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Transmission sensors. """ + """Sets up the Transmission sensors.""" import transmissionrpc from transmissionrpc.error import TransmissionError @@ -41,9 +39,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Missing config variable %s', CONF_HOST) return False - # import logging - # logging.getLogger('transmissionrpc').setLevel(logging.DEBUG) - transmission_api = transmissionrpc.Client( host, port=port, user=username, password=password) try: @@ -69,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TransmissionSensor(Entity): - """ A Transmission sensor. """ + """A Transmission sensor.""" def __init__(self, sensor_type, transmission_client, client_name): self._name = SENSOR_TYPES[sensor_type][0] @@ -81,16 +76,17 @@ class TransmissionSensor(Entity): @property def name(self): + """Returns the name of the sensor.""" return self.client_name + ' ' + self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" return self._unit_of_measurement def refresh_transmission_data(self): @@ -106,7 +102,7 @@ class TransmissionSensor(Entity): ) def update(self): - """ Gets the latest data from Transmission and updates the state. """ + """Gets the latest data from Transmission and updates the state.""" self.refresh_transmission_data() if self.type == 'current_status': if self.transmission_client.session: diff --git a/homeassistant/components/sensor/twitch.py b/homeassistant/components/sensor/twitch.py index ea743992fb4..db81c55b6a9 100644 --- a/homeassistant/components/sensor/twitch.py +++ b/homeassistant/components/sensor/twitch.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.twitch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A sensor for the Twitch stream status. +Support for the Twitch stream status. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.twitch/ @@ -21,13 +19,13 @@ DOMAIN = 'twitch' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Twitch platform. """ + """Sets up the Twitch platform.""" add_devices( [TwitchSensor(channel) for channel in config.get('channels', [])]) class TwitchSensor(Entity): - """ Represents an Twitch channel. """ + """Represents an Twitch channel.""" # pylint: disable=abstract-method def __init__(self, channel): @@ -40,22 +38,22 @@ class TwitchSensor(Entity): @property def should_poll(self): - """ Device should be polled. """ + """Device should be polled.""" return True @property def name(self): - """ Returns the name of the sensor. """ + """Returns the name of the sensor.""" return self._channel @property def state(self): - """ State of the sensor. """ + """State of the sensor.""" return self._state # pylint: disable=no-member def update(self): - """ Update device state. """ + """Update device state.""" from twitch.api import v3 as twitch stream = twitch.streams.by_channel(self._channel).get('stream') if stream: @@ -68,7 +66,7 @@ class TwitchSensor(Entity): @property def device_state_attributes(self): - """ Returns the state attributes. """ + """Returns the state attributes.""" if self._state == STATE_STREAMING: return { ATTR_GAME: self._game, @@ -78,5 +76,5 @@ class TwitchSensor(Entity): @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index d919b57e2cd..e490c031c9e 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.vera -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Vera sensors. For more details about this platform, please refer to the documentation at @@ -23,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def get_devices(hass, config): - """ Find and return Vera Sensors. """ + """Find and return Vera Sensors.""" import pyvera as veraApi base_url = config.get('vera_controller_url') @@ -40,7 +38,7 @@ def get_devices(hass, config): if created: def stop_subscription(event): - """ Shutdown Vera subscriptions and subscription thread on exit""" + """Shutdown Vera subscriptions and subscription thread on exit.""" _LOGGER.info("Shutting down subscriptions.") vera_controller.stop() @@ -54,7 +52,7 @@ def get_devices(hass, config): try: devices = vera_controller.get_devices(categories) except RequestException: - # There was a network related error connecting to the vera controller + # There was a network related error connecting to the vera controller. _LOGGER.exception("Error communicating with Vera API") return False @@ -71,12 +69,12 @@ def get_devices(hass, config): def setup_platform(hass, config, add_devices, discovery_info=None): - """ Performs setup for Vera controller devices. """ + """Performs setup for Vera controller devices.""" add_devices(get_devices(hass, config)) class VeraSensor(Entity): - """ Represents a Vera Sensor. """ + """Represents a Vera Sensor.""" def __init__(self, vera_device, controller, extra_data=None): self.vera_device = vera_device @@ -93,7 +91,7 @@ class VeraSensor(Entity): self.update() def _update_callback(self, _device): - """ Called by the vera device callback to update state. """ + """Called by the vera device callback to update state.""" self.update_ha_state(True) def __str__(self): @@ -101,16 +99,17 @@ class VeraSensor(Entity): @property def state(self): + """Returns the name of the sensor.""" return self.current_value @property def name(self): - """ Get the mame of the sensor. """ + """Get the mame of the sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """Unit of measurement of this entity, if any.""" if self.vera_device.category == "Temperature Sensor": return self._temperature_units elif self.vera_device.category == "Light Sensor": @@ -120,6 +119,7 @@ class VeraSensor(Entity): @property def device_state_attributes(self): + """Returns the sensor's attributes.""" attr = {} if self.vera_device.has_battery: attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' @@ -144,10 +144,11 @@ class VeraSensor(Entity): @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False def update(self): + """Updates the state.""" if self.vera_device.category == "Temperature Sensor": current_temp = self.vera_device.temperature vera_temp_units = ( diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index f397242615d..12cafea0f41 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.verisure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interfaces with Verisure sensors. For more details about this platform, please refer to the documentation at @@ -16,8 +14,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Verisure platform. """ - + """Sets up the Verisure platform.""" if not verisure.MY_PAGES: _LOGGER.error('A connection has not been made to Verisure mypages.') return False @@ -49,86 +46,86 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureThermometer(Entity): - """ represents a Verisure thermometer within home assistant. """ + """Represents a Verisure thermometer.""" def __init__(self, climate_status): self._id = climate_status.id @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the device.""" return '{} {}'.format( verisure.CLIMATE_STATUS[self._id].location, "Temperature") @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" # remove ° character return verisure.CLIMATE_STATUS[self._id].temperature[:-1] @property def unit_of_measurement(self): - """ Unit of measurement of this entity """ + """Unit of measurement of this entity.""" return TEMP_CELCIUS # can verisure report in fahrenheit? def update(self): - """ update sensor """ + """Update the sensor.""" verisure.update_climate() class VerisureHygrometer(Entity): - """ represents a Verisure hygrometer within home assistant. """ + """Represents a Verisure hygrometer.""" def __init__(self, climate_status): self._id = climate_status.id @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return '{} {}'.format( verisure.CLIMATE_STATUS[self._id].location, "Humidity") @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" # remove % character return verisure.CLIMATE_STATUS[self._id].humidity[:-1] @property def unit_of_measurement(self): - """ Unit of measurement of this entity """ + """Unit of measurement of this sensor.""" return "%" def update(self): - """ update sensor """ + """Update sensor the sensor.""" verisure.update_climate() class VerisureMouseDetection(Entity): - """ represents a Verisure mousedetector within home assistant. """ + """ Represents a Verisure mouse detector.""" def __init__(self, mousedetection_status): self._id = mousedetection_status.deviceLabel @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the sensor.""" return '{} {}'.format( verisure.MOUSEDETECTION_STATUS[self._id].location, "Mouse") @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the sensor.""" return verisure.MOUSEDETECTION_STATUS[self._id].count @property def unit_of_measurement(self): - """ Unit of measurement of this entity """ + """Unit of measurement of this sensor.""" return "Mice" def update(self): - """ update sensor """ + """Update the sensor.""" verisure.update_mousedetection() diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index 8f56742419d..ebdd01441c0 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.wink -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Wink sensors. For more details about this platform, please refer to the documentation at @@ -15,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Wink platform. """ + """Sets up the Wink platform.""" import pywink if discovery_info is None: @@ -34,57 +32,57 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WinkSensorDevice(Entity): - """ Represents a Wink sensor. """ + """Represents a Wink sensor.""" def __init__(self, wink): self.wink = wink @property def state(self): - """ Returns the state. """ + """Returns the state.""" return STATE_OPEN if self.is_open else STATE_CLOSED @property def unique_id(self): - """ Returns the id of this wink sensor """ + """Returns the id of this wink sensor.""" return "{}.{}".format(self.__class__, self.wink.device_id()) @property def name(self): - """ Returns the name of the sensor if any. """ + """Returns the name of the sensor if any.""" return self.wink.name() def update(self): - """ Update state of the sensor. """ + """Update state of the sensor.""" self.wink.update_state() @property def is_open(self): - """ True if door is open. """ + """True if door is open.""" return self.wink.state() class WinkEggMinder(Entity): - """ Represents a Wink Egg Minder. """ + """Represents a Wink Egg Minder.""" def __init__(self, wink): self.wink = wink @property def state(self): - """ Returns the state. """ + """Returns the state.""" return self.wink.state() @property def unique_id(self): - """ Returns the id of this wink Egg Minder """ + """Returns the id of this wink Egg Minder.""" return "{}.{}".format(self.__class__, self.wink.device_id()) @property def name(self): - """ Returns the name of the Egg Minder if any. """ + """Returns the name of the Egg Minder if any.""" return self.wink.name() def update(self): - """ Update state of the Egg Minder. """ + """Update state of the Egg Minder.""" self.wink.update_state() diff --git a/homeassistant/components/sensor/worldclock.py b/homeassistant/components/sensor/worldclock.py index 13ec4c2fb21..1f42ebafc17 100644 --- a/homeassistant/components/sensor/worldclock.py +++ b/homeassistant/components/sensor/worldclock.py @@ -1,8 +1,5 @@ """ -homeassistant.components.sensor.worldclock -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Worldclock sensor let you display the current time of a different time -zone. +Support for showing the time in a different time zone. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.worldclock/ @@ -18,8 +15,7 @@ ICON = 'mdi:clock' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Worldclock sensor. """ - + """Get the Worldclock sensor.""" try: time_zone = dt_util.get_time_zone(config.get('time_zone')) except AttributeError: @@ -37,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WorldClockSensor(Entity): - """ Implements a Worldclock sensor. """ + """Implements a Worldclock sensor.""" def __init__(self, time_zone, name): self._name = name @@ -47,20 +43,20 @@ class WorldClockSensor(Entity): @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Icon to use in the frontend, if any.""" return ICON def update(self): - """ Gets the time and updates the states. """ + """Gets the time and updates the states.""" self._state = dt_util.datetime_to_time_str( dt_util.now(time_zone=self._time_zone)) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index c4a743f21a0..6bb460b1373 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -1,7 +1,5 @@ """ -homeassistant.components.sensor.yr -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Yr.no weather service. +Support for Yr.no weather service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.yr/ @@ -41,8 +39,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """ Get the Yr.no sensor. """ - + """Get the Yr.no sensor.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) elevation = config.get('elevation') @@ -77,7 +74,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-instance-attributes class YrSensor(Entity): - """ Implements an Yr.no sensor. """ + """Implements an Yr.no sensor.""" def __init__(self, sensor_type, weather): self.client_name = 'yr' @@ -92,16 +89,17 @@ class YrSensor(Entity): @property def name(self): + """The name of the sensor.""" return '{} {}'.format(self.client_name, self._name) @property def state(self): - """ Returns the state of the device. """ + """Returns the state of the device.""" return self._state @property def device_state_attributes(self): - """ Returns state attributes. """ + """Returns state attributes. """ data = { 'about': "Weather forecast from yr.no, delivered by the" " Norwegian Meteorological Institute and the NRK" @@ -116,20 +114,19 @@ class YrSensor(Entity): @property def unit_of_measurement(self): - """ Unit of measurement of this entity, if any. """ + """ Unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): - """ Gets the latest data from yr.no and updates the states. """ - + """Gets the latest data from yr.no and updates the states.""" now = dt_util.utcnow() - # check if data should be updated + # Check if data should be updated if self._update is not None and now <= self._update: return self._weather.update() - # find sensor + # Find sensor for time_entry in self._weather.data['product']['time']: valid_from = dt_util.str_to_datetime( time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ") @@ -167,7 +164,7 @@ class YrSensor(Entity): # pylint: disable=too-few-public-methods class YrData(object): - """ Gets the latest data and updates the states. """ + """Gets the latest data and updates the states.""" def __init__(self, coordinates): self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \ @@ -178,8 +175,8 @@ class YrData(object): self.update() def update(self): - """ Gets the latest data from yr.no """ - # check if new will be available + """Gets the latest data from yr.no.""" + # Check if new will be available if self._nextrun is not None and dt_util.utcnow() <= self._nextrun: return try: diff --git a/homeassistant/components/sensor/zigbee.py b/homeassistant/components/sensor/zigbee.py index 1f165e97c44..45615e9397e 100644 --- a/homeassistant/components/sensor/zigbee.py +++ b/homeassistant/components/sensor/zigbee.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.zigbee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Contains functionality to use a ZigBee device as a sensor. For more details about this platform, please refer to the documentation at @@ -38,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ZigBeeTemperatureSensor(Entity): - """ Allows usage of an XBee Pro as a temperature sensor. """ + """Allows usage of an XBee Pro as a temperature sensor.""" def __init__(self, hass, config): self._config = config self._temp = None @@ -48,17 +46,21 @@ class ZigBeeTemperatureSensor(Entity): @property def name(self): + """The name of the sensor.""" return self._config.name @property def state(self): + """Returns the state of the sensor.""" return self._temp @property def unit_of_measurement(self): + """Unit the value is expressed in.""" return TEMP_CELCIUS def update(self, *args): + """Gets the latest data.""" try: self._temp = zigbee.DEVICE.get_temperature(self._config.address) except zigbee.ZIGBEE_TX_FAILURE: diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 4159b915f26..3ed3a7b90f0 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -1,10 +1,8 @@ """ -homeassistant.components.sensor.zwave -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interfaces with Z-Wave sensors. For more details about this platform, please refer to the documentation -at https://home-assistant.io/components/zwave/ +at https://home-assistant.io/components/sensor.zwave/ """ # Because we do not compile openzwave on CI # pylint: disable=import-error @@ -43,7 +41,7 @@ DEVICE_MAPPINGS = { def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up Z-Wave sensors. """ + """Sets up Z-Wave sensors.""" # Return on empty `discovery_info`. Given you configure HA with: # @@ -67,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): value.node.product_id, value.index) - # Check workaround mappings for specific devices + # Check workaround mappings for specific devices. if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT: # Default the multiplier to 4 @@ -78,7 +76,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): elif DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE: return - # generic Device mappings + # Generic Device mappings elif value.command_class == COMMAND_CLASS_SENSOR_MULTILEVEL: add_devices([ZWaveMultilevelSensor(value)]) @@ -91,7 +89,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ZWaveSensor(ZWaveDeviceEntity, Entity): - """ Represents a Z-Wave sensor. """ + """Represents a Z-Wave sensor.""" def __init__(self, sensor_value): from openzwave.network import ZWaveNetwork @@ -104,39 +102,34 @@ class ZWaveSensor(ZWaveDeviceEntity, Entity): @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" return self._value.data @property def unit_of_measurement(self): + """Unit the value is expressed in.""" return self._value.units def value_changed(self, value): - """ Called when a value has changed on the network. """ + """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: self.update_ha_state() class ZWaveTriggerSensor(ZWaveSensor): """ - Represents a stateless sensor which - triggers events just 'On' within Z-Wave. + Represents a stateless sensor which triggers events just 'On' + within Z-Wave. """ def __init__(self, sensor_value, hass, re_arm_sec=60): - """ - :param sensor_value: The z-wave node - :param hass: - :param re_arm_sec: Set state to Off re_arm_sec after the last On event - :return: - """ super(ZWaveTriggerSensor, self).__init__(sensor_value) self._hass = hass self.invalidate_after = dt_util.utcnow() self.re_arm_sec = re_arm_sec def value_changed(self, value): - """ Called when a value has changed on the network. """ + """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: self.update_ha_state() if value.data: @@ -149,7 +142,7 @@ class ZWaveTriggerSensor(ZWaveSensor): @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" if not self._value.data or \ (self.invalidate_after is not None and self.invalidate_after <= dt_util.utcnow()): @@ -159,11 +152,10 @@ class ZWaveTriggerSensor(ZWaveSensor): class ZWaveMultilevelSensor(ZWaveSensor): - """ Represents a multi level sensor Z-Wave sensor. """ - + """Represents a multi level sensor Z-Wave sensor.""" @property def state(self): - """ Returns the state of the sensor. """ + """Returns the state of the sensor.""" value = self._value.data if self._value.units in ('C', 'F'): @@ -175,6 +167,7 @@ class ZWaveMultilevelSensor(ZWaveSensor): @property def unit_of_measurement(self): + """Unit the value is expressed in.""" unit = self._value.units if unit == 'C': @@ -186,16 +179,15 @@ class ZWaveMultilevelSensor(ZWaveSensor): class ZWaveAlarmSensor(ZWaveSensor): - """ A Z-wave sensor that sends Alarm alerts + """ + A Z-wave sensor that sends Alarm alerts - Examples include certain Multisensors that have motion and - vibration capabilities. Z-Wave defines various alarm types - such as Smoke, Flood, Burglar, CarbonMonoxide, etc. + Examples include certain Multisensors that have motion and vibration + capabilities. Z-Wave defines various alarm types such as Smoke, Flood, + Burglar, CarbonMonoxide, etc. - This wraps these alarms and allows you to use them to - trigger things, etc. + This wraps these alarms and allows you to use them to trigger things, etc. COMMAND_CLASS_ALARM is what we get here. """ - # Empty subclass for now. Allows for later customizations pass From 532d807771a302800594cb742f1c7eb14415f141 Mon Sep 17 00:00:00 2001 From: Rowan Hine Date: Sat, 20 Feb 2016 23:08:18 +0000 Subject: [PATCH 137/186] Add Steam sensor --- .coveragerc | 1 + .../components/sensor/steam_online.py | 80 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 84 insertions(+) create mode 100644 homeassistant/components/sensor/steam_online.py diff --git a/.coveragerc b/.coveragerc index 27016770693..1e7f0b0a88d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -130,6 +130,7 @@ omit = homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rest.py homeassistant/components/sensor/sabnzbd.py + homeassistant/components/sensor/steam_online.py homeassistant/components/sensor/speedtest.py homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py new file mode 100644 index 00000000000..1264e9a8556 --- /dev/null +++ b/homeassistant/components/sensor/steam_online.py @@ -0,0 +1,80 @@ +""" +homeassistant.components.sensor.steam_online +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sensor for Steam account status. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.steam_online/ +""" +from homeassistant.helpers.entity import Entity +from homeassistant.const import (ATTR_ENTITY_PICTURE, CONF_API_KEY) + +ICON = 'mdi:steam' + +REQUIREMENTS = ['steamodd==4.21'] + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Steam platform. """ + import steam as steamod + steamod.api.key.set(config.get(CONF_API_KEY)) + add_devices( + [SteamSensor(account, + steamod) for account in config.get('accounts', [])]) + + +class SteamSensor(Entity): + """ Steam account. """ + + # pylint: disable=abstract-method + def __init__(self, account, steamod): + self._steamod = steamod + self._account = account + self.update() + + @property + def name(self): + """ Returns the name of the sensor. """ + return self._profile.persona + + @property + def entity_id(self): + """ Entity ID. """ + return 'sensor.steam_{}'.format(self._account) + + @property + def state(self): + """ State of the sensor. """ + if self._profile.status == 1: + account_state = 'Online' + elif self._profile.status == 2: + account_state = 'Busy' + elif self._profile.status == 3: + account_state = 'Away' + elif self._profile.status == 4: + account_state = 'Snooze' + elif self._profile.status == 5: + account_state = 'Trade' + elif self._profile.status == 6: + account_state = 'Play' + else: + account_state = 'Offline' + return account_state + + # pylint: disable=no-member + def update(self): + """ Update device state. """ + self._profile = self._steamod.user.profile(self._account) + + @property + def device_state_attributes(self): + """ Returns the state attributes. """ + return { + ATTR_ENTITY_PICTURE: self._profile.avatar_medium + } + + @property + def icon(self): + """ Icon to use in the frontend """ + return ICON diff --git a/requirements_all.txt b/requirements_all.txt index 1daebdb447f..0bcfae358f2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -240,6 +240,9 @@ somecomfort==0.2.0 # homeassistant.components.sensor.speedtest speedtest-cli==0.3.4 +# homeassistant.components.sensor.steam_online +steamodd==4.21 + # homeassistant.components.light.tellstick # homeassistant.components.sensor.tellstick # homeassistant.components.switch.tellstick From 9b0b3c474e250193730308b555034d458697e01b Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Tue, 23 Feb 2016 10:05:14 -0500 Subject: [PATCH 138/186] Fix dispatching of WeMo switch devices. I only have WeMo Link and Insight devices and assumed model names of other devices were fairly straightforward. But it looks like the regular WeMo switch uses 'Socket' as the model name. --- homeassistant/components/wemo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 1d4c1592dfc..fb1cc4e77d3 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -24,7 +24,7 @@ WEMO_MODEL_DISPATCH = { 'Insight': DISCOVER_SWITCHES, 'Maker': DISCOVER_SWITCHES, 'Motion': DISCOVER_MOTION, - 'Switch': DISCOVER_SWITCHES, + 'Socket': DISCOVER_SWITCHES, } WEMO_SERVICE_DISPATCH = { DISCOVER_LIGHTS: 'light', From f8240a9cda5717de696899f4f643ee70ae5ea743 Mon Sep 17 00:00:00 2001 From: Rowan Hine Date: Tue, 23 Feb 2016 18:01:50 +0000 Subject: [PATCH 139/186] Changed to use dictionary --- .../components/sensor/steam_online.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index 1264e9a8556..6b740d0639b 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -46,26 +46,20 @@ class SteamSensor(Entity): @property def state(self): """ State of the sensor. """ - if self._profile.status == 1: - account_state = 'Online' - elif self._profile.status == 2: - account_state = 'Busy' - elif self._profile.status == 3: - account_state = 'Away' - elif self._profile.status == 4: - account_state = 'Snooze' - elif self._profile.status == 5: - account_state = 'Trade' - elif self._profile.status == 6: - account_state = 'Play' - else: - account_state = 'Offline' - return account_state + return self._state # pylint: disable=no-member def update(self): """ Update device state. """ self._profile = self._steamod.user.profile(self._account) + self._state = { + 1: 'Online', + 2: 'Busy', + 3: 'Away', + 4: 'Snooze', + 5: 'Trade', + 6: 'Play', + }.get(self._profile.status, 'Offline') @property def device_state_attributes(self): From 05a1e11db225860d6aae3aa8ed9657d8eb8da7f6 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 23 Feb 2016 12:01:51 -0800 Subject: [PATCH 140/186] Filter out AirCam models in UVC camera platform The older (unsupported AirCam) models behave differently and also apparently suffer some under the last release of the NVR that supported them. Since they are EOL and not supported by current software, filter them out so we don't break while trying to extract an image from them. --- homeassistant/components/camera/uvc.py | 5 +++++ tests/components/camera/test_uvc.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index 5a84c535540..e34b0e26859 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -47,6 +47,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Unable to connect to NVR: %s', str(ex)) return False + # Filter out airCam models, which are not supported in the latest + # version of UnifiVideo and which are EOL by Ubiquiti + cameras = [camera for camera in cameras + if 'airCam' not in nvrconn.get_camera(camera['uuid'])['model']] + add_devices([UnifiVideoCamera(nvrconn, camera['uuid'], camera['name']) diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index 1c87945cff2..199fec4cc04 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -27,10 +27,19 @@ class TestUVCSetup(unittest.TestCase): fake_cameras = [ {'uuid': 'one', 'name': 'Front'}, {'uuid': 'two', 'name': 'Back'}, + {'uuid': 'three', 'name': 'Old AirCam'}, ] + + def fake_get_camera(uuid): + if uuid == 'three': + return {'model': 'airCam'} + else: + return {'model': 'UVC'} + hass = mock.MagicMock() add_devices = mock.MagicMock() mock_remote.return_value.index.return_value = fake_cameras + mock_remote.return_value.get_camera.side_effect = fake_get_camera self.assertTrue(uvc.setup_platform(hass, config, add_devices)) mock_remote.assert_called_once_with('foo', 123, 'secret') add_devices.assert_called_once_with([ @@ -54,6 +63,7 @@ class TestUVCSetup(unittest.TestCase): hass = mock.MagicMock() add_devices = mock.MagicMock() mock_remote.return_value.index.return_value = fake_cameras + mock_remote.return_value.get_camera.return_value = {'model': 'UVC'} self.assertTrue(uvc.setup_platform(hass, config, add_devices)) mock_remote.assert_called_once_with('foo', 7080, 'secret') add_devices.assert_called_once_with([ From 213cc920d0cd6e84bb03bec54ece61c0b835a77d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Feb 2016 21:04:48 +0100 Subject: [PATCH 141/186] Move template to helpers --- homeassistant/{util => helpers}/template.py | 0 tests/{util => helpers}/test_template.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename homeassistant/{util => helpers}/template.py (100%) rename tests/{util => helpers}/test_template.py (100%) diff --git a/homeassistant/util/template.py b/homeassistant/helpers/template.py similarity index 100% rename from homeassistant/util/template.py rename to homeassistant/helpers/template.py diff --git a/tests/util/test_template.py b/tests/helpers/test_template.py similarity index 100% rename from tests/util/test_template.py rename to tests/helpers/test_template.py From 582394bc3bcc5e4e5cd7b33c86780b27e164b3ac Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Feb 2016 21:06:50 +0100 Subject: [PATCH 142/186] Modify import of template and PEP257 --- homeassistant/components/alexa.py | 22 ++++---- homeassistant/components/api.py | 51 +++++++++---------- .../components/automation/numeric_state.py | 2 +- .../components/automation/template.py | 2 +- .../binary_sensor/command_sensor.py | 2 +- .../components/binary_sensor/mqtt.py | 2 +- .../components/binary_sensor/rest.py | 2 +- homeassistant/components/light/mqtt.py | 34 ++++++------- homeassistant/components/logbook.py | 28 +++++----- homeassistant/components/mqtt/__init__.py | 6 +-- homeassistant/components/notify/__init__.py | 16 ++---- .../rollershutter/command_rollershutter.py | 8 ++- .../components/rollershutter/mqtt.py | 22 ++++---- homeassistant/components/sensor/arest.py | 5 +- .../components/sensor/command_sensor.py | 3 +- homeassistant/components/sensor/dweet.py | 3 +- homeassistant/components/sensor/mqtt.py | 2 +- homeassistant/components/sensor/rest.py | 3 +- homeassistant/components/sensor/tcp.py | 2 +- homeassistant/components/sensor/template.py | 3 +- .../components/switch/command_switch.py | 31 +++++------ homeassistant/components/switch/mqtt.py | 29 +++++------ homeassistant/components/switch/template.py | 27 +++++----- homeassistant/helpers/template.py | 16 +++--- tests/helpers/test_template.py | 13 ++--- 25 files changed, 152 insertions(+), 182 deletions(-) diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 5678aa509b7..65d26a2360e 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -1,7 +1,5 @@ """ -components.alexa -~~~~~~~~~~~~~~~~ -Component to offer a service end point for an Alexa skill. +Support for Alexa skill service end point. For more details about this component, please refer to the documentation at https://home-assistant.io/components/alexa/ @@ -11,7 +9,7 @@ import logging from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY from homeassistant.helpers.service import call_from_config -from homeassistant.util import template +from homeassistant.helpers import template DOMAIN = 'alexa' DEPENDENCIES = ['http'] @@ -28,7 +26,7 @@ CONF_ACTION = 'action' def setup(hass, config): - """ Activate Alexa component. """ + """Activate Alexa component.""" _CONFIG.update(config[DOMAIN].get(CONF_INTENTS, {})) hass.http.register_path('POST', API_ENDPOINT, _handle_alexa, True) @@ -37,7 +35,7 @@ def setup(hass, config): def _handle_alexa(handler, path_match, data): - """ Handle Alexa. """ + """Handle Alexa.""" _LOGGER.debug('Received Alexa request: %s', data) req = data.get('request') @@ -99,19 +97,19 @@ def _handle_alexa(handler, path_match, data): class SpeechType(enum.Enum): - """ Alexa speech types. """ + """Alexa speech types.""" plaintext = "PlainText" ssml = "SSML" class CardType(enum.Enum): - """ Alexa card types. """ + """Alexa card types.""" simple = "Simple" link_account = "LinkAccount" class AlexaResponse(object): - """ Helps generating the response for Alexa. """ + """Helps generating the response for Alexa.""" def __init__(self, hass, intent=None): self.hass = hass @@ -154,7 +152,7 @@ class AlexaResponse(object): } def add_reprompt(self, speech_type, text): - """ Add repromopt if user does not answer. """ + """Add reprompt if user does not answer.""" assert self.reprompt is None key = 'ssml' if speech_type == SpeechType.ssml else 'text' @@ -165,7 +163,7 @@ class AlexaResponse(object): } def as_dict(self): - """ Returns response in an Alexa valid dict. """ + """Returns response in an Alexa valid dict.""" response = { 'shouldEndSession': self.should_end_session } @@ -188,5 +186,5 @@ class AlexaResponse(object): } def _render(self, template_string): - """ Render a response, adding data from intent if available. """ + """Render a response, adding data from intent if available.""" return template.render(self.hass, template_string, self.variables) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index e9c25ca5dac..6f785f19896 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -1,7 +1,5 @@ """ -homeassistant.components.api -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides a Rest API for Home Assistant. +Rest API for Home Assistant. For more details about the RESTful API, please refer to the documentation at https://home-assistant.io/developers/api/ @@ -23,7 +21,7 @@ from homeassistant.const import ( URL_API_STREAM, URL_API_TEMPLATE) from homeassistant.exceptions import TemplateError from homeassistant.helpers.state import TrackStates -from homeassistant.util import template +from homeassistant.helpers import template DOMAIN = 'api' DEPENDENCIES = ['http'] @@ -35,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Register the API with the HTTP interface. """ + """Register the API with the HTTP interface.""" # /api - for validation purposes hass.http.register_path('GET', URL_API, _handle_get_api) @@ -98,12 +96,12 @@ def setup(hass, config): def _handle_get_api(handler, path_match, data): - """ Renders the debug interface. """ + """Renders the debug interface.""" handler.write_json_message("API running.") def _handle_get_api_stream(handler, path_match, data): - """ Provide a streaming interface for the event bus. """ + """Provide a streaming interface for the event bus.""" gracefully_closed = False hass = handler.server.hass wfile = handler.wfile @@ -116,7 +114,7 @@ def _handle_get_api_stream(handler, path_match, data): restrict = restrict.split(',') def write_message(payload): - """ Writes a message to the output. """ + """Writes a message to the output.""" with write_lock: msg = "data: {}\n\n".format(payload) @@ -129,7 +127,7 @@ def _handle_get_api_stream(handler, path_match, data): block.set() def forward_events(event): - """ Forwards events to the open request. """ + """Forwards events to the open request.""" nonlocal gracefully_closed if block.is_set() or event.event_type == EVENT_TIME_CHANGED: @@ -173,17 +171,17 @@ def _handle_get_api_stream(handler, path_match, data): def _handle_get_api_config(handler, path_match, data): - """ Returns the Home Assistant config. """ + """Returns the Home Assistant configuration.""" handler.write_json(handler.server.hass.config.as_dict()) def _handle_get_api_states(handler, path_match, data): - """ Returns a dict containing all entity ids and their state. """ + """Returns a dict containing all entity ids and their state.""" handler.write_json(handler.server.hass.states.all()) def _handle_get_api_states_entity(handler, path_match, data): - """ Returns the state of a specific entity. """ + """Returns the state of a specific entity.""" entity_id = path_match.group('entity_id') state = handler.server.hass.states.get(entity_id) @@ -195,7 +193,7 @@ def _handle_get_api_states_entity(handler, path_match, data): def _handle_post_state_entity(handler, path_match, data): - """ Handles updating the state of an entity. + """Handles updating the state of an entity. This handles the following paths: /api/states/ @@ -242,12 +240,12 @@ def _handle_delete_state_entity(handler, path_match, data): def _handle_get_api_events(handler, path_match, data): - """ Handles getting overview of event listeners. """ + """Handles getting overview of event listeners.""" handler.write_json(events_json(handler.server.hass)) def _handle_api_post_events_event(handler, path_match, event_data): - """ Handles firing of an event. + """Handles firing of an event. This handles the following paths: /api/events/ @@ -278,13 +276,13 @@ def _handle_api_post_events_event(handler, path_match, event_data): def _handle_get_api_services(handler, path_match, data): - """ Handles getting overview of services. """ + """Handles getting overview of services.""" handler.write_json(services_json(handler.server.hass)) # pylint: disable=invalid-name def _handle_post_api_services_domain_service(handler, path_match, data): - """ Handles calling a service. + """Handles calling a service. This handles the following paths: /api/services// @@ -300,8 +298,7 @@ def _handle_post_api_services_domain_service(handler, path_match, data): # pylint: disable=invalid-name def _handle_post_api_event_forward(handler, path_match, data): - """ Handles adding an event forwarding target. """ - + """Handles adding an event forwarding target.""" try: host = data['host'] api_password = data['api_password'] @@ -334,8 +331,7 @@ def _handle_post_api_event_forward(handler, path_match, data): def _handle_delete_api_event_forward(handler, path_match, data): - """ Handles deleting an event forwarding target. """ - + """Handles deleting an event forwarding target.""" try: host = data['host'] except KeyError: @@ -358,26 +354,25 @@ def _handle_delete_api_event_forward(handler, path_match, data): def _handle_get_api_components(handler, path_match, data): - """ Returns all the loaded components. """ - + """Returns all the loaded components.""" handler.write_json(handler.server.hass.config.components) def _handle_get_api_error_log(handler, path_match, data): - """ Returns the logged errors for this session. """ + """Returns the logged errors for this session.""" handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME), False) def _handle_post_api_log_out(handler, path_match, data): - """ Log user out. """ + """Log user out.""" handler.send_response(HTTP_OK) handler.destroy_session() handler.end_headers() def _handle_post_api_template(handler, path_match, data): - """ Log user out. """ + """Log user out.""" template_string = data.get('template', '') try: @@ -393,12 +388,12 @@ def _handle_post_api_template(handler, path_match, data): def services_json(hass): - """ Generate services data to JSONify. """ + """Generate services data to JSONify.""" return [{"domain": key, "services": value} for key, value in hass.services.services.items()] def events_json(hass): - """ Generate event data to JSONify. """ + """Generate event data to JSONify.""" return [{"event": key, "listener_count": value} for key, value in hass.bus.listeners.items()] diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 96ab2121688..10c2402bb0e 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -11,7 +11,7 @@ from functools import partial from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers.event import track_state_change -from homeassistant.util import template +from homeassistant.helpers import template CONF_ENTITY_ID = "entity_id" CONF_BELOW = "below" diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 8615538c42a..4aaac359c46 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -10,7 +10,7 @@ import logging from homeassistant.const import CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED from homeassistant.exceptions import TemplateError -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/command_sensor.py b/homeassistant/components/binary_sensor/command_sensor.py index 677a4f2b95c..bd93ce27afa 100644 --- a/homeassistant/components/binary_sensor/command_sensor.py +++ b/homeassistant/components/binary_sensor/command_sensor.py @@ -11,7 +11,7 @@ from datetime import timedelta from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.command_sensor import CommandSensorData from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index 139720bb117..3712936e57e 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -9,7 +9,7 @@ import logging import homeassistant.components.mqtt as mqtt from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 63c7f7a3e19..0e05a24826f 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.rest import RestData from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 2cdb9ee3fc4..f6e2438b5cd 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.light.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a MQTT light. +Support for MQTT lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.mqtt/ @@ -12,7 +10,7 @@ from functools import partial import homeassistant.components.mqtt as mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) -from homeassistant.util.template import render_with_possible_json_value +from homeassistant.helpers.template import render_with_possible_json_value _LOGGER = logging.getLogger(__name__) @@ -26,7 +24,7 @@ DEPENDENCIES = ['mqtt'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add MQTT Light. """ + """Add MQTT Light.""" if config.get('command_topic') is None: _LOGGER.error("Missing required variable: command_topic") @@ -50,7 +48,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class MqttLight(Light): - """ Provides a MQTT light. """ + """Provides a MQTT light.""" # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__(self, hass, name, topic, templates, qos, payload, optimistic): @@ -71,7 +69,7 @@ class MqttLight(Light): for key, tpl in templates.items()} def state_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" payload = templates['state'](payload) if payload == self._payload["on"]: self._state = True @@ -85,7 +83,7 @@ class MqttLight(Light): state_received, self._qos) def brightness_received(topic, payload, qos): - """ A new MQTT message for the brightness has been received. """ + """A new MQTT message for the brightness has been received.""" self._brightness = int(templates['brightness'](payload)) self.update_ha_state() @@ -97,7 +95,7 @@ class MqttLight(Light): self._brightness = None def rgb_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" self._rgb = [int(val) for val in templates['rgb'](payload).split(',')] self.update_ha_state() @@ -111,27 +109,27 @@ class MqttLight(Light): @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Brightness of this light between 0..255.""" return self._brightness @property def rgb_color(self): - """ RGB color value. """ + """RGB color value.""" return self._rgb @property def should_poll(self): - """ No polling needed for a MQTT light. """ + """No polling needed for a MQTT light.""" return False @property def name(self): - """ Returns the name of the device if any. """ + """Returns the name of the device if any.""" return self._name @property def is_on(self): - """ True if device is on. """ + """True if device is on.""" return self._state @property @@ -140,7 +138,7 @@ class MqttLight(Light): return self._optimistic def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" should_update = False if ATTR_RGB_COLOR in kwargs and \ @@ -166,7 +164,7 @@ class MqttLight(Light): self._payload["on"], self._qos) if self._optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state. self._state = True should_update = True @@ -174,11 +172,11 @@ class MqttLight(Light): self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" mqtt.publish(self._hass, self._topic["command_topic"], self._payload["off"], self._qos) if self._optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state. self._state = False self.update_ha_state() diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 1e66c27686d..95fbcc9a793 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -1,7 +1,5 @@ """ -homeassistant.components.logbook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Parses events and generates a human log. +Event parser and human readable log generator. For more details about this component, please refer to the documentation at https://home-assistant.io/components/logbook/ @@ -19,7 +17,7 @@ from homeassistant.const import ( from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.core import State from homeassistant.helpers.entity import split_entity_id -from homeassistant.util import template +from homeassistant.helpers import template DOMAIN = "logbook" DEPENDENCIES = ['recorder', 'http'] @@ -43,7 +41,7 @@ ATTR_ENTITY_ID = 'entity_id' def log_entry(hass, name, message, domain=None, entity_id=None): - """ Adds an entry to the logbook. """ + """Adds an entry to the logbook.""" data = { ATTR_NAME: name, ATTR_MESSAGE: message @@ -57,10 +55,10 @@ def log_entry(hass, name, message, domain=None, entity_id=None): def setup(hass, config): - """ Listens for download events to download files. """ - # create service handler + """Listens for download events to download files.""" + def log_message(service): - """ Handle sending notification message service calls. """ + """Handle sending notification message service calls.""" message = service.data.get(ATTR_MESSAGE) name = service.data.get(ATTR_NAME) domain = service.data.get(ATTR_DOMAIN, None) @@ -78,7 +76,7 @@ def setup(hass, config): def _handle_get_logbook(handler, path_match, data): - """ Return logbook entries. """ + """Return logbook entries.""" date_str = path_match.group('date') if date_str: @@ -102,10 +100,9 @@ def _handle_get_logbook(handler, path_match, data): class Entry(object): - """ A human readable version of the log. """ + """A human readable version of the log.""" # pylint: disable=too-many-arguments, too-few-public-methods - def __init__(self, when=None, name=None, message=None, domain=None, entity_id=None): self.when = when @@ -115,7 +112,7 @@ class Entry(object): self.entity_id = entity_id def as_dict(self): - """ Convert Entry to a dict to be used within JSON. """ + """Convert entry to a dict to be used within JSON.""" return { 'when': dt_util.datetime_to_str(self.when), 'name': self.name, @@ -134,7 +131,6 @@ def humanify(events): - if home assistant stop and start happen in same minute call it restarted """ # pylint: disable=too-many-branches - # Group events in batches of GROUP_BY_MINUTES for _, g_events in groupby( events, @@ -145,7 +141,7 @@ def humanify(events): # Keep track of last sensor states last_sensor_event = {} - # group HA start/stop events + # Group HA start/stop events # Maps minute of event to 1: stop, 2: stop + start start_stop_events = {} @@ -182,7 +178,7 @@ def humanify(events): to_state = State.from_dict(event.data.get('new_state')) - # if last_changed != last_updated only attributes have changed + # If last_changed != last_updated only attributes have changed # we do not report on that yet. Also filter auto groups. if not to_state or \ to_state.last_changed != to_state.last_updated or \ @@ -238,7 +234,7 @@ def humanify(events): def _entry_message_from_state(domain, state): - """ Convert a state to a message for the logbook. """ + """Convert a state to a message for the logbook.""" # We pass domain in so we don't have to split entity_id again # pylint: disable=too-many-return-statements diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index ae47613339f..44b11b3df1b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1,7 +1,5 @@ """ -homeassistant.components.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -MQTT component, using paho-mqtt. +Support for MQTT message handling.. For more details about this component, please refer to the documentation at https://home-assistant.io/components/mqtt/ @@ -15,7 +13,7 @@ import time from homeassistant.config import load_yaml_config_file from homeassistant.exceptions import HomeAssistantError import homeassistant.util as util -from homeassistant.util import template +from homeassistant.helpers import template from homeassistant.helpers import validate_config from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index a44dd638479..2b1ece959ac 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to notify people. For more details about this component, please refer to the documentation at @@ -13,7 +11,7 @@ import os import homeassistant.bootstrap as bootstrap from homeassistant.config import load_yaml_config_file from homeassistant.helpers import config_per_platform -from homeassistant.util import template +from homeassistant.helpers import template from homeassistant.const import CONF_NAME @@ -35,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) def send_message(hass, message, title=None): - """ Send a notification message. """ + """Send a notification message.""" data = { ATTR_MESSAGE: message } @@ -47,14 +45,13 @@ def send_message(hass, message, title=None): def setup(hass, config): - """ Sets up notify services. """ + """Sets up notify services.""" success = False descriptions = load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER): - # get platform notify_implementation = bootstrap.prepare_setup_platform( hass, config, DOMAIN, platform) @@ -62,7 +59,6 @@ def setup(hass, config): _LOGGER.error("Unknown notification service specified.") continue - # create platform service notify_service = notify_implementation.get_service(hass, p_config) if notify_service is None: @@ -70,9 +66,8 @@ def setup(hass, config): platform) continue - # create service handler def notify_message(notify_service, call): - """ Handle sending notification message service calls. """ + """Handle sending notification message service calls.""" message = call.data.get(ATTR_MESSAGE) if message is None: @@ -85,7 +80,6 @@ def setup(hass, config): notify_service.send_message(message, title=title, target=target) - # register service service_call_handler = partial(notify_message, notify_service) service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY) hass.services.register(DOMAIN, service_notify, service_call_handler, @@ -97,7 +91,7 @@ def setup(hass, config): # pylint: disable=too-few-public-methods class BaseNotificationService(object): - """ Provides an ABC for notification services. """ + """Provides an ABC for notification services.""" def send_message(self, message, **kwargs): """ diff --git a/homeassistant/components/rollershutter/command_rollershutter.py b/homeassistant/components/rollershutter/command_rollershutter.py index bd6241f4648..1808901ef3a 100644 --- a/homeassistant/components/rollershutter/command_rollershutter.py +++ b/homeassistant/components/rollershutter/command_rollershutter.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rollershutter.command_rollershutter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a command rollershutter. +Support for command roller shutters. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter.command_rollershutter/ @@ -11,13 +9,13 @@ import subprocess from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return rollershutter controlled by shell commands. """ + """Setup rollershutter controlled by shell commands.""" rollershutters = config.get('rollershutters', {}) devices = [] diff --git a/homeassistant/components/rollershutter/mqtt.py b/homeassistant/components/rollershutter/mqtt.py index 97c8094f865..d5d5a7acb97 100644 --- a/homeassistant/components/rollershutter/mqtt.py +++ b/homeassistant/components/rollershutter/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rollershutter.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a MQTT rollershutter. +Support for MQTT roller shutters. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter.mqtt/ @@ -11,7 +9,7 @@ import logging import homeassistant.components.mqtt as mqtt from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) @@ -25,7 +23,7 @@ DEFAULT_PAYLOAD_STOP = "STOP" def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add MQTT Rollershutter """ + """Add MQTT Rollershutter.""" if config.get('command_topic') is None: _LOGGER.error("Missing required variable: command_topic") @@ -45,7 +43,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttRollershutter(RollershutterDevice): - """ Represents a rollershutter that can be controlled using MQTT. """ + """Represents a rollershutter that can be controlled using MQTT.""" def __init__(self, hass, name, state_topic, command_topic, qos, payload_up, payload_down, payload_stop, value_template): self._state = None @@ -62,7 +60,7 @@ class MqttRollershutter(RollershutterDevice): return def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" if value_template is not None: payload = template.render_with_possible_json_value( hass, value_template, payload) @@ -77,12 +75,12 @@ class MqttRollershutter(RollershutterDevice): @property def should_poll(self): - """ No polling needed """ + """No polling needed.""" return False @property def name(self): - """ The name of the rollershutter. """ + """The name of the rollershutter.""" return self._name @property @@ -94,16 +92,16 @@ class MqttRollershutter(RollershutterDevice): return self._state def move_up(self, **kwargs): - """ Move the rollershutter up. """ + """Move the rollershutter up.""" mqtt.publish(self.hass, self._command_topic, self._payload_up, self._qos) def move_down(self, **kwargs): - """ Move the rollershutter down. """ + """Move the rollershutter down.""" mqtt.publish(self.hass, self._command_topic, self._payload_down, self._qos) def stop(self, **kwargs): - """ Stop the device. """ + """Stop the device.""" mqtt.publish(self.hass, self._command_topic, self._payload_stop, self._qos) diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index c731060d6dc..46b42a3422f 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -13,11 +13,12 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle, template +from homeassistant.helpers import template +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -# Return cached results if last scan was less then this time ago +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) CONF_RESOURCE = 'resource' diff --git a/homeassistant/components/sensor/command_sensor.py b/homeassistant/components/sensor/command_sensor.py index d87010a5525..7f222f52b78 100644 --- a/homeassistant/components/sensor/command_sensor.py +++ b/homeassistant/components/sensor/command_sensor.py @@ -10,7 +10,8 @@ from datetime import timedelta from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle, template +from homeassistant.helpers import template +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py index a618594b1f9..f02c5706d8a 100644 --- a/homeassistant/components/sensor/dweet.py +++ b/homeassistant/components/sensor/dweet.py @@ -10,7 +10,8 @@ from datetime import timedelta from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle, template +from homeassistant.helpers import template +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['dweepy==0.2.0'] diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 1c7c549144c..c2a352ef154 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -9,7 +9,7 @@ import logging import homeassistant.components.mqtt as mqtt from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py index 126ac4265cb..2d023a7c542 100644 --- a/homeassistant/components/sensor/rest.py +++ b/homeassistant/components/sensor/rest.py @@ -11,7 +11,8 @@ import requests from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle, template +from homeassistant.helpers import template +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 8c021474753..2798d100153 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -9,7 +9,7 @@ import socket import select from homeassistant.const import CONF_NAME, CONF_HOST -from homeassistant.util import template +from homeassistant.helpers import template from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index bd4ece7d450..65df431d2b9 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -13,7 +13,8 @@ from homeassistant.const import ( from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity, generate_entity_id -from homeassistant.util import slugify, template +from homeassistant.helpers import template +from homeassistant.util import slugify ENTITY_ID_FORMAT = DOMAIN + '.{}' diff --git a/homeassistant/components/switch/command_switch.py b/homeassistant/components/switch/command_switch.py index a90ed61c3e2..cac705ae2b1 100644 --- a/homeassistant/components/switch/command_switch.py +++ b/homeassistant/components/switch/command_switch.py @@ -1,7 +1,5 @@ """ -homeassistant.components.switch.command_switch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure custom shell commands to turn a switch on/off. +Support for custom shell commands to turn a switch on/off. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.command_switch/ @@ -11,15 +9,14 @@ import subprocess from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return switches controlled by shell commands. """ - + """Find and return switches controlled by shell commands.""" switches = config.get('switches', {}) devices = [] @@ -37,7 +34,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class CommandSwitch(SwitchDevice): - """ Represents a switch that can be togggled using shell commands. """ + """Represents a switch that can be toggled using shell commands.""" # pylint: disable=too-many-arguments def __init__(self, hass, name, command_on, command_off, @@ -53,7 +50,7 @@ class CommandSwitch(SwitchDevice): @staticmethod def _switch(command): - """ Execute the actual commands. """ + """Execute the actual commands.""" _LOGGER.info('Running command: %s', command) success = (subprocess.call(command, shell=True) == 0) @@ -65,7 +62,7 @@ class CommandSwitch(SwitchDevice): @staticmethod def _query_state_value(command): - """ Execute state command for return value. """ + """Execute state command for return value.""" _LOGGER.info('Running state command: %s', command) try: @@ -76,27 +73,27 @@ class CommandSwitch(SwitchDevice): @staticmethod def _query_state_code(command): - """ Execute state command for return code. """ + """Execute state command for return code.""" _LOGGER.info('Running state command: %s', command) return subprocess.call(command, shell=True) == 0 @property def should_poll(self): - """ Only poll if we have statecmd. """ + """Only poll if we have state command.""" return self._command_state is not None @property def name(self): - """ The name of the switch. """ + """The name of the switch.""" return self._name @property def is_on(self): - """ True if device is on. """ + """True if device is on.""" return self._state def _query_state(self): - """ Query for state. """ + """Query for state.""" if not self._command_state: _LOGGER.error('No state command specified') return @@ -105,7 +102,7 @@ class CommandSwitch(SwitchDevice): return CommandSwitch._query_state_code(self._command_state) def update(self): - """ Update device state. """ + """Update device state.""" if self._command_state: payload = str(self._query_state()) if self._value_template: @@ -114,14 +111,14 @@ class CommandSwitch(SwitchDevice): self._state = (payload.lower() == "true") def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" if (CommandSwitch._switch(self._command_on) and not self._command_state): self._state = True self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" if (CommandSwitch._switch(self._command_off) and not self._command_state): self._state = False diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 6b8089daed3..40876e71891 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.switch.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a MQTT switch. +Support for MQTT switches. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.mqtt/ @@ -11,7 +9,7 @@ import logging import homeassistant.components.mqtt as mqtt from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_VALUE_TEMPLATE -from homeassistant.util import template +from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) @@ -27,7 +25,7 @@ DEPENDENCIES = ['mqtt'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add MQTT Switch. """ + """Add MQTT switch.""" if config.get('command_topic') is None: _LOGGER.error("Missing required variable: command_topic") @@ -48,7 +46,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttSwitch(SwitchDevice): - """ Represents a switch that can be toggled using MQTT. """ + """Represents a switch that can be toggled using MQTT.""" def __init__(self, hass, name, state_topic, command_topic, qos, retain, payload_on, payload_off, optimistic, value_template): self._state = False @@ -63,7 +61,7 @@ class MqttSwitch(SwitchDevice): self._optimistic = optimistic def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" if value_template is not None: payload = template.render_with_possible_json_value( hass, value_template, payload) @@ -75,26 +73,25 @@ class MqttSwitch(SwitchDevice): self.update_ha_state() if self._state_topic is None: - # force optimistic mode + # Force into optimistic mode. self._optimistic = True else: - # subscribe the state_topic mqtt.subscribe(hass, self._state_topic, message_received, self._qos) @property def should_poll(self): - """ No polling needed. """ + """No polling needed.""" return False @property def name(self): - """ The name of the switch. """ + """The name of the switch.""" return self._name @property def is_on(self): - """ True if device is on. """ + """True if device is on.""" return self._state @property @@ -103,19 +100,19 @@ class MqttSwitch(SwitchDevice): return self._optimistic def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" mqtt.publish(self.hass, self._command_topic, self._payload_on, self._qos, self._retain) if self._optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state. self._state = True self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" mqtt.publish(self.hass, self._command_topic, self._payload_off, self._qos, self._retain) if self._optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state. self._state = False self.update_ha_state() diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 5c8227346ea..ec6cb7d2e8e 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -1,7 +1,5 @@ """ -homeassistant.components.switch.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows the creation of a switch that integrates other components together +Support for switches which integrates with other components. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.template/ @@ -15,7 +13,8 @@ from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.service import call_from_config -from homeassistant.util import slugify, template +from homeassistant.helpers import template +from homeassistant.util import slugify ENTITY_ID_FORMAT = DOMAIN + '.{}' @@ -31,7 +30,7 @@ OFF_ACTION = 'turn_off' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the switches. """ + """Sets up the Template switch.""" switches = [] if config.get(CONF_SWITCHES) is None: @@ -80,7 +79,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SwitchTemplate(SwitchDevice): - """ Represents a Template Switch. """ + """Represents a Template switch.""" # pylint: disable=too-many-arguments def __init__(self, @@ -103,37 +102,37 @@ class SwitchTemplate(SwitchDevice): self.update() def _update_callback(_event): - """ Called when the target device changes state. """ + """Called when the target device changes state.""" self.update_ha_state(True) self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback) @property def name(self): - """ Returns the name of the device. """ + """Returns the name of the switch.""" return self._name @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False def turn_on(self, **kwargs): - """ Fires the on action. """ + """Fires the on action.""" call_from_config(self.hass, self._on_action, True) def turn_off(self, **kwargs): - """ Fires the off action. """ + """Fires the off action.""" call_from_config(self.hass, self._off_action, True) @property def is_on(self): - """ True if device is on. """ + """True if device is on.""" return self._value.lower() == 'true' or self._value == STATE_ON @property def is_off(self): - """ True if device is off. """ + """True if device is off.""" return self._value.lower() == 'false' or self._value == STATE_OFF @property @@ -142,7 +141,7 @@ class SwitchTemplate(SwitchDevice): return self.is_on or self.is_off def update(self): - """ Updates the state from the template. """ + """Updates the state from the template.""" try: self._value = template.render(self.hass, self._template) if not self.available: diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7b00fb6dcc1..bc80fd89a81 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,7 +1,5 @@ """ -homeassistant.util.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Template utility methods for rendering strings with HA data. +Template helper methods for rendering strings with HA data. """ # pylint: disable=too-few-public-methods import json @@ -23,8 +21,10 @@ _SENTINEL = object() def render_with_possible_json_value(hass, template, value, error_value=_SENTINEL): - """ Renders template with value exposed. - If valid JSON will expose value_json too. """ + """ + Renders template with value exposed. + If valid JSON will expose value_json too. + """ variables = { 'value': value } @@ -41,7 +41,7 @@ def render_with_possible_json_value(hass, template, value, def render(hass, template, variables=None, **kwargs): - """ Render given template. """ + """Render given template.""" if variables is not None: kwargs.update(variables) @@ -63,7 +63,7 @@ def render(hass, template, variables=None, **kwargs): class AllStates(object): - """ Class to expose all HA states as attributes. """ + """Class to expose all HA states as attributes.""" def __init__(self, hass): self._hass = hass @@ -80,7 +80,7 @@ class AllStates(object): class DomainStates(object): - """ Class to expose a specific HA domain as attributes. """ + """Class to expose a specific HA domain as attributes.""" def __init__(self, hass, domain): self._hass = hass diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 7a0b990a04a..7e75755d830 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1,8 +1,5 @@ """ -tests.util.test_template -~~~~~~~~~~~~~~~~~~~~~~~~ - -Tests Home Assistant template util methods. +Tests Home Assistant template helper methods. """ # pylint: disable=too-many-public-methods import unittest @@ -10,7 +7,7 @@ from unittest.mock import patch from homeassistant.components import group from homeassistant.exceptions import TemplateError -from homeassistant.util import template +from homeassistant.helpers import template import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -22,7 +19,7 @@ class TestUtilTemplate(unittest.TestCase): self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name - """ Stop down stuff we started. """ + """Stop down stuff we started.""" self.hass.stop() def test_referring_states_by_entity_id(self): @@ -174,7 +171,7 @@ class TestUtilTemplate(unittest.TestCase): template.render(self.hass, '{{ states("test.object2") }}')) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) - @patch('homeassistant.util.template.TemplateEnvironment.is_safe_callable', + @patch('homeassistant.helpers.template.TemplateEnvironment.is_safe_callable', return_value=True) def test_now(self, mock_is_safe, mock_utcnow): self.assertEqual( @@ -182,7 +179,7 @@ class TestUtilTemplate(unittest.TestCase): template.render(self.hass, '{{ now.isoformat() }}')) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) - @patch('homeassistant.util.template.TemplateEnvironment.is_safe_callable', + @patch('homeassistant.helpers.template.TemplateEnvironment.is_safe_callable', return_value=True) def test_utcnow(self, mock_is_safe, mock_utcnow): self.assertEqual( From a47d6c944ebe6bfb41b0eaefbbc95a681debd2d6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Feb 2016 21:31:38 +0100 Subject: [PATCH 143/186] Update docstrings to match PEP257 --- config/custom_components/example.py | 27 ++++++++++++------------ config/custom_components/hello_world.py | 16 +++++++------- config/custom_components/mqtt_example.py | 26 ++++++++++------------- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/config/custom_components/example.py b/config/custom_components/example.py index 08b3f4c2a83..3f961b99569 100644 --- a/config/custom_components/example.py +++ b/config/custom_components/example.py @@ -1,6 +1,5 @@ """ -custom_components.example -~~~~~~~~~~~~~~~~~~~~~~~~~ +Example of a custom component. Example component to target an entity_id to: - turn it on at 7AM in the morning @@ -37,21 +36,21 @@ import homeassistant.components as core from homeassistant.components import device_tracker from homeassistant.components import light -# The domain of your component. Should be equal to the name of your component +# The domain of your component. Should be equal to the name of your component. DOMAIN = "example" -# List of component names (string) your component depends upon +# List of component names (string) your component depends upon. # We depend on group because group will be loaded after all the components that # initialize devices have been setup. DEPENDENCIES = ['group', 'device_tracker', 'light'] -# Configuration key for the entity id we are targetting +# Configuration key for the entity id we are targeting. CONF_TARGET = 'target' -# Variable for storing configuration parameters +# Variable for storing configuration parameters. TARGET_ID = None -# Name of the service that we expose +# Name of the service that we expose. SERVICE_FLASH = 'flash' # Shortcut for the logger @@ -59,16 +58,16 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Setup example component. """ + """Setup example component.""" global TARGET_ID - # Validate that all required config options are given + # Validate that all required config options are given. if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER): return False TARGET_ID = config[DOMAIN][CONF_TARGET] - # Validate that the target entity id exists + # Validate that the target entity id exists. if hass.states.get(TARGET_ID) is None: _LOGGER.error("Target entity id %s does not exist", TARGET_ID) @@ -78,13 +77,13 @@ def setup(hass, config): TARGET_ID = None return False - # Tell the bootstrapper that we initialized successfully + # Tell the bootstrapper that we initialized successfully. return True @track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES) def track_devices(hass, entity_id, old_state, new_state): - """ Called when the group.all devices change state. """ + """Called when the group.all devices change state.""" # If the target id is not set, return if not TARGET_ID: return @@ -94,7 +93,7 @@ def track_devices(hass, entity_id, old_state, new_state): core.turn_on(hass, TARGET_ID) - # If all people leave the house and the entity is on, turn it off + # If all people leave the house and the entity is on, turn it off. elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID): core.turn_off(hass, TARGET_ID) @@ -116,7 +115,7 @@ def wake_up(hass, now): @track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF) def all_lights_off(hass, entity_id, old_state, new_state): - """ If all lights turn off, turn off. """ + """If all lights turn off, turn off.""" if not TARGET_ID: return diff --git a/config/custom_components/hello_world.py b/config/custom_components/hello_world.py index a3d4ce762bb..f24971a1462 100644 --- a/config/custom_components/hello_world.py +++ b/config/custom_components/hello_world.py @@ -1,7 +1,7 @@ """ -custom_components.hello_world -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Implements the bare minimum that a component should implement. +The "hello world" custom component. + +This component implements the bare minimum that a component should implement. Configuration: @@ -11,18 +11,18 @@ configuration.yaml file. hello_world: """ -# The domain of your component. Should be equal to the name of your component +# The domain of your component. Should be equal to the name of your component. DOMAIN = "hello_world" -# List of component names (string) your component depends upon +# List of component names (string) your component depends upon. DEPENDENCIES = [] def setup(hass, config): - """ Setup our skeleton component. """ + """Setup our skeleton component.""" - # States are in the format DOMAIN.OBJECT_ID + # States are in the format DOMAIN.OBJECT_ID. hass.states.set('hello_world.Hello_World', 'Works!') - # return boolean to indicate that initialization was successful + # Return boolean to indicate that initialization was successfully. return True diff --git a/config/custom_components/mqtt_example.py b/config/custom_components/mqtt_example.py index 98e16b6bfa9..b4e5e89a977 100644 --- a/config/custom_components/mqtt_example.py +++ b/config/custom_components/mqtt_example.py @@ -1,6 +1,6 @@ """ -custom_components.mqtt_example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Example of a custom MQTT component. + Shows how to communicate with MQTT. Follows a topic on MQTT and updates the state of an entity to the last message received on that topic. @@ -15,45 +15,41 @@ configuration.yaml file. mqtt_example: topic: home-assistant/mqtt_example - """ import homeassistant.loader as loader -# The domain of your component. Should be equal to the name of your component +# The domain of your component. Should be equal to the name of your component. DOMAIN = "mqtt_example" -# List of component names (string) your component depends upon +# List of component names (string) your component depends upon. DEPENDENCIES = ['mqtt'] - CONF_TOPIC = 'topic' DEFAULT_TOPIC = 'home-assistant/mqtt_example' def setup(hass, config): - """ Setup our mqtt_example component. """ + """Setup the MQTT example component.""" mqtt = loader.get_component('mqtt') topic = config[DOMAIN].get('topic', DEFAULT_TOPIC) entity_id = 'mqtt_example.last_message' - # Listen to a message on MQTT - + # Listen to a message on MQTT. def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" hass.states.set(entity_id, payload) mqtt.subscribe(hass, topic, message_received) hass.states.set(entity_id, 'No messages') - # Service to publish a message on MQTT - + # Service to publish a message on MQTT. def set_state_service(call): - """ Service to send a message. """ + """Service to send a message.""" mqtt.publish(hass, topic, call.data.get('new_state')) - # Register our service with Home Assistant + # Register our service with Home Assistant. hass.services.register(DOMAIN, 'set_state', set_state_service) - # return boolean to indicate that initialization was successful + # Return boolean to indicate that initialization was successfully. return True From 6b4dd64cfe9f2d2155c77c5cc3e5d0804a7b5846 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Feb 2016 22:06:45 +0100 Subject: [PATCH 144/186] Add break to fix length of lines --- tests/helpers/test_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 7e75755d830..1420ac44b6b 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -171,16 +171,16 @@ class TestUtilTemplate(unittest.TestCase): template.render(self.hass, '{{ states("test.object2") }}')) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) - @patch('homeassistant.helpers.template.TemplateEnvironment.is_safe_callable', - return_value=True) + @patch('homeassistant.helpers.template.TemplateEnvironment.' + 'is_safe_callable', return_value=True) def test_now(self, mock_is_safe, mock_utcnow): self.assertEqual( dt_util.utcnow().isoformat(), template.render(self.hass, '{{ now.isoformat() }}')) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) - @patch('homeassistant.helpers.template.TemplateEnvironment.is_safe_callable', - return_value=True) + @patch('homeassistant.helpers.template.TemplateEnvironment.' + 'is_safe_callable', return_value=True) def test_utcnow(self, mock_is_safe, mock_utcnow): self.assertEqual( dt_util.utcnow().isoformat(), From 967a751da1f5e31fdb522a066666fa765210f26d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Feb 2016 22:41:24 -0800 Subject: [PATCH 145/186] Add entity_picture property --- homeassistant/components/camera/__init__.py | 10 +++-- .../components/device_tracker/__init__.py | 10 +++-- .../components/media_player/__init__.py | 10 +++-- .../components/sensor/steam_online.py | 10 ++--- homeassistant/components/sensor/twitch.py | 8 +++- homeassistant/components/sensor/yr.py | 20 ++++----- homeassistant/helpers/entity.py | 44 ++++++++++++------- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 615554ae56f..2915f37dd21 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.components import bloomsky from homeassistant.const import ( - ATTR_ENTITY_PICTURE, HTTP_NOT_FOUND, ATTR_ENTITY_ID, ) @@ -132,6 +131,11 @@ class Camera(Entity): """No need to poll cameras.""" return False + @property + def entity_picture(self): + """Return a link to the camera feed as entity picture.""" + return ENTITY_IMAGE_URL.format(self.entity_id) + @property # pylint: disable=no-self-use def is_recording(self): @@ -195,9 +199,7 @@ class Camera(Entity): @property def state_attributes(self): """Camera state attributes.""" - attr = { - ATTR_ENTITY_PICTURE: ENTITY_IMAGE_URL.format(self.entity_id), - } + attr = {} if self.model: attr['model_name'] = self.model diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index c5b4ccd1c16..8c3fd62a0f8 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -25,7 +25,7 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_utc_time_change from homeassistant.const import ( - ATTR_ENTITY_PICTURE, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, + ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME) DOMAIN = "device_tracker" @@ -297,14 +297,16 @@ class Device(Entity): """ State of the device. """ return self._state + @property + def entity_picture(self): + """Picture of the device.""" + return self.config_picture + @property def state_attributes(self): """ Device state attributes. """ attr = {} - if self.config_picture: - attr[ATTR_ENTITY_PICTURE] = self.config_picture - if self.gps: attr[ATTR_LATITUDE] = self.gps[0] attr[ATTR_LONGITUDE] = self.gps[1] diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index d0f3015fb74..32bf57108f7 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.const import ( STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE, - ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, SERVICE_VOLUME_MUTE, SERVICE_TOGGLE, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, @@ -525,6 +525,11 @@ class MediaPlayerDevice(Entity): else: self.media_play() + @property + def entity_picture(self): + """Return image of the media playing.""" + return None if self.state == STATE_OFF else self.media_image_url + @property def state_attributes(self): """ Return the state attributes. """ @@ -538,7 +543,4 @@ class MediaPlayerDevice(Entity): in ATTR_TO_PROPERTY if getattr(self, attr) is not None } - if self.media_image_url: - state_attr[ATTR_ENTITY_PICTURE] = self.media_image_url - return state_attr diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index 6b740d0639b..5e12cb338c8 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -7,7 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.steam_online/ """ from homeassistant.helpers.entity import Entity -from homeassistant.const import (ATTR_ENTITY_PICTURE, CONF_API_KEY) +from homeassistant.const import CONF_API_KEY ICON = 'mdi:steam' @@ -62,11 +62,9 @@ class SteamSensor(Entity): }.get(self._profile.status, 'Offline') @property - def device_state_attributes(self): - """ Returns the state attributes. """ - return { - ATTR_ENTITY_PICTURE: self._profile.avatar_medium - } + def entity_picture(self): + """Avatar of the account.""" + return self._profile.avatar_medium @property def icon(self): diff --git a/homeassistant/components/sensor/twitch.py b/homeassistant/components/sensor/twitch.py index db81c55b6a9..abe257cadd8 100644 --- a/homeassistant/components/sensor/twitch.py +++ b/homeassistant/components/sensor/twitch.py @@ -4,7 +4,6 @@ Support for the Twitch stream status. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.twitch/ """ -from homeassistant.const import ATTR_ENTITY_PICTURE from homeassistant.helpers.entity import Entity STATE_STREAMING = 'streaming' @@ -51,6 +50,11 @@ class TwitchSensor(Entity): """State of the sensor.""" return self._state + @property + def entity_picture(self): + """Preview of current game.""" + return self._preview + # pylint: disable=no-member def update(self): """Update device state.""" @@ -62,6 +66,7 @@ class TwitchSensor(Entity): self._preview = stream.get('preview').get('small') self._state = STATE_STREAMING else: + self._preview = None self._state = STATE_OFFLINE @property @@ -71,7 +76,6 @@ class TwitchSensor(Entity): return { ATTR_GAME: self._game, ATTR_TITLE: self._title, - ATTR_ENTITY_PICTURE: self._preview } @property diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 6bb460b1373..907c0bed049 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -8,8 +8,7 @@ import logging import requests -from homeassistant.const import ( - ATTR_ENTITY_PICTURE, CONF_LATITUDE, CONF_LONGITUDE) +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util from homeassistant.util import location @@ -97,20 +96,21 @@ class YrSensor(Entity): """Returns the state of the device.""" return self._state + @property + def entity_picture(self): + """Weather symbol if type is symbol.""" + if self.type != 'symbol': + return None + return "http://api.met.no/weatherapi/weathericon/1.1/" \ + "?symbol={0};content_type=image/png".format(self._state) + @property def device_state_attributes(self): """Returns state attributes. """ - data = { + return { 'about': "Weather forecast from yr.no, delivered by the" " Norwegian Meteorological Institute and the NRK" } - if self.type == 'symbol': - symbol_nr = self._state - data[ATTR_ENTITY_PICTURE] = \ - "http://api.met.no/weatherapi/weathericon/1.1/" \ - "?symbol={0};content_type=image/png".format(symbol_nr) - - return data @property def unit_of_measurement(self): diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index afe5bc80291..d7d37135cb2 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -10,7 +10,8 @@ from collections import defaultdict from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON, - STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELCIUS, TEMP_FAHRENHEIT) + STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELCIUS, TEMP_FAHRENHEIT, + ATTR_ENTITY_PICTURE) from homeassistant.exceptions import NoEntitySpecifiedError from homeassistant.util import ensure_unique_string, slugify @@ -105,6 +106,11 @@ class Entity(object): """Return the icon to use in the frontend, if any.""" return None + @property + def entity_picture(self): + """Return the entity picture to use in the frontend, if any.""" + return None + @property def hidden(self): """Return True if the entity should be hidden from UIs.""" @@ -157,25 +163,18 @@ class Entity(object): if device_attr is not None: attr.update(device_attr) - if ATTR_UNIT_OF_MEASUREMENT not in attr and \ - self.unit_of_measurement is not None: - attr[ATTR_UNIT_OF_MEASUREMENT] = str(self.unit_of_measurement) + self._attr_setter('unit_of_measurement', str, ATTR_UNIT_OF_MEASUREMENT, + attr) if not self.available: state = STATE_UNAVAILABLE attr = {} - if self.name is not None: - attr[ATTR_FRIENDLY_NAME] = str(self.name) - - if self.icon is not None: - attr[ATTR_ICON] = str(self.icon) - - if self.hidden: - attr[ATTR_HIDDEN] = bool(self.hidden) - - if self.assumed_state: - attr[ATTR_ASSUMED_STATE] = bool(self.assumed_state) + self._attr_setter('name', str, ATTR_FRIENDLY_NAME, attr) + self._attr_setter('icon', str, ATTR_ICON, attr) + self._attr_setter('entity_picture', str, ATTR_ENTITY_PICTURE, attr) + self._attr_setter('hidden', bool, ATTR_HIDDEN, attr) + self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr) # overwrite properties that have been set in the config file attr.update(_OVERWRITE.get(self.entity_id, {})) @@ -195,6 +194,21 @@ class Entity(object): return self.hass.states.set(self.entity_id, state, attr) + def _attr_setter(self, name, typ, attr, attrs): + """Helper method to populate attributes based on properties.""" + if attr in attrs: + return + + value = getattr(self, name) + + if not value: + return + + try: + attrs[attr] = typ(value) + except (TypeError, ValueError): + pass + def __eq__(self, other): return (isinstance(other, Entity) and other.unique_id == self.unique_id) From 3f82b9d6b01fcbc2b8083d189d1d5171bd5d0906 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Feb 2016 10:07:54 +0100 Subject: [PATCH 146/186] Add link to docs and modify docstrings to match PEP257 --- .../components/garage_door/__init__.py | 23 ++++++++----------- homeassistant/components/garage_door/demo.py | 21 +++++++++-------- homeassistant/components/garage_door/wink.py | 18 +++++++-------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/garage_door/__init__.py b/homeassistant/components/garage_door/__init__.py index f49d520fc7d..05728480ce2 100644 --- a/homeassistant/components/garage_door/__init__.py +++ b/homeassistant/components/garage_door/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.garage_door -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to interface with garage doors that can be controlled remotely. For more details about this component, please refer to the documentation @@ -35,32 +33,32 @@ _LOGGER = logging.getLogger(__name__) def is_closed(hass, entity_id=None): - """ Returns if the garage door is closed based on the statemachine. """ + """Returns if the garage door is closed based on the statemachine.""" entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS return hass.states.is_state(entity_id, STATE_CLOSED) def close_door(hass, entity_id=None): - """ Closes all or specified garage door. """ + """Closes all or specified garage door.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_CLOSE, data) def open_door(hass, entity_id=None): - """ Open all or specified garage door. """ + """Open all or specified garage door.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_OPEN, data) def setup(hass, config): - """ Track states and offer events for garage door. """ + """Track states and offer events for garage door.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, GROUP_NAME_ALL_GARAGE_DOORS) component.setup(config) def handle_garage_door_service(service): - """ Handles calls to the garage door services. """ + """Handles calls to the garage door services.""" target_locks = component.extract_from_service(service) for item in target_locks: @@ -83,25 +81,24 @@ def setup(hass, config): class GarageDoorDevice(Entity): - """ Represents a garage door. """ + """Represents a garage door.""" # pylint: disable=no-self-use - @property def is_closed(self): - """ Is the garage door closed or opened. """ + """Return true if door is closed.""" return None def close_door(self): - """ Closes the garage door. """ + """Closes the garage door.""" raise NotImplementedError() def open_door(self): - """ Opens the garage door. """ + """Opens the garage door.""" raise NotImplementedError() @property def state(self): - """ State of the garage door. """ + """State of the garage door.""" closed = self.is_closed if closed is None: return STATE_UNKNOWN diff --git a/homeassistant/components/garage_door/demo.py b/homeassistant/components/garage_door/demo.py index 678565bbc32..4d29a816d52 100644 --- a/homeassistant/components/garage_door/demo.py +++ b/homeassistant/components/garage_door/demo.py @@ -1,7 +1,8 @@ """ -homeassistant.components.garage_door.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform that has two fake garage doors. +Demo garage door platform that has two fake doors. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.const import STATE_CLOSED, STATE_OPEN @@ -9,7 +10,7 @@ from homeassistant.const import STATE_CLOSED, STATE_OPEN # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return demo garage doors. """ + """Setup demo garage door platform.""" add_devices_callback([ DemoGarageDoor('Left Garage Door', STATE_CLOSED), DemoGarageDoor('Right Garage Door', STATE_OPEN) @@ -17,32 +18,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoGarageDoor(GarageDoorDevice): - """ Provides a demo garage door. """ + """Provides a demo garage door.""" def __init__(self, name, state): self._name = name self._state = state @property def should_poll(self): - """ No polling needed for a demo garage door. """ + """No polling needed for a demo garage door.""" return False @property def name(self): - """ Returns the name of the device if any. """ + """Returns the name of the device if any.""" return self._name @property def is_closed(self): - """ True if device is closed. """ + """True if device is closed.""" return self._state == STATE_CLOSED def close_door(self, **kwargs): - """ Close the device. """ + """Close the device.""" self._state = STATE_CLOSED self.update_ha_state() def open_door(self, **kwargs): - """ Open the device. """ + """Open the device.""" self._state = STATE_OPEN self.update_ha_state() diff --git a/homeassistant/components/garage_door/wink.py b/homeassistant/components/garage_door/wink.py index c863be2c513..ad2bc3631aa 100644 --- a/homeassistant/components/garage_door/wink.py +++ b/homeassistant/components/garage_door/wink.py @@ -1,6 +1,4 @@ """ -homeassistant.components.garage_door.wink -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Wink garage doors. For more details about this platform, please refer to the documentation at @@ -15,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.1'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Wink platform. """ + """Sets up the Wink garage door platform.""" import pywink if discovery_info is None: @@ -34,34 +32,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WinkGarageDoorDevice(GarageDoorDevice): - """ Represents a Wink garage door. """ + """Represents a Wink garage door.""" def __init__(self, wink): self.wink = wink @property def unique_id(self): - """ Returns the id of this wink garage door """ + """Returns the id of this wink garage door.""" return "{}.{}".format(self.__class__, self.wink.device_id()) @property def name(self): - """ Returns the name of the garage door if any. """ + """Returns the name of the garage door if any.""" return self.wink.name() def update(self): - """ Update the state of the garage door. """ + """Update the state of the garage door.""" self.wink.update_state() @property def is_closed(self): - """ True if device is closed. """ + """Returns true if door is closed.""" return self.wink.state() == 0 def close_door(self): - """ Close the device. """ + """Closes the door.""" self.wink.set_state(0) def open_door(self): - """ Open the device. """ + """Open the door.""" self.wink.set_state(1) From 04b48e0683b4c8bec218736135124e8114c4698c Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Wed, 24 Feb 2016 10:28:49 +0100 Subject: [PATCH 147/186] Moved the workaround for philio slim multi sensor. Changed the key matching to utilize integer values. It does not report always return the hex marker '0x' in the identifier strings. This Re closes issue #1349 --- .../components/binary_sensor/zwave.py | 70 ++++++++++++++++++- homeassistant/components/sensor/zwave.py | 66 ++--------------- 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 8b21500cc85..920e2b702db 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -5,11 +5,14 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/binary_sensor.zwave/ """ import logging +import datetime +import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import track_point_in_time from homeassistant.components.zwave import ( ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SENSOR_BINARY, NETWORK, - ZWaveDeviceEntity) + ZWaveDeviceEntity, get_config_value) from homeassistant.components.binary_sensor import ( DOMAIN, BinarySensorDevice) @@ -17,6 +20,16 @@ from homeassistant.components.binary_sensor import ( _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] +PHILIO = 0x013c +PHILIO_SLIM_SENSOR = 0x0002 +PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0) + +WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event' + +DEVICE_MAPPINGS = { + PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, +} + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Z-Wave platform for sensors.""" @@ -27,8 +40,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] value = node.values[discovery_info[ATTR_VALUE_ID]] + specific_sensor_key = (int(value.node.manufacturer_id, 16), + int(value.node.product_id, 16), + value.index) + value.set_change_verified(False) - if value.command_class == COMMAND_CLASS_SENSOR_BINARY: + if specific_sensor_key in DEVICE_MAPPINGS: + if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT: + # Default the multiplier to 4 + re_arm_multiplier = (get_config_value(value.node, 9) or 4) + add_devices([ + ZWaveTriggerSensor(value, "motion", + hass, re_arm_multiplier * 8) + ]) + + elif value.command_class == COMMAND_CLASS_SENSOR_BINARY: add_devices([ZWaveBinarySensor(value, "opening")]) @@ -65,3 +91,43 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: self.update_ha_state() + + +class ZWaveTriggerSensor(ZWaveBinarySensor): + """ + Represents a stateless sensor which triggers events just 'On' + within Z-Wave. + """ + + def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60): + super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class) + self._hass = hass + self.invalidate_after = dt_util.utcnow() + self.re_arm_sec = re_arm_sec + + def value_changed(self, value): + """Called when a value has changed on the network.""" + if self._value.value_id == value.value_id: + self.update_ha_state() + if value.data: + # only allow this value to be true for 60 secs + self.invalidate_after = dt_util.utcnow() + datetime.timedelta( + seconds=self.re_arm_sec) + track_point_in_time( + self._hass, self.update_ha_state, + self.invalidate_after) + + @property + def state(self): + """Returns the state of the sensor.""" + if not self._value.data or \ + (self.invalidate_after is not None and + self.invalidate_after <= dt_util.utcnow()): + return False + + return True + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self.state diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 3ed3a7b90f0..066ada12546 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -6,33 +6,22 @@ at https://home-assistant.io/components/sensor.zwave/ """ # Because we do not compile openzwave on CI # pylint: disable=import-error -import datetime - -import homeassistant.util.dt as dt_util from homeassistant.components.sensor import DOMAIN from homeassistant.components.zwave import ( ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_ALARM, COMMAND_CLASS_METER, COMMAND_CLASS_SENSOR_MULTILEVEL, NETWORK, - TYPE_DECIMAL, ZWaveDeviceEntity, get_config_value) -from homeassistant.const import ( - STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) + TYPE_DECIMAL, ZWaveDeviceEntity) +from homeassistant.const import (TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import track_point_in_time -PHILIO = '0x013c' -PHILIO_SLIM_SENSOR = '0x0002' -PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0) -FIBARO = '0x010f' -FIBARO_WALL_PLUG = '0x1000' +FIBARO = 0x010f +FIBARO_WALL_PLUG = 0x1000 FIBARO_WALL_PLUG_SENSOR_METER = (FIBARO, FIBARO_WALL_PLUG, 8) -WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event' WORKAROUND_IGNORE = 'ignore' DEVICE_MAPPINGS = { - PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, - # For some reason Fibaro Wall Plug reports 2 power consumptions. # One value updates as the power consumption changes # and the other does not change. @@ -61,19 +50,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # groups[1].associations): # node.groups[1].add_association(NETWORK.controller.node_id) - specific_sensor_key = (value.node.manufacturer_id, - value.node.product_id, + specific_sensor_key = (int(value.node.manufacturer_id, 16), + int(value.node.product_id, 16), value.index) # Check workaround mappings for specific devices. if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT: - # Default the multiplier to 4 - re_arm_multiplier = (get_config_value(value.node, 9) or 4) - add_devices([ - ZWaveTriggerSensor(value, hass, re_arm_multiplier * 8) - ]) - elif DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE: + if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE: return # Generic Device mappings @@ -116,41 +99,6 @@ class ZWaveSensor(ZWaveDeviceEntity, Entity): self.update_ha_state() -class ZWaveTriggerSensor(ZWaveSensor): - """ - Represents a stateless sensor which triggers events just 'On' - within Z-Wave. - """ - - def __init__(self, sensor_value, hass, re_arm_sec=60): - super(ZWaveTriggerSensor, self).__init__(sensor_value) - self._hass = hass - self.invalidate_after = dt_util.utcnow() - self.re_arm_sec = re_arm_sec - - def value_changed(self, value): - """Called when a value has changed on the network.""" - if self._value.value_id == value.value_id: - self.update_ha_state() - if value.data: - # only allow this value to be true for 60 secs - self.invalidate_after = dt_util.utcnow() + datetime.timedelta( - seconds=self.re_arm_sec) - track_point_in_time( - self._hass, self.update_ha_state, - self.invalidate_after) - - @property - def state(self): - """Returns the state of the sensor.""" - if not self._value.data or \ - (self.invalidate_after is not None and - self.invalidate_after <= dt_util.utcnow()): - return STATE_OFF - - return STATE_ON - - class ZWaveMultilevelSensor(ZWaveSensor): """Represents a multi level sensor Z-Wave sensor.""" @property From 4563c54a3e86a7cacbc9fd64703a5ef2e2e4613b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Feb 2016 10:38:06 +0100 Subject: [PATCH 148/186] Add link to docs and modify docstrings to match PEP257 --- .../components/alarm_control_panel/demo.py | 7 +- .../components/binary_sensor/demo.py | 10 +- homeassistant/components/camera/demo.py | 15 +-- homeassistant/components/demo.py | 10 +- homeassistant/components/garage_door/demo.py | 8 +- homeassistant/components/light/demo.py | 26 ++--- homeassistant/components/lock/demo.py | 20 ++-- homeassistant/components/media_player/demo.py | 105 +++++++++--------- homeassistant/components/notify/demo.py | 12 +- .../components/rollershutter/demo.py | 28 ++--- homeassistant/components/sensor/demo.py | 11 +- homeassistant/components/switch/demo.py | 26 ++--- homeassistant/components/thermostat/demo.py | 29 +++-- 13 files changed, 155 insertions(+), 152 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/demo.py b/homeassistant/components/alarm_control_panel/demo.py index 0ace53167de..9ac98924b2b 100644 --- a/homeassistant/components/alarm_control_panel/demo.py +++ b/homeassistant/components/alarm_control_panel/demo.py @@ -1,13 +1,14 @@ """ -homeassistant.components.alarm_control_panel.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo platform that has two fake alarm control panels. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ import homeassistant.components.alarm_control_panel.manual as manual def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo alarm control panels. """ + """Setup the Demo alarm control panel platform.""" add_devices([ manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10), ]) diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index 3759ccb7662..8a1a3ea30bd 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -1,11 +1,14 @@ """ Demo platform that has two fake binary sensors. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_devices, discovery_info=None): - """Sets up the Demo binary sensors """ + """Setup the Demo binary sensor platform.""" add_devices([ DemoBinarySensor('Basement Floor Wet', False, 'moisture'), DemoBinarySensor('Movement Backyard', True, 'motion'), @@ -14,7 +17,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoBinarySensor(BinarySensorDevice): """A Demo binary sensor.""" - def __init__(self, name, state, sensor_class): self._name = name self._state = state @@ -32,10 +34,10 @@ class DemoBinarySensor(BinarySensorDevice): @property def name(self): - """Returns the name of the binary sensor.""" + """Return the name of the binary sensor.""" return self._name @property def is_on(self): - """True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return self._state diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index 89d9b9600e8..15ddeb31d72 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -1,7 +1,8 @@ """ -homeassistant.components.camera.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform that has a fake camera. +Demo camera platform that has a fake camera. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ import os @@ -10,21 +11,21 @@ from homeassistant.components.camera import Camera def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo camera. """ + """Setup the Demo camera platform.""" add_devices([ DemoCamera('Demo camera') ]) class DemoCamera(Camera): - """ A Demo camera. """ + """A Demo camera.""" def __init__(self, name): super().__init__() self._name = name def camera_image(self): - """ Return a faked still image response. """ + """Return a faked still image response.""" now = dt_util.utcnow() image_path = os.path.join(os.path.dirname(__file__), @@ -34,5 +35,5 @@ class DemoCamera(Camera): @property def name(self): - """ Return the name of this device. """ + """Return the name of this camera.""" return self._name diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 9418a037b8d..f025cfd008a 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -1,8 +1,8 @@ """ -homeassistant.components.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Sets up a demo environment that mimics interaction with devices. + +For more details about this component, please refer to the documentation +https://home-assistant.io/components/demo/ """ import time @@ -33,7 +33,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [ def setup(hass, config): - """ Setup a demo environment. """ + """Setup a demo environment.""" group = loader.get_component('group') configurator = loader.get_component('configurator') @@ -116,7 +116,7 @@ def setup(hass, config): configurator_ids = [] def hue_configuration_callback(data): - """ Fake callback, mark config as done. """ + """Fake callback, mark config as done.""" time.sleep(2) # First time it is called, pretend it failed. diff --git a/homeassistant/components/garage_door/demo.py b/homeassistant/components/garage_door/demo.py index 4d29a816d52..2b13b6fdc92 100644 --- a/homeassistant/components/garage_door/demo.py +++ b/homeassistant/components/garage_door/demo.py @@ -30,20 +30,20 @@ class DemoGarageDoor(GarageDoorDevice): @property def name(self): - """Returns the name of the device if any.""" + """Return the name of the device if any.""" return self._name @property def is_closed(self): - """True if device is closed.""" + """Return true if garage door is closed.""" return self._state == STATE_CLOSED def close_door(self, **kwargs): - """Close the device.""" + """Close the garage door.""" self._state = STATE_CLOSED self.update_ha_state() def open_door(self, **kwargs): - """Open the device.""" + """Open the garage door.""" self._state = STATE_OPEN self.update_ha_state() diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index fecec2d17d4..b4a3515d29b 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -1,8 +1,8 @@ """ -homeassistant.components.light.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform that implements lights. +Demo light platform that implements lights. +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ import random @@ -18,7 +18,7 @@ LIGHT_TEMPS = [240, 380] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return demo lights. """ + """Setup demo light platform.""" add_devices_callback([ DemoLight("Bed Light", False), DemoLight("Ceiling Lights", True, LIGHT_COLORS[0], LIGHT_TEMPS[1]), @@ -27,7 +27,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoLight(Light): - """ Provides a demo switch. """ + """Provides a demo light.""" # pylint: disable=too-many-arguments def __init__(self, name, state, rgb=None, ct=None, brightness=180): self._name = name @@ -38,36 +38,36 @@ class DemoLight(Light): @property def should_poll(self): - """ No polling needed for a demo light. """ + """No polling needed for a demo light.""" return False @property def name(self): - """ Returns the name of the device if any. """ + """Return the name of the light if any.""" return self._name @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" return self._brightness @property def rgb_color(self): - """ rgb color value. """ + """Return the RBG color value.""" return self._rgb @property def color_temp(self): - """ CT color temperature. """ + """Return the CT color temperature.""" return self._ct @property def is_on(self): - """ True if device is on. """ + """Return true if light is on.""" return self._state def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the light on.""" self._state = True if ATTR_RGB_COLOR in kwargs: @@ -82,6 +82,6 @@ class DemoLight(Light): self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the light off.""" self._state = False self.update_ha_state() diff --git a/homeassistant/components/lock/demo.py b/homeassistant/components/lock/demo.py index 472b17f46bf..319ee760e57 100644 --- a/homeassistant/components/lock/demo.py +++ b/homeassistant/components/lock/demo.py @@ -1,8 +1,8 @@ """ -homeassistant.components.lock.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Demo lock platform that has two fake locks. -Demo platform that has two fake locks. +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.lock import LockDevice from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED @@ -10,7 +10,7 @@ from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return demo locks. """ + """Setup the demo lock platform. """ add_devices_callback([ DemoLock('Front Door', STATE_LOCKED), DemoLock('Kitchen Door', STATE_UNLOCKED) @@ -18,32 +18,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoLock(LockDevice): - """ Provides a demo lock. """ + """Provides a demo lock.""" def __init__(self, name, state): self._name = name self._state = state @property def should_poll(self): - """ No polling needed for a demo lock. """ + """No polling needed for a demo lock.""" return False @property def name(self): - """ Returns the name of the device if any. """ + """Return the name of the lock if any.""" return self._name @property def is_locked(self): - """ True if device is locked. """ + """Return true if lock is locked.""" return self._state == STATE_LOCKED def lock(self, **kwargs): - """ Lock the device. """ + """Lock the device.""" self._state = STATE_LOCKED self.update_ha_state() def unlock(self, **kwargs): - """ Unlock the device. """ + """Unlock the device.""" self._state = STATE_UNLOCKED self.update_ha_state() diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index dfe8d781117..04fe7bb49c3 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -1,7 +1,8 @@ """ -homeassistant.components.media_player.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Demo implementation of the media player. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, @@ -13,7 +14,7 @@ from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the cast platform. """ + """Setup the media palyer demo platform.""" add_devices([ DemoYoutubePlayer( 'Living Room', 'eyU3bRy2x44', @@ -38,10 +39,9 @@ NETFLIX_PLAYER_SUPPORT = \ class AbstractDemoPlayer(MediaPlayerDevice): - """ Base class for demo media players. """ + """A demo media players""" # We only implement the methods that we support # pylint: disable=abstract-method - def __init__(self, name): self._name = name self._player_state = STATE_PLAYING @@ -50,65 +50,64 @@ class AbstractDemoPlayer(MediaPlayerDevice): @property def should_poll(self): - """ We will push an update after each command. """ + """Push an update after each command.""" return False @property def name(self): - """ Name of the media player. """ + """Return the name of the media player.""" return self._name @property def state(self): - """ State of the player. """ + """Return the state of the player.""" return self._player_state @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Return the volume level of the media player (0..1).""" return self._volume_level @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Return boolean if volume is currently muted.""" return self._volume_muted def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" self._player_state = STATE_PLAYING self.update_ha_state() def turn_off(self): - """ turn the media player off. """ + """Turn the media player off.""" self._player_state = STATE_OFF self.update_ha_state() def mute_volume(self, mute): - """ mute the volume. """ + """Mute the volume.""" self._volume_muted = mute self.update_ha_state() def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set the volume level, range 0..1.""" self._volume_level = volume self.update_ha_state() def media_play(self): - """ Send play commmand. """ + """Send play command.""" self._player_state = STATE_PLAYING self.update_ha_state() def media_pause(self): - """ Send pause command. """ + """Send pause command.""" self._player_state = STATE_PAUSED self.update_ha_state() class DemoYoutubePlayer(AbstractDemoPlayer): - """ A Demo media player that only supports YouTube. """ + """A Demo media player that only supports YouTube.""" # We only implement the methods that we support # pylint: disable=abstract-method - def __init__(self, name, youtube_id=None, media_title=None): super().__init__(name) self.youtube_id = youtube_id @@ -116,50 +115,49 @@ class DemoYoutubePlayer(AbstractDemoPlayer): @property def media_content_id(self): - """ Content ID of current playing media. """ + """Return the content ID of current playing media.""" return self.youtube_id @property def media_content_type(self): - """ Content type of current playing media. """ + """Return the content type of current playing media.""" return MEDIA_TYPE_VIDEO @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """ Return the duration of current playing media in seconds.""" return 360 @property def media_image_url(self): - """ Image url of current playing media. """ + """Return the image url of current playing media.""" return YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id) @property def media_title(self): - """ Title of current playing media. """ + """Return the title of current playing media.""" return self._media_title @property def app_name(self): - """ Current running app. """ + """Return the current running application.""" return "YouTube" @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flags of media commands that are supported.""" return YOUTUBE_PLAYER_SUPPORT def play_media(self, media_type, media_id): - """ Plays a piece of media. """ + """Play a piece of media.""" self.youtube_id = media_id self.update_ha_state() class DemoMusicPlayer(AbstractDemoPlayer): - """ A Demo media player that only supports YouTube. """ + """A Demo media player that only supports YouTube.""" # We only implement the methods that we support # pylint: disable=abstract-method - tracks = [ ('Technohead', 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)'), ('Paul Elstak', 'Luv U More'), @@ -188,48 +186,50 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_content_id(self): - """ Content ID of current playing media. """ + """Return the content ID of current playing media.""" return 'bounzz-1' @property def media_content_type(self): - """ Content type of current playing media. """ + """Return the content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Return the duration of current playing media in seconds.""" return 213 @property def media_image_url(self): - """ Image url of current playing media. """ + """Return the image url of current playing media.""" return 'https://graph.facebook.com/107771475912710/picture' @property def media_title(self): - """ Title of current playing media. """ + """Return the title of current playing media.""" return self.tracks[self._cur_track][1] @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Return the artist of current playing media (Music track only).""" return self.tracks[self._cur_track][0] @property def media_album_name(self): - """ Album of current playing media. (Music track only) """ + """Return the album of current playing media (Music track only).""" # pylint: disable=no-self-use return "Bounzz" @property def media_track(self): - """ Track number of current playing media. (Music track only) """ + """ + Return the track number of current playing media (Music track only). + """ return self._cur_track + 1 @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flags of media commands that are supported.""" support = MUSIC_PLAYER_SUPPORT if self._cur_track > 0: @@ -241,23 +241,22 @@ class DemoMusicPlayer(AbstractDemoPlayer): return support def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" if self._cur_track > 0: self._cur_track -= 1 self.update_ha_state() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" if self._cur_track < len(self.tracks)-1: self._cur_track += 1 self.update_ha_state() class DemoTVShowPlayer(AbstractDemoPlayer): - """ A Demo media player that only supports YouTube. """ + """A Demo media player that only supports YouTube.""" # We only implement the methods that we support # pylint: disable=abstract-method - def __init__(self): super().__init__('Lounge room') self._cur_episode = 1 @@ -265,52 +264,52 @@ class DemoTVShowPlayer(AbstractDemoPlayer): @property def media_content_id(self): - """ Content ID of current playing media. """ + """Return the content ID of current playing media.""" return 'house-of-cards-1' @property def media_content_type(self): - """ Content type of current playing media. """ + """Return the content type of current playing media.""" return MEDIA_TYPE_TVSHOW @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Return the duration of current playing media in seconds.""" return 3600 @property def media_image_url(self): - """ Image url of current playing media. """ + """Return the image url of current playing media.""" return 'https://graph.facebook.com/HouseofCards/picture' @property def media_title(self): - """ Title of current playing media. """ + """Return the title of current playing media.""" return 'Chapter {}'.format(self._cur_episode) @property def media_series_title(self): - """ Series title of current playing media. (TV Show only)""" + """Return the series title of current playing media (TV Show only).""" return 'House of Cards' @property def media_season(self): - """ Season of current playing media. (TV Show only) """ + """Return the season of current playing media (TV Show only).""" return 1 @property def media_episode(self): - """ Episode of current playing media. (TV Show only) """ + """Return the episode of current playing media (TV Show only).""" return self._cur_episode @property def app_name(self): - """ Current running app. """ + """Return the current running application.""" return "Netflix" @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" support = NETFLIX_PLAYER_SUPPORT if self._cur_episode > 1: @@ -322,13 +321,13 @@ class DemoTVShowPlayer(AbstractDemoPlayer): return support def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" if self._cur_episode > 1: self._cur_episode -= 1 self.update_ha_state() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" if self._cur_episode < self._episode_count: self._cur_episode += 1 self.update_ha_state() diff --git a/homeassistant/components/notify/demo.py b/homeassistant/components/notify/demo.py index 4ff4ec3931c..d4fbf08f7b9 100644 --- a/homeassistant/components/notify/demo.py +++ b/homeassistant/components/notify/demo.py @@ -1,8 +1,8 @@ """ -homeassistant.components.notify.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Demo notification service. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService @@ -10,15 +10,13 @@ EVENT_NOTIFY = "notify" def get_service(hass, config): - """ Get the demo notification service. """ - + """Get the demo notification service.""" return DemoNotificationService(hass) # pylint: disable=too-few-public-methods class DemoNotificationService(BaseNotificationService): - """ Implements demo notification service. """ - + """Implements demo notification service.""" def __init__(self, hass): self.hass = hass diff --git a/homeassistant/components/rollershutter/demo.py b/homeassistant/components/rollershutter/demo.py index d4af86d4cab..2910f102d11 100644 --- a/homeassistant/components/rollershutter/demo.py +++ b/homeassistant/components/rollershutter/demo.py @@ -1,7 +1,8 @@ """ -homeassistant.components.rollershutter.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform for the rollorshutter component. +Demo platform for the rollor shutter component. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.const import EVENT_TIME_CHANGED @@ -9,7 +10,7 @@ from homeassistant.helpers.event import track_utc_time_change def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo rollershutters. """ + """Setup the Demo roller shutters.""" add_devices([ DemoRollershutter(hass, 'Kitchen Window', 0), DemoRollershutter(hass, 'Living Room Window', 100), @@ -17,9 +18,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoRollershutter(RollershutterDevice): - """ Represents a rollershutter.. """ + """Represents a roller shutter.""" # pylint: disable=no-self-use - def __init__(self, hass, name, position): self.hass = hass self._name = name @@ -29,21 +29,21 @@ class DemoRollershutter(RollershutterDevice): @property def name(self): - """ Returns the name of the rollershutter. """ + """Returns the name of the roller shutter.""" return self._name @property def should_poll(self): - """ No polling needed for a demo rollershutter. """ + """No polling needed for a demo roller shutter.""" return False @property def current_position(self): - """ Returns the current position of the rollershutter. """ + """Return the current position of the roller shutter.""" return self._position def move_up(self, **kwargs): - """ Move the rollershutter down. """ + """Move the roller shutter down.""" if self._position == 0: return @@ -51,7 +51,7 @@ class DemoRollershutter(RollershutterDevice): self._moving_up = True def move_down(self, **kwargs): - """ Move the rollershutter up. """ + """Move the roller shutter up.""" if self._position == 100: return @@ -59,19 +59,19 @@ class DemoRollershutter(RollershutterDevice): self._moving_up = False def stop(self, **kwargs): - """ Stop the rollershutter. """ + """Stops the roller shutter.""" if self._listener is not None: self.hass.bus.remove_listener(EVENT_TIME_CHANGED, self._listener) self._listener = None def _listen(self): - """ Listens for changes. """ + """Listen for changes.""" if self._listener is None: self._listener = track_utc_time_change(self.hass, self._time_changed) def _time_changed(self, now): - """ Track time changes. """ + """Track time changes.""" if self._moving_up: self._position -= 10 else: diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index 04541db15a3..cd19568bcda 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -1,5 +1,8 @@ """ Demo platform that has a couple of fake sensors. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELCIUS from homeassistant.helpers.entity import Entity @@ -29,22 +32,22 @@ class DemoSensor(Entity): @property def name(self): - """Returns the name of the sensor.""" + """Return the name of the sensor.""" return self._name @property def state(self): - """Returns the state of the sensor.""" + """Return the state of the sensor.""" return self._state @property def unit_of_measurement(self): - """Unit this state is expressed in.""" + """Return the unit this state is expressed in.""" return self._unit_of_measurement @property def device_state_attributes(self): - """Returns the state attributes.""" + """Return the state attributes.""" if self._battery: return { ATTR_BATTERY_LEVEL: self._battery, diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 0f6529dff52..e34f7c7ccb2 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -1,8 +1,8 @@ """ -homeassistant.components.switch.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Demo platform that has two fake switches. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.switch import SwitchDevice from homeassistant.const import DEVICE_DEFAULT_NAME @@ -10,7 +10,7 @@ from homeassistant.const import DEVICE_DEFAULT_NAME # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return demo switches. """ + """Setup the demo switches.""" add_devices_callback([ DemoSwitch('Decorative Lights', True, None, True), DemoSwitch('AC', False, 'mdi:air-conditioner', False) @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoSwitch(SwitchDevice): - """ Provides a demo switch. """ + """Provide a demo switch.""" def __init__(self, name, state, icon, assumed): self._name = name or DEVICE_DEFAULT_NAME self._state = state @@ -27,17 +27,17 @@ class DemoSwitch(SwitchDevice): @property def should_poll(self): - """ No polling needed for a demo switch. """ + """No polling needed for a demo switch.""" return False @property def name(self): - """ Returns the name of the device if any. """ + """Return the name of the device if any.""" return self._name @property def icon(self): - """ Returns the icon to use for device if any. """ + """Return the icon to use for device if any.""" return self._icon @property @@ -47,26 +47,26 @@ class DemoSwitch(SwitchDevice): @property def current_power_mwh(self): - """ Current power usage in mwh. """ + """Returns the current power usage in mwh.""" if self._state: return 100 @property def today_power_mw(self): - """ Today total power usage in mw. """ + """Return the today total power usage in mw.""" return 1500 @property def is_on(self): - """ True if device is on. """ + """Return true if switch is on.""" return self._state def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the switch on.""" self._state = True self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" self._state = False self.update_ha_state() diff --git a/homeassistant/components/thermostat/demo.py b/homeassistant/components/thermostat/demo.py index 9ad9e8995cd..4ea1628c9a1 100644 --- a/homeassistant/components/thermostat/demo.py +++ b/homeassistant/components/thermostat/demo.py @@ -1,15 +1,15 @@ """ -homeassistant.components.thermostat.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Demo platform that offers a fake thermostat. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ """ from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Demo thermostats. """ + """Setup the Demo thermostats.""" add_devices([ DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19), DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77), @@ -18,8 +18,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments class DemoThermostat(ThermostatDevice): - """ Represents a HeatControl thermostat. """ - + """Represents a HeatControl thermostat.""" def __init__(self, name, target_temperature, unit_of_measurement, away, current_temperature): self._name = name @@ -30,42 +29,42 @@ class DemoThermostat(ThermostatDevice): @property def should_poll(self): - """ No polling needed for a demo thermostat. """ + """No polling needed for a demo thermostat.""" return False @property def name(self): - """ Returns the name. """ + """Return the thermostat.""" return self._name @property def unit_of_measurement(self): - """ Returns the unit of measurement. """ + """Return the unit of measurement.""" return self._unit_of_measurement @property def current_temperature(self): - """ Returns the current temperature. """ + """Return the current temperature.""" return self._current_temperature @property def target_temperature(self): - """ Returns the temperature we try to reach. """ + """Return the temperature we try to reach.""" return self._target_temperature @property def is_away_mode_on(self): - """ Returns if away mode is on. """ + """Return if away mode is on.""" return self._away def set_temperature(self, temperature): - """ Set new target temperature. """ + """Set new target temperature.""" self._target_temperature = temperature def turn_away_mode_on(self): - """ Turns away mode on. """ + """Turn away mode on.""" self._away = True def turn_away_mode_off(self): - """ Turns away mode off. """ + """Turn away mode off.""" self._away = False From 5406028acea190cb7e41a8f16fa3f72de26d5daf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Feb 2016 10:47:35 +0100 Subject: [PATCH 149/186] Modify docstrings to match PEP257 --- .../components/garage_door/__init__.py | 6 +++--- .../components/sensor/steam_online.py | 19 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/garage_door/__init__.py b/homeassistant/components/garage_door/__init__.py index 05728480ce2..956fac5621d 100644 --- a/homeassistant/components/garage_door/__init__.py +++ b/homeassistant/components/garage_door/__init__.py @@ -89,16 +89,16 @@ class GarageDoorDevice(Entity): return None def close_door(self): - """Closes the garage door.""" + """Close the garage door.""" raise NotImplementedError() def open_door(self): - """Opens the garage door.""" + """Open the garage door.""" raise NotImplementedError() @property def state(self): - """State of the garage door.""" + """Returns the state of the garage door.""" closed = self.is_closed if closed is None: return STATE_UNKNOWN diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index 6b740d0639b..5a29a0e3976 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -1,6 +1,4 @@ """ -homeassistant.components.sensor.steam_online -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sensor for Steam account status. For more details about this platform, please refer to the documentation at @@ -16,7 +14,7 @@ REQUIREMENTS = ['steamodd==4.21'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Steam platform. """ + """Setup the Steam platform.""" import steam as steamod steamod.api.key.set(config.get(CONF_API_KEY)) add_devices( @@ -25,8 +23,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SteamSensor(Entity): - """ Steam account. """ - + """A class for the Steam account.""" # pylint: disable=abstract-method def __init__(self, account, steamod): self._steamod = steamod @@ -35,22 +32,22 @@ class SteamSensor(Entity): @property def name(self): - """ Returns the name of the sensor. """ + """Return the name of the sensor.""" return self._profile.persona @property def entity_id(self): - """ Entity ID. """ + """Return the entity ID.""" return 'sensor.steam_{}'.format(self._account) @property def state(self): - """ State of the sensor. """ + """Return the state of the sensor.""" return self._state # pylint: disable=no-member def update(self): - """ Update device state. """ + """Update device state.""" self._profile = self._steamod.user.profile(self._account) self._state = { 1: 'Online', @@ -63,12 +60,12 @@ class SteamSensor(Entity): @property def device_state_attributes(self): - """ Returns the state attributes. """ + """Return the state attributes.""" return { ATTR_ENTITY_PICTURE: self._profile.avatar_medium } @property def icon(self): - """ Icon to use in the frontend """ + """Return the icon to use in the frontend.""" return ICON From 8840461af3468f7d4326c5a8a2fae98d0ff6d38e Mon Sep 17 00:00:00 2001 From: Flyte Date: Wed, 24 Feb 2016 13:28:39 +0000 Subject: [PATCH 150/186] Bring TCP component test coverage to 100% --- tests/components/binary_sensor/test_tcp.py | 60 ++++++++++++++++ tests/components/sensor/test_tcp.py | 80 +++++++++++++++++++++- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/tests/components/binary_sensor/test_tcp.py b/tests/components/binary_sensor/test_tcp.py index e69de29bb2d..00ec16db74e 100644 --- a/tests/components/binary_sensor/test_tcp.py +++ b/tests/components/binary_sensor/test_tcp.py @@ -0,0 +1,60 @@ +""" +tests.components.sensor.tcp +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests TCP sensor. +""" +from copy import copy + +from unittest.mock import Mock + +from homeassistant.components.sensor import tcp +from homeassistant.components.binary_sensor import tcp as bin_tcp +from tests.common import get_test_home_assistant +from tests.components.sensor import test_tcp + + +def test_setup_platform_valid_config(): + """ Should check the supplied config and call add_entities with Sensor. """ + add_entities = Mock() + ret = bin_tcp.setup_platform(None, test_tcp.TEST_CONFIG, add_entities) + assert ret is None, "setup_platform() should return None if successful." + assert add_entities.called + assert isinstance(add_entities.call_args[0][0][0], bin_tcp.BinarySensor) + + +def test_setup_platform_invalid_config(): + """ Should check the supplied config and return False if it is invalid. """ + config = copy(test_tcp.TEST_CONFIG) + del config[tcp.CONF_HOST] + assert bin_tcp.setup_platform(None, config, None) is False + + +class TestTCPBinarySensor(): + """ Test the TCP Binary Sensor. """ + + def setup_class(cls): + cls.hass = get_test_home_assistant() + + def teardown_class(cls): + cls.hass.stop() + + def test_requires_additional_values(self): + """ Should require the additional config values specified. """ + config = copy(test_tcp.TEST_CONFIG) + for key in bin_tcp.BinarySensor.required: + del config[key] + assert len(config) != len(test_tcp.TEST_CONFIG) + assert not bin_tcp.BinarySensor.validate_config(config) + + def test_is_on_true(self): + """ Should return True if _state is the same as value_on. """ + sensor = bin_tcp.BinarySensor(self.hass, test_tcp.TEST_CONFIG) + sensor._state = test_tcp.TEST_CONFIG[tcp.CONF_VALUE_ON] + assert sensor.is_on + + def test_is_on_false(self): + """ Should return False if _state is not the same as value_on. """ + sensor = bin_tcp.BinarySensor(self.hass, test_tcp.TEST_CONFIG) + sensor._state = "%s abc" % test_tcp.TEST_CONFIG[tcp.CONF_VALUE_ON] + assert not sensor.is_on diff --git a/tests/components/sensor/test_tcp.py b/tests/components/sensor/test_tcp.py index 1acb8aa79c0..d18749744b9 100644 --- a/tests/components/sensor/test_tcp.py +++ b/tests/components/sensor/test_tcp.py @@ -6,10 +6,12 @@ Tests TCP sensor. """ import socket from copy import copy +from uuid import uuid4 -from unittest.mock import patch +from unittest.mock import patch, Mock from homeassistant.components.sensor import tcp +from homeassistant.helpers.entity import Entity from tests.common import get_test_home_assistant @@ -34,7 +36,22 @@ KEYS_AND_DEFAULTS = { } -# class TestTCPSensor(unittest.TestCase): +def test_setup_platform_valid_config(): + """ Should check the supplied config and call add_entities with Sensor. """ + add_entities = Mock() + ret = tcp.setup_platform(None, TEST_CONFIG, add_entities) + assert ret is None, "setup_platform() should return None if successful." + assert add_entities.called + assert isinstance(add_entities.call_args[0][0][0], tcp.Sensor) + + +def test_setup_platform_invalid_config(): + """ Should check the supplied config and return False if it is invalid. """ + config = copy(TEST_CONFIG) + del config[tcp.CONF_HOST] + assert tcp.setup_platform(None, config, None) is False + + class TestTCPSensor(): """ Test the TCP Sensor. """ @@ -44,6 +61,31 @@ class TestTCPSensor(): def teardown_class(cls): cls.hass.stop() + def test_name(self): + """ Should return the name if set in the config. """ + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + assert sensor.name == TEST_CONFIG[tcp.CONF_NAME] + + def test_name_not_set(self): + """ Should return the superclass name property if not set in config """ + config = copy(TEST_CONFIG) + del config[tcp.CONF_NAME] + entity = Entity() + sensor = tcp.Sensor(self.hass, config) + assert sensor.name == entity.name + + def test_state(self): + """ Should return the contents of _state. """ + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + uuid = str(uuid4()) + sensor._state = uuid + assert sensor.state == uuid + + def test_unit_of_measurement(self): + """ Should return the configured unit of measurement. """ + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + assert sensor.unit_of_measurement == TEST_CONFIG[tcp.CONF_UNIT] + @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_config_valid_keys(self, *args): """ @@ -142,7 +184,7 @@ class TestTCPSensor(): TEST_CONFIG[tcp.CONF_PORT])) @patch("socket.socket.connect", side_effect=socket.error()) - def test_update_returns_if_connecting_fails(self, mock_socket): + def test_update_returns_if_connecting_fails(self, *args): """ Should return if connecting to host fails. """ @@ -150,6 +192,25 @@ class TestTCPSensor(): sensor = tcp.Sensor(self.hass, TEST_CONFIG) assert sensor.update() is None + @patch("socket.socket.connect") + @patch("socket.socket.send", side_effect=socket.error()) + def test_update_returns_if_sending_fails(self, *args): + """ + Should return if sending fails. + """ + with patch("homeassistant.components.sensor.tcp.Sensor.update"): + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + assert sensor.update() is None + + @patch("socket.socket.connect") + @patch("socket.socket.send") + @patch("select.select", return_value=(False, False, False)) + def test_update_returns_if_select_fails(self, *args): + """ Should return if select fails to return a socket. """ + with patch("homeassistant.components.sensor.tcp.Sensor.update"): + sensor = tcp.Sensor(self.hass, TEST_CONFIG) + assert sensor.update() is None + @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_sends_payload(self, mock_select, mock_socket): @@ -201,3 +262,16 @@ class TestTCPSensor(): config[tcp.CONF_VALUE_TEMPLATE] = "{{ value }} {{ 1+1 }}" sensor = tcp.Sensor(self.hass, config) assert sensor._state == "%s 2" % test_value + + @patch("socket.socket") + @patch("select.select", return_value=(True, False, False)) + def test_update_returns_if_template_render_fails( + self, mock_select, mock_socket): + """ Should return None if rendering the template fails. """ + test_value = "test_value" + mock_socket = mock_socket().__enter__() + mock_socket.recv.return_value = test_value.encode() + config = copy(TEST_CONFIG) + config[tcp.CONF_VALUE_TEMPLATE] = "{{ this won't work" + sensor = tcp.Sensor(self.hass, config) + assert sensor.update() is None From b0960044493e524e911a7e9328b113a8b3550c30 Mon Sep 17 00:00:00 2001 From: Malte Deiseroth Date: Sun, 21 Feb 2016 12:18:18 +0100 Subject: [PATCH 151/186] New deutsche_bahn component Uses the (schiene)[https://pypi.python.org/pypi/schiene/0.14] API to communicate with the webserver of bahn.de and pulls iformation about a specific connection from the (bahn.de)[http://www.bahn.de/p/view/index.shtml] webpage. The departure time of the next train for the given connection is shown. In case of delay, the delay is also shown. Additional `ATTRIBUTES` are used to inform about e.g. the type of the train, price and if it is ontime. Usage: sensor: platform: deutsche_bahn from: name_of_start_station to: name_of_final_station Problems: I'm testing it for quite some time, but I have never seen the `ATTRIBUTES` in case of a delayed train. The `ATTRIBUTES` are directly passed from the `schiene` API. So this usecase has not been tested yet. deutsche_bahn ist not supporting the `schiene` api unlike in the swiss_public_transport case. It's not guaranteed that `schiene` will work forever, infact it can happen that Bahn AG will intentionally brake the API at some point. In the past Bahn AG has not allways been very supportive to the opensource community. --- .coveragerc | 1 + .../components/sensor/deutsche_bahn.py | 98 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 102 insertions(+) create mode 100644 homeassistant/components/sensor/deutsche_bahn.py diff --git a/.coveragerc b/.coveragerc index aa010d67b2b..0c2e7c54954 100644 --- a/.coveragerc +++ b/.coveragerc @@ -119,6 +119,7 @@ omit = homeassistant/components/sensor/arest.py homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/cpuspeed.py + homeassistant/components/sensor/deutsche_bahn.py homeassistant/components/sensor/dht.py homeassistant/components/sensor/dweet.py homeassistant/components/sensor/efergy.py diff --git a/homeassistant/components/sensor/deutsche_bahn.py b/homeassistant/components/sensor/deutsche_bahn.py new file mode 100644 index 00000000000..5bba782391c --- /dev/null +++ b/homeassistant/components/sensor/deutsche_bahn.py @@ -0,0 +1,98 @@ +''' +homeassistant.components.sensor.bahn +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The deutsche_bahn sensor tells you if your next train is on time, or delayed. + +''' + +import logging +from datetime import timedelta, datetime +from homeassistant.util import Throttle +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['schiene==0.14'] + +ICON = 'mdi:train' + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add the Bahn Sensor. """ + start = config.get('from') + goal = config.get('to') + + if start is None: + _LOGGER.error('Missing required variable: "from"') + return False + + if goal is None: + _LOGGER.error('Missing required variable: "to"') + return False + + dev = [] + dev.append(DeutscheBahnSensor(start, goal)) + add_devices_callback(dev) + + +# pylint: disable=too-few-public-methods +class DeutscheBahnSensor(Entity): + """Implement a DeutscheBahn sensor + start: starting station + goal: target station""" + def __init__(self, start, goal): + self._name = start + ' to ' + goal + self.data = SchieneData(start, goal) + self.update() + + @property + def name(self): + """ return the name.""" + return self._name + + @property + def icon(self): + """ Icon for the frontend""" + return ICON + + @property + def state(self): + """Return the depature time of the next train""" + return self._state + + @property + def state_attributes(self): + return self.data.connections[0] + + def update(self): + """ Gets the latest delay from bahn.de and updates the state""" + self.data.update() + self._state = self.data.connections[0].get('departure', 'Unknown') + delay = self.data.connections[0].get('delay', + {'delay_departure': 0, + 'delay_arrival': 0}) + if delay['delay_departure'] != 0: + self._state += " + {}".format(delay['delay_departure']) + + +# pylint: disable=too-few-public-methods +class SchieneData(object): + """ Pulls data from the bahn.de web page""" + def __init__(self, start, goal): + import schiene + self.start = start + self.goal = goal + self.schiene = schiene.Schiene() + self.connections = [{}] + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ update connection data""" + self.connections = self.schiene.connections(self.start, + self.goal, + datetime.now()) + for con in self.connections: + if 'details' in con: + con.pop('details') # details info is not usefull diff --git a/requirements_all.txt b/requirements_all.txt index c3e56d357f7..a4ac57fe5f0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -225,6 +225,9 @@ radiotherm==1.2 # homeassistant.components.media_player.samsungtv samsungctl==0.5.1 +# homeassistant.components.sensor.deutsche_bahn +schiene==0.14 + # homeassistant.components.scsgate scsgate==0.1.0 From eb5f208a098e16e7f9e9416399f8279d57ea1262 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 23 Feb 2016 15:20:51 -0800 Subject: [PATCH 152/186] Add a few more sensor classes This adds hot, cold, and moving sensor_class values for things that we may want to support. --- homeassistant/components/binary_sensor/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index c3e13047aad..313ae865816 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -26,6 +26,9 @@ SENSOR_CLASSES = [ 'light', # Lightness threshold 'power', # Power, over-current, etc 'safety', # Generic on=unsafe, off=safe + 'heat', # On means hot (or too hot) + 'cold', # On means cold (or too cold) + 'moving', # On means moving, Off means stopped ] # Maps discovered services to their platforms From db06d5a03d58a6837d29301e5f285febd808026f Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Wed, 24 Feb 2016 22:44:49 +0100 Subject: [PATCH 153/186] Fixed the z-wave trigger sensor workaround --- .../components/binary_sensor/zwave.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 920e2b702db..bd3ff69e8cf 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -55,7 +55,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ]) elif value.command_class == COMMAND_CLASS_SENSOR_BINARY: - add_devices([ZWaveBinarySensor(value, "opening")]) + add_devices([ZWaveBinarySensor(value, None)]) class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): @@ -102,32 +102,30 @@ class ZWaveTriggerSensor(ZWaveBinarySensor): def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60): super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class) self._hass = hass - self.invalidate_after = dt_util.utcnow() self.re_arm_sec = re_arm_sec + self.invalidate_after = dt_util.utcnow() + datetime.timedelta( + seconds=self.re_arm_sec) + # If it's active make sure that we set the timeout tracker + if sensor_value.data: + track_point_in_time( + self._hass, self.update_ha_state, + self.invalidate_after) def value_changed(self, value): """Called when a value has changed on the network.""" if self._value.value_id == value.value_id: self.update_ha_state() if value.data: - # only allow this value to be true for 60 secs + # only allow this value to be true for re_arm secs self.invalidate_after = dt_util.utcnow() + datetime.timedelta( seconds=self.re_arm_sec) track_point_in_time( self._hass, self.update_ha_state, self.invalidate_after) - @property - def state(self): - """Returns the state of the sensor.""" - if not self._value.data or \ - (self.invalidate_after is not None and - self.invalidate_after <= dt_util.utcnow()): - return False - - return True - @property def is_on(self): - """Return True if the binary sensor is on.""" - return self.state + """Return True if movement has happened within the rearm time.""" + return self._value.data and \ + (self.invalidate_after is None or + self.invalidate_after > dt_util.utcnow()) From 2790ee0b0236f9e3fcb787e82082bd5fb9365c0f Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 25 Feb 2016 00:24:31 +0100 Subject: [PATCH 154/186] Use get_component instead of importing component --- .../components/binary_sensor/mysensors.py | 12 ++++++++---- homeassistant/components/light/mysensors.py | 11 +++++++---- homeassistant/components/sensor/mysensors.py | 14 +++++++++----- homeassistant/components/switch/mysensors.py | 14 +++++++++----- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index 77b4432e000..8d1b9eb2ea7 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -6,11 +6,11 @@ https://home-assistant.io/components/binary_sensor.mysensors/ """ import logging -import homeassistant.components.mysensors as mysensors from homeassistant.const import ( ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON) from homeassistant.components.binary_sensor import ( BinarySensorDevice, SENSOR_CLASSES) +from homeassistant.loader import get_component _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] @@ -23,6 +23,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None: return + mysensors = get_component('mysensors') + for gateway in mysensors.GATEWAYS.values(): # Define the S_TYPES and V_TYPES that the platform should handle as # states. Map them in a dict of lists. @@ -74,6 +76,7 @@ class MySensorsBinarySensor(BinarySensorDevice): child_type (str): Child type of child. battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. + mysensors (module): Mysensors main component module. """ self.gateway = gateway self.node_id = node_id @@ -83,6 +86,7 @@ class MySensorsBinarySensor(BinarySensorDevice): self.child_type = child_type self.battery_level = 0 self._values = {} + self.mysensors = get_component('mysensors') @property def should_poll(self): @@ -98,9 +102,9 @@ class MySensorsBinarySensor(BinarySensorDevice): def device_state_attributes(self): """Return device specific state attributes.""" attr = { - mysensors.ATTR_PORT: self.gateway.port, - mysensors.ATTR_NODE_ID: self.node_id, - mysensors.ATTR_CHILD_ID: self.child_id, + self.mysensors.ATTR_PORT: self.gateway.port, + self.mysensors.ATTR_NODE_ID: self.node_id, + self.mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, } diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index c0daca374cb..f4fd7bc3c79 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -6,10 +6,10 @@ https://home-assistant.io/components/light.mysensors/ """ import logging -import homeassistant.components.mysensors as mysensors from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON +from homeassistant.loader import get_component from homeassistant.util.color import rgb_hex_to_rgb_list _LOGGER = logging.getLogger(__name__) @@ -25,6 +25,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None: return + mysensors = get_component('mysensors') + for gateway in mysensors.GATEWAYS.values(): # Define the S_TYPES and V_TYPES that the platform should handle as # states. Map them in a dict of lists. @@ -72,6 +74,7 @@ class MySensorsLight(Light): self._brightness = None self._rgb = None self._white = None + self.mysensors = get_component('mysensors') @property def should_poll(self): @@ -102,9 +105,9 @@ class MySensorsLight(Light): def device_state_attributes(self): """Return device specific state attributes.""" device_attr = { - mysensors.ATTR_PORT: self.gateway.port, - mysensors.ATTR_NODE_ID: self.node_id, - mysensors.ATTR_CHILD_ID: self.child_id, + self.mysensors.ATTR_PORT: self.gateway.port, + self.mysensors.ATTR_NODE_ID: self.node_id, + self.mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, } for value_type, value in self._values.items(): diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index ffea4892988..ee05b731268 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -6,10 +6,10 @@ https://home-assistant.io/components/sensor.mysensors/ """ import logging -import homeassistant.components.mysensors as mysensors from homeassistant.const import ( ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity +from homeassistant.loader import get_component _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] @@ -22,6 +22,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None: return + mysensors = get_component('mysensors') + for gateway in mysensors.GATEWAYS.values(): # Define the S_TYPES and V_TYPES that the platform should handle as # states. Map them in a dict of lists. @@ -77,7 +79,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsSensor(Entity): """Represent the value of a MySensors child node.""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__( self, gateway, node_id, child_id, name, value_type, child_type): @@ -99,6 +101,7 @@ class MySensorsSensor(Entity): value_type (str): Value type of child. Value is entity state. battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. + mysensors (module): Mysensors main component module. """ self.gateway = gateway self.node_id = node_id @@ -107,6 +110,7 @@ class MySensorsSensor(Entity): self.value_type = value_type self.battery_level = 0 self._values = {} + self.mysensors = get_component('mysensors') @property def should_poll(self): @@ -156,9 +160,9 @@ class MySensorsSensor(Entity): def device_state_attributes(self): """Return device specific state attributes.""" attr = { - mysensors.ATTR_PORT: self.gateway.port, - mysensors.ATTR_NODE_ID: self.node_id, - mysensors.ATTR_CHILD_ID: self.child_id, + self.mysensors.ATTR_PORT: self.gateway.port, + self.mysensors.ATTR_NODE_ID: self.node_id, + self.mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, } diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index e0797e42c54..9921ddb3602 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -6,9 +6,9 @@ https://home-assistant.io/components/switch.mysensors/ """ import logging -import homeassistant.components.mysensors as mysensors from homeassistant.components.switch import SwitchDevice from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON +from homeassistant.loader import get_component _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] @@ -21,6 +21,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None: return + mysensors = get_component('mysensors') + for gateway in mysensors.GATEWAYS.values(): # Define the S_TYPES and V_TYPES that the platform should handle as # states. Map them in a dict of lists. @@ -50,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsSwitch(SwitchDevice): """Represent the value of a MySensors child node.""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__( self, gateway, node_id, child_id, name, value_type, child_type): @@ -72,6 +74,7 @@ class MySensorsSwitch(SwitchDevice): value_type (str): Value type of child. Value is entity state. battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. + mysensors (module): Mysensors main component module. """ self.gateway = gateway self.node_id = node_id @@ -80,6 +83,7 @@ class MySensorsSwitch(SwitchDevice): self.value_type = value_type self.battery_level = 0 self._values = {} + self.mysensors = get_component('mysensors') @property def should_poll(self): @@ -95,9 +99,9 @@ class MySensorsSwitch(SwitchDevice): def device_state_attributes(self): """Return device specific state attributes.""" attr = { - mysensors.ATTR_PORT: self.gateway.port, - mysensors.ATTR_NODE_ID: self.node_id, - mysensors.ATTR_CHILD_ID: self.child_id, + self.mysensors.ATTR_PORT: self.gateway.port, + self.mysensors.ATTR_NODE_ID: self.node_id, + self.mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, } From 9c538d11c21ad5e33082a1faa4f2409e5c014c26 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 25 Feb 2016 00:48:29 +0100 Subject: [PATCH 155/186] Fix mysensors switch & light types * Move S_LIGHT and V_LIGHT from light back to switch platform, to avoid double devices showing. * Remove MySensorsLightPlain class from light platform, since it's not needed anymore. * A light switch with only a switch, ie no dimmer or RGB controls, will show as a regular switch device. --- homeassistant/components/light/mysensors.py | 22 -------------------- homeassistant/components/switch/mysensors.py | 2 ++ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index c0daca374cb..6bc16515760 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -31,11 +31,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres = gateway.const.Presentation set_req = gateway.const.SetReq map_sv_types = { - pres.S_LIGHT: [set_req.V_LIGHT], pres.S_DIMMER: [set_req.V_DIMMER], } device_class_map = { - pres.S_LIGHT: MySensorsLightPlain, pres.S_DIMMER: MySensorsLightDimmer, } if float(gateway.version) >= 1.5: @@ -43,7 +41,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): map_sv_types.update({ pres.S_RGB_LIGHT: [set_req.V_RGB], }) - map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS) map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE) device_class_map.update({ pres.S_RGB_LIGHT: MySensorsLightRGB, @@ -260,25 +257,6 @@ class MySensorsLight(Light): self._values[value_type] = value -class MySensorsLightPlain(MySensorsLight): - """Light child class to MySensorsLight.""" - - def turn_on(self, **kwargs): - """Turn the device on.""" - self._turn_on_light() - - def turn_off(self, **kwargs): - """Turn the device off.""" - ret = self._turn_off_light() - self._turn_off_main(value_type=ret[ - ATTR_VALUE_TYPE], value=ret[ATTR_VALUE]) - - def update(self): - """Update the controller with the latest value from a sensor.""" - self._update_main() - self._update_light() - - class MySensorsLightDimmer(MySensorsLight): """Dimmer child class to MySensorsLight.""" diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index e0797e42c54..9da57b2f714 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -30,6 +30,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_DOOR: [set_req.V_ARMED], pres.S_MOTION: [set_req.V_ARMED], pres.S_SMOKE: [set_req.V_ARMED], + pres.S_LIGHT: [set_req.V_LIGHT], pres.S_LOCK: [set_req.V_LOCK_STATUS], } if float(gateway.version) >= 1.5: @@ -41,6 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_VIBRATION: [set_req.V_ARMED], pres.S_MOISTURE: [set_req.V_ARMED], }) + map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS) devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( From db4bf7319f716a146b25ba588bd3e2748878faa0 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 24 Feb 2016 10:41:49 -0800 Subject: [PATCH 156/186] Add float() operator to templates --- homeassistant/helpers/template.py | 9 +++++++++ tests/helpers/test_template.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index bc80fd89a81..9c9ebd42913 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -52,6 +52,7 @@ def render(hass, template, variables=None, **kwargs): return ENV.from_string(template, { 'closest': location_methods.closest, 'distance': location_methods.distance, + 'float': forgiving_float, 'is_state': hass.states.is_state, 'is_state_attr': hass.states.is_state_attr, 'now': dt_util.as_local(utcnow), @@ -240,6 +241,14 @@ def multiply(value, amount): return value +def forgiving_float(value): + """Try to convert value to a float.""" + try: + return float(value) + except (ValueError, TypeError): + return value + + class TemplateEnvironment(ImmutableSandboxedEnvironment): """Home Assistant template environment.""" diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 1420ac44b6b..4aefdcf4fc5 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -51,6 +51,21 @@ class TestUtilTemplate(unittest.TestCase): {% for state in states.sensor %}{{ state.state }}{% endfor %} """)) + def test_float(self): + self.hass.states.set('sensor.temperature', '12') + + self.assertEqual( + '12.0', + template.render( + self.hass, + '{{ float(states.sensor.temperature.state) }}')) + + self.assertEqual( + 'True', + template.render( + self.hass, + '{{ float(states.sensor.temperature.state) > 11 }}')) + def test_rounding_value(self): self.hass.states.set('sensor.temperature', 12.78) From a7519bb38b5d0815f412ef85f98fcdc0d4d967f2 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 23 Feb 2016 15:16:18 -0800 Subject: [PATCH 157/186] Add a template binary_sensor platform --- .../components/binary_sensor/template.py | 122 ++++++++++++++++++ .../components/binary_sensor/test_template.py | 111 ++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 homeassistant/components/binary_sensor/template.py create mode 100644 tests/components/binary_sensor/test_template.py diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py new file mode 100644 index 00000000000..f5a8899af96 --- /dev/null +++ b/homeassistant/components/binary_sensor/template.py @@ -0,0 +1,122 @@ +""" +homeassistant.components.binary_sensor.template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for exposing a templated binary_sensor +""" +import logging + +from homeassistant.components.binary_sensor import (BinarySensorDevice, + DOMAIN, + SENSOR_CLASSES) +from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE +from homeassistant.core import EVENT_STATE_CHANGED +from homeassistant.exceptions import TemplateError +from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers import template +from homeassistant.util import slugify + +ENTITY_ID_FORMAT = DOMAIN + '.{}' +CONF_SENSORS = 'sensors' +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup template binary sensors.""" + + sensors = [] + if config.get(CONF_SENSORS) is None: + _LOGGER.error('Missing configuration data for binary_sensor platform') + return False + + for device, device_config in config[CONF_SENSORS].items(): + + if device != slugify(device): + _LOGGER.error('Found invalid key for binary_sensor.template: %s. ' + 'Use %s instead', device, slugify(device)) + continue + + if not isinstance(device_config, dict): + _LOGGER.error('Missing configuration data for binary_sensor %s', + device) + continue + + friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) + sensor_class = device_config.get('sensor_class') + value_template = device_config.get(CONF_VALUE_TEMPLATE) + + if sensor_class not in SENSOR_CLASSES: + _LOGGER.error('Sensor class is not valid') + continue + + if value_template is None: + _LOGGER.error( + 'Missing %s for sensor %s', CONF_VALUE_TEMPLATE, device) + continue + + sensors.append( + BinarySensorTemplate( + hass, + device, + friendly_name, + sensor_class, + value_template) + ) + if not sensors: + _LOGGER.error('No sensors added') + return False + add_devices(sensors) + + return True + + +class BinarySensorTemplate(BinarySensorDevice): + """A virtual binary_sensor that triggers from another sensor.""" + + # pylint: disable=too-many-arguments + def __init__(self, hass, device, friendly_name, sensor_class, + value_template): + self._hass = hass + self._device = device + self._name = friendly_name + self._sensor_class = sensor_class + self._template = value_template + self._state = None + + self.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, device, + hass=hass) + + _LOGGER.info('Started template sensor %s', device) + hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + + def _event_listener(self, event): + self.update_ha_state(True) + + @property + def should_poll(self): + return False + + @property + def sensor_class(self): + return self._sensor_class + + @property + def name(self): + return self._name + + @property + def is_on(self): + return self._state + + def update(self): + try: + value = template.render(self._hass, self._template) + except TemplateError as ex: + if ex.args and ex.args[0].startswith( + "UndefinedError: 'None' has no attribute"): + # Common during HA startup - so just a warning + _LOGGER.warning(ex) + return + _LOGGER.error(ex) + value = 'false' + self._state = value.lower() == 'true' diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py new file mode 100644 index 00000000000..7fc18a930f1 --- /dev/null +++ b/tests/components/binary_sensor/test_template.py @@ -0,0 +1,111 @@ +""" +tests.components.binary_sensor.test_template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests for template binary_sensor. +""" + +import unittest +from unittest import mock + +from homeassistant.components.binary_sensor import template +from homeassistant.exceptions import TemplateError + + +class TestBinarySensorTemplate(unittest.TestCase): + @mock.patch.object(template, 'BinarySensorTemplate') + def test_setup(self, mock_template): + config = { + 'sensors': { + 'test': { + 'friendly_name': 'virtual thingy', + 'value_template': '{{ foo }}', + 'sensor_class': 'motion', + }, + } + } + hass = mock.MagicMock() + add_devices = mock.MagicMock() + result = template.setup_platform(hass, config, add_devices) + self.assertTrue(result) + mock_template.assert_called_once_with(hass, 'test', 'virtual thingy', + 'motion', '{{ foo }}') + add_devices.assert_called_once_with([mock_template.return_value]) + + def test_setup_no_sensors(self): + config = {} + result = template.setup_platform(None, config, None) + self.assertFalse(result) + + def test_setup_invalid_device(self): + config = { + 'sensors': { + 'foo bar': {}, + }, + } + result = template.setup_platform(None, config, None) + self.assertFalse(result) + + def test_setup_invalid_sensor_class(self): + config = { + 'sensors': { + 'test': { + 'value_template': '{{ foo }}', + 'sensor_class': 'foobarnotreal', + }, + }, + } + result = template.setup_platform(None, config, None) + self.assertFalse(result) + + def test_setup_invalid_missing_template(self): + config = { + 'sensors': { + 'test': { + 'sensor_class': 'motion', + }, + }, + } + result = template.setup_platform(None, config, None) + self.assertFalse(result) + + def test_attributes(self): + hass = mock.MagicMock() + vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', + 'motion', '{{ 1 > 1 }}') + self.assertFalse(vs.should_poll) + self.assertEqual('motion', vs.sensor_class) + self.assertEqual('Parent', vs.name) + + vs.update() + self.assertFalse(vs.is_on) + + vs._template = "{{ 2 > 1 }}" + vs.update() + self.assertTrue(vs.is_on) + + def test_event(self): + hass = mock.MagicMock() + vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', + 'motion', '{{ 1 > 1 }}') + with mock.patch.object(vs, 'update_ha_state') as mock_update: + vs._event_listener(None) + mock_update.assert_called_once_with(True) + + def test_update(self): + hass = mock.MagicMock() + vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', + 'motion', '{{ 2 > 1 }}') + self.assertEqual(None, vs._state) + vs.update() + self.assertTrue(vs._state) + + @mock.patch('homeassistant.helpers.template.render') + def test_update_template_error(self, mock_render): + hass = mock.MagicMock() + vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', + 'motion', '{{ 1 > 1 }}') + mock_render.side_effect = TemplateError('foo') + vs.update() + mock_render.side_effect = TemplateError( + "UndefinedError: 'None' has no attribute") + vs.update() From 23db6e753f02c5f4a6698143ffb51107a21ae503 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 23 Feb 2016 18:01:53 +0100 Subject: [PATCH 158/186] refactor rfxtrx code --- homeassistant/components/light/rfxtrx.py | 58 ++++++++++----------- homeassistant/components/rfxtrx.py | 1 + homeassistant/components/sensor/rfxtrx.py | 62 +++++++++++++++-------- homeassistant/components/switch/rfxtrx.py | 50 +++++++++--------- 4 files changed, 93 insertions(+), 78 deletions(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 60d61c95a42..194676c8054 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -26,24 +26,22 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): import RFXtrx as rfxtrxmod lights = [] - devices = config.get('devices', None) signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) - if devices: - for entity_id, entity_info in devices.items(): - if entity_id not in rfxtrx.RFX_DEVICES: - _LOGGER.info("Add %s rfxtrx.light", entity_info[ATTR_NAME]) + for device_id, entity_info in config.get('devices', {}).items(): + if device_id not in rfxtrx.RFX_DEVICES: + _LOGGER.info("Add %s rfxtrx.light", entity_info[ATTR_NAME]) - # Check if i must fire event - fire_event = entity_info.get(ATTR_FIREEVENT, False) - datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} + # Check if i must fire event + fire_event = entity_info.get(ATTR_FIREEVENT, False) + datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} - rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) - new_light = RfxtrxLight( - entity_info[ATTR_NAME], rfxobject, datas, - signal_repetitions) - rfxtrx.RFX_DEVICES[entity_id] = new_light - lights.append(new_light) + rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + new_light = RfxtrxLight( + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) + rfxtrx.RFX_DEVICES[device_id] = new_light + lights.append(new_light) add_devices_callback(lights) @@ -54,33 +52,33 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return # Add entity if not exist and the automatic_add is True - entity_id = slugify(event.device.id_string.lower()) - if entity_id not in rfxtrx.RFX_DEVICES: + device_id = slugify(event.device.id_string.lower()) + if device_id not in rfxtrx.RFX_DEVICES: automatic_add = config.get('automatic_add', False) if not automatic_add: return _LOGGER.info( "Automatic add %s rfxtrx.light (Class: %s Sub: %s)", - entity_id, + device_id, event.device.__class__.__name__, event.device.subtype ) pkt_id = "".join("{0:02x}".format(x) for x in event.data) - entity_name = "%s : %s" % (entity_id, pkt_id) + entity_name = "%s : %s" % (device_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) new_light = RfxtrxLight(entity_name, event, datas, signal_repetitions) - rfxtrx.RFX_DEVICES[entity_id] = new_light + rfxtrx.RFX_DEVICES[device_id] = new_light add_devices_callback([new_light]) # Check if entity exists or previously added automatically - if entity_id in rfxtrx.RFX_DEVICES: + if device_id in rfxtrx.RFX_DEVICES: _LOGGER.debug( "EntityID: %s light_update. Command: %s", - entity_id, + device_id, event.values['Command'] ) @@ -90,27 +88,27 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # Update the rfxtrx device state is_on = event.values['Command'] == 'On' # pylint: disable=protected-access - rfxtrx.RFX_DEVICES[entity_id]._state = is_on - rfxtrx.RFX_DEVICES[entity_id].update_ha_state() + rfxtrx.RFX_DEVICES[device_id]._state = is_on + rfxtrx.RFX_DEVICES[device_id].update_ha_state() elif event.values['Command'] == 'Set level': # pylint: disable=protected-access - rfxtrx.RFX_DEVICES[entity_id]._brightness = \ + rfxtrx.RFX_DEVICES[device_id]._brightness = \ (event.values['Dim level'] * 255 // 100) # Update the rfxtrx device state - is_on = rfxtrx.RFX_DEVICES[entity_id]._brightness > 0 - rfxtrx.RFX_DEVICES[entity_id]._state = is_on - rfxtrx.RFX_DEVICES[entity_id].update_ha_state() + is_on = rfxtrx.RFX_DEVICES[device_id]._brightness > 0 + rfxtrx.RFX_DEVICES[device_id]._state = is_on + rfxtrx.RFX_DEVICES[device_id].update_ha_state() else: return # Fire event - if rfxtrx.RFX_DEVICES[entity_id].should_fire_event: - rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire( + if rfxtrx.RFX_DEVICES[device_id].should_fire_event: + rfxtrx.RFX_DEVICES[device_id].hass.bus.fire( EVENT_BUTTON_PRESSED, { ATTR_ENTITY_ID: - rfxtrx.RFX_DEVICES[entity_id].entity_id, + rfxtrx.RFX_DEVICES[device_id].device_id, ATTR_STATE: event.values['Command'].lower() } ) diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index 4789348c69a..4b971530584 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -21,6 +21,7 @@ ATTR_STATE = 'state' ATTR_NAME = 'name' ATTR_PACKETID = 'packetid' ATTR_FIREEVENT = 'fire_event' +ATTR_DATA_TYPE = 'data_type' EVENT_BUTTON_PRESSED = 'button_pressed' diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index d424d899bcc..4c1a96ce40d 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -11,6 +11,8 @@ import homeassistant.components.rfxtrx as rfxtrx from homeassistant.const import TEMP_CELCIUS from homeassistant.helpers.entity import Entity from homeassistant.util import slugify +from homeassistant.components.rfxtrx import ( + ATTR_PACKETID, ATTR_NAME, ATTR_DATA_TYPE) DEPENDENCIES = ['rfxtrx'] @@ -29,25 +31,41 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Setup the RFXtrx platform.""" from RFXtrx import SensorEvent + sensors = [] + for device_id, entity_info in config.get('devices', {}).items(): + if device_id not in rfxtrx.RFX_DEVICES: + _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) + event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME], + entity_info.get(ATTR_DATA_TYPE, None)) + rfxtrx.RFX_DEVICES[device_id] = new_sensor + sensors.append(new_sensor) + + add_devices_callback(sensors) + def sensor_update(event): """Callback for sensor updates from the RFXtrx gateway.""" - if isinstance(event, SensorEvent): - entity_id = slugify(event.device.id_string.lower()) + if not isinstance(event, SensorEvent): + return - # Add entity if not exist and the automatic_add is True - if entity_id not in rfxtrx.RFX_DEVICES: - automatic_add = config.get('automatic_add', True) - if automatic_add: - _LOGGER.info("Automatic add %s rfxtrx.sensor", entity_id) - new_sensor = RfxtrxSensor(event) - rfxtrx.RFX_DEVICES[entity_id] = new_sensor - add_devices_callback([new_sensor]) - else: - _LOGGER.debug( - "EntityID: %s sensor_update", - entity_id, - ) - rfxtrx.RFX_DEVICES[entity_id].event = event + device_id = "sensor_" + slugify(event.device.id_string.lower()) + + if device_id in rfxtrx.RFX_DEVICES: + rfxtrx.RFX_DEVICES[device_id].event = event + return + + # Add entity if not exist and the automatic_add is True + if config.get('automatic_add', True): + pkt_id = "".join("{0:02x}".format(x) for x in event.data) + entity_name = "%s : %s" % (device_id, pkt_id) + _LOGGER.info( + "Automatic add rfxtrx.sensor: (%s : %s)", + device_id, + pkt_id) + + new_sensor = RfxtrxSensor(event, entity_name) + rfxtrx.RFX_DEVICES[device_id] = new_sensor + add_devices_callback([new_sensor]) if sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(sensor_update) @@ -56,21 +74,21 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class RfxtrxSensor(Entity): """Represents a RFXtrx sensor.""" - def __init__(self, event): + def __init__(self, event, name, data_type=None): self.event = event self._unit_of_measurement = None self._data_type = None + self._name = name + if data_type: + self._data_type = data_type + self._unit_of_measurement = DATA_TYPES[data_type] + return for data_type in DATA_TYPES: if data_type in self.event.values: self._unit_of_measurement = DATA_TYPES[data_type] self._data_type = data_type break - id_string = int(event.device.id_string.replace(":", ""), 16) - self._name = "{} {} ({})".format(self._data_type, - self.event.device.type_string, - id_string) - def __str__(self): """Returns the name.""" return self._name diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 52cbac2319d..52168db3167 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -27,23 +27,21 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # Add switch from config file switchs = [] - devices = config.get('devices') signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) - if devices: - for entity_id, entity_info in devices.items(): - if entity_id not in rfxtrx.RFX_DEVICES: - _LOGGER.info("Add %s rfxtrx.switch", entity_info[ATTR_NAME]) + for device_id, entity_info in config.get('devices', {}).items(): + if device_id not in rfxtrx.RFX_DEVICES: + _LOGGER.info("Add %s rfxtrx.switch", entity_info[ATTR_NAME]) - # Check if i must fire event - fire_event = entity_info.get(ATTR_FIREEVENT, False) - datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} + # Check if i must fire event + fire_event = entity_info.get(ATTR_FIREEVENT, False) + datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} - rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) - newswitch = RfxtrxSwitch( - entity_info[ATTR_NAME], rfxobject, datas, - signal_repetitions) - rfxtrx.RFX_DEVICES[entity_id] = newswitch - switchs.append(newswitch) + rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + newswitch = RfxtrxSwitch( + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) + rfxtrx.RFX_DEVICES[device_id] = newswitch + switchs.append(newswitch) add_devices_callback(switchs) @@ -54,33 +52,33 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return # Add entity if not exist and the automatic_add is True - entity_id = slugify(event.device.id_string.lower()) - if entity_id not in rfxtrx.RFX_DEVICES: + device_id = slugify(event.device.id_string.lower()) + if device_id not in rfxtrx.RFX_DEVICES: automatic_add = config.get('automatic_add', False) if not automatic_add: return _LOGGER.info( "Automatic add %s rfxtrx.switch (Class: %s Sub: %s)", - entity_id, + device_id, event.device.__class__.__name__, event.device.subtype ) pkt_id = "".join("{0:02x}".format(x) for x in event.data) - entity_name = "%s : %s" % (entity_id, pkt_id) + entity_name = "%s : %s" % (device_id, pkt_id) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) new_switch = RfxtrxSwitch(entity_name, event, datas, signal_repetitions) - rfxtrx.RFX_DEVICES[entity_id] = new_switch + rfxtrx.RFX_DEVICES[device_id] = new_switch add_devices_callback([new_switch]) # Check if entity exists or previously added automatically - if entity_id in rfxtrx.RFX_DEVICES: + if device_id in rfxtrx.RFX_DEVICES: _LOGGER.debug( "EntityID: %s switch_update. Command: %s", - entity_id, + device_id, event.values['Command'] ) if event.values['Command'] == 'On'\ @@ -89,15 +87,15 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # Update the rfxtrx device state is_on = event.values['Command'] == 'On' # pylint: disable=protected-access - rfxtrx.RFX_DEVICES[entity_id]._state = is_on - rfxtrx.RFX_DEVICES[entity_id].update_ha_state() + rfxtrx.RFX_DEVICES[device_id]._state = is_on + rfxtrx.RFX_DEVICES[device_id].update_ha_state() # Fire event - if rfxtrx.RFX_DEVICES[entity_id].should_fire_event: - rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire( + if rfxtrx.RFX_DEVICES[device_id].should_fire_event: + rfxtrx.RFX_DEVICES[device_id].hass.bus.fire( EVENT_BUTTON_PRESSED, { ATTR_ENTITY_ID: - rfxtrx.RFX_DEVICES[entity_id].entity_id, + rfxtrx.RFX_DEVICES[device_id].device_id, ATTR_STATE: event.values['Command'].lower() } ) From 8ffa3684e3f08b4a2a84a10e15f31fe9ab7cd768 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 25 Feb 2016 17:40:24 +0100 Subject: [PATCH 159/186] rfxtrx refactor --- homeassistant/components/light/rfxtrx.py | 23 ++++++++++++----------- homeassistant/components/sensor/rfxtrx.py | 15 ++++++++------- homeassistant/components/switch/rfxtrx.py | 23 ++++++++++++----------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 194676c8054..60d30645e9f 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -29,19 +29,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) for device_id, entity_info in config.get('devices', {}).items(): - if device_id not in rfxtrx.RFX_DEVICES: - _LOGGER.info("Add %s rfxtrx.light", entity_info[ATTR_NAME]) + if device_id in rfxtrx.RFX_DEVICES: + continue + _LOGGER.info("Add %s rfxtrx.light", entity_info[ATTR_NAME]) - # Check if i must fire event - fire_event = entity_info.get(ATTR_FIREEVENT, False) - datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} + # Check if i must fire event + fire_event = entity_info.get(ATTR_FIREEVENT, False) + datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} - rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) - new_light = RfxtrxLight( - entity_info[ATTR_NAME], rfxobject, datas, - signal_repetitions) - rfxtrx.RFX_DEVICES[device_id] = new_light - lights.append(new_light) + rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + new_light = RfxtrxLight( + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) + rfxtrx.RFX_DEVICES[device_id] = new_light + lights.append(new_light) add_devices_callback(lights) diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 4c1a96ce40d..e4d1c4e37b4 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -33,13 +33,14 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): sensors = [] for device_id, entity_info in config.get('devices', {}).items(): - if device_id not in rfxtrx.RFX_DEVICES: - _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) - event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) - new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME], - entity_info.get(ATTR_DATA_TYPE, None)) - rfxtrx.RFX_DEVICES[device_id] = new_sensor - sensors.append(new_sensor) + if device_id in rfxtrx.RFX_DEVICES: + continue + _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) + event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME], + entity_info.get(ATTR_DATA_TYPE, None)) + rfxtrx.RFX_DEVICES[device_id] = new_sensor + sensors.append(new_sensor) add_devices_callback(sensors) diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 52168db3167..799053920ae 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -29,19 +29,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): switchs = [] signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) for device_id, entity_info in config.get('devices', {}).items(): - if device_id not in rfxtrx.RFX_DEVICES: - _LOGGER.info("Add %s rfxtrx.switch", entity_info[ATTR_NAME]) + if device_id in rfxtrx.RFX_DEVICES: + continue + _LOGGER.info("Add %s rfxtrx.switch", entity_info[ATTR_NAME]) - # Check if i must fire event - fire_event = entity_info.get(ATTR_FIREEVENT, False) - datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} + # Check if i must fire event + fire_event = entity_info.get(ATTR_FIREEVENT, False) + datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} - rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) - newswitch = RfxtrxSwitch( - entity_info[ATTR_NAME], rfxobject, datas, - signal_repetitions) - rfxtrx.RFX_DEVICES[device_id] = newswitch - switchs.append(newswitch) + rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) + newswitch = RfxtrxSwitch( + entity_info[ATTR_NAME], rfxobject, datas, + signal_repetitions) + rfxtrx.RFX_DEVICES[device_id] = newswitch + switchs.append(newswitch) add_devices_callback(switchs) From a54986159cdf5c9de1a09fad5469bef4e985fe3c Mon Sep 17 00:00:00 2001 From: Rowan Hine Date: Thu, 25 Feb 2016 17:05:00 +0000 Subject: [PATCH 160/186] Update Steam sensor to show currently played game --- homeassistant/components/sensor/steam_online.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index d0f2e8b8c22..e65ffb20533 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -49,6 +49,7 @@ class SteamSensor(Entity): def update(self): """Update device state.""" self._profile = self._steamod.user.profile(self._account) + self._game = self._profile.current_game[2] self._state = { 1: 'Online', 2: 'Busy', @@ -58,6 +59,13 @@ class SteamSensor(Entity): 6: 'Play', }.get(self._profile.status, 'Offline') + @property + def device_state_attributes(self): + """Returns the state attributes.""" + if self._game == None: + self._game = 'None' + return {'Game': self._game} + @property def entity_picture(self): """Avatar of the account.""" From 393df2da49c9c13a8f1b21c8ab01fa654d075059 Mon Sep 17 00:00:00 2001 From: Rowan Hine Date: Thu, 25 Feb 2016 17:46:51 +0000 Subject: [PATCH 161/186] Update steam_online.py --- homeassistant/components/sensor/steam_online.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index e65ffb20533..3ba7b2a0d92 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -49,7 +49,10 @@ class SteamSensor(Entity): def update(self): """Update device state.""" self._profile = self._steamod.user.profile(self._account) - self._game = self._profile.current_game[2] + if self._profile.current_game[2] is None: + self._game = 'None' + else: + self._game = self._profile.current_game[2] self._state = { 1: 'Online', 2: 'Busy', @@ -62,8 +65,6 @@ class SteamSensor(Entity): @property def device_state_attributes(self): """Returns the state attributes.""" - if self._game == None: - self._game = 'None' return {'Game': self._game} @property From 5a64ef2c98aa9cb40d8ffb5ee2b18b044bd0cc6e Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Tue, 23 Feb 2016 14:20:54 -0500 Subject: [PATCH 162/186] Moved Wink binary sensors to a binary sensor class --- .../components/binary_sensor/__init__.py | 29 ++++--- .../components/binary_sensor/wink.py | 81 +++++++++++++++++++ homeassistant/components/garage_door/wink.py | 2 +- homeassistant/components/light/wink.py | 2 +- homeassistant/components/lock/wink.py | 2 +- homeassistant/components/sensor/wink.py | 29 ++++++- homeassistant/components/switch/wink.py | 2 +- homeassistant/components/wink.py | 4 +- requirements_all.txt | 3 +- 9 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/binary_sensor/wink.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 313ae865816..2fddef9ca3a 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -10,25 +10,27 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import (bloomsky, mysensors, zwave) +from homeassistant.components import (bloomsky, mysensors, zwave, wink) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' SENSOR_CLASSES = [ - None, # Generic on/off - 'opening', # Door, window, etc - 'motion', # Motion sensor - 'gas', # CO, CO2, etc - 'smoke', # Smoke detector - 'moisture', # Specifically a wetness sensor - 'light', # Lightness threshold - 'power', # Power, over-current, etc - 'safety', # Generic on=unsafe, off=safe - 'heat', # On means hot (or too hot) - 'cold', # On means cold (or too cold) - 'moving', # On means moving, Off means stopped + None, # Generic on/off + 'opening', # Door, window, etc + 'motion', # Motion sensor + 'gas', # CO, CO2, etc + 'smoke', # Smoke detector + 'moisture', # Specifically a wetness sensor + 'light', # Lightness threshold + 'power', # Power, over-current, etc + 'safety', # Generic on=unsafe, off=safe + 'heat', # On means hot (or too hot) + 'cold', # On means cold (or too cold) + 'moving', # On means moving, Off means stopped + 'sound', # On means sound detected, Off means no sound + 'vibration', # On means vibration detected, Off means no vibration ] # Maps discovered services to their platforms @@ -36,6 +38,7 @@ DISCOVERY_PLATFORMS = { bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', zwave.DISCOVER_BINARY_SENSORS: 'zwave', + wink.DISCOVER_BINARY_SENSORS: 'wink' } diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py new file mode 100644 index 00000000000..8d858901849 --- /dev/null +++ b/homeassistant/components/binary_sensor/wink.py @@ -0,0 +1,81 @@ +""" +Support for Wink sensors. + +For more details about this platform, please refer to the documentation at +at https://home-assistant.io/components/sensor.wink/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['python-wink==0.6.2'] + +# These are the available sensors mapped to binary_sensor class +SENSOR_TYPES = { + "opened": "opening", + "brightness": "light", + "vibration": "vibration", + "loudness": "sound" +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Sets up the Wink platform.""" + import pywink + + if discovery_info is None: + token = config.get(CONF_ACCESS_TOKEN) + + if token is None: + logging.getLogger(__name__).error( + "Missing wink access_token. " + "Get one at https://winkbearertoken.appspot.com/") + return + + pywink.set_bearer_token(token) + + for sensor in pywink.get_sensors(): + if sensor.capability() in SENSOR_TYPES: + add_devices([WinkBinarySensorDevice(sensor)]) + + +class WinkBinarySensorDevice(BinarySensorDevice, Entity): + """Represents a Wink sensor.""" + + def __init__(self, wink): + self.wink = wink + self._unit_of_measurement = self.wink.UNIT + self.capability = self.wink.capability() + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + if self.capability == "loudness": + return self.wink.loudness_boolean() + elif self.capability == "vibration": + return self.wink.vibration_boolean() + elif self.capability == "brightness": + return self.wink.brightness_boolean() + else: + return self.wink.state() + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return SENSOR_TYPES.get(self.capability) + + @property + def unique_id(self): + """ Returns the id of this wink sensor """ + return "{}.{}".format(self.__class__, self.wink.device_id()) + + @property + def name(self): + """ Returns the name of the sensor if any. """ + return self.wink.name() + + def update(self): + """ Update state of the sensor. """ + self.wink.update_state() diff --git a/homeassistant/components/garage_door/wink.py b/homeassistant/components/garage_door/wink.py index ad2bc3631aa..b6ac8aa6cd8 100644 --- a/homeassistant/components/garage_door/wink.py +++ b/homeassistant/components/garage_door/wink.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 07f18b0d612..560d08119df 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 675b138d2c8..77037c4b205 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.lock import LockDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index ebdd01441c0..107bc25acf6 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -6,10 +6,13 @@ at https://home-assistant.io/components/sensor.wink/ """ import logging -from homeassistant.const import CONF_ACCESS_TOKEN, STATE_CLOSED, STATE_OPEN +from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED, + STATE_OPEN, TEMP_CELCIUS) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] + +SENSOR_TYPES = ['temperature', 'humidity'] def setup_platform(hass, config, add_devices, discovery_info=None): @@ -27,7 +30,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pywink.set_bearer_token(token) - add_devices(WinkSensorDevice(sensor) for sensor in pywink.get_sensors()) + for sensor in pywink.get_sensors(): + if sensor.capability() in SENSOR_TYPES: + add_devices([WinkSensorDevice(sensor)]) + add_devices(WinkEggMinder(eggtray) for eggtray in pywink.get_eggtrays()) @@ -36,11 +42,26 @@ class WinkSensorDevice(Entity): def __init__(self, wink): self.wink = wink + self.capability = self.wink.capability() + if self.wink.UNIT == "°": + self._unit_of_measurement = TEMP_CELCIUS + else: + self._unit_of_measurement = self.wink.UNIT @property def state(self): """Returns the state.""" - return STATE_OPEN if self.is_open else STATE_CLOSED + if self.capability == "humidity": + return self.wink.humidity_percentage() + elif self.capability == "temperature": + return self.wink.temperature_float() + else: + return STATE_OPEN if self.is_open else STATE_CLOSED + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement @property def unique_id(self): diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 35e1fabc9a0..143bc391951 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -11,7 +11,7 @@ import logging from homeassistant.components.wink import WinkToggleDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 5e1572a90d9..fadfbc9c83e 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -16,11 +16,12 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.loader import get_component DOMAIN = "wink" -REQUIREMENTS = ['python-wink==0.6.1'] +REQUIREMENTS = ['python-wink==0.6.2'] DISCOVER_LIGHTS = "wink.lights" DISCOVER_SWITCHES = "wink.switches" DISCOVER_SENSORS = "wink.sensors" +DISCOVER_BINARY_SENSORS = "wink.binary_sensors" DISCOVER_LOCKS = "wink.locks" DISCOVER_GARAGE_DOORS = "wink.garage_doors" @@ -41,6 +42,7 @@ def setup(hass, config): ('switch', lambda: pywink.get_switches or pywink.get_sirens or pywink.get_powerstrip_outlets, DISCOVER_SWITCHES), + ('binary_sensor', pywink.get_sensors, DISCOVER_BINARY_SENSORS), ('sensor', lambda: pywink.get_sensors or pywink.get_eggtrays, DISCOVER_SENSORS), ('lock', pywink.get_locks, DISCOVER_LOCKS), diff --git a/requirements_all.txt b/requirements_all.txt index 91ece24547b..0337da10a41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -201,12 +201,13 @@ python-telegram-bot==3.2.0 python-twitch==1.2.0 # homeassistant.components.wink +# homeassistant.components.binary_sensor.wink # homeassistant.components.garage_door.wink # homeassistant.components.light.wink # homeassistant.components.lock.wink # homeassistant.components.sensor.wink # homeassistant.components.switch.wink -python-wink==0.6.1 +python-wink==0.6.2 # homeassistant.components.keyboard pyuserinput==0.1.9 From 72144945b4eaeb3887eba00d1aa16b57f299acab Mon Sep 17 00:00:00 2001 From: pavoni Date: Thu, 25 Feb 2016 22:46:14 +0000 Subject: [PATCH 163/186] Move standby from state to attribute, add optimistic switch status. --- homeassistant/components/switch/wemo.py | 47 +++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 0e6a1014ca3..624b8688a1c 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -9,7 +9,8 @@ https://home-assistant.io/components/switch.wemo/ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY +from homeassistant.const import ( + STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN) from homeassistant.loader import get_component DEPENDENCIES = ['wemo'] @@ -18,10 +19,18 @@ _LOGGER = logging.getLogger(__name__) ATTR_SENSOR_STATE = "sensor_state" ATTR_SWITCH_MODE = "switch_mode" +ATTR_CURRENT_STATE_DETAIL = 'state_detail' MAKER_SWITCH_MOMENTARY = "momentary" MAKER_SWITCH_TOGGLE = "toggle" +MAKER_SWITCH_MOMENTARY = "momentary" +MAKER_SWITCH_TOGGLE = "toggle" + +WEMO_ON = 1 +WEMO_OFF = 0 +WEMO_STANDBY = 8 + # pylint: disable=unused-argument, too-many-function-args def setup_platform(hass, config, add_devices_callback, discovery_info=None): @@ -43,6 +52,7 @@ class WemoSwitch(SwitchDevice): self.wemo = device self.insight_params = None self.maker_params = None + self._state = None wemo = get_component('wemo') wemo.SUBSCRIPTION_REGISTRY.register(self.wemo) @@ -88,17 +98,10 @@ class WemoSwitch(SwitchDevice): else: attr[ATTR_SWITCH_MODE] = MAKER_SWITCH_TOGGLE - return attr + if self.insight_params: + attr[ATTR_CURRENT_STATE_DETAIL] = self.detail_state - @property - def state(self): - """Returns the state.""" - is_on = self.is_on - if not is_on: - return STATE_OFF - elif self.is_standby: - return STATE_STANDBY - return STATE_ON + return attr @property def current_power_mwh(self): @@ -113,21 +116,25 @@ class WemoSwitch(SwitchDevice): return self.insight_params['todaymw'] @property - def is_standby(self): + def detail_state(self): """Is the device on - or in standby.""" if self.insight_params: standby_state = self.insight_params['state'] # Standby is actually '8' but seems more defensive # to check for the On and Off states - if standby_state == '1' or standby_state == '0': - return False + if standby_state == WEMO_ON: + return STATE_OFF + elif standby_state == WEMO_OFF: + return STATE_OFF + elif standby_state == WEMO_STANDBY: + return STATE_STANDBY else: - return True + return STATE_UNKNOWN @property def is_on(self): - """True if switch is on.""" - return self.wemo.get_state() + """True if switch is on. Standby is on!""" + return self._state @property def available(self): @@ -143,16 +150,20 @@ class WemoSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turns the switch on.""" + self._state = WEMO_ON + self.update_ha_state() self.wemo.on() def turn_off(self): """Turns the switch off.""" + self._state = WEMO_OFF + self.update_ha_state() self.wemo.off() def update(self): """Update WeMo state.""" try: - self.wemo.get_state(True) + self._state = self.wemo.get_state(True) if self.wemo.model_name == 'Insight': self.insight_params = self.wemo.insight_params self.insight_params['standby_state'] = ( From bbf8897a5139555cfbd140b98cd71fd6dd7af65a Mon Sep 17 00:00:00 2001 From: pavoni Date: Thu, 25 Feb 2016 23:02:36 +0000 Subject: [PATCH 164/186] Add LightSwitch to discovery, default unnown devices to switches for compatability. --- homeassistant/components/wemo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index fb1cc4e77d3..9dc144266a8 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -25,6 +25,7 @@ WEMO_MODEL_DISPATCH = { 'Maker': DISCOVER_SWITCHES, 'Motion': DISCOVER_MOTION, 'Socket': DISCOVER_SWITCHES, + 'LightSwitch': DISCOVER_SWITCHES } WEMO_SERVICE_DISPATCH = { DISCOVER_LIGHTS: 'light', @@ -64,12 +65,11 @@ def setup(hass, config): return KNOWN_DEVICES.append(mac) - service = WEMO_MODEL_DISPATCH.get(model_name) + service = WEMO_MODEL_DISPATCH.get(model_name) or DISCOVER_SWITCHES component = WEMO_SERVICE_DISPATCH.get(service) - if service is not None: - discovery.discover(hass, service, discovery_info, - component, config) + discovery.discover(hass, service, discovery_info, + component, config) discovery.listen(hass, discovery.SERVICE_WEMO, discovery_dispatch) From 86973226b147c8567166938035d10efa15000cc6 Mon Sep 17 00:00:00 2001 From: pavoni Date: Thu, 25 Feb 2016 23:13:20 +0000 Subject: [PATCH 165/186] Remove ghost comment. --- homeassistant/components/switch/wemo.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 624b8688a1c..5eac5a14ed5 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -120,8 +120,6 @@ class WemoSwitch(SwitchDevice): """Is the device on - or in standby.""" if self.insight_params: standby_state = self.insight_params['state'] - # Standby is actually '8' but seems more defensive - # to check for the On and Off states if standby_state == WEMO_ON: return STATE_OFF elif standby_state == WEMO_OFF: From 37e5b98919d18505439755f0a4c85c10a15b0cf7 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 25 Feb 2016 15:48:46 -0800 Subject: [PATCH 166/186] Fix mFi error handling in setup_platform The exception we were catching incorrectly referenced the client variable in local scope instead of the module. Also, if we fail to connect we can get a requests exception, so catch and handle that as well. --- homeassistant/components/sensor/mfi.py | 6 ++++-- homeassistant/components/switch/mfi.py | 6 ++++-- tests/components/sensor/test_mfi.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index 53b1726a210..37f1e73c607 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -6,6 +6,8 @@ https://home-assistant.io/components/sensor.mfi/ """ import logging +import requests + from homeassistant.components.sensor import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS from homeassistant.helpers import validate_config @@ -48,11 +50,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - from mficlient.client import MFiClient + from mficlient.client import FailedToLogin, MFiClient try: client = MFiClient(host, username, password, port=port) - except client.FailedToLogin as ex: + except (FailedToLogin, requests.exceptions.ConnectionError) as ex: _LOGGER.error('Unable to connect to mFi: %s', str(ex)) return False diff --git a/homeassistant/components/switch/mfi.py b/homeassistant/components/switch/mfi.py index 42abe75af60..77346d224bd 100644 --- a/homeassistant/components/switch/mfi.py +++ b/homeassistant/components/switch/mfi.py @@ -8,6 +8,8 @@ https://home-assistant.io/components/switch.mfi/ """ import logging +import requests + from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config @@ -41,11 +43,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config.get('username') password = config.get('password') - from mficlient.client import MFiClient + from mficlient.client import FailedToLogin, MFiClient try: client = MFiClient(host, username, password, port=port) - except client.FailedToLogin as ex: + except (FailedToLogin, requests.exceptions.ConnectionError) as ex: _LOGGER.error('Unable to connect to mFi: %s', str(ex)) return False diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index 58506bdff1b..987466853b6 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -7,6 +7,8 @@ Tests mFi sensor. import unittest import unittest.mock as mock +import requests + import homeassistant.components.sensor as sensor import homeassistant.components.sensor.mfi as mfi from homeassistant.const import TEMP_CELCIUS @@ -54,6 +56,15 @@ class TestMfiSensorSetup(unittest.TestCase): dict(self.GOOD_CONFIG), None)) + @mock.patch('mficlient.client') + def test_setup_failed_connect(self, mock_client): + mock_client.FailedToLogin = Exception() + mock_client.MFiClient.side_effect = requests.exceptions.ConnectionError + self.assertFalse( + self.PLATFORM.setup_platform(self.hass, + dict(self.GOOD_CONFIG), + None)) + @mock.patch('mficlient.client.MFiClient') def test_setup_minimum(self, mock_client): config = dict(self.GOOD_CONFIG) From ddbceebd656e24d87599f6f32c203f021be948b0 Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 26 Feb 2016 00:16:33 +0000 Subject: [PATCH 167/186] Cast standby_state to int before testing. --- homeassistant/components/switch/wemo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 5eac5a14ed5..5a38e7d20ce 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -119,7 +119,7 @@ class WemoSwitch(SwitchDevice): def detail_state(self): """Is the device on - or in standby.""" if self.insight_params: - standby_state = self.insight_params['state'] + standby_state = int(self.insight_params['state']) if standby_state == WEMO_ON: return STATE_OFF elif standby_state == WEMO_OFF: From b2d32114c819a7600c6c90924fa5ccc9c51b281c Mon Sep 17 00:00:00 2001 From: pavoni Date: Fri, 26 Feb 2016 08:28:17 +0000 Subject: [PATCH 168/186] Fix typo. --- homeassistant/components/switch/wemo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 5a38e7d20ce..13611f816ca 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -121,7 +121,7 @@ class WemoSwitch(SwitchDevice): if self.insight_params: standby_state = int(self.insight_params['state']) if standby_state == WEMO_ON: - return STATE_OFF + return STATE_ON elif standby_state == WEMO_OFF: return STATE_OFF elif standby_state == WEMO_STANDBY: From e525b6e0a5f06ba3aab4b75fddb1410aa282cf7c Mon Sep 17 00:00:00 2001 From: "St. John Johnson" Date: Fri, 26 Feb 2016 10:33:32 -0800 Subject: [PATCH 169/186] Add brightness scale support for MQTT Lights. Converts 0..255 values that HA expects into a device 0..SCALE value Example: HA considers "hall light" at 25 brightness or 10% of 255 Device considers "hall light" at 100 brightness or 10% of 1000 This allows our existing MQTT devices to not change their data format to be used in HA --- homeassistant/components/light/mqtt.py | 19 ++++++-- tests/components/light/test_mqtt.py | 63 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index f6e2438b5cd..a1ad6ea7e52 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -19,6 +19,7 @@ DEFAULT_QOS = 0 DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_OPTIMISTIC = False +DEFAULT_BRIGHTNESS_SCALE = 255 DEPENDENCIES = ['mqtt'] @@ -44,14 +45,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): 'on': config.get('payload_on', DEFAULT_PAYLOAD_ON), 'off': config.get('payload_off', DEFAULT_PAYLOAD_OFF) }, - config.get('optimistic', DEFAULT_OPTIMISTIC))]) + config.get('optimistic', DEFAULT_OPTIMISTIC), + config.get('brightness_scale', DEFAULT_BRIGHTNESS_SCALE) + )]) class MqttLight(Light): """Provides a MQTT light.""" # pylint: disable=too-many-arguments,too-many-instance-attributes - def __init__(self, hass, name, topic, templates, qos, payload, optimistic): + def __init__(self, hass, name, topic, templates, qos, payload, optimistic, + brightness_scale): self._hass = hass self._name = name @@ -62,6 +66,7 @@ class MqttLight(Light): self._optimistic_rgb = optimistic or topic["rgb_state_topic"] is None self._optimistic_brightness = (optimistic or topic["brightness_state_topic"] is None) + self._brightness_scale = brightness_scale self._state = False templates = {key: ((lambda value: value) if tpl is None else @@ -84,7 +89,9 @@ class MqttLight(Light): def brightness_received(topic, payload, qos): """A new MQTT message for the brightness has been received.""" - self._brightness = int(templates['brightness'](payload)) + device_value = float(templates['brightness'](payload)) + percent_bright = device_value / self._brightness_scale + self._brightness = int(percent_bright * 255) self.update_ha_state() if self._topic["brightness_state_topic"] is not None: @@ -143,6 +150,7 @@ class MqttLight(Light): if ATTR_RGB_COLOR in kwargs and \ self._topic["rgb_command_topic"] is not None: + mqtt.publish(self._hass, self._topic["rgb_command_topic"], "{},{},{}".format(*kwargs[ATTR_RGB_COLOR]), self._qos) @@ -152,9 +160,10 @@ class MqttLight(Light): if ATTR_BRIGHTNESS in kwargs and \ self._topic["brightness_command_topic"] is not None: - + percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 + device_brightness = int(percent_bright * self._brightness_scale) mqtt.publish(self._hass, self._topic["brightness_command_topic"], - kwargs[ATTR_BRIGHTNESS], self._qos) + device_brightness, self._qos) if self._optimistic_brightness: self._brightness = kwargs[ATTR_BRIGHTNESS] diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 139a0be8f20..0c7703eaa78 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -42,6 +42,24 @@ light: qos: 0 payload_on: "on" payload_off: "off" + +config for RGB Version with brightness and scale: + +light: + platform: mqtt + name: "Office Light RGB" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + brightness_state_topic: "office/rgb1/brightness/status" + brightness_command_topic: "office/rgb1/brightness/set" + brightness_scale: 99 + rgb_state_topic: "office/rgb1/rgb/status" + rgb_command_topic: "office/rgb1/rgb/set" + rgb_scale: 99 + qos: 0 + payload_on: "on" + payload_off: "off" + """ import unittest @@ -153,6 +171,51 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual([125, 125, 125], light_state.attributes.get('rgb_color')) + def test_controlling_scale(self): + self.assertTrue(light.setup(self.hass, { + 'light': { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_scale/status', + 'command_topic': 'test_scale/set', + 'brightness_state_topic': 'test_scale/brightness/status', + 'brightness_command_topic': 'test_scale/brightness/set', + 'brightness_scale': '99', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + self.assertIsNone(state.attributes.get('brightness')) + self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'test_scale/status', 'on') + self.hass.pool.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + self.assertEqual(255, state.attributes.get('brightness')) + + fire_mqtt_message(self.hass, 'test_scale/status', 'off') + self.hass.pool.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + + fire_mqtt_message(self.hass, 'test_scale/status', 'on') + self.hass.pool.block_till_done() + + fire_mqtt_message(self.hass, 'test_scale/brightness/status', '99') + self.hass.pool.block_till_done() + + light_state = self.hass.states.get('light.test') + self.hass.pool.block_till_done() + self.assertEqual(255, + light_state.attributes['brightness']) + def test_controlling_state_via_topic_with_templates(self): self.assertTrue(light.setup(self.hass, { 'light': { From 2609852d6e82eff04df3a9c083377c0e09545e31 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 26 Feb 2016 23:52:54 +0100 Subject: [PATCH 170/186] Modify docstrings to match PEP257 --- homeassistant/components/browser.py | 11 ++++----- homeassistant/components/graphite.py | 25 ++++++++------------ homeassistant/components/influxdb.py | 14 ++++------- homeassistant/components/mqtt_eventstream.py | 25 ++++++++------------ homeassistant/components/splunk.py | 12 ++++------ homeassistant/components/statsd.py | 9 +++---- 6 files changed, 36 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/browser.py b/homeassistant/components/browser.py index 88548e2a1b3..d171e4b5901 100644 --- a/homeassistant/components/browser.py +++ b/homeassistant/components/browser.py @@ -1,21 +1,18 @@ """ -homeassistant.components.browser -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to launch a webbrowser on the host machine. +Provides functionality to launch a web browser on the host machine. For more details about this component, please refer to the documentation at https://home-assistant.io/components/browser/ """ DOMAIN = "browser" - SERVICE_BROWSE_URL = "browse_url" def setup(hass, config): - """ Listen for browse_url events and open - the url in the default webbrowser. """ - + """ + Listen for browse_url events and open the url in the default web browser. + """ import webbrowser hass.services.register(DOMAIN, SERVICE_BROWSE_URL, diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 49ac6690edf..4e726f06bb5 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -1,8 +1,6 @@ """ -homeassistant.components.graphite -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component that records all events and state changes and feeds the data to -a graphite installation. +a Graphite installation. For more details about this component, please refer to the documentation at https://home-assistant.io/components/graphite/ @@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Setup graphite feeder. """ + """Setup the Graphite feeder.""" graphite_config = config.get('graphite', {}) host = graphite_config.get('host', 'localhost') prefix = graphite_config.get('prefix', 'ha') @@ -37,14 +35,13 @@ def setup(hass, config): class GraphiteFeeder(threading.Thread): - """ Feeds data to graphite. """ + """Feeds data to Graphite.""" def __init__(self, hass, host, port, prefix): super(GraphiteFeeder, self).__init__(daemon=True) self._hass = hass self._host = host self._port = port - # rstrip any trailing dots in case they think they - # need it + # rstrip any trailing dots in case they think they need it self._prefix = prefix.rstrip('.') self._queue = queue.Queue() self._quit_object = object() @@ -59,23 +56,18 @@ class GraphiteFeeder(threading.Thread): self._host, self._port) def start_listen(self, event): - """ Start event-processing thread. """ + """Start event-processing thread.""" _LOGGER.debug('Event processing thread started') self._we_started = True self.start() def shutdown(self, event): - """ Tell the thread that we are done. - - This does not block because there is nothing to - clean up (and no penalty for killing in-process - connections to graphite. - """ + """Signal shutdown of processing event.""" _LOGGER.debug('Event processing signaled exit') self._queue.put(self._quit_object) def event_listener(self, event): - """ Queue an event for processing. """ + """Queue an event for processing.""" if self.is_alive() or not self._we_started: _LOGGER.debug('Received event') self._queue.put(event) @@ -84,6 +76,7 @@ class GraphiteFeeder(threading.Thread): 'queuing event!') def _send_to_graphite(self, data): + """Send data to Graphite.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) sock.connect((self._host, self._port)) @@ -92,6 +85,7 @@ class GraphiteFeeder(threading.Thread): sock.close() def _report_attributes(self, entity_id, new_state): + """Report the attributes.""" now = time.time() things = dict(new_state.attributes) try: @@ -114,6 +108,7 @@ class GraphiteFeeder(threading.Thread): _LOGGER.exception('Failed to send data to graphite') def run(self): + """Run the process to export the data.""" while True: event = self._queue.get() if event == self._quit_object: diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index 3e19fd8692a..749dd4bd097 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -1,7 +1,5 @@ """ -homeassistant.components.influxdb -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -InfluxDB component which allows you to send data to an Influx database. +A component which allows you to send data to an Influx database. For more details about this component, please refer to the documentation at https://home-assistant.io/components/influxdb/ @@ -36,8 +34,7 @@ CONF_VERIFY_SSL = 'verify_ssl' def setup(hass, config): - """ Setup the InfluxDB component. """ - + """Setup the InfluxDB component.""" from influxdb import InfluxDBClient, exceptions if not validate_config(config, {DOMAIN: ['host', @@ -63,13 +60,12 @@ def setup(hass, config): influx.query("select * from /.*/ LIMIT 1;") except exceptions.InfluxDBClientError as exc: _LOGGER.error("Database host is not accessible due to '%s', please " - "check your entries in the configuration file and that" - " the database exists and is READ/WRITE.", exc) + "check your entries in the configuration file and that " + "the database exists and is READ/WRITE.", exc) return False def influx_event_listener(event): - """ Listen for new messages on the bus and sends them to Influx. """ - + """Listen for new messages on the bus and sends them to Influx.""" state = event.data.get('new_state') if state is None or state.state in (STATE_UNKNOWN, ''): return diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py index 0574c07ebf9..fe4c8c546ca 100644 --- a/homeassistant/components/mqtt_eventstream.py +++ b/homeassistant/components/mqtt_eventstream.py @@ -1,10 +1,8 @@ """ -homeassistant.components.mqtt_eventstream -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Connect two Home Assistant instances via MQTT.. +Connect two Home Assistant instances via MQTT. For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mqtt_eventstream.html +https://home-assistant.io/components/mqtt_eventstream/ """ import json @@ -17,21 +15,18 @@ from homeassistant.const import ( from homeassistant.core import EventOrigin, State from homeassistant.remote import JSONEncoder -# The domain of your component. Should be equal to the name of your component DOMAIN = "mqtt_eventstream" - -# List of component names (string) your component depends upon DEPENDENCIES = ['mqtt'] def setup(hass, config): - """ Setup th MQTT eventstream component. """ + """Setup th MQTT eventstream component.""" mqtt = loader.get_component('mqtt') pub_topic = config[DOMAIN].get('publish_topic', None) sub_topic = config[DOMAIN].get('subscribe_topic', None) def _event_publisher(event): - """ Handle events by publishing them on the MQTT queue. """ + """Handle events by publishing them on the MQTT queue.""" if event.origin != EventOrigin.local: return if event.event_type == EVENT_TIME_CHANGED: @@ -61,15 +56,15 @@ def setup(hass, config): msg = json.dumps(event_info, cls=JSONEncoder) mqtt.publish(hass, pub_topic, msg) - # Only listen for local events if you are going to publish them + # Only listen for local events if you are going to publish them. if pub_topic: hass.bus.listen(MATCH_ALL, _event_publisher) - # Process events from a remote server that are received on a queue + # Process events from a remote server that are received on a queue. def _event_receiver(topic, payload, qos): """ - Receive events published by the other HA instance and fire - them on this hass instance. + Receive events published by the other HA instance and fire them on + this hass instance. """ event = json.loads(payload) event_type = event.get('event_type') @@ -92,10 +87,10 @@ def setup(hass, config): origin=EventOrigin.remote ) - # Only subscribe if you specified a topic + # Only subscribe if you specified a topic. if sub_topic: mqtt.subscribe(hass, sub_topic, _event_receiver) hass.states.set('{domain}.initialized'.format(domain=DOMAIN), True) - # return boolean to indicate that initialization was successful + return True diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk.py index 6db577865eb..584031dfd80 100644 --- a/homeassistant/components/splunk.py +++ b/homeassistant/components/splunk.py @@ -1,8 +1,6 @@ """ -homeassistant.components.splunk -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Splunk component which allows you to send data to an Splunk instance -utilizing the HTTP Event Collector. +A component which allows you to send data to an Splunk instance utilizing the +HTTP Event Collector. For more details about this component, please refer to the documentation at https://home-assistant.io/components/splunk/ @@ -33,8 +31,7 @@ CONF_SSL = 'SSL' def setup(hass, config): - """ Setup the Splunk component. """ - + """Setup the Splunk component.""" if not validate_config(config, {DOMAIN: ['token']}, _LOGGER): _LOGGER.error("You must include the token for your HTTP " "Event Collector input in Splunk.") @@ -55,8 +52,7 @@ def setup(hass, config): headers = {'Authorization': 'Splunk ' + token} def splunk_event_listener(event): - """ Listen for new messages on the bus and sends them to Splunk. """ - + """Listen for new messages on the bus and sends them to Splunk.""" state = event.data.get('new_state') if state is None: diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd.py index 6dbf1ae8bda..6de3f6805b7 100644 --- a/homeassistant/components/statsd.py +++ b/homeassistant/components/statsd.py @@ -1,7 +1,5 @@ """ -homeassistant.components.statsd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -StatsD component which allows you to send data to many backends. +A component which allows you to send data to StatsD. For more details about this component, please refer to the documentation at https://home-assistant.io/components/statsd/ @@ -31,8 +29,7 @@ CONF_RATE = 'rate' def setup(hass, config): - """ Setup the StatsD component. """ - + """Setup the StatsD component.""" from statsd.compat import NUM_TYPES import statsd @@ -53,7 +50,7 @@ def setup(hass, config): meter = statsd.Gauge(prefix, statsd_connection) def statsd_event_listener(event): - """ Listen for new messages on the bus and sends them to StatsD. """ + """Listen for new messages on the bus and sends them to StatsD.""" state = event.data.get('new_state') From cbd69583bde4d058e437d422a15d32680e4f7bf6 Mon Sep 17 00:00:00 2001 From: Karen Goode Date: Fri, 26 Feb 2016 15:23:44 -0800 Subject: [PATCH 171/186] Update demo.py Update to demo site to include input_selects, notify, inline camera and two views. --- homeassistant/components/demo.py | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index f025cfd008a..a305ecea0dd 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -62,15 +62,18 @@ def setup(hass, config): lights = sorted(hass.states.entity_ids('light')) switches = sorted(hass.states.entity_ids('switch')) media_players = sorted(hass.states.entity_ids('media_player')) - group.Group(hass, 'living room', [ - lights[2], lights[1], switches[0], media_players[1], - 'scene.romantic_lights']) - group.Group(hass, 'bedroom', [lights[0], switches[1], - media_players[0]]) - group.Group(hass, 'Rooms', [ - 'group.living_room', 'group.bedroom', - 'scene.romantic_lights', 'rollershutter.kitchen_window', - 'rollershutter.living_room_window', + group.Group(hass, 'living room', [lights[1], switches[0], 'input_select.living_room_preset','rollershutter.living_room_window', + media_players[1],'scene.romantic_lights']) + group.Group(hass, 'bedroom', [lights[0], switches[1], media_players[0]]) + group.Group(hass, 'kitchen', [lights[2], 'rollershutter.kitchen_window','lock.kitchen_door']) + group.Group(hass, 'doors', ['lock.front_door','lock.kitchen_door','garage_door.right_garage_door', 'garage_door.left_garage_door']) + group.Group(hass, 'automations', ['input_select.who_cooks','input_boolean.notify', ]) + group.Group(hass, 'people', ['device_tracker.demo_anne_therese','device_tracker.demo_home_boy', 'device_tracker.demo_paulus',]) + group.Group(hass, 'thermostats', ['thermostat.nest', 'thermostat.thermostat']) + group.Group(hass, 'downstairs', ['group.living_room', 'group.kitchen', 'scene.romantic_lights', 'rollershutter.kitchen_window', + 'rollershutter.living_room_window', 'group.doors','thermostat.nest', + ], view=True) + group.Group(hass, 'Upstairs', ['thermostat.thermostat', 'group.bedroom', ], view=True) # Setup scripts @@ -112,6 +115,23 @@ def setup(hass, config): }}, ]}) + #Set up input select + bootstrap.setup_component( + hass, 'input_select', + {'input_select': {'living_room_preset': {'options': ['Visitors', + 'Visitors with kids', + 'Home Alone']}, + 'who_cooks': {'icon': 'mdi:panda', + 'initial': 'Anne Therese', + 'name': 'Who cooks today', + 'options': ['Paulus', 'Anne Therese']}}}) + #Set up input boolean + bootstrap.setup_component( + hass, 'input_boolean', + {'input_boolean': {'notify': {'icon': 'mdi:car', + 'initial': False, + 'name': 'Notify Anne Therese is home'}}}) + # Setup configurator configurator_ids = [] From 124a9b7a8186c70ec175e17d6cab0df65e20c887 Mon Sep 17 00:00:00 2001 From: Alexander Fortin Date: Fri, 26 Feb 2016 20:05:34 +0100 Subject: [PATCH 172/186] ADD only_if_coordinator decorator to sonos Currently we interact with players regardless of thir coordinator role, hence soco.exceptions.SoCoSlaveException are thrown. The use of the decorator for each interactive method should address this --- .../components/media_player/sonos.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index c0f9133a3d0..70b7d911cca 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -66,6 +66,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return True +def only_if_coordinator(func): + """ + If used as decorator, avoid calling the decorated method if + player is not a coordinator. + If not, a grouped speaker (not in coordinator role) + will throw soco.exceptions.SoCoSlaveException + """ + + def wrapper(*args, **kwargs): + """ Decorator wrapper """ + if args[0].is_coordinator: + return func(*args, **kwargs) + else: + _LOGGER.debug('Ignore command "%s" for Sonos device "%s" ' + '(not coordinator)', + func.__name__, args[0].name) + + return wrapper + + # pylint: disable=too-many-instance-attributes, too-many-public-methods # pylint: disable=abstract-method class SonosDevice(MediaPlayerDevice): @@ -107,6 +127,11 @@ class SonosDevice(MediaPlayerDevice): return STATE_IDLE return STATE_UNKNOWN + @property + def is_coordinator(self): + """ Returns true if player is a coordinator """ + return self._player.is_coordinator + def update(self): """ Retrieve latest state. """ self._name = self._player.get_speaker_info()['zone_name'].replace( @@ -170,46 +195,57 @@ class SonosDevice(MediaPlayerDevice): """ Flags of media commands that are supported. """ return SUPPORT_SONOS + @only_if_coordinator def turn_off(self): """ Turn off media player. """ self._player.pause() + @only_if_coordinator def volume_up(self): """ Volume up media player. """ self._player.volume += 1 + @only_if_coordinator def volume_down(self): """ Volume down media player. """ self._player.volume -= 1 + @only_if_coordinator def set_volume_level(self, volume): """ Set volume level, range 0..1. """ self._player.volume = str(int(volume * 100)) + @only_if_coordinator def mute_volume(self, mute): """ Mute (true) or unmute (false) media player. """ self._player.mute = mute + @only_if_coordinator def media_play(self): """ Send paly command. """ self._player.play() + @only_if_coordinator def media_pause(self): """ Send pause command. """ self._player.pause() + @only_if_coordinator def media_next_track(self): """ Send next track command. """ self._player.next() + @only_if_coordinator def media_previous_track(self): """ Send next track command. """ self._player.previous() + @only_if_coordinator def media_seek(self, position): """ Send seek command. """ self._player.seek(str(datetime.timedelta(seconds=int(position)))) + @only_if_coordinator def turn_on(self): """ Turn the media player on. """ self._player.play() From 712ba65d26855e981552e6c5a664f8063ddb9ab4 Mon Sep 17 00:00:00 2001 From: Karen Goode Date: Fri, 26 Feb 2016 18:12:03 -0800 Subject: [PATCH 173/186] Update demo.py --- homeassistant/components/demo.py | 46 ++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index a305ecea0dd..9d4bde02866 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -62,18 +62,30 @@ def setup(hass, config): lights = sorted(hass.states.entity_ids('light')) switches = sorted(hass.states.entity_ids('switch')) media_players = sorted(hass.states.entity_ids('media_player')) - group.Group(hass, 'living room', [lights[1], switches[0], 'input_select.living_room_preset','rollershutter.living_room_window', - media_players[1],'scene.romantic_lights']) + group.Group(hass, 'living room', [ + lights[1], switches[0], 'input_select.living_room_preset', + 'rollershutter.living_room_window', media_players[1], + 'scene.romantic_lights']) group.Group(hass, 'bedroom', [lights[0], switches[1], media_players[0]]) - group.Group(hass, 'kitchen', [lights[2], 'rollershutter.kitchen_window','lock.kitchen_door']) - group.Group(hass, 'doors', ['lock.front_door','lock.kitchen_door','garage_door.right_garage_door', 'garage_door.left_garage_door']) - group.Group(hass, 'automations', ['input_select.who_cooks','input_boolean.notify', ]) - group.Group(hass, 'people', ['device_tracker.demo_anne_therese','device_tracker.demo_home_boy', 'device_tracker.demo_paulus',]) - group.Group(hass, 'thermostats', ['thermostat.nest', 'thermostat.thermostat']) - group.Group(hass, 'downstairs', ['group.living_room', 'group.kitchen', 'scene.romantic_lights', 'rollershutter.kitchen_window', - 'rollershutter.living_room_window', 'group.doors','thermostat.nest', + group.Group(hass, 'kitchen', [ + lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door']) + group.Group(hass, 'doors', [ + 'lock.front_door', 'lock.kitchen_door', + 'garage_door.right_garage_door', 'garage_door.left_garage_door']) + group.Group(hass, 'automations', [ + 'input_select.who_cooks', 'input_boolean.notify', ]) + group.Group(hass, 'people', [ + 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy', + 'device_tracker.demo_paulus']) + group.Group(hass, 'thermostats', [ + 'thermostat.nest', 'thermostat.thermostat']) + group.Group(hass, 'downstairs', [ + 'group.living_room', 'group.kitchen', + 'scene.romantic_lights', 'rollershutter.kitchen_window', + 'rollershutter.living_room_window', 'group.doors', 'thermostat.nest', ], view=True) - group.Group(hass, 'Upstairs', ['thermostat.thermostat', 'group.bedroom', + group.Group(hass, 'Upstairs', [ + 'thermostat.thermostat', 'group.bedroom', ], view=True) # Setup scripts @@ -118,10 +130,11 @@ def setup(hass, config): #Set up input select bootstrap.setup_component( hass, 'input_select', - {'input_select': {'living_room_preset': {'options': ['Visitors', - 'Visitors with kids', - 'Home Alone']}, - 'who_cooks': {'icon': 'mdi:panda', + {'input_select': + {'living_room_preset': {'options': ['Visitors', + 'Visitors with kids', + 'Home Alone']}, + 'who_cooks': {'icon': 'mdi:panda', 'initial': 'Anne Therese', 'name': 'Who cooks today', 'options': ['Paulus', 'Anne Therese']}}}) @@ -129,9 +142,8 @@ def setup(hass, config): bootstrap.setup_component( hass, 'input_boolean', {'input_boolean': {'notify': {'icon': 'mdi:car', - 'initial': False, - 'name': 'Notify Anne Therese is home'}}}) - + 'initial': False, + 'name': 'Notify Anne Therese is home'}}}) # Setup configurator configurator_ids = [] From 2a01b09d31dc6c828385b9f8df57c743214115d6 Mon Sep 17 00:00:00 2001 From: Karen Goode Date: Fri, 26 Feb 2016 18:25:39 -0800 Subject: [PATCH 174/186] Update demo.py --- homeassistant/components/demo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 9d4bde02866..331201a6e0f 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -135,9 +135,9 @@ def setup(hass, config): 'Visitors with kids', 'Home Alone']}, 'who_cooks': {'icon': 'mdi:panda', - 'initial': 'Anne Therese', - 'name': 'Who cooks today', - 'options': ['Paulus', 'Anne Therese']}}}) + 'initial': 'Anne Therese', + 'name': 'Who cooks today', + 'options': ['Paulus', 'Anne Therese']}}}) #Set up input boolean bootstrap.setup_component( hass, 'input_boolean', From 02faefdab3b2172a64e7adf20a025d5135944c95 Mon Sep 17 00:00:00 2001 From: Karen Goode Date: Fri, 26 Feb 2016 18:43:50 -0800 Subject: [PATCH 175/186] Update demo.py --- homeassistant/components/demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 331201a6e0f..3a70be00b38 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -127,7 +127,7 @@ def setup(hass, config): }}, ]}) - #Set up input select + # Set up input select bootstrap.setup_component( hass, 'input_select', {'input_select': @@ -138,7 +138,7 @@ def setup(hass, config): 'initial': 'Anne Therese', 'name': 'Who cooks today', 'options': ['Paulus', 'Anne Therese']}}}) - #Set up input boolean + # Set up input boolean bootstrap.setup_component( hass, 'input_boolean', {'input_boolean': {'notify': {'icon': 'mdi:car', From f99a536b98c415edc98eb1bc572ad5a0607c0313 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 26 Feb 2016 22:38:15 +0100 Subject: [PATCH 176/186] Add initial templates --- .github/ISSUE_TEMPLATE.md | 14 ++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..d5af5533dbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,14 @@ +Please add a meaningful title and a short description of your issue. It's hard to help you if you only add a trackback with no comments or a one-liner. + +Add the following details to your report. Especially if you are unsure about that to include. + +- [ ] Your Home Assistant release, `hass --version` +- [ ] Your operating system +- [ ] The component/platform involved +- [ ] The config entry for the component/platform from your `configuration.yml` file +- [ ] Your traceback + +It helps if you use the formatting options provided by GitHub ;-) + +Anyway, make sure that you have **searched** the closed issues first. Thanks! + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..ad8566ecba2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +This Pull Request includes a + +- [ ] Bug fix +- [ ] New component +- [ ] New platform + +The following changes were made: + +- +- +- + +Example entry for the `configuration.yaml` file (if it's a PR for a component/platform): + +```yaml + +``` + +If this PR is related to an existing ticket, please include a link to it as well. Thanks! From f8f0ed860c70f9f1f889e62140e5096cd808eba8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 27 Feb 2016 12:52:25 +0100 Subject: [PATCH 177/186] Sync with template content --- CONTRIBUTING.md | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f63e5ffca34..722fcd1992a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ The process is straight-forward. - Fork the Home Assistant [git repository](https://github.com/balloob/home-assistant). - Write the code for your device, notification service, sensor, or IoT thing. - - Ensure tests work + - Ensure tests work. - Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant. Still interested? Then you should read the next sections and get more details. @@ -17,12 +17,13 @@ For help on building your component, please see the [developer documentation](ht After you finish adding support for your device: + - Check that all dependencies are included via the `REQUIREMENTS` variable in your platform/component and only imported inside functions that use them. - Add any new dependencies to `requirements_all.txt` if needed. Use `script/gen_requirements_all.py`. - - Update the `.coveragerc` file to exclude your platform if there are no tests available. + - Update the `.coveragerc` file to exclude your platform if there are no tests available or your new code uses a 3rd party library for communication with the device/service/sensor. - Provide some documentation for [home-assistant.io](https://home-assistant.io/). It's OK to just add a docstring with configuration details (sample entry for `configuration.yaml` file and alike) to the file header as a start. Visit the [website documentation](https://home-assistant.io/developers/website/) for further information on contributing to [home-assistant.io](https://github.com/balloob/home-assistant.io). - - Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `./script/lint`. + - Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `tox` or `script/lint`. - Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant. - - Check for comments and suggestions on your Pull Request and keep an eye on the [Travis output](https://travis-ci.org/balloob/home-assistant/). + - Check for comments and suggestions on your Pull Request and keep an eye on the [CI output](https://travis-ci.org/balloob/home-assistant/). If you add a platform for an existing component, there is usually no need for updating the frontend. Only if you've added a new component that should show up in the frontend, there are more steps needed: @@ -75,20 +76,12 @@ To test your code before submission, used the `tox` tool. > tox ``` -This will run unit tests against python 3.4 and 3.5 (if both are -available locally), as well as run a set of tests which validate -`pep8` and `pylint` style of the code. +This will run unit tests against python 3.4 and 3.5 (if both are available locally), as well as run a set of tests which validate `pep8` and `pylint` style of the code. -You can optionally run tests on only one tox target using the `-e` -option to select an environment. +You can optionally run tests on only one tox target using the `-e` option to select an environment. -For instance `tox -e lint` will run the linters only, `tox -e py34` -will run unit tests only on python 3.4. +For instance `tox -e lint` will run the linters only, `tox -e py34` will run unit tests only on python 3.4. ### Notes on PyLint and PEP8 validation -In case a PyLint warning cannot be avoided, add a comment to disable -the PyLint check for that line. This can be done using the format -`# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint -warning is if you do not use the passed in datetime if you're -listening for time change. +In case a PyLint warning cannot be avoided, add a comment to disable the PyLint check for that line. This can be done using the format `# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint warning is if you do not use the passed in datetime if you're listening for time change. From d7e3c6a442fa43ca0597200618ea77577299cf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Sat, 27 Feb 2016 21:50:19 +0100 Subject: [PATCH 178/186] verisure refactoring and fix reconnect --- .../alarm_control_panel/__init__.py | 2 +- .../alarm_control_panel/verisure.py | 59 +++-- homeassistant/components/lock/verisure.py | 49 ++--- homeassistant/components/sensor/verisure.py | 77 +++---- homeassistant/components/switch/verisure.py | 38 ++-- homeassistant/components/verisure.py | 205 +++++++++--------- requirements_all.txt | 2 +- 7 files changed, 213 insertions(+), 219 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 310a65c6184..840350d231d 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -21,7 +21,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { - verisure.DISCOVER_SENSORS: 'verisure' + verisure.DISCOVER_ALARMS: 'verisure' } SERVICE_TO_METHOD = { diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index 40df158dce9..d89992eafed 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -9,7 +9,8 @@ https://home-assistant.io/components/verisure/ import logging import homeassistant.components.alarm_control_panel as alarm -import homeassistant.components.verisure as verisure +from homeassistant.components.verisure import HUB as hub + from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) @@ -20,18 +21,13 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Verisure platform. """ - if not verisure.MY_PAGES: - _LOGGER.error('A connection has not been made to Verisure mypages.') - return False - alarms = [] - - alarms.extend([ - VerisureAlarm(value) - for value in verisure.ALARM_STATUS.values() - if verisure.SHOW_ALARM - ]) - + if int(hub.config.get('alarm', '1')): + hub.update_alarms() + alarms.extend([ + VerisureAlarm(value.id) + for value in hub.alarm_status.values() + ]) add_devices(alarms) @@ -39,9 +35,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureAlarm(alarm.AlarmControlPanel): """ Represents a Verisure alarm status. """ - def __init__(self, alarm_status): - self._id = alarm_status.id + def __init__(self, device_id): + self._id = device_id self._state = STATE_UNKNOWN + self._digits = int(hub.config.get('code_digits', '4')) @property def name(self): @@ -55,41 +52,41 @@ class VerisureAlarm(alarm.AlarmControlPanel): @property def code_format(self): - """ Four digit code required. """ - return '^\\d{%s}$' % verisure.CODE_DIGITS + """ code format as regex """ + return '^\\d{%s}$' % self._digits def update(self): """ Update alarm status """ - verisure.update_alarm() + hub.update_alarms() - if verisure.ALARM_STATUS[self._id].status == 'unarmed': + if hub.alarm_status[self._id].status == 'unarmed': self._state = STATE_ALARM_DISARMED - elif verisure.ALARM_STATUS[self._id].status == 'armedhome': + elif hub.alarm_status[self._id].status == 'armedhome': self._state = STATE_ALARM_ARMED_HOME - elif verisure.ALARM_STATUS[self._id].status == 'armed': + elif hub.alarm_status[self._id].status == 'armed': self._state = STATE_ALARM_ARMED_AWAY - elif verisure.ALARM_STATUS[self._id].status != 'pending': + elif hub.alarm_status[self._id].status != 'pending': _LOGGER.error( 'Unknown alarm state %s', - verisure.ALARM_STATUS[self._id].status) + hub.alarm_status[self._id].status) def alarm_disarm(self, code=None): """ Send disarm command. """ - verisure.MY_PAGES.alarm.set(code, 'DISARMED') + hub.my_pages.alarm.set(code, 'DISARMED') _LOGGER.info('verisure alarm disarming') - verisure.MY_PAGES.alarm.wait_while_pending() - verisure.update_alarm() + hub.my_pages.alarm.wait_while_pending() + self.update() def alarm_arm_home(self, code=None): """ Send arm home command. """ - verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME') + hub.my_pages.alarm.set(code, 'ARMED_HOME') _LOGGER.info('verisure alarm arming home') - verisure.MY_PAGES.alarm.wait_while_pending() - verisure.update_alarm() + hub.my_pages.alarm.wait_while_pending() + self.update() def alarm_arm_away(self, code=None): """ Send arm away command. """ - verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY') + hub.my_pages.alarm.set(code, 'ARMED_AWAY') _LOGGER.info('verisure alarm arming away') - verisure.MY_PAGES.alarm.wait_while_pending() - verisure.update_alarm() + hub.my_pages.alarm.wait_while_pending() + self.update() diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/lock/verisure.py index 6b73793e612..2542e51bacc 100644 --- a/homeassistant/components/lock/verisure.py +++ b/homeassistant/components/lock/verisure.py @@ -8,7 +8,7 @@ https://home-assistant.io/components/verisure/ """ import logging -import homeassistant.components.verisure as verisure +from homeassistant.components.verisure import HUB as hub from homeassistant.components.lock import LockDevice from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED @@ -19,16 +19,13 @@ ATTR_CODE = 'code' def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Verisure platform. """ - if not verisure.MY_PAGES: - _LOGGER.error('A connection has not been made to Verisure mypages.') - return False - locks = [] - - locks.extend([VerisureDoorlock(value) - for value in verisure.LOCK_STATUS.values() - if verisure.SHOW_LOCKS]) - + if int(hub.config.get('locks', '1')): + hub.update_locks() + locks.extend([ + VerisureDoorlock(device_id) + for device_id in hub.lock_status.keys() + ]) add_devices(locks) @@ -36,10 +33,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureDoorlock(LockDevice): """ Represents a Verisure doorlock status. """ - def __init__(self, lock_status, code=None): - self._id = lock_status.id + def __init__(self, device_id): + self._id = device_id self._state = STATE_UNKNOWN - self._code = code + self._digits = int(hub.config.get('code_digits', '4')) @property def name(self): @@ -54,36 +51,36 @@ class VerisureDoorlock(LockDevice): @property def code_format(self): """ Six digit code required. """ - return '^\\d{%s}$' % verisure.CODE_DIGITS + return '^\\d{%s}$' % self._digits def update(self): """ Update lock status """ - verisure.update_lock() + hub.update_locks() - if verisure.LOCK_STATUS[self._id].status == 'unlocked': + if hub.lock_status[self._id].status == 'unlocked': self._state = STATE_UNLOCKED - elif verisure.LOCK_STATUS[self._id].status == 'locked': + elif hub.lock_status[self._id].status == 'locked': self._state = STATE_LOCKED - elif verisure.LOCK_STATUS[self._id].status != 'pending': + elif hub.lock_status[self._id].status != 'pending': _LOGGER.error( 'Unknown lock state %s', - verisure.LOCK_STATUS[self._id].status) + hub.lock_status[self._id].status) @property def is_locked(self): """ True if device is locked. """ - return verisure.LOCK_STATUS[self._id].status + return hub.lock_status[self._id].status def unlock(self, **kwargs): """ Send unlock command. """ - verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED') + hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED') _LOGGER.info('verisure doorlock unlocking') - verisure.MY_PAGES.lock.wait_while_pending() - verisure.update_lock() + hub.my_pages.lock.wait_while_pending() + self.update() def lock(self, **kwargs): """ Send lock command. """ - verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED') + hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED') _LOGGER.info('verisure doorlock locking') - verisure.MY_PAGES.lock.wait_while_pending() - verisure.update_lock() + hub.my_pages.lock.wait_while_pending() + self.update() diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 12cafea0f41..c42642bed72 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -6,7 +6,7 @@ documentation at https://home-assistant.io/components/verisure/ """ import logging -import homeassistant.components.verisure as verisure +from homeassistant.components.verisure import HUB as hub from homeassistant.const import TEMP_CELCIUS from homeassistant.helpers.entity import Entity @@ -15,32 +15,33 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """Sets up the Verisure platform.""" - if not verisure.MY_PAGES: - _LOGGER.error('A connection has not been made to Verisure mypages.') - return False sensors = [] - sensors.extend([ - VerisureThermometer(value) - for value in verisure.CLIMATE_STATUS.values() - if verisure.SHOW_THERMOMETERS and - hasattr(value, 'temperature') and value.temperature - ]) + if int(hub.config.get('temperature', '1')): + hub.update_climate() + sensors.extend([ + VerisureThermometer(value.id) + for value in hub.climate_status.values() + if hasattr(value, 'temperature') and value.temperature + ]) - sensors.extend([ - VerisureHygrometer(value) - for value in verisure.CLIMATE_STATUS.values() - if verisure.SHOW_HYGROMETERS and - hasattr(value, 'humidity') and value.humidity - ]) + if int(hub.config.get('hygrometers', '1')): + hub.update_climate() + sensors.extend([ + VerisureHygrometer(value.id) + for value in hub.climate_status.values() + if hasattr(value, 'humidity') and value.humidity + ]) - sensors.extend([ - VerisureMouseDetection(value) - for value in verisure.MOUSEDETECTION_STATUS.values() - if verisure.SHOW_MOUSEDETECTION and - hasattr(value, 'amountText') and value.amountText - ]) + if int(hub.config.get('mouse', '1')): + hub.update_mousedetection() + sensors.extend([ + VerisureMouseDetection(value.deviceLabel) + for value in hub.mouse_status.values() + # is this if needed? + if hasattr(value, 'amountText') and value.amountText + ]) add_devices(sensors) @@ -48,21 +49,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureThermometer(Entity): """Represents a Verisure thermometer.""" - def __init__(self, climate_status): - self._id = climate_status.id + def __init__(self, device_id): + self._id = device_id @property def name(self): """Returns the name of the device.""" return '{} {}'.format( - verisure.CLIMATE_STATUS[self._id].location, + hub.climate_status[self._id].location, "Temperature") @property def state(self): """Returns the state of the device.""" # remove ° character - return verisure.CLIMATE_STATUS[self._id].temperature[:-1] + return hub.climate_status[self._id].temperature[:-1] @property def unit_of_measurement(self): @@ -71,27 +72,27 @@ class VerisureThermometer(Entity): def update(self): """Update the sensor.""" - verisure.update_climate() + hub.update_climate() class VerisureHygrometer(Entity): """Represents a Verisure hygrometer.""" - def __init__(self, climate_status): - self._id = climate_status.id + def __init__(self, device_id): + self._id = device_id @property def name(self): """Returns the name of the sensor.""" return '{} {}'.format( - verisure.CLIMATE_STATUS[self._id].location, + hub.climate_status[self._id].location, "Humidity") @property def state(self): """Returns the state of the sensor.""" # remove % character - return verisure.CLIMATE_STATUS[self._id].humidity[:-1] + return hub.climate_status[self._id].humidity[:-1] @property def unit_of_measurement(self): @@ -99,27 +100,27 @@ class VerisureHygrometer(Entity): return "%" def update(self): - """Update sensor the sensor.""" - verisure.update_climate() + """Update the sensor.""" + hub.update_climate() class VerisureMouseDetection(Entity): """ Represents a Verisure mouse detector.""" - def __init__(self, mousedetection_status): - self._id = mousedetection_status.deviceLabel + def __init__(self, device_id): + self._id = device_id @property def name(self): """Returns the name of the sensor.""" return '{} {}'.format( - verisure.MOUSEDETECTION_STATUS[self._id].location, + hub.mouse_status[self._id].location, "Mouse") @property def state(self): """Returns the state of the sensor.""" - return verisure.MOUSEDETECTION_STATUS[self._id].count + return hub.mouse_status[self._id].count @property def unit_of_measurement(self): @@ -128,4 +129,4 @@ class VerisureMouseDetection(Entity): def update(self): """Update the sensor.""" - verisure.update_mousedetection() + hub.update_mousedetection() diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index c698a33ce18..815e7a18631 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -8,7 +8,7 @@ documentation at https://home-assistant.io/components/verisure/ """ import logging -import homeassistant.components.verisure as verisure +from homeassistant.components.verisure import HUB as hub from homeassistant.components.switch import SwitchDevice _LOGGER = logging.getLogger(__name__) @@ -16,49 +16,43 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Verisure platform. """ - - if not verisure.MY_PAGES: - _LOGGER.error('A connection has not been made to Verisure mypages.') + if not int(hub.config.get('smartplugs', '1')): return False + hub.update_smartplugs() switches = [] - switches.extend([ - VerisureSmartplug(value) - for value in verisure.SMARTPLUG_STATUS.values() - if verisure.SHOW_SMARTPLUGS - ]) - + VerisureSmartplug(value.id) + for value in hub.smartplug_status.values()]) add_devices(switches) class VerisureSmartplug(SwitchDevice): """ Represents a Verisure smartplug. """ - def __init__(self, smartplug_status): - self._id = smartplug_status.id + def __init__(self, device_id): + self._id = device_id @property def name(self): """ Get the name (location) of the smartplug. """ - return verisure.SMARTPLUG_STATUS[self._id].location + return hub.smartplug_status[self._id].location @property def is_on(self): """ Returns True if on """ - plug_status = verisure.SMARTPLUG_STATUS[self._id].status - return plug_status == 'on' + return hub.smartplug_status[self._id].status == 'on' def turn_on(self): """ Set smartplug status on. """ - verisure.MY_PAGES.smartplug.set(self._id, 'on') - verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'on') - verisure.update_smartplug() + hub.my_pages.smartplug.set(self._id, 'on') + hub.my_pages.smartplug.wait_while_updating(self._id, 'on') + self.update() def turn_off(self): """ Set smartplug status off. """ - verisure.MY_PAGES.smartplug.set(self._id, 'off') - verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'off') - verisure.update_smartplug() + hub.my_pages.smartplug.set(self._id, 'off') + hub.my_pages.smartplug.wait_while_updating(self._id, 'off') + self.update() def update(self): - verisure.update_smartplug() + hub.update_smartplugs() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 2e8af9b3129..18085c25ee9 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -7,6 +7,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/verisure/ """ import logging +import threading import time from datetime import timedelta @@ -24,33 +25,11 @@ DISCOVER_SWITCHES = 'verisure.switches' DISCOVER_ALARMS = 'verisure.alarm_control_panel' DISCOVER_LOCKS = 'verisure.lock' -DEPENDENCIES = ['alarm_control_panel'] -REQUIREMENTS = ['vsure==0.5.1'] +REQUIREMENTS = ['vsure==0.6.1'] _LOGGER = logging.getLogger(__name__) -MY_PAGES = None -ALARM_STATUS = {} -SMARTPLUG_STATUS = {} -CLIMATE_STATUS = {} -LOCK_STATUS = {} -MOUSEDETECTION_STATUS = {} - -VERISURE_LOGIN_ERROR = None -VERISURE_ERROR = None - -SHOW_THERMOMETERS = True -SHOW_HYGROMETERS = True -SHOW_ALARM = True -SHOW_SMARTPLUGS = True -SHOW_LOCKS = True -SHOW_MOUSEDETECTION = True -CODE_DIGITS = 4 - -# if wrong password was given don't try again -WRONG_PASSWORD_GIVEN = False - -MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=1) +HUB = None def setup(hass, config): @@ -61,48 +40,19 @@ def setup(hass, config): _LOGGER): return False - from verisure import MyPages, LoginError, Error - - global SHOW_THERMOMETERS, SHOW_HYGROMETERS,\ - SHOW_ALARM, SHOW_SMARTPLUGS, SHOW_LOCKS, SHOW_MOUSEDETECTION,\ - CODE_DIGITS - SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1')) - SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1')) - SHOW_ALARM = int(config[DOMAIN].get('alarm', '1')) - SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1')) - SHOW_LOCKS = int(config[DOMAIN].get('locks', '1')) - SHOW_MOUSEDETECTION = int(config[DOMAIN].get('mouse', '1')) - CODE_DIGITS = int(config[DOMAIN].get('code_digits', '4')) - - global MY_PAGES - MY_PAGES = MyPages( - config[DOMAIN][CONF_USERNAME], - config[DOMAIN][CONF_PASSWORD]) - global VERISURE_LOGIN_ERROR, VERISURE_ERROR - VERISURE_LOGIN_ERROR = LoginError - VERISURE_ERROR = Error - - try: - MY_PAGES.login() - except (ConnectionError, Error) as ex: - _LOGGER.error('Could not log in to verisure mypages, %s', ex) + import verisure + global HUB + HUB = VerisureHub(config[DOMAIN], verisure) + if not HUB.login(): return False - update_alarm() - update_climate() - update_smartplug() - update_lock() - update_mousedetection() - # Load components for the devices in the ISY controller that we support for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), ('switch', DISCOVER_SWITCHES), ('alarm_control_panel', DISCOVER_ALARMS), ('lock', DISCOVER_LOCKS))): component = get_component(comp_name) - _LOGGER.info(config[DOMAIN]) bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {ATTR_SERVICE: discovery, ATTR_DISCOVERED: {}}) @@ -110,58 +60,113 @@ def setup(hass, config): return True -def reconnect(): - """ Reconnect to verisure mypages. """ - try: - time.sleep(1) - MY_PAGES.login() - except VERISURE_LOGIN_ERROR as ex: - _LOGGER.error("Could not login to Verisure mypages, %s", ex) - global WRONG_PASSWORD_GIVEN - WRONG_PASSWORD_GIVEN = True - except (ConnectionError, VERISURE_ERROR) as ex: - _LOGGER.error("Could not login to Verisure mypages, %s", ex) +# pylint: disable=too-many-instance-attributes +class VerisureHub(object): + """ Verisure wrapper class """ + MAX_PASSWORD_RETRIES = 2 + MIN_TIME_BETWEEN_REQUESTS = 1 -@Throttle(MIN_TIME_BETWEEN_REQUESTS) -def update_alarm(): - """ Updates the status of alarms. """ - update_component(MY_PAGES.alarm.get, ALARM_STATUS) + def __init__(self, domain_config, verisure): + self.alarm_status = {} + self.lock_status = {} + self.climate_status = {} + self.mouse_status = {} + self.smartplug_status = {} + self.config = domain_config + self._verisure = verisure -@Throttle(MIN_TIME_BETWEEN_REQUESTS) -def update_climate(): - """ Updates the status of climate sensors. """ - update_component(MY_PAGES.climate.get, CLIMATE_STATUS) + self._lock = threading.Lock() + self._password_retries = VerisureHub.MAX_PASSWORD_RETRIES + self._wrong_password_given = False + self._reconnect_timeout = time.time() -@Throttle(MIN_TIME_BETWEEN_REQUESTS) -def update_smartplug(): - """ Updates the status of smartplugs. """ - update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS) + self.my_pages = verisure.MyPages( + domain_config[CONF_USERNAME], + domain_config[CONF_PASSWORD]) + def login(self): + """ Login to MyPages """ + try: + self.my_pages.login() + except self._verisure.Error as ex: + _LOGGER.error('Could not log in to verisure mypages, %s', ex) + return False + return True -def update_lock(): - """ Updates the status of alarms. """ - update_component(MY_PAGES.lock.get, LOCK_STATUS) + @Throttle(timedelta(seconds=1)) + def update_alarms(self): + """ Updates the status of the alarm. """ + self.update_component( + self.my_pages.alarm.get, + self.alarm_status) + @Throttle(timedelta(seconds=1)) + def update_locks(self): + """ Updates the status of the alarm. """ + self.update_component( + self.my_pages.lock.get, + self.lock_status) -def update_mousedetection(): - """ Updates the status of mouse detectors. """ - update_component(MY_PAGES.mousedetection.get, MOUSEDETECTION_STATUS) + @Throttle(timedelta(seconds=60)) + def update_climate(self): + """ Updates the status of the smartplugs. """ + self.update_component( + self.my_pages.climate.get, + self.climate_status) + @Throttle(timedelta(seconds=60)) + def update_mousedetection(self): + """ Updates the status of the smartplugs. """ + self.update_component( + self.my_pages.mousedetection.get, + self.mouse_status) -def update_component(get_function, status): - """ Updates the status of verisure components. """ - if WRONG_PASSWORD_GIVEN: - _LOGGER.error('Wrong password') - return - try: - for overview in get_function(): - try: - status[overview.id] = overview - except AttributeError: - status[overview.deviceLabel] = overview - except (ConnectionError, VERISURE_ERROR) as ex: - _LOGGER.error('Caught connection error %s, tries to reconnect', ex) - reconnect() + @Throttle(timedelta(seconds=1)) + def update_smartplugs(self): + """ Updates the status of the smartplugs. """ + self.update_component( + self.my_pages.smartplug.get, + self.smartplug_status) + + def update_component(self, get_function, status): + """ Updates the status of verisure components. """ + if self._wrong_password_given: + _LOGGER.error('Wrong password for Verisure, update config') + return + try: + for overview in get_function(): + try: + status[overview.id] = overview + except AttributeError: + status[overview.deviceLabel] = overview + except self._verisure.Error as ex: + _LOGGER.error('Caught connection error %s, tries to reconnect', ex) + self.reconnect() + + def reconnect(self): + """ Reconnect to verisure mypages. """ + if self._reconnect_timeout > time.time(): + return + if not self._lock.acquire(blocking=False): + return + try: + self.my_pages.login() + self._password_retries = VerisureHub.MAX_PASSWORD_RETRIES + except self._verisure.LoginError as ex: + _LOGGER.error("Wrong user name or password for Verisure MyPages") + if self._password_retries > 0: + self._password_retries -= 1 + self._reconnect_timeout = time.time() + 15 * 60 + else: + self._wrong_password_given = True + except self._verisure.MaintenanceError: + self._reconnect_timeout = time.time() + 60 + _LOGGER.error("Verisure MyPages down for maintenance") + except self._verisure.Error as ex: + _LOGGER.error("Could not login to Verisure MyPages, %s", ex) + self._reconnect_timeout = time.time() + 5 + finally: + self._lock.release() diff --git a/requirements_all.txt b/requirements_all.txt index 91ece24547b..c3732e5af07 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -271,7 +271,7 @@ urllib3 uvcclient==0.8 # homeassistant.components.verisure -vsure==0.5.1 +vsure==0.6.1 # homeassistant.components.zigbee xbee-helper==0.0.6 From 9b371a8c667ea589391a53743ffaa0a4c62bb8b9 Mon Sep 17 00:00:00 2001 From: Justin Moy Date: Sat, 27 Feb 2016 14:44:39 -0700 Subject: [PATCH 179/186] Add SendGrid notify component --- .coveragerc | 1 + homeassistant/components/notify/sendgrid.py | 51 +++++++++++++++++++++ requirements_all.txt | 3 ++ 3 files changed, 55 insertions(+) create mode 100644 homeassistant/components/notify/sendgrid.py diff --git a/.coveragerc b/.coveragerc index 2e992fad9a1..b9ef057bc21 100644 --- a/.coveragerc +++ b/.coveragerc @@ -116,6 +116,7 @@ omit = homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushover.py homeassistant/components/notify/rest.py + homeassistant/components/notify/sendgrid.py homeassistant/components/notify/slack.py homeassistant/components/notify/smtp.py homeassistant/components/notify/syslog.py diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/notify/sendgrid.py new file mode 100644 index 00000000000..f56bbb59d3e --- /dev/null +++ b/homeassistant/components/notify/sendgrid.py @@ -0,0 +1,51 @@ +""" +homeassistant.components.notify.sendgrid +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +SendGrid notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.sendgrid/ +""" +import logging + +from homeassistant.components.notify import ( + ATTR_TITLE, DOMAIN, BaseNotificationService) +from homeassistant.helpers import validate_config + +REQUIREMENTS = ['sendgrid>=1.6.0,<1.7.0'] +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config): + """ Get the SendGrid notification service """ + if not validate_config({DOMAIN: config}, + {DOMAIN: ['api_key', 'sender', 'recipient']}, + _LOGGER): + return None + + api_key = config['api_key'] + sender = config['sender'] + recipient = config['recipient'] + return SendgridNotificationService(api_key, sender, recipient) + + +# pylint: disable=too-few-public-methods +class SendgridNotificationService(BaseNotificationService): + """ Implements the notification service for email via Sendgrid. """ + + def __init__(self, api_key, sender, recipient): + self.api_key = api_key + self.sender = sender + self.recipient = recipient + + from sendgrid import SendGridClient + self._sg = SendGridClient(self.api_key) + + def send_message(self, message='', **kwargs): + """ Send an email to a user via SendGrid. """ + subject = kwargs.get(ATTR_TITLE) + + from sendgrid import Mail + mail = Mail(from_email=self.sender, to=self.recipient, + html=message, text=message, subject=subject) + self._sg.send(mail) diff --git a/requirements_all.txt b/requirements_all.txt index 0337da10a41..7af8c763e8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -232,6 +232,9 @@ schiene==0.14 # homeassistant.components.scsgate scsgate==0.1.0 +# homeassistant.components.notify.sendgrid +sendgrid>=1.6.0,<1.7.0 + # homeassistant.components.notify.slack slacker==0.6.8 From cd21142d5beb9233be977ab84b0bb9650a710435 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 27 Feb 2016 23:17:20 +0100 Subject: [PATCH 180/186] Update with comments from PR --- .github/ISSUE_TEMPLATE.md | 35 ++++++++++++++++++++++++-------- .github/PULL_REQUEST_TEMPLATE.md | 35 ++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d5af5533dbf..7b5a0f1980f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,14 +1,31 @@ -Please add a meaningful title and a short description of your issue. It's hard to help you if you only add a trackback with no comments or a one-liner. +**Home Assistant release (`hass --version`):** -Add the following details to your report. Especially if you are unsure about that to include. -- [ ] Your Home Assistant release, `hass --version` -- [ ] Your operating system -- [ ] The component/platform involved -- [ ] The config entry for the component/platform from your `configuration.yml` file -- [ ] Your traceback +**Python release (`python3 --version`):** -It helps if you use the formatting options provided by GitHub ;-) -Anyway, make sure that you have **searched** the closed issues first. Thanks! +**Component/platform:** + + +**Description of problem:** + + +**Expected:** + + +**Problem-relevant `configuration.yaml` entries and steps to reproduce:** +```yaml + +``` + +1. +2. +3. + +**Traceback (if applicable):** +```bash + +``` + +**Additional info:** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ad8566ecba2..7f63274df03 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,28 @@ -This Pull Request includes a +**Description:** -- [ ] Bug fix -- [ ] New component -- [ ] New platform - -The following changes were made: - -- -- -- - -Example entry for the `configuration.yaml` file (if it's a PR for a component/platform): +**Related issue (if applicable):** # +**Example entry for `configuration.yaml` (if applicable):** ```yaml ``` -If this PR is related to an existing ticket, please include a link to it as well. Thanks! +**Checklist:** + +- [ ] Local tests with `tox` ran successfully. +- [ ] No CI failures. **Your PR cannot be merged unless CI is green!** +- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR. +- If code communicates with devices: + - [ ] 3rd party library/libraries for communication is/are added as dependencies via the `REQUIREMENTS` variable ([example][ex-requir]). + - [ ] 3rd party dependencies are imported inside functions that use them ([example][ex-import]). + - [ ] `requirements_all.txt` is up-to-date, `script/gen_requirements_all.py` ran and the updated file is included in the PR. + - [ ] New files were added to `.coveragerc`. +- If the code does not depend on external Python module: + - [ ] Tests to verify that the code works are included. +- [ ] [Commits will be squashed][squash] when the PR is ready to be merged. + +[fork]: http://stackoverflow.com/a/7244456 +[squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit +[ex-requir]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16 +[ex-import]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51 + From 562db5ea4cef9b77c3788e67abb634cb0324684d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Sat, 27 Feb 2016 23:18:56 +0100 Subject: [PATCH 181/186] Throttle for two methonds in same class --- homeassistant/util/__init__.py | 19 +++++++++++-------- tests/util/test_init.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 7a396b44d3b..ca89226846e 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -260,25 +260,28 @@ class Throttle(object): else: host = args[0] if args else wrapper - if not hasattr(host, '_throttle_lock'): - host._throttle_lock = threading.Lock() + if not hasattr(host, '_throttle'): + host._throttle = {} - if not host._throttle_lock.acquire(False): + if id(self) not in host._throttle: + host._throttle[id(self)] = [threading.Lock(), None] + throttle = host._throttle[id(self)] + + if not throttle[0].acquire(False): return None - last_call = getattr(host, '_throttle_last_call', None) # Check if method is never called or no_throttle is given - force = not last_call or kwargs.pop('no_throttle', False) + force = not throttle[1] or kwargs.pop('no_throttle', False) try: - if force or utcnow() - last_call > self.min_time: + if force or utcnow() - throttle[1] > self.min_time: result = method(*args, **kwargs) - host._throttle_last_call = utcnow() + throttle[1] = utcnow() return result else: return None finally: - host._throttle_lock.release() + throttle[0].release() return wrapper diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 1a80d74b065..abe12a0af1b 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -238,3 +238,20 @@ class TestUtil(unittest.TestCase): self.assertTrue(throttled()) self.assertIsNone(throttled()) + + def test_throttle_on_two_method(self): + """ Test that throttle works when wrapping two methods. """ + + class Tester(object): + @util.Throttle(timedelta(seconds=1)) + def hello(self): + return True + + @util.Throttle(timedelta(seconds=1)) + def goodbye(self): + return True + + tester = Tester() + + self.assertTrue(tester.hello()) + self.assertTrue(tester.goodbye()) From 73ec049d1ca800a9bbc51ff6cf76e9fc8fc6c51e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 27 Feb 2016 14:27:16 -0800 Subject: [PATCH 182/186] Do not expect Hue light to have a brightness --- homeassistant/components/light/hue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index a970a8681c9..a183d1b533b 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -194,7 +194,7 @@ class HueLight(Light): @property def brightness(self): """ Brightness of this light between 0..255. """ - return self.info['state']['bri'] + return self.info['state'].get('bri') @property def xy_color(self): From 9e4ddc405dc160895633016b2da738bdb3a1e096 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 27 Feb 2016 14:34:52 -0800 Subject: [PATCH 183/186] Update frontend --- .../components/frontend/mdi_version.py | 2 +- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 112 +++++++++--------- .../www_static/home-assistant-polymer | 2 +- .../components/frontend/www_static/mdi.html | 2 +- 5 files changed, 60 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/frontend/mdi_version.py b/homeassistant/components/frontend/mdi_version.py index 90765572d2c..cf3ee39d8f2 100644 --- a/homeassistant/components/frontend/mdi_version.py +++ b/homeassistant/components/frontend/mdi_version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by update_mdi script """ -VERSION = "a1a203680639ff1abcc7b68cdb29c57a" +VERSION = "2f4adc5d3ad6d2f73bf69ed29b7594fd" diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 0f962812fbb..c62b6c52b4d 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "78b7312c6b47f942d0009ee5aa898399" +VERSION = "a4d021cb50ed079fcfda7369ed2f0d4a" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index ff0def0fc7f..7dd6f68c12f 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,7 +1,7 @@ -