diff --git a/.coveragerc b/.coveragerc index b9ef057bc21..c37e4f47291 100644 --- a/.coveragerc +++ b/.coveragerc @@ -51,15 +51,15 @@ omit = homeassistant/components/zwave.py homeassistant/components/*/zwave.py - homeassistant/components/rfxtrx.py - homeassistant/components/*/rfxtrx.py - homeassistant/components/mysensors.py homeassistant/components/*/mysensors.py homeassistant/components/nest.py homeassistant/components/*/nest.py + homeassistant/components/rfxtrx.py + homeassistant/components/*/rfxtrx.py + homeassistant/components/rpi_gpio.py homeassistant/components/*/rpi_gpio.py @@ -123,6 +123,7 @@ omit = homeassistant/components/notify/telegram.py homeassistant/components/notify/twitter.py homeassistant/components/notify/xmpp.py + homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/cpuspeed.py @@ -139,8 +140,8 @@ 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/steam_online.py homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/temper.py @@ -150,8 +151,8 @@ omit = homeassistant/components/sensor/twitch.py homeassistant/components/sensor/worldclock.py homeassistant/components/switch/arest.py - homeassistant/components/switch/edimax.py homeassistant/components/switch/dlink.py + homeassistant/components/switch/edimax.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/mystrom.py homeassistant/components/switch/orvibo.py @@ -162,6 +163,7 @@ omit = homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/radiotherm.py + [report] # Regexes for lines to exclude from consideration exclude_lines = diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f63274df03..c654a8c75e6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,6 +2,7 @@ **Related issue (if applicable):** # + **Example entry for `configuration.yaml` (if applicable):** ```yaml @@ -9,17 +10,17 @@ **Checklist:** -- [ ] Local tests with `tox` ran successfully. -- [ ] No CI failures. **Your PR cannot be merged unless CI is green!** +- [ ] Local tests with `tox` run successfully. +- [ ] TravisCI does not fail. **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. +- [ ] Commits have been [squashed][squash]. - 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 dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]). + - [ ] New dependencies are only imported inside functions that use them ([example][ex-import]). + - [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`. - [ ] 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. +- If the code does not interact with devices: + - [ ] Tests have been added to verify that the new code works. [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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 722fcd1992a..2028161458e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,10 +71,10 @@ When you are done with development and ready to commit your changes, run `build_ To test your code before submission, used the `tox` tool. - ```shell - > pip install -U tox - > tox - ``` +```bash +> 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. diff --git a/Dockerfile b/Dockerfile index 0d41841f452..8970df523be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,19 +6,17 @@ VOLUME /config RUN mkdir -p /usr/src/app WORKDIR /usr/src/app -RUN pip3 install --no-cache-dir colorlog +RUN pip3 install --no-cache-dir colorlog cython # For the nmap tracker RUN apt-get update && \ - apt-get install -y --no-install-recommends nmap net-tools && \ + apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* COPY script/build_python_openzwave script/build_python_openzwave -RUN apt-get update && \ - apt-get install -y cython3 libudev-dev && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ - pip3 install "cython<0.23" && \ - script/build_python_openzwave +RUN script/build_python_openzwave && \ + mkdir -p /usr/local/share/python-openzwave && \ + ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config COPY requirements_all.txt requirements_all.txt RUN pip3 install --no-cache-dir -r requirements_all.txt diff --git a/config/custom_components/example.py b/config/custom_components/example.py index 3f961b99569..4d3df9328d8 100644 --- a/config/custom_components/example.py +++ b/config/custom_components/example.py @@ -101,9 +101,10 @@ def track_devices(hass, entity_id, old_state, new_state): @track_time_change(hour=7, minute=0, second=0) def wake_up(hass, now): - """ - Turn it on in the morning (7 AM) if there are people home and - it is not already on. + """Turn light on in the morning. + + Turn the light on at 7 AM if there are people home and it is not already + on. """ if not TARGET_ID: return @@ -126,8 +127,9 @@ def all_lights_off(hass, entity_id, old_state, new_state): @service(DOMAIN, SERVICE_FLASH) def flash_service(hass, call): - """ - Service that will turn the target off for 10 seconds if on and vice versa. + """Service that will toggle the target. + + Set the light to off for 10 seconds if on and vice versa. """ if not TARGET_ID: return diff --git a/config/custom_components/hello_world.py b/config/custom_components/hello_world.py index f24971a1462..b35e9f6c0ed 100644 --- a/config/custom_components/hello_world.py +++ b/config/custom_components/hello_world.py @@ -20,7 +20,6 @@ DEPENDENCIES = [] def setup(hass, config): """Setup our skeleton component.""" - # States are in the format DOMAIN.OBJECT_ID. hass.states.set('hello_world.Hello_World', 'Works!') diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index e69de29bb2d..32da6ab0afb 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -0,0 +1 @@ +"""Init file for Home Assistant.""" diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index f35e0c1c1f0..02ccc239f2b 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,4 +1,4 @@ -""" Starts home assistant. """ +"""Starts home assistant.""" from __future__ import print_function import argparse @@ -12,21 +12,26 @@ from multiprocessing import Process import homeassistant.config as config_util from homeassistant import bootstrap from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, RESTART_EXIT_CODE, __version__) + __version__, + EVENT_HOMEASSISTANT_START, + REQUIRED_PYTHON_VER, + RESTART_EXIT_CODE, +) def validate_python(): - """ Validate we're running the right Python version. """ + """Validate we're running the right Python version.""" major, minor = sys.version_info[:2] + req_major, req_minor = REQUIRED_PYTHON_VER - if major < 3 or (major == 3 and minor < 4): - print("Home Assistant requires atleast Python 3.4") + if major < req_major or (major == req_major and minor < req_minor): + print("Home Assistant requires at least Python {}.{}".format( + req_major, req_minor)) sys.exit(1) def ensure_config_path(config_dir): - """ Validates configuration directory. """ - + """Validate the configuration directory.""" lib_dir = os.path.join(config_dir, 'lib') # Test if configuration directory exists @@ -54,7 +59,7 @@ def ensure_config_path(config_dir): def ensure_config_file(config_dir): - """ Ensure configuration file exists. """ + """Ensure configuration file exists.""" config_path = config_util.ensure_config_exists(config_dir) if config_path is None: @@ -65,7 +70,7 @@ def ensure_config_file(config_dir): def get_arguments(): - """ Get parsed passed in arguments. """ + """Get parsed passed in arguments.""" parser = argparse.ArgumentParser( description="Home Assistant: Observe, Control, Automate.") parser.add_argument('--version', action='version', version=__version__) @@ -130,25 +135,25 @@ def get_arguments(): def daemonize(): - """ Move current process to daemon process """ - # create first fork + """Move current process to daemon process.""" + # Create first fork pid = os.fork() if pid > 0: sys.exit(0) - # decouple fork + # Decouple fork os.setsid() os.umask(0) - # create second fork + # Create second fork pid = os.fork() if pid > 0: sys.exit(0) def check_pid(pid_file): - """ Check that HA is not already running """ - # check pid file + """Check that HA is not already running.""" + # Check pid file try: pid = int(open(pid_file, 'r').readline()) except IOError: @@ -165,7 +170,7 @@ def check_pid(pid_file): def write_pid(pid_file): - """ Create PID File """ + """Create a PID File.""" pid = os.getpid() try: open(pid_file, 'w').write(str(pid)) @@ -175,7 +180,7 @@ def write_pid(pid_file): def install_osx(): - """ Setup to run via launchd on OS X """ + """Setup to run via launchd on OS X.""" with os.popen('which hass') as inp: hass_path = inp.read().strip() @@ -207,7 +212,7 @@ def install_osx(): def uninstall_osx(): - """ Unload from launchd on OS X """ + """Unload from launchd on OS X.""" path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist") os.popen('launchctl unload ' + path) @@ -215,9 +220,10 @@ def uninstall_osx(): def setup_and_run_hass(config_dir, args, top_process=False): - """ - Setup HASS and run. Block until stopped. Will assume it is running in a - subprocess unless top_process is set to true. + """Setup HASS and run. + + Block until stopped. Will assume it is running in a subprocess unless + top_process is set to true. """ if args.demo_mode: config = { @@ -237,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False): if args.open_ui: def open_browser(event): - """ Open the webinterface in a browser. """ + """Open the webinterface in a browser.""" if hass.config.api is not None: import webbrowser webbrowser.open(hass.config.api.base_url) @@ -253,12 +259,12 @@ def setup_and_run_hass(config_dir, args, top_process=False): def run_hass_process(hass_proc): - """ Runs a child hass process. Returns True if it should be restarted. """ + """Run a child hass process. Returns True if it should be restarted.""" requested_stop = threading.Event() hass_proc.daemon = True def request_stop(*args): - """ request hass stop, *args is for signal handler callback """ + """Request hass stop, *args is for signal handler callback.""" requested_stop.set() hass_proc.terminate() @@ -283,7 +289,7 @@ def run_hass_process(hass_proc): def main(): - """ Starts Home Assistant. """ + """Start Home Assistant.""" validate_python() args = get_arguments() @@ -291,7 +297,7 @@ def main(): config_dir = os.path.join(os.getcwd(), args.config) ensure_config_path(config_dir) - # os x launchd functions + # OS X launchd functions if args.install_osx: install_osx() return 0 @@ -305,7 +311,7 @@ def main(): install_osx() return 0 - # daemon functions + # Daemon functions if args.pid_file: check_pid(args.pid_file) if args.daemon: diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 9db3f79e498..c6f1a75b8a6 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log' def setup_component(hass, domain, config=None): - """ Setup a component and all its dependencies. """ - + """Setup a component and all its dependencies.""" if domain in hass.config.components: return True @@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None): def _handle_requirements(hass, component, name): - """ Installs requirements for component. """ + """Install the requirements for a component.""" if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'): return True @@ -126,7 +125,7 @@ def _setup_component(hass, domain, config): def prepare_setup_platform(hass, config, domain, platform_name): - """ Loads a platform and makes sure dependencies are setup. """ + """Load a platform and makes sure dependencies are setup.""" _ensure_loader_prepared(hass) platform_path = PLATFORM_FORMAT.format(domain, platform_name) @@ -158,7 +157,7 @@ def prepare_setup_platform(hass, config, domain, platform_name): def mount_local_lib_path(config_dir): - """ Add local library to Python Path """ + """Add local library to Python Path.""" sys.path.insert(0, os.path.join(config_dir, 'lib')) @@ -166,8 +165,7 @@ def mount_local_lib_path(config_dir): def from_config_dict(config, hass=None, config_dir=None, enable_log=True, verbose=False, daemon=False, skip_pip=False, log_rotate_days=None): - """ - Tries to configure Home Assistant from a config dict. + """Try to configure Home Assistant from a config dict. Dynamically loads required components and its dependencies. """ @@ -209,7 +207,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True, _LOGGER.info('Home Assistant core initialized') - # give event decorators access to HASS + # Give event decorators access to HASS event_decorators.HASS = hass service.HASS = hass @@ -222,9 +220,9 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True, def from_config_file(config_path, hass=None, verbose=False, daemon=False, skip_pip=True, log_rotate_days=None): - """ - Reads the configuration file and tries to start all the required - functionality. Will add functionality to 'hass' parameter if given, + """Read the configuration file and try to start all the functionality. + + Will add functionality to 'hass' parameter if given, instantiates a new Home Assistant object if 'hass' is not given. """ if hass is None: @@ -244,7 +242,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False, def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None): - """ Setup the logging for home assistant. """ + """Setup the logging.""" if not daemon: logging.basicConfig(level=logging.INFO) fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " @@ -297,7 +295,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None): def process_ha_config_upgrade(hass): - """ Upgrade config if necessary. """ + """Upgrade config if necessary.""" version_path = hass.config.path('.HA_VERSION') try: @@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass): def process_ha_core_config(hass, config): - """ Processes the [homeassistant] section from the config. """ + """Process the [homeassistant] section from the config.""" hac = hass.config def set_time_zone(time_zone_str): - """ Helper method to set time zone in HA. """ + """Helper method to set time zone.""" if time_zone_str is None: return @@ -397,6 +395,6 @@ def process_ha_core_config(hass, config): def _ensure_loader_prepared(hass): - """ Ensure Home Assistant loader is prepared. """ + """Ensure Home Assistant loader is prepared.""" if not loader.PREPARED: loader.prepare(hass) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 0d82e1d2882..f2696bbbd1a 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -1,16 +1,11 @@ """ -homeassistant.components -~~~~~~~~~~~~~~~~~~~~~~~~ This package contains components that can be plugged into Home Assistant. Component design guidelines: - -Each component defines a constant DOMAIN that is equal to its filename. - -Each component that tracks states should create state entity names in the -format ".". - -Each component should publish services only under its own domain. +- Each component defines a constant DOMAIN that is equal to its filename. +- Each component that tracks states should create state entity names in the + format ".". +- Each component should publish services only under its own domain. """ import itertools as it import logging @@ -26,8 +21,10 @@ _LOGGER = logging.getLogger(__name__) def is_on(hass, entity_id=None): - """ Loads up the module to call the is_on method. - If there is no entity id given we will check all. """ + """Load up the module to call the is_on method. + + If there is no entity id given we will check all. + """ if entity_id: group = get_component('group') @@ -53,7 +50,7 @@ def is_on(hass, entity_id=None): def turn_on(hass, entity_id=None, **service_data): - """ Turns specified entity on if possible. """ + """Turn specified entity on if possible.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id @@ -61,7 +58,7 @@ def turn_on(hass, entity_id=None, **service_data): def turn_off(hass, entity_id=None, **service_data): - """ Turns specified entity off. """ + """Turn specified entity off.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id @@ -69,7 +66,7 @@ def turn_off(hass, entity_id=None, **service_data): def toggle(hass, entity_id=None, **service_data): - """ Toggles specified entity. """ + """Toggle specified entity.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id @@ -77,10 +74,9 @@ def toggle(hass, entity_id=None, **service_data): def setup(hass, config): - """ Setup general services related to homeassistant. """ - + """Setup general services related to Home Assistant.""" def handle_turn_service(service): - """ Method to handle calls to homeassistant.turn_on/off. """ + """Method to handle calls to homeassistant.turn_on/off.""" entity_ids = extract_entity_ids(hass, service) # Generic turn on/off method requires entity id diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 840350d231d..f70da3d54ec 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -1,14 +1,15 @@ """ -homeassistant.components.alarm_control_panel -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Component to interface with a alarm control panel. +Component to interface with an alarm control panel. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/alarm_control_panel/ """ import logging import os from homeassistant.components import verisure from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, + ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity @@ -31,9 +32,6 @@ SERVICE_TO_METHOD = { SERVICE_ALARM_TRIGGER: 'alarm_trigger' } -ATTR_CODE = 'code' -ATTR_CODE_FORMAT = 'code_format' - ATTR_TO_PROPERTY = [ ATTR_CODE, ATTR_CODE_FORMAT @@ -41,7 +39,7 @@ ATTR_TO_PROPERTY = [ 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) @@ -49,7 +47,7 @@ def setup(hass, config): component.setup(config) def alarm_service_handler(service): - """ Maps services to methods on Alarm. """ + """Map services to methods on Alarm.""" target_alarms = component.extract_from_service(service) if ATTR_CODE not in service.data: @@ -75,7 +73,7 @@ def setup(hass, config): def alarm_disarm(hass, code=None, entity_id=None): - """ Send the alarm the command for disarm. """ + """Send the alarm the command for disarm.""" data = {} if code: data[ATTR_CODE] = code @@ -86,7 +84,7 @@ def alarm_disarm(hass, code=None, entity_id=None): def alarm_arm_home(hass, code=None, entity_id=None): - """ Send the alarm the command for arm home. """ + """Send the alarm the command for arm home.""" data = {} if code: data[ATTR_CODE] = code @@ -97,7 +95,7 @@ def alarm_arm_home(hass, code=None, entity_id=None): def alarm_arm_away(hass, code=None, entity_id=None): - """ Send the alarm the command for arm away. """ + """Send the alarm the command for arm away.""" data = {} if code: data[ATTR_CODE] = code @@ -108,7 +106,7 @@ def alarm_arm_away(hass, code=None, entity_id=None): def alarm_trigger(hass, code=None, entity_id=None): - """ Send the alarm the command for trigger. """ + """Send the alarm the command for trigger.""" data = {} if code: data[ATTR_CODE] = code @@ -120,33 +118,33 @@ def alarm_trigger(hass, code=None, entity_id=None): # pylint: disable=no-self-use class AlarmControlPanel(Entity): - """ ABC for alarm control devices. """ + """An abstract class for alarm control devices.""" @property def code_format(self): - """ regex for code format or None if no code is required. """ + """Regex for code format or None if no code is required.""" return None def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" raise NotImplementedError() def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" raise NotImplementedError() def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" raise NotImplementedError() def alarm_trigger(self, code=None): - """ Send alarm trigger command. """ + """Send alarm trigger command.""" raise NotImplementedError() @property def state_attributes(self): - """ Return the state attributes. """ + """Return the state attributes.""" state_attr = { ATTR_CODE_FORMAT: self.code_format, - } + } return state_attr diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index b563d57a686..385cabb7d02 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -1,7 +1,5 @@ """ -homeassistant.components.alarm_control_panel.alarmdotcom -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Interfaces with Verisure alarm control panel. +Interfaces with Alarm.com alarm control panels. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.alarmdotcom/ @@ -23,8 +21,7 @@ DEFAULT_NAME = 'Alarm.com' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup an Alarm.com control panel. """ - + """Setup an Alarm.com control panel.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -42,9 +39,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=abstract-method class AlarmDotCom(alarm.AlarmControlPanel): - """ Represents a Alarm.com status. """ + """Represent an Alarm.com status.""" def __init__(self, hass, name, code, username, password): + """Initialize the Alarm.com status.""" from pyalarmdotcom.pyalarmdotcom import Alarmdotcom self._alarm = Alarmdotcom(username, password, timeout=10) self._hass = hass @@ -55,22 +53,22 @@ class AlarmDotCom(alarm.AlarmControlPanel): @property def should_poll(self): - """ No polling needed. """ + """No polling needed.""" return True @property def name(self): - """ Returns the name of the device. """ + """Return the name of the alarm.""" return self._name @property def code_format(self): - """ One or more characters if code is defined. """ + """One or more characters if code is defined.""" return None if self._code is None else '.+' @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self._alarm.state == 'Disarmed': return STATE_ALARM_DISARMED elif self._alarm.state == 'Armed Stay': @@ -81,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): return STATE_UNKNOWN def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" if not self._validate_code(code, 'arming home'): return from pyalarmdotcom.pyalarmdotcom import Alarmdotcom @@ -90,7 +88,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): _alarm.disarm() def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" if not self._validate_code(code, 'arming home'): return from pyalarmdotcom.pyalarmdotcom import Alarmdotcom @@ -99,7 +97,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): _alarm.arm_stay() def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" if not self._validate_code(code, 'arming home'): return from pyalarmdotcom.pyalarmdotcom import Alarmdotcom @@ -108,7 +106,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): _alarm.arm_away() def _validate_code(self, code, state): - """ Validate given code. """ + """Validate given code.""" check = self._code is None or code == self._code if not check: _LOGGER.warning('Wrong code entered for %s', state) diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index a6e280d1dd1..3e904601638 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -1,6 +1,4 @@ """ -homeassistant.components.alarm_control_panel.manual -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for manual alarms. For more details about this platform, please refer to the documentation at @@ -24,8 +22,7 @@ DEFAULT_TRIGGER_TIME = 120 def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the manual alarm platform. """ - + """Setup the manual alarm platform.""" add_devices([ManualAlarm( hass, config.get('name', DEFAULT_ALARM_NAME), @@ -47,6 +44,7 @@ class ManualAlarm(alarm.AlarmControlPanel): """ def __init__(self, hass, name, code, pending_time, trigger_time): + """Initalize the manual alarm panel.""" self._state = STATE_ALARM_DISARMED self._hass = hass self._name = name @@ -57,17 +55,17 @@ class ManualAlarm(alarm.AlarmControlPanel): @property def should_poll(self): - """ No polling needed. """ + """No polling needed.""" return False @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \ self._pending_time and self._state_ts + self._pending_time > \ @@ -85,11 +83,11 @@ class ManualAlarm(alarm.AlarmControlPanel): @property def code_format(self): - """ One or more characters. """ + """One or more characters.""" return None if self._code is None else '.+' def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" if not self._validate_code(code, STATE_ALARM_DISARMED): return @@ -98,7 +96,7 @@ class ManualAlarm(alarm.AlarmControlPanel): self.update_ha_state() def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" if not self._validate_code(code, STATE_ALARM_ARMED_HOME): return @@ -112,7 +110,7 @@ class ManualAlarm(alarm.AlarmControlPanel): self._state_ts + self._pending_time) def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" if not self._validate_code(code, STATE_ALARM_ARMED_AWAY): return @@ -126,7 +124,7 @@ class ManualAlarm(alarm.AlarmControlPanel): self._state_ts + self._pending_time) def alarm_trigger(self, code=None): - """ Send alarm trigger command. No code needed. """ + """Send alarm trigger command. No code needed.""" self._state = STATE_ALARM_TRIGGERED self._state_ts = dt_util.utcnow() self.update_ha_state() @@ -141,7 +139,7 @@ class ManualAlarm(alarm.AlarmControlPanel): self._state_ts + self._pending_time + self._trigger_time) def _validate_code(self, code, state): - """ Validate given code. """ + """Validate given code.""" check = self._code is None or code == self._code if not check: _LOGGER.warning('Invalid code given for %s', state) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index 7ba6ba057ed..0e86e0df875 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -1,6 +1,4 @@ """ -homeassistant.components.alarm_control_panel.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This platform enables the possibility to control a MQTT alarm. For more details about this platform, please refer to the documentation at @@ -26,8 +24,7 @@ DEPENDENCIES = ['mqtt'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the MQTT platform. """ - + """Setup the MQTT platform.""" if config.get('state_topic') is None: _LOGGER.error("Missing required variable: state_topic") return False @@ -51,10 +48,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=abstract-method class MqttAlarm(alarm.AlarmControlPanel): - """ represents a MQTT alarm status within home assistant. """ + """Represent a MQTT alarm status.""" def __init__(self, hass, name, state_topic, command_topic, qos, payload_disarm, payload_arm_home, payload_arm_away, code): + """Initalize the MQTT alarm panel.""" self._state = STATE_UNKNOWN self._hass = hass self._name = name @@ -67,7 +65,7 @@ class MqttAlarm(alarm.AlarmControlPanel): self._code = str(code) if code else None def message_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """A new MQTT message has been received.""" if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED): @@ -80,47 +78,47 @@ class MqttAlarm(alarm.AlarmControlPanel): @property def should_poll(self): - """ No polling needed """ + """No polling needed.""" return False @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" return self._state @property def code_format(self): - """ One or more characters if code is defined """ + """One or more characters if code is defined.""" return None if self._code is None else '.+' def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" if not self._validate_code(code, 'disarming'): return mqtt.publish(self.hass, self._command_topic, self._payload_disarm, self._qos) def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" if not self._validate_code(code, 'arming home'): return mqtt.publish(self.hass, self._command_topic, self._payload_arm_home, self._qos) def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" if not self._validate_code(code, 'arming away'): return mqtt.publish(self.hass, self._command_topic, self._payload_arm_away, self._qos) def _validate_code(self, code, state): - """ Validate given code. """ + """Validate given code.""" check = self._code is None or code == self._code if not check: _LOGGER.warning('Wrong code entered for %s', state) diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index e696ec682b2..2b3facbdb0e 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -1,6 +1,4 @@ """ -homeassistant.components.alarm_control_panel.nx584 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for NX584 alarm control panels. For more details about this platform, please refer to the documentation at @@ -16,12 +14,11 @@ from homeassistant.const import ( STATE_UNKNOWN) REQUIREMENTS = ['pynx584==0.2'] - _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup nx584. """ + """Setup nx584 platform.""" host = config.get('host', 'localhost:5007') try: @@ -32,8 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NX584Alarm(alarm.AlarmControlPanel): - """ NX584-based alarm panel. """ + """Represents the NX584-based alarm panel.""" + def __init__(self, hass, host, name): + """Initalize the nx584 alarm panel.""" from nx584 import client self._hass = hass self._host = host @@ -46,22 +45,22 @@ class NX584Alarm(alarm.AlarmControlPanel): @property def should_poll(self): - """ Polling needed. """ + """Polling needed.""" return True @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def code_format(self): - """ Characters if code is defined. """ + """The characters if code is defined.""" return '[0-9]{4}([0-9]{2})?' @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" try: part = self._alarm.list_partitions()[0] zones = self._alarm.list_zones() @@ -90,17 +89,17 @@ class NX584Alarm(alarm.AlarmControlPanel): return STATE_ALARM_ARMED_AWAY def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" self._alarm.disarm(code) def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" self._alarm.arm('home') def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" self._alarm.arm('auto') def alarm_trigger(self, code=None): - """ Alarm trigger command. """ + """Alarm trigger command.""" raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index d89992eafed..9bc25af3b69 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -1,10 +1,8 @@ """ -homeassistant.components.alarm_control_panel.verisure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interfaces with Verisure alarm control panel. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/verisure/ +https://home-assistant.io/components/alarm_control_panel.verisure/ """ import logging @@ -19,8 +17,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Verisure platform. """ - + """Setup the Verisure platform.""" alarms = [] if int(hub.config.get('alarm', '1')): hub.update_alarms() @@ -33,30 +30,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=abstract-method class VerisureAlarm(alarm.AlarmControlPanel): - """ Represents a Verisure alarm status. """ + """Represent a Verisure alarm status.""" def __init__(self, device_id): + """Initalize the Verisure alarm panel.""" self._id = device_id self._state = STATE_UNKNOWN self._digits = int(hub.config.get('code_digits', '4')) @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return 'Alarm {}'.format(self._id) @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" return self._state @property def code_format(self): - """ code format as regex """ + """The code format as regex.""" return '^\\d{%s}$' % self._digits def update(self): - """ Update alarm status """ + """Update alarm status.""" hub.update_alarms() if hub.alarm_status[self._id].status == 'unarmed': @@ -71,21 +69,21 @@ class VerisureAlarm(alarm.AlarmControlPanel): hub.alarm_status[self._id].status) def alarm_disarm(self, code=None): - """ Send disarm command. """ + """Send disarm command.""" hub.my_pages.alarm.set(code, 'DISARMED') _LOGGER.info('verisure alarm disarming') hub.my_pages.alarm.wait_while_pending() self.update() def alarm_arm_home(self, code=None): - """ Send arm home command. """ + """Send arm home command.""" hub.my_pages.alarm.set(code, 'ARMED_HOME') _LOGGER.info('verisure alarm arming home') hub.my_pages.alarm.wait_while_pending() self.update() def alarm_arm_away(self, code=None): - """ Send arm away command. """ + """Send arm away command.""" hub.my_pages.alarm.set(code, 'ARMED_AWAY') _LOGGER.info('verisure alarm arming away') hub.my_pages.alarm.wait_while_pending() diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 65d26a2360e..806d6874a8d 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -97,21 +97,24 @@ def _handle_alexa(handler, path_match, data): class SpeechType(enum.Enum): - """Alexa speech types.""" + """The Alexa speech types.""" + plaintext = "PlainText" ssml = "SSML" class CardType(enum.Enum): - """Alexa card types.""" + """The Alexa card types.""" + simple = "Simple" link_account = "LinkAccount" class AlexaResponse(object): - """Helps generating the response for Alexa.""" + """Help generating the response for Alexa.""" def __init__(self, hass, intent=None): + """Initialize the response.""" self.hass = hass self.speech = None self.card = None @@ -125,7 +128,7 @@ class AlexaResponse(object): self.variables = {} def add_card(self, card_type, title, content): - """ Add a card to the response. """ + """Add a card to the response.""" assert self.card is None card = { @@ -141,7 +144,7 @@ class AlexaResponse(object): self.card = card def add_speech(self, speech_type, text): - """ Add speech to the response. """ + """Add speech to the response.""" assert self.speech is None key = 'ssml' if speech_type == SpeechType.ssml else 'text' @@ -163,7 +166,7 @@ class AlexaResponse(object): } def as_dict(self): - """Returns response in an Alexa valid dict.""" + """Return response in an Alexa valid dict.""" response = { 'shouldEndSession': self.should_end_session } diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py index b7c22b3a7d9..fd064075458 100644 --- a/homeassistant/components/apcupsd.py +++ b/homeassistant/components/apcupsd.py @@ -1,8 +1,5 @@ """ -homeassistant.components.apcupsd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sets up and provides access to the status output of APCUPSd via its Network -Information Server (NIS). +Support for status output of APCUPSd via its Network Information Server (NIS). For more details about this component, please refer to the documentation at https://home-assistant.io/components/apcupsd/ @@ -34,7 +31,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Use config values to set up a function enabling status retrieval. """ + """Use config values to set up a function enabling status retrieval.""" global DATA host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST) @@ -54,11 +51,14 @@ def setup(hass, config): class APCUPSdData(object): + """Stores the data retrieved from APCUPSd. + + For each entity to use, acts as the single point responsible for fetching + updates from the server. """ - Stores the data retrieved from APCUPSd for each entity to use, acts as the - single point responsible for fetching updates from the server. - """ + def __init__(self, host, port): + """Initialize the data oject.""" from apcaccess import status self._host = host self._port = port @@ -68,17 +68,15 @@ class APCUPSdData(object): @property def status(self): - """ Get latest update if throttle allows. Return status. """ + """Get latest update if throttle allows. Return status.""" self.update() return self._status def _get_status(self): - """ Get the status from APCUPSd and parse it into a dict. """ + """Get the status from APCUPSd and parse it into a dict.""" return self._parse(self._get(host=self._host, port=self._port)) @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self, **kwargs): - """ - Fetch the latest status from APCUPSd and store it in self._status. - """ + """Fetch the latest status from APCUPSd.""" self._status = self._get_status() diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 6f785f19896..65ef2d84cfd 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -34,7 +34,6 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): """Register the API with the HTTP interface.""" - # /api - for validation purposes hass.http.register_path('GET', URL_API, _handle_get_api) @@ -96,7 +95,7 @@ def setup(hass, config): def _handle_get_api(handler, path_match, data): - """Renders the debug interface.""" + """Render the debug interface.""" handler.write_json_message("API running.") @@ -114,7 +113,7 @@ def _handle_get_api_stream(handler, path_match, data): restrict = restrict.split(',') def write_message(payload): - """Writes a message to the output.""" + """Write a message to the output.""" with write_lock: msg = "data: {}\n\n".format(payload) @@ -127,7 +126,7 @@ def _handle_get_api_stream(handler, path_match, data): block.set() def forward_events(event): - """Forwards events to the open request.""" + """Forward events to the open request.""" nonlocal gracefully_closed if block.is_set() or event.event_type == EVENT_TIME_CHANGED: @@ -171,17 +170,17 @@ def _handle_get_api_stream(handler, path_match, data): def _handle_get_api_config(handler, path_match, data): - """Returns the Home Assistant configuration.""" + """Return 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.""" + """Return 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.""" + """Return the state of a specific entity.""" entity_id = path_match.group('entity_id') state = handler.server.hass.states.get(entity_id) @@ -193,7 +192,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. + """Handle updating the state of an entity. This handles the following paths: /api/states/ @@ -240,15 +239,14 @@ def _handle_delete_state_entity(handler, path_match, data): def _handle_get_api_events(handler, path_match, data): - """Handles getting overview of event listeners.""" + """Handle 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. + """Handle firing of an event. - This handles the following paths: - /api/events/ + This handles the following paths: /api/events/ Events from /api are threated as remote events. """ @@ -276,16 +274,15 @@ 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.""" + """Handle 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. + """Handle calling a service. - This handles the following paths: - /api/services// + This handles the following paths: /api/services// """ domain = path_match.group('domain') service = path_match.group('service') @@ -298,7 +295,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.""" + """Handle adding an event forwarding target.""" try: host = data['host'] api_password = data['api_password'] @@ -331,7 +328,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.""" + """Handle deleting an event forwarding target.""" try: host = data['host'] except KeyError: @@ -354,12 +351,12 @@ def _handle_delete_api_event_forward(handler, path_match, data): def _handle_get_api_components(handler, path_match, data): - """Returns all the loaded components.""" + """Return 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.""" + """Return the logged errors for this session.""" handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME), False) diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py index 05db57394dd..767d5c491cf 100644 --- a/homeassistant/components/arduino.py +++ b/homeassistant/components/arduino.py @@ -1,8 +1,5 @@ """ -components.arduino -~~~~~~~~~~~~~~~~~~ -Arduino component that connects to a directly attached Arduino board which -runs with the Firmata firmware. +Support for Arduino boards running with the Firmata firmware. For more details about this component, please refer to the documentation at https://home-assistant.io/components/arduino/ @@ -20,8 +17,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Setup the Arduino component. """ - + """Setup the Arduino component.""" if not validate_config(config, {DOMAIN: ['port']}, _LOGGER): @@ -40,11 +36,11 @@ def setup(hass, config): return False def stop_arduino(event): - """ Stop the Arduino service. """ + """Stop the Arduino service.""" BOARD.disconnect() def start_arduino(event): - """ Start the Arduino service. """ + """Start the Arduino service.""" hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) @@ -53,15 +49,16 @@ def setup(hass, config): class ArduinoBoard(object): - """ Represents an Arduino board. """ + """Representation of an Arduino board.""" def __init__(self, port): + """Initialize the board.""" from PyMata.pymata import PyMata self._port = port self._board = PyMata(self._port, verbose=False) def set_mode(self, pin, direction, mode): - """ Sets the mode and the direction of a given pin. """ + """Set the mode and the direction of a given pin.""" if mode == 'analog' and direction == 'in': self._board.set_pin_mode(pin, self._board.INPUT, @@ -84,31 +81,31 @@ class ArduinoBoard(object): self._board.PWM) def get_analog_inputs(self): - """ Get the values from the pins. """ + """Get the values from the pins.""" self._board.capability_query() return self._board.get_analog_response_table() def set_digital_out_high(self, pin): - """ Sets a given digital pin to high. """ + """Set a given digital pin to high.""" self._board.digital_write(pin, 1) def set_digital_out_low(self, pin): - """ Sets a given digital pin to low. """ + """Set a given digital pin to low.""" self._board.digital_write(pin, 0) def get_digital_in(self, pin): - """ Gets the value from a given digital pin. """ + """Get the value from a given digital pin.""" self._board.digital_read(pin) def get_analog_in(self, pin): - """ Gets the value from a given analog pin. """ + """Get the value from a given analog pin.""" self._board.analog_read(pin) def get_firmata(self): - """ Return the version of the Firmata firmware. """ + """Return the version of the Firmata firmware.""" return self._board.get_firmata_version() def disconnect(self): - """ Disconnects the board and closes the serial connection. """ + """Disconnect the board and close the serial connection.""" self._board.reset() self._board.close() diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9c464f6954e..31cac543b6c 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to setup simple automation rules via the config file. +Allow to setup simple automation rules via the config file. For more details about this component, please refer to the documentation at https://home-assistant.io/components/automation/ @@ -12,13 +10,14 @@ from homeassistant.bootstrap import prepare_setup_platform from homeassistant.const import CONF_PLATFORM from homeassistant.components import logbook from homeassistant.helpers.service import call_from_config +from homeassistant.helpers.service import validate_service_call + DOMAIN = 'automation' DEPENDENCIES = ['group'] CONF_ALIAS = 'alias' -CONF_SERVICE = 'service' CONF_CONDITION = 'condition' CONF_ACTION = 'action' @@ -35,25 +34,25 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Sets up automation. """ + """Setup the automation.""" config_key = DOMAIN found = 1 while config_key in config: - # check for one block syntax + # Check for one block syntax if isinstance(config[config_key], dict): config_block = _migrate_old_config(config[config_key]) name = config_block.get(CONF_ALIAS, config_key) _setup_automation(hass, config_block, name, config) - # check for multiple block syntax + # Check for multiple block syntax elif isinstance(config[config_key], list): for list_no, config_block in enumerate(config[config_key]): name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key, list_no)) _setup_automation(hass, config_block, name, config) - # any scalar value is incorrect + # Any scalar value is incorrect else: _LOGGER.error('Error in config in section %s.', config_key) @@ -64,8 +63,7 @@ def setup(hass, config): def _setup_automation(hass, config_block, name, config): - """ Setup one instance of automation """ - + """Setup one instance of automation.""" action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) if action is None: @@ -83,14 +81,14 @@ def _setup_automation(hass, config_block, name, config): def _get_action(hass, config, name): - """ Return an action based on a config. """ - - if CONF_SERVICE not in config: - _LOGGER.error('Error setting up %s, no action specified.', name) + """Return an action based on a configuration.""" + validation_error = validate_service_call(config) + if validation_error: + _LOGGER.error(validation_error) return None def action(): - """ Action to be executed. """ + """Action to be executed.""" _LOGGER.info('Executing %s', name) logbook.log_entry(hass, name, 'has been triggered', DOMAIN) @@ -100,7 +98,7 @@ def _get_action(hass, config, name): def _migrate_old_config(config): - """ Migrate old config to new. """ + """Migrate old configuration to new.""" if CONF_PLATFORM not in config: return config @@ -134,8 +132,7 @@ def _migrate_old_config(config): def _process_if(hass, config, p_config, action): - """ Processes if checks. """ - + """Process if checks.""" cond_type = p_config.get(CONF_CONDITION_TYPE, DEFAULT_CONDITION_TYPE).lower() @@ -165,12 +162,12 @@ def _process_if(hass, config, p_config, action): if cond_type == CONDITION_TYPE_AND: def if_action(): - """ AND all conditions. """ + """AND all conditions.""" if all(check() for check in checks): action() else: def if_action(): - """ OR all conditions. """ + """OR all conditions.""" if any(check() for check in checks): action() @@ -178,7 +175,7 @@ def _process_if(hass, config, p_config, action): def _process_trigger(hass, config, trigger_configs, name, action): - """ Setup triggers. """ + """Setup the triggers.""" if isinstance(trigger_configs, dict): trigger_configs = [trigger_configs] @@ -195,7 +192,7 @@ def _process_trigger(hass, config, trigger_configs, name, action): def _resolve_platform(method, hass, config, platform): - """ Find automation platform. """ + """Find the automation platform.""" if platform is None: return None platform = prepare_setup_platform(hass, config, DOMAIN, platform) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 7fc33df0031..80dd6c29f6b 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers event listening automation rules. +Offer event listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#event-trigger @@ -15,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def trigger(hass, config, action): - """ Listen for events based on config. """ + """Listen for events based on configuration.""" event_type = config.get(CONF_EVENT_TYPE) if event_type is None: @@ -25,7 +23,7 @@ def trigger(hass, config, action): event_data = config.get(CONF_EVENT_DATA) def handle_event(event): - """ Listens for events and calls the action when data matches. """ + """Listen for events and calls the action when data matches.""" if not event_data or all(val == event.data.get(key) for key, val in event_data.items()): action() diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 8ea5f1bc6e5..db63d81e54b 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers MQTT listening automation rules. +Offer MQTT listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#mqtt-trigger @@ -17,7 +15,7 @@ CONF_PAYLOAD = 'payload' def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" topic = config.get(CONF_TOPIC) payload = config.get(CONF_PAYLOAD) @@ -27,7 +25,7 @@ def trigger(hass, config, action): return False def mqtt_automation_listener(msg_topic, msg_payload, qos): - """ Listens for MQTT messages. """ + """Listen for MQTT messages.""" if payload is None or payload == msg_payload: action() diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 10c2402bb0e..74f5b3ba805 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.numeric_state -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers numeric state listening automation rules. +Offer numeric state listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#numeric-state-trigger @@ -21,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) def _renderer(hass, value_template, state): - """Render state value.""" + """Render the state value.""" if value_template is None: return state.state @@ -29,7 +27,7 @@ def _renderer(hass, value_template, state): def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) if entity_id is None: @@ -50,8 +48,7 @@ def trigger(hass, config, action): # pylint: disable=unused-argument def state_automation_listener(entity, from_s, to_s): - """ Listens for state changes and calls action. """ - + """Listen for state changes and calls action.""" # Fire action if we go from outside range into range if _in_range(above, below, renderer(to_s)) and \ (from_s is None or not _in_range(above, below, renderer(from_s))): @@ -64,8 +61,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with state based condition. """ - + """Wrap action method with state based condition.""" entity_id = config.get(CONF_ENTITY_ID) if entity_id is None: @@ -85,7 +81,7 @@ def if_action(hass, config): renderer = partial(_renderer, hass, value_template) def if_numeric_state(): - """ Test numeric state condition. """ + """Test numeric state condition.""" state = hass.states.get(entity_id) return state is not None and _in_range(above, below, renderer(state)) @@ -93,7 +89,7 @@ def if_action(hass, config): def _in_range(range_start, range_end, value): - """ Checks if value is inside the range """ + """Check if value is inside the range.""" try: value = float(value) except ValueError: diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index b9c4164e584..d87d7e3fff6 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.state -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers state listening automation rules. +Offer state listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#state-trigger @@ -25,7 +23,7 @@ CONF_FOR = "for" def get_time_config(config): - """ Helper function to extract the time specified in the config """ + """Helper function to extract the time specified in the configuration.""" if CONF_FOR not in config: return None @@ -51,7 +49,7 @@ def get_time_config(config): def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) if entity_id is None: @@ -72,17 +70,15 @@ def trigger(hass, config, action): return None def state_automation_listener(entity, from_s, to_s): - """ Listens for state changes and calls action. """ - + """Listen for state changes and calls action.""" def state_for_listener(now): - """ Fires on state changes after a delay and calls action. """ + """Fire on state changes after a delay and calls action.""" 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. """ + """Fire on changes and cancel for listener if changed.""" if inner_to_s == to_s: return hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener) @@ -106,7 +102,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with state based condition. """ + """Wrap action method with state based condition.""" entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) @@ -123,7 +119,7 @@ def if_action(hass, config): state = str(state) def if_state(): - """ Test if condition. """ + """Test if condition.""" is_state = hass.states.is_state(entity_id, state) return (time_delta is None and is_state or time_delta is not None and diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 9cd50fedbd9..63d7715ef7d 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.sun -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers sun based automation rules. +Offer sun based automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#sun-trigger @@ -29,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) def trigger(hass, config, action): - """ Listen for events based on config. """ + """Listen for events based on configuration.""" event = config.get(CONF_EVENT) if event is None: @@ -55,7 +53,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with sun based condition. """ + """Wrap action method with sun based condition.""" before = config.get(CONF_BEFORE) after = config.get(CONF_AFTER) @@ -106,8 +104,7 @@ def if_action(hass, config): return sun.next_setting(hass) + after_offset def time_if(): - """ Validate time based if-condition """ - + """Validate time based if-condition.""" now = dt_util.now() before = before_func() after = after_func() @@ -126,6 +123,7 @@ def if_action(hass, config): def _parse_offset(raw_offset): + """Parse the offset.""" if raw_offset is None: return timedelta(0) diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 4aaac359c46..aae892ea80d 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers template automation rules. +Offer template automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#template-trigger @@ -16,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is None: @@ -27,7 +25,7 @@ def trigger(hass, config, action): already_triggered = False def event_listener(event): - """ Listens for state changes and calls action. """ + """Listen for state changes and calls action.""" nonlocal already_triggered template_result = _check_template(hass, value_template) @@ -43,8 +41,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with state based condition. """ - + """Wrap action method with state based condition.""" value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is None: @@ -55,7 +52,7 @@ def if_action(hass, config): def _check_template(hass, value_template): - """ Checks if result of template is true """ + """Check if result of template is true.""" try: value = template.render(hass, value_template, {}) except TemplateError: diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index d02765f75c6..f28e95c6f7a 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.time -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers time listening automation rules. +Offer time listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#time-trigger @@ -24,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" if CONF_AFTER in config: after = dt_util.parse_time_str(config[CONF_AFTER]) if after is None: @@ -42,7 +40,7 @@ def trigger(hass, config, action): return False def time_automation_listener(now): - """ Listens for time changes and calls action. """ + """Listen for time changes and calls action.""" action() track_time_change(hass, time_automation_listener, @@ -52,7 +50,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with time based condition. """ + """Wrap action method with time based condition.""" before = config.get(CONF_BEFORE) after = config.get(CONF_AFTER) weekday = config.get(CONF_WEEKDAY) @@ -76,7 +74,7 @@ def if_action(hass, config): return None def time_if(): - """ Validate time based if-condition """ + """Validate time based if-condition.""" now = dt_util.now() if before is not None and now > now.replace(hour=before.hour, minute=before.minute): @@ -99,7 +97,7 @@ def if_action(hass, config): def _error_time(value, key): - """ Helper method to print error. """ + """Helper method to print error.""" _LOGGER.error( "Received invalid value for '%s': %s", key, value) if isinstance(value, int): diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 7dc551de32c..a5f7259b112 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -1,7 +1,5 @@ """ -homeassistant.components.automation.zone -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Offers zone automation rules. +Offer zone automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#zone-trigger @@ -22,7 +20,7 @@ DEFAULT_EVENT = EVENT_ENTER def trigger(hass, config, action): - """ Listen for state changes based on `config`. """ + """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) @@ -35,7 +33,7 @@ def trigger(hass, config, action): event = config.get(CONF_EVENT, DEFAULT_EVENT) def zone_automation_listener(entity, from_s, to_s): - """ Listens for state changes and calls action. """ + """Listen for state changes and calls action.""" if from_s and None in (from_s.attributes.get(ATTR_LATITUDE), from_s.attributes.get(ATTR_LONGITUDE)) or \ None in (to_s.attributes.get(ATTR_LATITUDE), @@ -57,7 +55,7 @@ def trigger(hass, config, action): def if_action(hass, config): - """ Wraps action method with zone based condition. """ + """Wrap action method with zone based condition.""" entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) @@ -68,14 +66,14 @@ def if_action(hass, config): return False def if_in_zone(): - """ Test if condition. """ + """Test if condition.""" return _in_zone(hass, zone_entity_id, hass.states.get(entity_id)) return if_in_zone def _in_zone(hass, zone_entity_id, state): - """ Check if state is in zone. """ + """Check if state is in zone.""" if not state or None in (state.attributes.get(ATTR_LATITUDE), state.attributes.get(ATTR_LONGITUDE)): return False diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 2fddef9ca3a..fe47a91abb1 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -1,6 +1,5 @@ """ -Component to interface with binary sensors (sensors which only know two states) -that can be monitored. +Component to interface with binary sensors. For more details about this component, please refer to the documentation at https://home-assistant.io/components/binary_sensor/ @@ -10,7 +9,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, zwave, wink) +from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -38,6 +37,7 @@ DISCOVERY_PLATFORMS = { bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', zwave.DISCOVER_BINARY_SENSORS: 'zwave', + wemo.DISCOVER_BINARY_SENSORS: 'wemo', wink.DISCOVER_BINARY_SENSORS: 'wink' } diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index 8a08ef3a6ac..0c3fed960ea 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -1,5 +1,5 @@ """ -Provides a binary sensor to track online status of a UPS. +Support for tracking the 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/ @@ -17,8 +17,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class OnlineStatus(BinarySensorDevice): - """Binary sensor to represent UPS online status.""" + """Represent UPS online status.""" + def __init__(self, config, data): + """Initialize the APCUPSd device.""" self._config = config self._data = data self._state = None @@ -26,17 +28,14 @@ class OnlineStatus(BinarySensorDevice): @property def name(self): - """ The name of the UPS online status sensor. """ + """Return the name of the UPS online status sensor.""" return self._config.get("name", DEFAULT_NAME) @property def is_on(self): - """True if the UPS is online, else False.""" + """Return true if the UPS is online, else false.""" return self._state == apcupsd.VALUE_ONLINE def update(self): - """ - Get the status report from APCUPSd (or cache) and set this entity's - state. - """ + """Get the status report from APCUPSd and set this entity's state.""" self._state = self._data.status[apcupsd.KEY_STATUS] diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index b56d906b2e6..61e0202fcdb 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -1,5 +1,5 @@ """ -The arest sensor will consume an exposed aREST API of a device. +Support for exposed aREST RESTful API of a device. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.arest/ @@ -22,7 +22,7 @@ CONF_PIN = 'pin' def setup_platform(hass, config, add_devices, discovery_info=None): - """Get the aREST binary sensor.""" + """Setup the aREST binary sensor.""" resource = config.get(CONF_RESOURCE) pin = config.get(CONF_PIN) @@ -53,9 +53,10 @@ 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.""" + """Implement an aREST binary sensor for a pin.""" def __init__(self, arest, resource, name, pin): + """Initialize the aREST device.""" self.arest = arest self._resource = resource self._name = name @@ -70,30 +71,32 @@ class ArestBinarySensor(BinarySensorDevice): @property def name(self): - """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 bool(self.arest.data.get('state')) def update(self): - """Gets the latest data from aREST API.""" + """Get 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.""" + def __init__(self, resource, pin): + """Initialize the aREST data object.""" self._resource = resource self._pin = pin self.data = {} @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """Gets the latest data from aREST device.""" + """Get 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 32ccad6df91..f9e192c7984 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -19,7 +19,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the available BloomSky weather binary sensors.""" + """Setup the available BloomSky weather binary sensors.""" logger = logging.getLogger(__name__) bloomsky = get_component('bloomsky') sensors = config.get('monitored_conditions', SENSOR_TYPES) @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BloomSkySensor(BinarySensorDevice): - """ Represents a single binary sensor in a BloomSky device. """ + """Represent a single binary sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): """Initialize a BloomSky binary sensor.""" @@ -53,7 +53,7 @@ class BloomSkySensor(BinarySensorDevice): @property def unique_id(self): - """Unique ID for this sensor.""" + """Return the unique ID for this sensor.""" return self._unique_id @property @@ -63,7 +63,7 @@ class BloomSkySensor(BinarySensorDevice): @property def is_on(self): - """If binary sensor is on.""" + """Return true if binary sensor is on.""" return self._state def update(self): diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index 2d58951ae74..17e3fe9c9a7 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -1,6 +1,5 @@ """ -Allows to configure custom shell commands to turn a value into a logical value -for a binary sensor. +Support for custom shell commands to to retrieve values. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.command/ @@ -25,7 +24,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.""" + """Setup the Command Sensor.""" if config.get('command') is None: _LOGGER.error('Missing required variable: "command"') return False @@ -44,11 +43,11 @@ 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. - """ + """Represent a command line binary sensor.""" + def __init__(self, hass, data, name, payload_on, payload_off, value_template): + """Initialize the Command line binary sensor.""" self._hass = hass self.data = data self._name = name @@ -60,16 +59,16 @@ class CommandBinarySensor(BinarySensorDevice): @property def name(self): - """The name of the sensor.""" + """Return the name of the 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 def update(self): - """Gets the latest data and updates the state.""" + """Get 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 8a1a3ea30bd..6f7d59c65fd 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -17,7 +17,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoBinarySensor(BinarySensorDevice): """A Demo binary sensor.""" + def __init__(self, name, state, sensor_class): + """Initialize the demo sensor.""" self._name = name self._state = state self._sensor_type = sensor_class diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index 3712936e57e..88cbceccc45 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -1,5 +1,5 @@ """ -Allows to configure a MQTT binary sensor. +Support for MQTT binary sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.mqtt/ @@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.mqtt/ import logging import homeassistant.components.mqtt as mqtt -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import (BinarySensorDevice, + SENSOR_CLASSES) from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers import template @@ -24,15 +25,20 @@ DEPENDENCIES = ['mqtt'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Add MQTT binary sensor.""" - if config.get('state_topic') is None: _LOGGER.error('Missing required variable: state_topic') return False + sensor_class = config.get('sensor_class') + if sensor_class not in SENSOR_CLASSES: + _LOGGER.warning('Unknown sensor class: %s', sensor_class) + sensor_class = None + add_devices([MqttBinarySensor( hass, config.get('name', DEFAULT_NAME), config.get('state_topic', None), + sensor_class, config.get('qos', DEFAULT_QOS), config.get('payload_on', DEFAULT_PAYLOAD_ON), config.get('payload_off', DEFAULT_PAYLOAD_OFF), @@ -41,13 +47,16 @@ 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.""" - def __init__(self, hass, name, state_topic, qos, payload_on, payload_off, - value_template): + """Representation a binary sensor that is updated by MQTT.""" + + def __init__(self, hass, name, state_topic, sensor_class, qos, payload_on, + payload_off, value_template): + """Initialize the MQTT binary sensor.""" self._hass = hass self._name = name self._state = False self._state_topic = state_topic + self._sensor_class = sensor_class self._payload_on = payload_on self._payload_off = payload_off self._qos = qos @@ -73,10 +82,15 @@ class MqttBinarySensor(BinarySensorDevice): @property def name(self): - """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 + + @property + def sensor_class(self): + """Return the class of this sensor.""" + return self._sensor_class diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index 8d1b9eb2ea7..cada64bc78f 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -90,7 +90,7 @@ class MySensorsBinarySensor(BinarySensorDevice): @property def should_poll(self): - """MySensor gateway pushes its state to HA.""" + """Mysensor gateway pushes its state to HA.""" return False @property diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index f686a368100..79530bad52f 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -26,7 +26,6 @@ BINARY_TYPES = ['fan', def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Nest binary sensors.""" - logger = logging.getLogger(__name__) try: for structure in nest.NEST.structures: diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index b5e36395a7a..5c4ca78b14e 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -66,6 +66,7 @@ class NX584ZoneSensor(BinarySensorDevice): """Represents a NX584 zone as a sensor.""" def __init__(self, zone, zone_type): + """Initialize the nx594 binary sensor.""" self._zone = zone self._zone_type = zone_type @@ -81,7 +82,7 @@ class NX584ZoneSensor(BinarySensorDevice): @property def name(self): - """Name of the binary sensor.""" + """Return the name of the binary sensor.""" return self._zone['name'] @property @@ -95,6 +96,7 @@ class NX584Watcher(threading.Thread): """Event listener thread to process NX584 events.""" def __init__(self, client, zone_sensors): + """Initialize nx584 watcher thread.""" super(NX584Watcher, self).__init__() self.daemon = True self._client = client @@ -115,7 +117,7 @@ class NX584Watcher(threading.Thread): self._process_zone_event(event) def _run(self): - # Throw away any existing events so we don't replay history + """Throw away any existing events so we don't replay history.""" self._client.get_events() while True: events = self._client.get_events() @@ -123,6 +125,7 @@ class NX584Watcher(threading.Thread): self._process_events(events) def run(self): + """Run the watcher.""" while True: try: self._run() diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 0e05a24826f..ece9d706646 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -1,5 +1,5 @@ """ -The rest binary sensor will consume responses sent by an exposed REST API. +Support for RESTful binary sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.rest/ @@ -52,7 +52,7 @@ class RestBinarySensor(BinarySensorDevice): @property def name(self): - """Name of the binary sensor.""" + """Return the name of the binary sensor.""" return self._name @property diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index 22df2795076..a52b020dfe2 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -1,5 +1,5 @@ """ -Allows to configure a binary sensor using RPi GPIO. +Support for 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/ @@ -20,8 +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.""" - + """Setup the Raspberry PI GPIO devices.""" pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE) bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME) invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC) @@ -36,10 +35,11 @@ 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.""" - def __init__(self, name, port, pull_mode, bouncetime, invert_logic): - # pylint: disable=no-member + """Represent a binary sensor that uses Raspberry Pi GPIO.""" + def __init__(self, name, port, pull_mode, bouncetime, invert_logic): + """Initialize the RPi binary sensor.""" + # pylint: disable=no-member self._name = name or DEVICE_DEFAULT_NAME self._port = port self._pull_mode = pull_mode @@ -50,9 +50,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice): self._state = rpi_gpio.read_input(self._port) def read_gpio(port): - """Reads state from GPIO.""" + """Read 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 @@ -62,10 +63,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice): @property def name(self): - """The name of the sensor.""" + """Return the name of the sensor.""" return self._name @property def is_on(self): - """Returns the state of the entity.""" + """Return 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 125e0f12b91..4048c884df6 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -23,6 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class BinarySensor(BinarySensorDevice, Sensor): """A binary sensor which is on when its state == CONF_VALUE_ON.""" + required = (CONF_VALUE_ON,) @property diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index f5a8899af96..ef33d128bd7 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -1,7 +1,8 @@ """ -homeassistant.components.binary_sensor.template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Support for exposing a templated binary_sensor +Support for exposing a templated binary sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.template/ """ import logging @@ -22,7 +23,6 @@ _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') @@ -70,11 +70,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BinarySensorTemplate(BinarySensorDevice): - """A virtual binary_sensor that triggers from another sensor.""" + """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): + """Initialize the Template binary sensor.""" self._hass = hass self._device = device self._name = friendly_name @@ -90,25 +91,32 @@ class BinarySensorTemplate(BinarySensorDevice): hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) def _event_listener(self, event): + if not hasattr(self, 'hass'): + return self.update_ha_state(True) @property def should_poll(self): + """No polling needed.""" return False @property def sensor_class(self): + """Return the sensor class of the sensor.""" return self._sensor_class @property def name(self): + """Return the name of the sensor.""" return self._name @property def is_on(self): + """Return true if sensor is on.""" return self._state def update(self): + """Get the latest data and update the state.""" try: value = template.render(self._hass, self._template) except TemplateError as ex: diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py new file mode 100644 index 00000000000..f3cd91baa04 --- /dev/null +++ b/homeassistant/components/binary_sensor/wemo.py @@ -0,0 +1,75 @@ +""" +Support for WeMo sensors. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.wemo/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.loader import get_component + +DEPENDENCIES = ['wemo'] + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument, too-many-function-args +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Register discovered WeMo binary sensors.""" + import pywemo.discovery as discovery + + if discovery_info is not None: + location = discovery_info[2] + mac = discovery_info[3] + device = discovery.device_from_description(location, mac) + + if device: + add_devices_callback([WemoBinarySensor(device)]) + + +class WemoBinarySensor(BinarySensorDevice): + """Represents a WeMo binary sensor.""" + + def __init__(self, device): + """Initialize the WeMo sensor.""" + self.wemo = device + self._state = None + + wemo = get_component('wemo') + wemo.SUBSCRIPTION_REGISTRY.register(self.wemo) + wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) + + def _update_callback(self, _device, _params): + """Called by the wemo device callback to update state.""" + _LOGGER.info( + 'Subscription update for %s', + _device) + self.update_ha_state(True) + + @property + def should_poll(self): + """No polling needed with subscriptions.""" + return False + + @property + def unique_id(self): + """Return the id of this WeMo device.""" + return "{}.{}".format(self.__class__, self.wemo.serialnumber) + + @property + def name(self): + """Return the name of the sevice if any.""" + return self.wemo.name + + @property + def is_on(self): + """True if sensor is on.""" + return self._state + + def update(self): + """Update WeMo state.""" + try: + self._state = self.wemo.get_state(True) + except AttributeError: + _LOGGER.warning('Could not update status for %s', self.name) diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index 8d858901849..3fe092f6cc8 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -22,7 +22,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Sets up the Wink platform.""" + """Setup the Wink platform.""" import pywink if discovery_info is None: @@ -42,16 +42,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WinkBinarySensorDevice(BinarySensorDevice, Entity): - """Represents a Wink sensor.""" + """Representation of a Wink sensor.""" def __init__(self, wink): + """Initialize the Wink binary sensor.""" 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.""" + """Return true if the binary sensor is on.""" if self.capability == "loudness": return self.wink.loudness_boolean() elif self.capability == "vibration": @@ -68,14 +69,14 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity): @property def unique_id(self): - """ Returns the id of this wink sensor """ + """Return 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 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() diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 49b6f12ed5c..ac9e542c9d5 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -19,8 +19,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice): - """ - Use multiple inheritance to turn a ZigBeeDigitalIn into a - BinarySensorDevice. - """ + """Use ZigBeeDigitalIn as binary sensor.""" + pass diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 683dbe117b5..ac846cb8df2 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -23,17 +23,19 @@ DEPENDENCIES = [] PHILIO = 0x013c PHILIO_SLIM_SENSOR = 0x0002 PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0) +WENZHOU = 0x0118 +WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0) WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event' DEVICE_MAPPINGS = { PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, + WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, } def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Z-Wave platform for sensors.""" - if discovery_info is None or NETWORK is None: return @@ -63,9 +65,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): - """Represents a binary sensor within Z-Wave.""" + """Representation of a binary sensor within Z-Wave.""" def __init__(self, value, sensor_class): + """Initialize the sensor.""" self._sensor_type = sensor_class # pylint: disable=import-error from openzwave.network import ZWaveNetwork @@ -98,12 +101,10 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): class ZWaveTriggerSensor(ZWaveBinarySensor): - """ - Represents a stateless sensor which triggers events just 'On' - within Z-Wave. - """ + """Representation of a stateless sensor within Z-Wave.""" def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60): + """Initialize the sensor.""" super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class) self._hass = hass self.re_arm_sec = re_arm_sec diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index 44a90007725..de9f4a18b91 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -1,6 +1,4 @@ """ -homeassistant.components.bloomsky -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for BloomSky weather station. For more details about this component, please refer to the documentation at @@ -32,7 +30,7 @@ DISCOVER_CAMERAS = 'bloomsky.camera' # pylint: disable=unused-argument,too-few-public-methods def setup(hass, config): - """ Setup BloomSky component. """ + """Setup BloomSky component.""" if not validate_config( config, {DOMAIN: [CONF_API_KEY]}, @@ -57,13 +55,13 @@ def setup(hass, config): class BloomSky(object): - """ Handle all communication with the BloomSky API. """ + """Handle all communication with the BloomSky API.""" # API documentation at http://weatherlution.com/bloomsky-api/ - API_URL = "https://api.bloomsky.com/api/skydata" def __init__(self, api_key): + """Initialize the BookSky.""" self._api_key = api_key self.devices = {} _LOGGER.debug("Initial bloomsky device load...") @@ -71,10 +69,7 @@ class BloomSky(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def refresh_devices(self): - """ - Uses the API to retreive a list of devices associated with an - account along with all the sensors on the device. - """ + """Use the API to retreive a list of devices.""" _LOGGER.debug("Fetching bloomsky update") response = requests.get(self.API_URL, headers={"Authorization": self._api_key}, @@ -84,7 +79,7 @@ class BloomSky(object): elif response.status_code != 200: _LOGGER.error("Invalid HTTP response: %s", response.status_code) return - # create dictionary keyed off of the device unique id + # Create dictionary keyed off of the device unique id self.devices.update({ device["DeviceID"]: device for device in response.json() }) diff --git a/homeassistant/components/browser.py b/homeassistant/components/browser.py index d171e4b5901..edfe1008c6e 100644 --- a/homeassistant/components/browser.py +++ b/homeassistant/components/browser.py @@ -10,9 +10,7 @@ SERVICE_BROWSE_URL = "browse_url" def setup(hass, config): - """ - Listen for browse_url events and open the url in the default web browser. - """ + """Listen for browse_url events.""" import webbrowser hass.services.register(DOMAIN, SERVICE_BROWSE_URL, diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 2915f37dd21..41860a62cb7 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -42,7 +42,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' # pylint: disable=too-many-branches def setup(hass, config): - """Initialize camera component.""" + """Setup the camera component.""" component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS) diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/camera/bloomsky.py index 6e03a136c6b..cd40b91a21c 100644 --- a/homeassistant/components/camera/bloomsky.py +++ b/homeassistant/components/camera/bloomsky.py @@ -1,6 +1,4 @@ """ -homeassistant.components.camera.bloomsky -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for a camera of a BloomSky weather station. For more details about this component, please refer to the documentation at @@ -18,17 +16,17 @@ DEPENDENCIES = ["bloomsky"] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ set up access to BloomSky cameras """ + """Setup access to BloomSky cameras.""" bloomsky = get_component('bloomsky') for device in bloomsky.BLOOMSKY.devices.values(): add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)]) class BloomSkyCamera(Camera): - """ Represents the images published from the BloomSky's camera. """ + """Representation of the images published from the BloomSky's camera.""" def __init__(self, bs, device): - """ set up for access to the BloomSky camera images """ + """Setup for access to the BloomSky camera images.""" super(BloomSkyCamera, self).__init__() self._name = device["DeviceName"] self._id = device["DeviceID"] @@ -37,16 +35,16 @@ class BloomSkyCamera(Camera): self._last_url = "" # _last_image will store images as they are downloaded so that the # frequent updates in home-assistant don't keep poking the server - # to download the same image over and over + # to download the same image over and over. self._last_image = "" self._logger = logging.getLogger(__name__) def camera_image(self): - """ Update the camera's image if it has changed. """ + """Update the camera's image if it has changed.""" try: self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"] self._bloomsky.refresh_devices() - # if the url hasn't changed then the image hasn't changed + # If the URL hasn't changed then the image hasn't changed. if self._url != self._last_url: response = requests.get(self._url, timeout=10) self._last_url = self._url @@ -59,5 +57,5 @@ class BloomSkyCamera(Camera): @property def name(self): - """ The name of this BloomSky device. """ + """Return the name of this BloomSky device.""" return self._name diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index 15ddeb31d72..e3ab0d6b059 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -21,6 +21,7 @@ class DemoCamera(Camera): """A Demo camera.""" def __init__(self, name): + """Initialize demo camera component.""" super().__init__() self._name = name diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index 47ebf1b7d5a..f8ba7dfe27d 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -1,6 +1,4 @@ """ -homeassistant.components.camera.foscam -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This component provides basic support for Foscam IP cameras. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Adds a Foscam IP Camera. """ + """Setup a Foscam IP Camera.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['username', 'password', 'ip']}, _LOGGER): return None @@ -28,9 +26,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-instance-attributes class FoscamCamera(Camera): - """ An implementation of a Foscam IP camera. """ + """An implementation of a Foscam IP camera.""" def __init__(self, device_info): + """Initialize a Foscam camera.""" super(FoscamCamera, self).__init__() ip_address = device_info.get('ip') @@ -48,8 +47,7 @@ class FoscamCamera(Camera): self._name, self._snap_picture_url) def camera_image(self): - """ Return a still image reponse from the camera. """ - + """Return a still image reponse from the camera.""" # Send the request to snap a picture and return raw jpg data response = requests.get(self._snap_picture_url) @@ -57,5 +55,5 @@ class FoscamCamera(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/camera/generic.py b/homeassistant/components/camera/generic.py index 514e94db1ef..bad0e3af6d9 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -1,6 +1,4 @@ """ -homeassistant.components.camera.generic -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for IP Cameras. For more details about this platform, please refer to the documentation at @@ -19,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Adds a generic IP Camera. """ + """Setup a generic IP Camera.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']}, _LOGGER): return None @@ -29,11 +27,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-instance-attributes class GenericCamera(Camera): - """ - A generic implementation of an IP camera that is reachable over a URL. - """ + """A generic implementation of an IP camera.""" def __init__(self, device_info): + """Initialize a generic camera.""" super().__init__() self._name = device_info.get('name', 'Generic Camera') self._username = device_info.get('username') @@ -41,7 +38,7 @@ class GenericCamera(Camera): self._still_image_url = device_info['still_image_url'] def camera_image(self): - """ Return a still image response from the camera. """ + """Return a still image response from the camera.""" if self._username and self._password: try: response = requests.get( @@ -61,5 +58,5 @@ class GenericCamera(Camera): @property def name(self): - """ Return the name of this device. """ + """Return the name of this device.""" return self._name diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 95bf9813b39..9d5c9d96b92 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -1,6 +1,4 @@ """ -homeassistant.components.camera.mjpeg -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for IP Cameras. 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 setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Adds a mjpeg IP Camera. """ + """Setup a MJPEG IP Camera.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']}, _LOGGER): return None @@ -33,11 +31,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-instance-attributes class MjpegCamera(Camera): - """ - A generic implementation of an IP camera that is reachable over a URL. - """ + """An implementation of an IP camera that is reachable over a URL.""" def __init__(self, device_info): + """Initialize a MJPEG camera.""" super().__init__() self._name = device_info.get('name', 'Mjpeg Camera') self._username = device_info.get('username') @@ -45,7 +42,7 @@ class MjpegCamera(Camera): self._mjpeg_url = device_info['mjpeg_url'] def camera_stream(self): - """ Return a mjpeg stream image response directly from the camera. """ + """Return a MJPEG stream image response directly from the camera.""" if self._username and self._password: return requests.get(self._mjpeg_url, auth=HTTPBasicAuth(self._username, @@ -56,10 +53,9 @@ class MjpegCamera(Camera): stream=True) def camera_image(self): - """ Return a still image response from the camera. """ - + """Return a still image response from the camera.""" def process_response(response): - """ Take in a response object, return the jpg from it. """ + """Take in a response object, return the jpg from it.""" data = b'' for chunk in response.iter_content(1024): data += chunk @@ -73,7 +69,7 @@ class MjpegCamera(Camera): return process_response(response) def mjpeg_stream(self, handler): - """ Generate an HTTP MJPEG stream from the camera. """ + """Generate an HTTP MJPEG stream from the camera.""" response = self.camera_stream() content_type = response.headers[CONTENT_TYPE_HEADER] @@ -88,5 +84,5 @@ class MjpegCamera(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/camera/uvc.py b/homeassistant/components/camera/uvc.py index e34b0e26859..0aaf147e4fb 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -1,6 +1,4 @@ """ -homeassistant.components.camera.uvc -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Ubiquiti's UVC cameras. For more details about this platform, please refer to the documentation at @@ -20,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """ Discover cameras on a Unifi NVR. """ + """Discover cameras on a Unifi NVR.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']}, _LOGGER): return None @@ -60,9 +58,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class UnifiVideoCamera(Camera): - """ A Ubiquiti Unifi Video Camera. """ + """A Ubiquiti Unifi Video Camera.""" def __init__(self, nvr, uuid, name): + """Initialize an Unifi camera.""" super(UnifiVideoCamera, self).__init__() self._nvr = nvr self._uuid = uuid @@ -73,23 +72,28 @@ class UnifiVideoCamera(Camera): @property def name(self): + """Return the name of this camera.""" return self._name @property def is_recording(self): + """Return true if the camera is recording.""" caminfo = self._nvr.get_camera(self._uuid) return caminfo['recordingSettings']['fullTimeRecordEnabled'] @property def brand(self): + """Return the brand of this camera.""" return 'Ubiquiti' @property def model(self): + """Return the model of this camera.""" caminfo = self._nvr.get_camera(self._uuid) return caminfo['model'] def _login(self): + """Login to the camera.""" from uvcclient import camera as uvc_camera from uvcclient import store as uvc_store @@ -131,6 +135,7 @@ class UnifiVideoCamera(Camera): return True def camera_image(self): + """Return the image of this camera.""" from uvcclient import camera as uvc_camera if not self._camera: if not self._login(): diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 681cc80cc9c..aecaa1cfadc 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -1,8 +1,5 @@ """ -homeassistant.components.configurator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A component to allow pieces of code to request configuration from the user. +Support to allow pieces of code to request configuration from the user. Initiate a request by calling the `request_config` method with a callback. This will return a request id that has to be used for future calls. @@ -38,9 +35,10 @@ _LOGGER = logging.getLogger(__name__) def request_config( hass, name, callback, description=None, description_image=None, submit_caption=None, fields=None): - """ Create a new request for config. - Will return an ID to be used for sequent calls. """ + """Create a new request for configuration. + Will return an ID to be used for sequent calls. + """ instance = _get_instance(hass) request_id = instance.request_config( @@ -53,7 +51,7 @@ def request_config( def notify_errors(request_id, error): - """ Add errors to a config request. """ + """Add errors to a config request.""" try: _REQUESTS[request_id].notify_errors(request_id, error) except KeyError: @@ -62,7 +60,7 @@ def notify_errors(request_id, error): def request_done(request_id): - """ Mark a config request as done. """ + """Mark a configuration request as done.""" try: _REQUESTS.pop(request_id).request_done(request_id) except KeyError: @@ -71,12 +69,12 @@ def request_done(request_id): def setup(hass, config): - """ Set up Configurator. """ + """Setup the configurator component.""" return True def _get_instance(hass): - """ Get an instance per hass object. """ + """Get an instance per hass object.""" try: return _INSTANCES[hass] except KeyError: @@ -89,11 +87,10 @@ def _get_instance(hass): class Configurator(object): - """ - Class to keep track of current configuration requests. - """ + """The class to keep track of current configuration requests.""" def __init__(self, hass): + """Initialize the configurator.""" self.hass = hass self._cur_id = 0 self._requests = {} @@ -104,8 +101,7 @@ class Configurator(object): def request_config( self, name, callback, description, description_image, submit_caption, fields): - """ Setup a request for configuration. """ - + """Setup a request for configuration.""" entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) if fields is None: @@ -133,7 +129,7 @@ class Configurator(object): return request_id def notify_errors(self, request_id, error): - """ Update the state with errors. """ + """Update the state with errors.""" if not self._validate_request_id(request_id): return @@ -147,7 +143,7 @@ class Configurator(object): self.hass.states.set(entity_id, STATE_CONFIGURE, new_data) def request_done(self, request_id): - """ Remove the config request. """ + """Remove the configuration request.""" if not self._validate_request_id(request_id): return @@ -160,13 +156,13 @@ class Configurator(object): self.hass.states.set(entity_id, STATE_CONFIGURED) def deferred_remove(event): - """ Remove the request state. """ + """Remove the request state.""" self.hass.states.remove(entity_id) self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove) def handle_service_call(self, call): - """ Handle a configure service call. """ + """Handle a configure service call.""" request_id = call.data.get(ATTR_CONFIGURE_ID) if not self._validate_request_id(request_id): @@ -180,10 +176,10 @@ class Configurator(object): callback(call.data.get(ATTR_FIELDS, {})) def _generate_unique_id(self): - """ Generates a unique configurator id. """ + """Generate a unique configurator ID.""" self._cur_id += 1 return "{}-{}".format(id(self), self._cur_id) def _validate_request_id(self, request_id): - """ Validate that the request belongs to this instance. """ + """Validate that the request belongs to this instance.""" return request_id in self._requests diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 9cf70fa2b62..74ba0648c74 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -1,7 +1,5 @@ """ -homeassistant.components.conversation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to have conversations with Home Assistant. +Support for functionality to have conversations with Home Assistant. For more details about this component, please refer to the documentation at https://home-assistant.io/components/conversation/ @@ -25,19 +23,18 @@ REQUIREMENTS = ['fuzzywuzzy==0.8.0'] def setup(hass, config): - """ Registers the process service. """ + """Register the process service.""" from fuzzywuzzy import process as fuzzyExtract logger = logging.getLogger(__name__) def process(service): - """ Parses text into commands for Home Assistant. """ + """Parse text into commands.""" if ATTR_TEXT not in service.data: logger.error("Received process service call without a text") return text = service.data[ATTR_TEXT].lower() - match = REGEX_TURN_COMMAND.match(text) if not match: @@ -45,11 +42,8 @@ def setup(hass, config): return name, command = match.groups() - entities = {state.entity_id: state.name for state in hass.states.all()} - - entity_ids = fuzzyExtract.extractOne(name, - entities, + entity_ids = fuzzyExtract.extractOne(name, entities, score_cutoff=65)[2] if not entity_ids: diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index a8cf5b5d417..a954ae8fd0f 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_sun_light_trigger -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to turn on lights based on the state of the sun and -devices. +Provides functionality to turn on lights based on the states. For more details about this component, please refer to the documentation at https://home-assistant.io/components/device_sun_light_trigger/ @@ -12,9 +9,9 @@ from datetime import timedelta import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME -from homeassistant.helpers.event import track_point_in_time, track_state_change - -from . import device_tracker, group, light, sun +from homeassistant.helpers.event import track_point_in_time +from homeassistant.helpers.event_decorators import track_state_change +from homeassistant.loader import get_component DOMAIN = "device_sun_light_trigger" DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun'] @@ -29,28 +26,26 @@ CONF_LIGHT_GROUP = 'light_group' CONF_DEVICE_GROUP = 'device_group' -# pylint: disable=too-many-branches +# pylint: disable=too-many-locals def setup(hass, config): - """ Triggers to turn lights on or off based on device precense. """ + """The triggers to turn lights on or off based on device presence.""" + logger = logging.getLogger(__name__) + device_tracker = get_component('device_tracker') + group = get_component('group') + light = get_component('light') + sun = get_component('sun') disable_turn_off = 'disable_turn_off' in config[DOMAIN] - light_group = config[DOMAIN].get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) - light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE) - device_group = config[DOMAIN].get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) - - logger = logging.getLogger(__name__) - device_entity_ids = group.get_entity_ids(hass, device_group, device_tracker.DOMAIN) if not device_entity_ids: logger.error("No devices found to track") - return False # Get the light IDs from the specified group @@ -58,116 +53,105 @@ def setup(hass, config): if not light_ids: logger.error("No lights found to turn on ") - return False def calc_time_for_light_when_sunset(): - """ Calculates the time when to start fading lights in when sun sets. - Returns None if no next_setting data available. """ + """Calculate the time when to start fading lights in when sun sets. + + Returns None if no next_setting data available. + """ next_setting = sun.next_setting(hass) - - if next_setting: - return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) - else: + if not next_setting: return None + return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) - def schedule_light_on_sun_rise(entity, old_state, new_state): - """The moment sun sets we want to have all the lights on. - We will schedule to have each light start after one another - and slowly transition in.""" + def turn_light_on_before_sunset(light_id): + """Helper function to turn on lights. - def turn_light_on_before_sunset(light_id): - """ Helper function to turn on lights slowly if there - are devices home and the light is not on yet. """ - if device_tracker.is_on(hass) and not light.is_on(hass, light_id): - - light.turn_on(hass, light_id, - transition=LIGHT_TRANSITION_TIME.seconds, - profile=light_profile) - - def turn_on(light_id): - """ Lambda can keep track of function parameters but not local - parameters. If we put the lambda directly in the below statement - only the last light will be turned on.. """ - return lambda now: turn_light_on_before_sunset(light_id) - - start_point = calc_time_for_light_when_sunset() - - if start_point: - for index, light_id in enumerate(light_ids): - track_point_in_time( - hass, turn_on(light_id), - (start_point + index * LIGHT_TRANSITION_TIME)) + Speed is slow if there are devices home and the light is not on yet. + """ + if not device_tracker.is_on(hass) or light.is_on(hass, light_id): + return + light.turn_on(hass, light_id, + transition=LIGHT_TRANSITION_TIME.seconds, + profile=light_profile) # Track every time sun rises so we can schedule a time-based # pre-sun set event - track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise, - sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) + @track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON, + sun.STATE_ABOVE_HORIZON) + def schedule_lights_at_sun_set(hass, entity, old_state, new_state): + """The moment sun sets we want to have all the lights on. - # If the sun is already above horizon - # schedule the time-based pre-sun set event + We will schedule to have each light start after one another + and slowly transition in. + """ + start_point = calc_time_for_light_when_sunset() + if not start_point: + return + + def turn_on(light_id): + """Lambda can keep track of function parameters. + + No local parameters. If we put the lambda directly in the below + statement only the last light will be turned on. + """ + return lambda now: turn_light_on_before_sunset(light_id) + + for index, light_id in enumerate(light_ids): + track_point_in_time(hass, turn_on(light_id), + start_point + index * LIGHT_TRANSITION_TIME) + + # If the sun is already above horizon schedule the time-based pre-sun set + # event. if sun.is_on(hass): - schedule_light_on_sun_rise(None, None, None) + schedule_lights_at_sun_set(hass, None, None, None) - def check_light_on_dev_state_change(entity, old_state, new_state): - """ Function to handle tracked device state changes. """ + @track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME) + def check_light_on_dev_state_change(hass, entity, old_state, new_state): + """Handle tracked device state changes.""" + # pylint: disable=unused-variable lights_are_on = group.is_on(hass, light_group) light_needed = not (lights_are_on or sun.is_on(hass)) - # Specific device came home ? - if entity != device_tracker.ENTITY_ID_ALL_DEVICES and \ - new_state.state == STATE_HOME: + # These variables are needed for the elif check + now = dt_util.now() + start_point = calc_time_for_light_when_sunset() - # These variables are needed for the elif check - now = dt_util.now() - start_point = calc_time_for_light_when_sunset() + # Do we need lights? + if light_needed: + logger.info("Home coming event for %s. Turning lights on", entity) + light.turn_on(hass, light_ids, profile=light_profile) - # Do we need lights? - if light_needed: + # Are we in the time span were we would turn on the lights + # if someone would be home? + # Check this by seeing if current time is later then the point + # in time when we would start putting the lights on. + elif (start_point and + start_point < now < sun.next_setting(hass)): - logger.info( - "Home coming event for %s. Turning lights on", entity) + # Check for every light if it would be on if someone was home + # when the fading in started and turn it on if so + for index, light_id in enumerate(light_ids): + if now > start_point + index * LIGHT_TRANSITION_TIME: + light.turn_on(hass, light_id) - light.turn_on(hass, light_ids, profile=light_profile) + else: + # If this light didn't happen to be turned on yet so + # will all the following then, break. + break - # Are we in the time span were we would turn on the lights - # if someone would be home? - # Check this by seeing if current time is later then the point - # in time when we would start putting the lights on. - elif (start_point and - start_point < now < sun.next_setting(hass)): - - # Check for every light if it would be on if someone was home - # when the fading in started and turn it on if so - for index, light_id in enumerate(light_ids): - - if now > start_point + index * LIGHT_TRANSITION_TIME: - light.turn_on(hass, light_id) - - else: - # If this light didn't happen to be turned on yet so - # will all the following then, break. - break - - # Did all devices leave the house? - elif (entity == device_group and - new_state.state == STATE_NOT_HOME and lights_are_on and - not disable_turn_off): + if not disable_turn_off: + @track_state_change(device_group, STATE_HOME, STATE_NOT_HOME) + def turn_off_lights_when_all_leave(hass, entity, old_state, new_state): + """Handle device group state change.""" + # pylint: disable=unused-variable + if not group.is_on(hass, light_group): + return logger.info( "Everyone has left but there are lights on. Turning them off") - light.turn_off(hass, light_ids) - # Track home coming of each device - track_state_change( - hass, device_entity_ids, check_light_on_dev_state_change, - STATE_NOT_HOME, STATE_HOME) - - # Track when all devices are gone to shut down lights - track_state_change( - hass, device_group, check_light_on_dev_state_change, - STATE_HOME, STATE_NOT_HOME) - return True diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 8c3fd62a0f8..4278d3db655 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -1,14 +1,11 @@ """ -homeassistant.components.device_tracker -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to keep track of devices. +Provide functionality to keep track of devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/device_tracker/ """ # pylint: disable=too-many-instance-attributes, too-many-arguments # pylint: disable=too-many-locals -import csv from datetime import timedelta import logging import os @@ -36,7 +33,6 @@ ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices') ENTITY_ID_FORMAT = DOMAIN + '.{}' -CSV_DEVICES = "known_devices.csv" YAML_DEVICES = 'known_devices.yaml' CONF_TRACK_NEW = "track_new_devices" @@ -72,7 +68,7 @@ _LOGGER = logging.getLogger(__name__) def is_on(hass, entity_id=None): - """ Returns if any or specified device is home. """ + """Return the state if any or a specified device is home.""" entity = entity_id or ENTITY_ID_ALL_DEVICES return hass.states.is_state(entity, STATE_HOME) @@ -80,23 +76,21 @@ def is_on(hass, entity_id=None): def see(hass, mac=None, dev_id=None, host_name=None, location_name=None, gps=None, gps_accuracy=None, battery=None): - """ Call service to notify you see device. """ + """Call service to notify you see device.""" data = {key: value for key, value in ((ATTR_MAC, mac), (ATTR_DEV_ID, dev_id), (ATTR_HOST_NAME, host_name), (ATTR_LOCATION_NAME, location_name), - (ATTR_GPS, gps)) if value is not None} + (ATTR_GPS, gps), + (ATTR_GPS_ACCURACY, gps_accuracy), + (ATTR_BATTERY, battery)) if value is not None} hass.services.call(DOMAIN, SERVICE_SEE, data) def setup(hass, config): - """ Setup device tracker """ + """Setup device tracker.""" yaml_path = hass.config.path(YAML_DEVICES) - csv_path = hass.config.path(CSV_DEVICES) - if os.path.isfile(csv_path) and not os.path.isfile(yaml_path) and \ - convert_csv_config(csv_path, yaml_path): - os.remove(csv_path) conf = config.get(DOMAIN, {}) if isinstance(conf, list): @@ -114,7 +108,7 @@ def setup(hass, config): devices) def setup_platform(p_type, p_config, disc_info=None): - """ Setup a device tracker platform. """ + """Setup a device tracker platform.""" platform = prepare_setup_platform(hass, config, DOMAIN, p_type) if platform is None: return @@ -140,21 +134,21 @@ def setup(hass, config): setup_platform(p_type, p_config) def device_tracker_discovered(service, info): - """ Called when a device tracker platform is discovered. """ + """Called when a device tracker platform is discovered.""" setup_platform(DISCOVERY_PLATFORMS[service], {}, info) discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), device_tracker_discovered) def update_stale(now): - """ Clean up stale devices. """ + """Clean up stale devices.""" tracker.update_stale(now) track_utc_time_change(hass, update_stale, second=range(0, 60, 5)) tracker.setup_group() def see_service(call): - """ Service to see a device. """ + """Service to see a device.""" args = {key: value for key, value in call.data.items() if key in (ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)} @@ -169,8 +163,10 @@ def setup(hass, config): class DeviceTracker(object): - """ Track devices """ + """Representation of a device tracker.""" + def __init__(self, hass, consider_home, track_new, home_range, devices): + """Initialize a device tracker.""" self.hass = hass self.devices = {dev.dev_id: dev for dev in devices} self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac} @@ -187,7 +183,7 @@ class DeviceTracker(object): def see(self, mac=None, dev_id=None, host_name=None, location_name=None, gps=None, gps_accuracy=None, battery=None): - """ Notify device tracker that you see a device. """ + """Notify the device tracker that you see a device.""" with self.lock: if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') @@ -226,14 +222,14 @@ class DeviceTracker(object): update_config(self.hass.config.path(YAML_DEVICES), dev_id, device) def setup_group(self): - """ Initializes group for all tracked devices. """ + """Initialize group for all tracked devices.""" entity_ids = (dev.entity_id for dev in self.devices.values() if dev.track) self.group = group.Group( self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False) def update_stale(self, now): - """ Update stale devices. """ + """Update stale devices.""" with self.lock: for device in self.devices.values(): if (device.track and device.last_update_home and @@ -242,7 +238,7 @@ class DeviceTracker(object): class Device(Entity): - """ Tracked device. """ + """Represent a tracked device.""" host_name = None location_name = None @@ -251,12 +247,13 @@ class Device(Entity): last_seen = None battery = None - # Track if the last update of this device was HOME + # Track if the last update of this device was HOME. last_update_home = False _state = STATE_NOT_HOME def __init__(self, hass, consider_home, home_range, track, dev_id, mac, name=None, picture=None, away_hide=False): + """Initialize a device.""" self.hass = hass self.entity_id = ENTITY_ID_FORMAT.format(dev_id) @@ -282,29 +279,29 @@ class Device(Entity): @property def gps_home(self): - """ Return if device is within range of home. """ + """Return if device is within range of home.""" distance = max( 0, self.hass.config.distance(*self.gps) - self.gps_accuracy) return self.gps is not None and distance <= self.home_range @property def name(self): - """ Returns the name of the entity. """ + """Return the name of the entity.""" return self.config_name or self.host_name or DEVICE_DEFAULT_NAME @property def state(self): - """ State of the device. """ + """Return the state of the device.""" return self._state @property def entity_picture(self): - """Picture of the device.""" + """Return the picture of the device.""" return self.config_picture @property def state_attributes(self): - """ Device state attributes. """ + """Return the device state attributes.""" attr = {} if self.gps: @@ -319,12 +316,12 @@ class Device(Entity): @property def hidden(self): - """ If device should be hidden. """ + """If device should be hidden.""" return self.away_hide and self.state != STATE_HOME def seen(self, host_name=None, location_name=None, gps=None, gps_accuracy=0, battery=None): - """ Mark the device as seen. """ + """Mark the device as seen.""" self.last_seen = dt_util.utcnow() self.host_name = host_name self.location_name = location_name @@ -342,12 +339,12 @@ class Device(Entity): self.update() def stale(self, now=None): - """ Return if device state is stale. """ + """Return if device state is stale.""" return self.last_seen and \ (now or dt_util.utcnow()) - self.last_seen > self.consider_home def update(self): - """ Update state of entity. """ + """Update state of entity.""" if not self.last_seen: return elif self.location_name: @@ -370,23 +367,8 @@ class Device(Entity): self.last_update_home = True -def convert_csv_config(csv_path, yaml_path): - """ Convert CSV config file format to YAML. """ - used_ids = set() - with open(csv_path) as inp: - for row in csv.DictReader(inp): - dev_id = util.ensure_unique_string( - (util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(), - used_ids) - used_ids.add(dev_id) - device = Device(None, None, None, row['track'] == '1', dev_id, - row['device'], row['name'], row['picture']) - update_config(yaml_path, dev_id, device) - return True - - def load_config(path, hass, consider_home, home_range): - """ Load devices from YAML config file. """ + """Load devices from YAML configuration file.""" if not os.path.isfile(path): return [] return [ @@ -398,7 +380,7 @@ def load_config(path, hass, consider_home, home_range): def setup_scanner_platform(hass, config, scanner, see_device): - """ Helper method to connect scanner-based platform to device tracker. """ + """Helper method to connect scanner-based platform to device tracker.""" interval = util.convert(config.get(CONF_SCAN_INTERVAL), int, DEFAULT_SCAN_INTERVAL) @@ -406,7 +388,7 @@ def setup_scanner_platform(hass, config, scanner, see_device): seen = set() def device_tracker_scan(now): - """ Called when interval matches. """ + """Called when interval matches.""" for mac in scanner.scan_devices(): if mac in seen: host_name = None @@ -422,7 +404,7 @@ def setup_scanner_platform(hass, config, scanner, see_device): def update_config(path, dev_id, device): - """ Add device to YAML config file. """ + """Add device to YAML configuration file.""" with open(path, 'a') as out: out.write('\n') out.write('{}:\n'.format(device.dev_id)) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index d9cb8718a70..5de139b9e1f 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.actiontec -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning an Actiontec MI424WR -(Verizon FIOS) router for device presence. +Support for Actiontec MI424WR (Verizon FIOS) routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.actiontec/ @@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -34,7 +31,7 @@ _LEASES_REGEX = re.compile( # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns an Actiontec scanner. """ + """Validate the configuration and return an Actiontec scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -46,12 +43,10 @@ Device = namedtuple("Device", ["mac", "ip", "last_update"]) class ActiontecDeviceScanner(object): - """ - This class queries a an actiontec router for connected devices. - Adapted from DD-WRT scanner. - """ + """This class queries a an actiontec router for connected devices.""" def __init__(self, config): + """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] self.password = config[CONF_PASSWORD] @@ -62,15 +57,12 @@ class ActiontecDeviceScanner(object): _LOGGER.info("actiontec scanner initialized") def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return [client.mac for client in self.last_results] def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: @@ -80,9 +72,9 @@ class ActiontecDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the Actiontec MI424WR router is up - to date. Returns boolean if scanning successful. + """Ensure the information from the router is up to date. + + Return boolean if scanning successful. """ _LOGGER.info("Scanning") if not self.success_init: @@ -100,7 +92,7 @@ class ActiontecDeviceScanner(object): return True def get_actiontec_data(self): - """ Retrieve data from Actiontec MI424WR and return parsed result. """ + """Retrieve data from Actiontec MI424WR and return parsed result.""" try: telnet = telnetlib.Telnet(self.host) telnet.read_until(b'Username: ') diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 4bc3f9e3258..a62306b5619 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.aruba -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a Aruba Access Point for device -presence. +Support for Aruba Access Points. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.aruba/ @@ -31,7 +28,7 @@ _DEVICES_REGEX = re.compile( # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns a Aruba scanner. """ + """Validate the configuration and return a Aruba scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -43,9 +40,10 @@ def get_scanner(hass, config): class ArubaDeviceScanner(object): - """ This class queries a Aruba Acces Point for connected devices. """ + """This class queries a Aruba Access Point for connected devices.""" def __init__(self, config): + """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] self.password = config[CONF_PASSWORD] @@ -54,20 +52,17 @@ class ArubaDeviceScanner(object): self.last_results = {} - # Test the router is accessible + # Test the router is accessible. data = self.get_aruba_data() self.success_init = data is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device IDs. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return [client['mac'] for client in self.last_results] def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: @@ -77,9 +72,9 @@ class ArubaDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the Aruba Access Point is up to date. - Returns boolean if scanning successful. + """Ensure the information from the Aruba Access Point is up to date. + + Return boolean if scanning successful. """ if not self.success_init: return False @@ -93,8 +88,7 @@ class ArubaDeviceScanner(object): return True def get_aruba_data(self): - """ Retrieve data from Aruba Access Point and return parsed result. """ - + """Retrieve data from Aruba Access Point and return parsed result.""" import pexpect connect = "ssh {}@{}" ssh = pexpect.spawn(connect.format(self.username, self.host)) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index d9b6d1a809e..b4784505d2d 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.asuswrt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a ASUSWRT router for device -presence. +Support for ASUSWRT routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.asuswrt/ @@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -39,7 +36,7 @@ _IP_NEIGH_REGEX = re.compile( # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns an ASUS-WRT scanner. """ + """Validate the configuration and return an ASUS-WRT scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -51,12 +48,10 @@ def get_scanner(hass, config): class AsusWrtDeviceScanner(object): - """ - This class queries a router running ASUSWRT firmware - for connected devices. Adapted from DD-WRT scanner. - """ + """This class queries a router running ASUSWRT firmware.""" def __init__(self, config): + """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = str(config[CONF_USERNAME]) self.password = str(config[CONF_PASSWORD]) @@ -65,20 +60,17 @@ class AsusWrtDeviceScanner(object): self.last_results = {} - # Test the router is accessible + # Test the router is accessible. data = self.get_asuswrt_data() self.success_init = data is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device IDs. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return [client['mac'] for client in self.last_results] def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: @@ -88,9 +80,9 @@ class AsusWrtDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the ASUSWRT router is up to date. - Returns boolean if scanning successful. + """Ensure the information from the ASUSWRT router is up to date. + + Return boolean if scanning successful. """ if not self.success_init: return False @@ -109,7 +101,7 @@ class AsusWrtDeviceScanner(object): return True def get_asuswrt_data(self): - """ Retrieve data from ASUSWRT and return parsed result. """ + """Retrieve data from ASUSWRT and return parsed result.""" try: telnet = telnetlib.Telnet(self.host) telnet.read_until(b'login: ') @@ -138,9 +130,8 @@ class AsusWrtDeviceScanner(object): _LOGGER.warning("Could not parse lease row: %s", lease) continue - # For leases where the client doesn't set a hostname, ensure - # it is blank and not '*', which breaks the entity_id down - # the line + # For leases where the client doesn't set a hostname, ensure it is + # blank and not '*', which breaks the entity_id down the line. host = match.group('host') if host == '*': host = '' diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 82a8ed81537..02f49fe7475 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.ddwrt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a DD-WRT router for device -presence. +Support for DD-WRT routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.ddwrt/ @@ -19,7 +16,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -30,7 +27,7 @@ _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns a DD-WRT scanner. """ + """Validate the configuration and return a DD-WRT scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -43,12 +40,10 @@ def get_scanner(hass, config): # pylint: disable=too-many-instance-attributes class DdWrtDeviceScanner(object): - """ - This class queries a wireless router running DD-WRT firmware - for connected devices. Adapted from Tomato scanner. - """ + """This class queries a wireless router running DD-WRT firmware.""" def __init__(self, config): + """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] self.password = config[CONF_PASSWORD] @@ -65,19 +60,15 @@ class DdWrtDeviceScanner(object): self.success_init = data is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return self.last_results def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ - + """Return the name of the given device or None if we don't know.""" with self.lock: - # if not initialised and not already scanned and not found + # If not initialised and not already scanned and not found. if device not in self.mac2name: url = 'http://{}/Status_Lan.live.asp'.format(self.host) data = self.get_ddwrt_data(url) @@ -90,15 +81,15 @@ class DdWrtDeviceScanner(object): if not dhcp_leases: return None - # remove leading and trailing single quotes + # Remove leading and trailing single quotes. cleaned_str = dhcp_leases.strip().strip('"') elements = cleaned_str.split('","') num_clients = int(len(elements)/5) self.mac2name = {} for idx in range(0, num_clients): - # this is stupid but the data is a single array + # This is stupid but the data is a single array # every 5 elements represents one hosts, the MAC - # is the third element and the name is the first + # is the third element and the name is the first. mac_index = (idx * 5) + 2 if mac_index < len(elements): mac = elements[mac_index] @@ -108,9 +99,9 @@ class DdWrtDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the DD-WRT router is up to date. - Returns boolean if scanning successful. + """Ensure the information from the DD-WRT router is up to date. + + Return boolean if scanning successful. """ if not self.success_init: return False @@ -135,7 +126,7 @@ class DdWrtDeviceScanner(object): # regex's out values so I guess I have to do the same, # LAME!!! - # remove leading and trailing single quotes + # Remove leading and trailing single quotes. clean_str = active_clients.strip().strip("'") elements = clean_str.split("','") @@ -145,7 +136,7 @@ class DdWrtDeviceScanner(object): return True def get_ddwrt_data(self, url): - """ Retrieve data from DD-WRT and return parsed result. """ + """Retrieve data from DD-WRT and return parsed result.""" try: response = requests.get( url, @@ -167,7 +158,7 @@ class DdWrtDeviceScanner(object): def _parse_ddwrt_response(data_str): - """ Parse the DD-WRT data format. """ + """Parse the DD-WRT data format.""" return { key: val for key, val in _DDWRT_DATA_REGEX .findall(data_str)} diff --git a/homeassistant/components/device_tracker/demo.py b/homeassistant/components/device_tracker/demo.py index 43b7915ee3c..08242c2034d 100644 --- a/homeassistant/components/device_tracker/demo.py +++ b/homeassistant/components/device_tracker/demo.py @@ -1,25 +1,17 @@ -""" -homeassistant.components.device_tracker.demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform for the device tracker. - -device_tracker: - platform: demo -""" +"""Demo platform for the device tracker.""" import random from homeassistant.components.device_tracker import DOMAIN def setup_scanner(hass, config, see): - """ Set up a demo tracker. """ - + """Setup the demo tracker.""" def offset(): - """ Return random offset. """ + """Return random offset.""" return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1)) def random_see(dev_id, name): - """ Randomize a sighting. """ + """Randomize a sighting.""" see( dev_id=dev_id, host_name=name, @@ -30,7 +22,7 @@ def setup_scanner(hass, config, see): ) def observe(call=None): - """ Observe three entities. """ + """Observe three entities.""" random_see('demo_paulus', 'Paulus') random_see('demo_anne_therese', 'Anne Therese') diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index 3ca3461f557..e3a453dad3a 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.fritz -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a FRITZ!Box router for device -presence. +Support for FRITZ!Box routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.fritz/ @@ -17,15 +14,14 @@ from homeassistant.util import Throttle REQUIREMENTS = ['fritzconnection==0.4.6'] -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) -# noinspection PyUnusedLocal def get_scanner(hass, config): - """ Validates config and returns FritzBoxScanner. """ + """Validate the configuration and return FritzBoxScanner.""" if not validate_config(config, {DOMAIN: []}, _LOGGER): @@ -37,22 +33,12 @@ def get_scanner(hass, config): # pylint: disable=too-many-instance-attributes class FritzBoxScanner(object): - """ - This class queries a FRITZ!Box router. It is using the - fritzconnection library for communication with the router. + """This class queries a FRITZ!Box router.""" - The API description can be found under: - https://pypi.python.org/pypi/fritzconnection/0.4.6 - - This scanner retrieves the list of known hosts and checks their - corresponding states (on, or off). - - Due to a bug of the fritzbox api (router side) it is not possible - to track more than 16 hosts. - """ def __init__(self, config): + """Initialize the scanner.""" self.last_results = [] - self.host = '169.254.1.1' # This IP is valid for all fritzboxes + self.host = '169.254.1.1' # This IP is valid for all FRITZ!Box router. self.username = 'admin' self.password = '' self.success_init = True @@ -68,7 +54,7 @@ class FritzBoxScanner(object): if CONF_PASSWORD in config.keys(): self.password = config[CONF_PASSWORD] - # Establish a connection to the FRITZ!Box + # Establish a connection to the FRITZ!Box. try: self.fritz_box = fc.FritzHosts(address=self.host, user=self.username, @@ -77,7 +63,7 @@ class FritzBoxScanner(object): self.fritz_box = None # At this point it is difficult to tell if a connection is established. - # So just check for null objects ... + # So just check for null objects. if self.fritz_box is None or not self.fritz_box.modelname: self.success_init = False @@ -90,7 +76,7 @@ class FritzBoxScanner(object): "with IP: %s", self.host) def scan_devices(self): - """ Scan for new devices and return a list of found device ids. """ + """Scan for new devices and return a list of found device ids.""" self._update_info() active_hosts = [] for known_host in self.last_results: @@ -99,7 +85,7 @@ class FritzBoxScanner(object): return active_hosts def get_device_name(self, mac): - """ Returns the name of the given device or None if is not known. """ + """Return the name of the given device or None if is not known.""" ret = self.fritz_box.get_specific_host_entry(mac)["NewHostName"] if ret == {}: return None @@ -107,7 +93,7 @@ class FritzBoxScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ Retrieves latest information from the FRITZ!Box. """ + """Retrieve latest information from the FRITZ!Box.""" if not self.success_init: return False diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index 4b13098dde0..051cd962945 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -1,7 +1,5 @@ """ -homeassistant.components.device_tracker.icloud -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning iCloud devices. +Support for iCloud connected devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.icloud/ @@ -21,12 +19,12 @@ DEFAULT_INTERVAL = 8 def setup_scanner(hass, config, see): - """ Set up the iCloud Scanner. """ + """Setup the iCloud Scanner.""" from pyicloud import PyiCloudService from pyicloud.exceptions import PyiCloudFailedLoginException from pyicloud.exceptions import PyiCloudNoDevicesException - # Get the username and password from the configuration + # Get the username and password from the configuration. username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -45,14 +43,14 @@ def setup_scanner(hass, config, see): return False def keep_alive(now): - """ Keeps authenticating iCloud connection. """ + """Keep authenticating iCloud connection.""" api.authenticate() _LOGGER.info("Authenticate against iCloud") track_utc_time_change(hass, keep_alive, second=0) def update_icloud(now): - """ Authenticate against iCloud and scan for devices. """ + """Authenticate against iCloud and scan for devices.""" try: # The session timeouts if we are not using it so we # have to re-authenticate. This will send an email. diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index d1912b06b3d..0bb5b5ed318 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -1,7 +1,5 @@ """ -homeassistant.components.device_tracker.locative -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Locative platform for the device tracker. +Support for the Locative platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.locative/ @@ -20,12 +18,10 @@ URL_API_LOCATIVE_ENDPOINT = "/api/locative" def setup_scanner(hass, config, see): - """ Set up an endpoint for the Locative app. """ - + """Setup an endpoint for the Locative application.""" # POST would be semantically better, but that currently does not work # since Locative sends the data as key1=value1&key2=value2 # in the request body, while Home Assistant expects json there. - hass.http.register_path( 'GET', URL_API_LOCATIVE_ENDPOINT, partial(_handle_get_api_locative, hass, see)) @@ -34,8 +30,7 @@ def setup_scanner(hass, config, see): def _handle_get_api_locative(hass, see, handler, path_match, data): - """ Locative message received. """ - + """Locative message received.""" if not _check_data(handler, data): return @@ -76,6 +71,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): def _check_data(handler, data): + """Check the data.""" if 'latitude' not in data or 'longitude' not in data: handler.write_text("Latitude and longitude not specified.", HTTP_UNPROCESSABLE_ENTITY) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index 5745138bf8e..3b0c7c0bbe5 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.luci -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a OpenWRT router for device -presence. +Support for OpenWRT (luci) routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.luci/ @@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) def get_scanner(hass, config): - """ Validates config and returns a Luci scanner. """ + """Validate the configuration and return a Luci scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -40,20 +37,13 @@ def get_scanner(hass, config): # pylint: disable=too-many-instance-attributes class LuciDeviceScanner(object): - """ - This class queries a wireless router running OpenWrt firmware - for connected devices. Adapted from Tomato scanner. + """This class queries a wireless router running OpenWrt firmware. - # opkg install luci-mod-rpc - for this to work on the router. - - The API is described here: - http://luci.subsignal.org/trac/wiki/Documentation/JsonRpcHowTo - - (Currently, we do only wifi iwscan, and no DHCP lease access.) + Adapted from Tomato scanner. """ def __init__(self, config): + """Initialize the scanner.""" host = config[CONF_HOST] username, password = config[CONF_USERNAME], config[CONF_PASSWORD] @@ -70,17 +60,12 @@ class LuciDeviceScanner(object): self.success_init = self.token is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() - return self.last_results def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ - + """Return the name of the given device or None if we don't know.""" with self.lock: if self.mac2name is None: url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host) @@ -100,8 +85,8 @@ class LuciDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the Luci router is up to date. + """Ensure the information from the Luci router is up to date. + Returns boolean if scanning successful. """ if not self.success_init: @@ -127,7 +112,7 @@ class LuciDeviceScanner(object): def _req_json_rpc(url, method, *args, **kwargs): - """ Perform one JSON RPC operation. """ + """Perform one JSON RPC operation.""" data = json.dumps({'method': method, 'params': args}) try: res = requests.post(url, data=data, timeout=5, **kwargs) @@ -157,6 +142,6 @@ def _req_json_rpc(url, method, *args, **kwargs): def _get_token(host, username, password): - """ Get authentication token for the given host+username+password. """ + """Get authentication token for the given host+username+password.""" url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host) return _req_json_rpc(url, 'login', username, password) diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py index 6fca13e6892..d754156f217 100644 --- a/homeassistant/components/device_tracker/mqtt.py +++ b/homeassistant/components/device_tracker/mqtt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.device_tracker.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -MQTT platform for the device tracker. +Support for tracking MQTT enabled devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.mqtt/ @@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see): - """ Set up a MQTT tracker. """ + """Setup the MQTT tracker.""" devices = config.get(CONF_DEVICES) qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS) @@ -34,7 +32,7 @@ def setup_scanner(hass, config, see): dev_id_lookup = {} def device_tracker_message_received(topic, payload, qos): - """ MQTT message received. """ + """MQTT message received.""" see(dev_id=dev_id_lookup[topic], location_name=payload) for dev_id, topic in devices.items(): diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index eebb68c043a..f1225d2fb73 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.netgear -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a Netgear router for device -presence. +Support for Netgear routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.netgear/ @@ -15,7 +12,7 @@ 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 +# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -23,7 +20,7 @@ REQUIREMENTS = ['pynetgear==0.3.2'] def get_scanner(hass, config): - """ Validates config and returns a Netgear scanner. """ + """Validate the configuration and returns a Netgear scanner.""" info = config[DOMAIN] host = info.get(CONF_HOST) username = info.get(CONF_USERNAME) @@ -39,9 +36,10 @@ def get_scanner(hass, config): class NetgearDeviceScanner(object): - """ This class queries a Netgear wireless router using the SOAP-API. """ + """Queries a Netgear wireless router using the SOAP-API.""" def __init__(self, host, username, password): + """Initialize the scanner.""" import pynetgear self.last_results = [] @@ -66,15 +64,13 @@ class NetgearDeviceScanner(object): _LOGGER.error("Failed to Login") def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ + """Scan for new devices and return a list with found device IDs.""" self._update_info() return (device.mac for device in self.last_results) def get_device_name(self, mac): - """ Returns the name of the given device or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" try: return next(device.name for device in self.last_results if device.mac == mac) @@ -83,8 +79,8 @@ class NetgearDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Retrieves latest information from the Netgear router. + """Retrieve latest information from the Netgear router. + Returns boolean if scanning successful. """ if not self.success_init: diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index 596986e8bb7..deb761b1714 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -1,7 +1,5 @@ """ -homeassistant.components.device_tracker.nmap -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a network with nmap. +Support for scanning a network with nmap. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.nmap_scanner/ @@ -30,7 +28,7 @@ REQUIREMENTS = ['python-nmap==0.4.3'] def get_scanner(hass, config): - """ Validates config and returns a Nmap scanner. """ + """Validate the configuration and return a Nmap scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOSTS]}, _LOGGER): return None @@ -43,7 +41,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) def _arp(ip_address): - """ Get the MAC address for a given IP. """ + """Get the MAC address for a given IP.""" cmd = ['arp', '-n', ip_address] arp = subprocess.Popen(cmd, stdout=subprocess.PIPE) out, _ = arp.communicate() @@ -55,9 +53,10 @@ def _arp(ip_address): class NmapDeviceScanner(object): - """ This class scans for devices using nmap. """ + """This class scans for devices using nmap.""" def __init__(self, config): + """Initialize the scanner.""" self.last_results = [] self.hosts = config[CONF_HOSTS] @@ -68,17 +67,13 @@ class NmapDeviceScanner(object): _LOGGER.info("nmap scanner initialized") def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return [device.mac for device in self.last_results] def get_device_name(self, mac): - """ Returns the name of the given device or None if we don't know. """ - + """Return the name of the given device or None if we don't know.""" filter_named = [device.name for device in self.last_results if device.mac == mac] @@ -89,8 +84,8 @@ class NmapDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Scans the network for devices. + """Scan the network for devices. + Returns boolean if scanning successful. """ _LOGGER.info("Scanning") diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 3f430d798a4..b8d202907b0 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -1,7 +1,5 @@ """ -homeassistant.components.device_tracker.owntracks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -OwnTracks platform for the device tracker. +Support the OwnTracks platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.owntracks/ @@ -28,13 +26,15 @@ _LOGGER = logging.getLogger(__name__) LOCK = threading.Lock() +CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' + def setup_scanner(hass, config, see): - """ Set up an OwnTracks tracker. """ + """Setup an OwnTracks tracker.""" + max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) def owntracks_location_update(topic, payload, qos): - """ MQTT message received. """ - + """MQTT message received.""" # Docs on available data: # http://owntracks.org/booklet/tech/json/#_typelocation try: @@ -45,7 +45,9 @@ def setup_scanner(hass, config, see): 'Unable to parse payload as JSON: %s', payload) return - if not isinstance(data, dict) or data.get('_type') != 'location': + if (not isinstance(data, dict) or data.get('_type') != 'location') or ( + 'acc' in data and max_gps_accuracy is not None and data[ + 'acc'] > max_gps_accuracy): return dev_id, kwargs = _parse_see_args(topic, data) @@ -63,8 +65,7 @@ def setup_scanner(hass, config, see): def owntracks_event_update(topic, payload, qos): # pylint: disable=too-many-branches, too-many-statements - """ MQTT event (geofences) received. """ - + """MQTT event (geofences) received.""" # Docs on available data: # http://owntracks.org/booklet/tech/json/#_typetransition try: @@ -124,12 +125,20 @@ def setup_scanner(hass, config, see): kwargs['location_name'] = new_region _set_gps_from_zone(kwargs, zone) _LOGGER.info("Exit to %s", new_region) + see(**kwargs) + see_beacons(dev_id, kwargs) else: _LOGGER.info("Exit to GPS") + # Check for GPS accuracy + if not ('acc' in data and + max_gps_accuracy is not None and + data['acc'] > max_gps_accuracy): - see(**kwargs) - see_beacons(dev_id, kwargs) + see(**kwargs) + see_beacons(dev_id, kwargs) + else: + _LOGGER.info("Inaccurate GPS reported") beacons = MOBILE_BEACONS_ACTIVE[dev_id] if location in beacons: @@ -143,8 +152,7 @@ def setup_scanner(hass, config, see): return def see_beacons(dev_id, kwargs_param): - """ Set active beacons to the current location """ - + """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) @@ -154,16 +162,13 @@ def setup_scanner(hass, config, see): see(**kwargs) mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) - mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) return True def _parse_see_args(topic, data): - """ Parse the OwnTracks location parameters, - into the format see expects. """ - + """Parse the OwnTracks location parameters, into the format see expects.""" parts = topic.split('/') dev_id = '{}_{}'.format(parts[1], parts[2]) host_name = parts[1] @@ -180,8 +185,7 @@ def _parse_see_args(topic, data): def _set_gps_from_zone(kwargs, zone): - """ Set the see parameters from the zone parameters """ - + """Set the see parameters from the zone parameters.""" if zone is not None: kwargs['gps'] = ( zone.attributes['latitude'], diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/device_tracker/snmp.py index b7b59fbf95a..f6df3f0a509 100644 --- a/homeassistant/components/device_tracker/snmp.py +++ b/homeassistant/components/device_tracker/snmp.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.snmp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports fetching WiFi associations -through SNMP. +Support for fetching WiFi associations through SNMP. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.snmp/ @@ -17,7 +14,7 @@ from homeassistant.const import CONF_HOST from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) @@ -29,7 +26,7 @@ CONF_BASEOID = "baseoid" # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns an snmp scanner """ + """Validate the configuration and return an snmp scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]}, _LOGGER): @@ -41,10 +38,10 @@ def get_scanner(hass, config): class SnmpScanner(object): - """ - This class queries any SNMP capable Acces Point for connected devices. - """ + """Queries any SNMP capable Access Point for connected devices.""" + def __init__(self, config): + """Initialize the scanner.""" from pysnmp.entity.rfc3413.oneliner import cmdgen self.snmp = cmdgen.CommandGenerator() @@ -61,25 +58,23 @@ class SnmpScanner(object): self.success_init = data is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device IDs. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [client['mac'] for client in self.last_results] + return [client['mac'] for client in self.last_results + if client.get('mac')] # Supressing no-self-use warning # pylint: disable=R0201 def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" # We have no names return None @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the WAP is up to date. - Returns boolean if scanning successful. + """Ensure the information from the WAP is up to date. + + Return boolean if scanning successful. """ if not self.success_init: return False @@ -93,8 +88,7 @@ class SnmpScanner(object): return True def get_snmp_data(self): - """ Fetch mac addresses from WAP via SNMP. """ - + """Fetch MAC addresses from WAP via SNMP.""" devices = [] errindication, errstatus, errindex, restable = self.snmp.nextCmd( @@ -111,6 +105,7 @@ class SnmpScanner(object): for resrow in restable: for _, val in resrow: mac = binascii.hexlify(val.asOctets()).decode('utf-8') + _LOGGER.debug('Found mac %s', mac) mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)]) devices.append({'mac': mac}) return devices diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index 7c7667400f5..be3249c8b00 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.thomson -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a THOMSON router for device -presence. +Support for THOMSON routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.thomson/ @@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) @@ -35,7 +32,7 @@ _DEVICES_REGEX = re.compile( # pylint: disable=unused-argument def get_scanner(hass, config): - """ Validates config and returns a THOMSON scanner. """ + """Validate the configuration and return a THOMSON scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -47,12 +44,10 @@ def get_scanner(hass, config): class ThomsonDeviceScanner(object): - """ - This class queries a router running THOMSON firmware - for connected devices. Adapted from ASUSWRT scanner. - """ + """This class queries a router running THOMSON firmware.""" def __init__(self, config): + """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] self.password = config[CONF_PASSWORD] @@ -61,20 +56,17 @@ class ThomsonDeviceScanner(object): self.last_results = {} - # Test the router is accessible + # Test the router is accessible. data = self.get_thomson_data() self.success_init = data is not None def scan_devices(self): - """ Scans for new devices and return a - list containing found device ids. """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return [client['mac'] for client in self.last_results] def get_device_name(self, device): - """ Returns the name of the given device - or None if we don't know. """ + """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: @@ -84,9 +76,9 @@ class ThomsonDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the THOMSON router is up to date. - Returns boolean if scanning successful. + """Ensure the information from the THOMSON router is up to date. + + Return boolean if scanning successful. """ if not self.success_init: return False @@ -97,14 +89,14 @@ class ThomsonDeviceScanner(object): if not data: return False - # flag C stands for CONNECTED + # Flag C stands for CONNECTED active_clients = [client for client in data.values() if client['status'].find('C') != -1] self.last_results = active_clients return True def get_thomson_data(self): - """ Retrieve data from THOMSON and return parsed result. """ + """Retrieve data from THOMSON and return parsed result.""" try: telnet = telnetlib.Telnet(self.host) telnet.read_until(b'Username : ') diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index 8f1e956dcb6..f5282feb733 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.tomato -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a Tomato router for device -presence. +Support for Tomato routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.tomato/ @@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) CONF_HTTP_ID = "http_id" @@ -29,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) def get_scanner(hass, config): - """ Validates config and returns a Tomato scanner. """ + """Validate the configuration and returns a Tomato scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_HTTP_ID]}, @@ -40,14 +37,10 @@ def get_scanner(hass, config): class TomatoDeviceScanner(object): - """ This class queries a wireless router running Tomato firmware - for connected devices. - - A description of the Tomato API can be found on - http://paulusschoutsen.nl/blog/2013/10/tomato-api-documentation/ - """ + """This class queries a wireless router running Tomato firmware.""" def __init__(self, config): + """Initialize the scanner.""" host, http_id = config[CONF_HOST], config[CONF_HTTP_ID] username, password = config[CONF_USERNAME], config[CONF_PASSWORD] @@ -68,16 +61,13 @@ class TomatoDeviceScanner(object): self.success_init = self._update_tomato_info() def scan_devices(self): - """ Scans for new devices and return a - list containing found device ids. """ - + """Scan for new devices and return a list with found device IDs.""" self._update_tomato_info() return [item[1] for item in self.last_results['wldev']] def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ - + """Return the name of the given device or None if we don't know.""" filter_named = [item[0] for item in self.last_results['dhcpd_lease'] if item[2] == device] @@ -88,19 +78,17 @@ class TomatoDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_tomato_info(self): - """ Ensures the information from the Tomato router is up to date. - Returns boolean if scanning successful. """ + """Ensure the information from the Tomato router is up to date. + Return boolean if scanning successful. + """ with self.lock: self.logger.info("Scanning") try: response = requests.Session().send(self.req, timeout=3) - # Calling and parsing the Tomato api here. We only need the - # wldev and dhcpd_lease values. For API description see: - # http://paulusschoutsen.nl/ - # blog/2013/10/tomato-api-documentation/ + # wldev and dhcpd_lease values. if response.status_code == 200: for param, value in \ @@ -109,7 +97,6 @@ class TomatoDeviceScanner(object): if param == 'wldev' or param == 'dhcpd_lease': self.last_results[param] = \ json.loads(value.replace("'", '"')) - return True elif response.status_code == 401: @@ -117,29 +104,25 @@ class TomatoDeviceScanner(object): self.logger.exception(( "Failed to authenticate, " "please check your username and password")) - return False except requests.exceptions.ConnectionError: # We get this if we could not connect to the router or - # an invalid http_id was supplied + # an invalid http_id was supplied. self.logger.exception(( "Failed to connect to the router" " or invalid http_id supplied")) - return False except requests.exceptions.Timeout: # We get this if we could not connect to the router or - # an invalid http_id was supplied + # an invalid http_id was supplied. self.logger.exception( "Connection to the router timed out") - return False except ValueError: - # If json decoder could not parse the response + # If JSON decoder could not parse the response. self.logger.exception( "Failed to parse response from router") - return False diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 30a920961cb..37d4d2b4471 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -1,13 +1,11 @@ """ -homeassistant.components.device_tracker.tplink -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a TP-Link router for device -presence. +Support for TP-Link routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.tplink/ """ import base64 +import hashlib import logging import re import threading @@ -27,30 +25,31 @@ _LOGGER = logging.getLogger(__name__) def get_scanner(hass, config): - """ Validates config and returns a TP-Link scanner. """ + """Validate the configuration and return a TP-Link scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): return None - scanner = Tplink3DeviceScanner(config[DOMAIN]) + scanner = Tplink4DeviceScanner(config[DOMAIN]) + + if not scanner.success_init: + scanner = Tplink3DeviceScanner(config[DOMAIN]) if not scanner.success_init: scanner = Tplink2DeviceScanner(config[DOMAIN]) - if not scanner.success_init: - scanner = TplinkDeviceScanner(config[DOMAIN]) + if not scanner.success_init: + scanner = TplinkDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None class TplinkDeviceScanner(object): - """ - This class queries a wireless router running TP-Link firmware - for connected devices. - """ + """This class queries a wireless router running TP-Link firmware.""" def __init__(self, config): + """Initialize the scanner.""" host = config[CONF_HOST] username, password = config[CONF_USERNAME], config[CONF_PASSWORD] @@ -66,29 +65,21 @@ class TplinkDeviceScanner(object): self.success_init = self._update_info() def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() - return self.last_results # pylint: disable=no-self-use def get_device_name(self, device): - """ - The TP-Link firmware doesn't save the name of the wireless device. - """ - + """The firmware doesn't save the name of the wireless device.""" return None @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the TP-Link router is up to date. - Returns boolean if scanning successful. - """ + """Ensure the information from the TP-Link router is up to date. + Return boolean if scanning successful. + """ with self.lock: _LOGGER.info("Loading wireless clients...") @@ -107,34 +98,24 @@ class TplinkDeviceScanner(object): class Tplink2DeviceScanner(TplinkDeviceScanner): - """ - This class queries a wireless router running newer version of TP-Link - firmware for connected devices. - """ + """This class queries a router with newer version of TP-Link firmware.""" def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return self.last_results.keys() # pylint: disable=no-self-use def get_device_name(self, device): - """ - The TP-Link firmware doesn't save the name of the wireless device. - """ - + """The firmware doesn't save the name of the wireless device.""" return self.last_results.get(device) @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the TP-Link router is up to date. - Returns boolean if scanning successful. - """ + """Ensure the information from the TP-Link router is up to date. + Return boolean if scanning successful. + """ with self.lock: _LOGGER.info("Loading wireless clients...") @@ -172,46 +153,36 @@ class Tplink2DeviceScanner(TplinkDeviceScanner): class Tplink3DeviceScanner(TplinkDeviceScanner): - """ - This class queries the Archer C9 router running version 150811 or higher - of TP-Link firmware for connected devices. - """ + """This class queries the Archer C9 router with version 150811 or high.""" def __init__(self, config): + """Initialize the scanner.""" self.stok = '' self.sysauth = '' super(Tplink3DeviceScanner, self).__init__(config) def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() return self.last_results.keys() # pylint: disable=no-self-use def get_device_name(self, device): - """ - The TP-Link firmware doesn't save the name of the wireless device. + """The firmware doesn't save the name of the wireless device. + We are forced to use the MAC address as name here. """ - return self.last_results.get(device) def _get_auth_tokens(self): - """ - Retrieves auth tokens from the router. - """ - + """Retrieve auth tokens from the router.""" _LOGGER.info("Retrieving auth tokens...") url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \ .format(self.host) referer = 'http://{}/webpages/login.html'.format(self.host) - # if possible implement rsa encryption of password here - + # If possible implement rsa encryption of password here. response = requests.post(url, params={'operation': 'login', 'username': self.username, @@ -232,11 +203,10 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the TP-Link router is up to date. - Returns boolean if scanning successful. - """ + """Ensure the information from the TP-Link router is up to date. + Return boolean if scanning successful. + """ with self.lock: if (self.stok == '') or (self.sysauth == ''): self._get_auth_tokens() @@ -281,3 +251,81 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): return True return False + + +class Tplink4DeviceScanner(TplinkDeviceScanner): + """This class queries an Archer C7 router with TP-Link firmware 150427.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.credentials = '' + self.token = '' + super(Tplink4DeviceScanner, self).__init__(config) + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return self.last_results + + # pylint: disable=no-self-use + def get_device_name(self, device): + """The firmware doesn't save the name of the wireless device.""" + return None + + def _get_auth_tokens(self): + """Retrieve auth tokens from the router.""" + _LOGGER.info("Retrieving auth tokens...") + url = 'http://{}/userRpm/LoginRpm.htm?Save=Save'.format(self.host) + + # Generate md5 hash of password + password = hashlib.md5(self.password.encode('utf')).hexdigest() + credentials = '{}:{}'.format(self.username, password).encode('utf') + + # Encode the credentials to be sent as a cookie. + self.credentials = base64.b64encode(credentials).decode('utf') + + # Create the authorization cookie. + cookie = 'Authorization=Basic {}'.format(self.credentials) + + response = requests.get(url, headers={'cookie': cookie}) + + try: + result = re.search(r'window.parent.location.href = ' + r'"https?:\/\/.*\/(.*)\/userRpm\/Index.htm";', + response.text) + if not result: + return False + self.token = result.group(1) + return True + except ValueError: + _LOGGER.error("Couldn't fetch auth tokens!") + return False + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """Ensure the information from the TP-Link router is up to date. + + Return boolean if scanning successful. + """ + with self.lock: + if (self.credentials == '') or (self.token == ''): + self._get_auth_tokens() + + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/{}/userRpm/WlanStationRpm.htm' \ + .format(self.host, self.token) + referer = 'http://{}'.format(self.host) + cookie = 'Authorization=Basic {}'.format(self.credentials) + + page = requests.get(url, headers={ + 'cookie': cookie, + 'referer': referer + }) + result = self.parse_macs.findall(page.text) + + if not result: + return False + + self.last_results = [mac.replace("-", ":") for mac in result] + return True diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index 073588008b0..736c1ba3168 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -1,8 +1,5 @@ """ -homeassistant.components.device_tracker.ubus -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a OpenWRT router for device -presence. +Support for OpenWRT (ubus) routers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.ubus/ @@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import validate_config from homeassistant.util import Throttle -# 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_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) def get_scanner(hass, config): - """ Validates config and returns a Luci scanner. """ + """Validate the configuration and return an ubus scanner.""" if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, _LOGGER): @@ -41,23 +38,13 @@ def get_scanner(hass, config): # pylint: disable=too-many-instance-attributes class UbusDeviceScanner(object): """ - This class queries a wireless router running OpenWrt firmware - for connected devices. Adapted from Tomato scanner. - - Configure your routers' ubus ACL based on following instructions: - - http://wiki.openwrt.org/doc/techref/ubus - - Read only access will be fine. - - To use this class you have to install rpcd-mod-file package - in your OpenWrt router: - - opkg install rpcd-mod-file + This class queries a wireless router running OpenWrt firmware. + Adapted from Tomato scanner. """ def __init__(self, config): + """Initialize the scanner.""" host = config[CONF_HOST] username, password = config[CONF_USERNAME], config[CONF_PASSWORD] @@ -73,17 +60,12 @@ class UbusDeviceScanner(object): self.success_init = self.session_id is not None def scan_devices(self): - """ - Scans for new devices and return a list containing found device ids. - """ - + """Scan for new devices and return a list with found device IDs.""" self._update_info() - return self.last_results def get_device_name(self, device): - """ Returns the name of the given device or None if we don't know. """ - + """Return the name of the given device or None if we don't know.""" with self.lock: if self.leasefile is None: result = _req_json_rpc(self.url, self.session_id, @@ -112,8 +94,8 @@ class UbusDeviceScanner(object): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """ - Ensures the information from the Luci router is up to date. + """Ensure the information from the Luci router is up to date. + Returns boolean if scanning successful. """ if not self.success_init: @@ -141,8 +123,7 @@ class UbusDeviceScanner(object): def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params): - """ Perform one JSON RPC operation. """ - + """Perform one JSON RPC operation.""" data = json.dumps({"jsonrpc": "2.0", "id": 1, "method": rpcmethod, @@ -167,7 +148,7 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params): def _get_session_id(url, username, password): - """ Get authentication token for the given host+username+password. """ + """Get the authentication token for the given host+username+password.""" res = _req_json_rpc(url, "00000000000000000000000000000000", 'call', 'session', 'login', username=username, password=password) diff --git a/homeassistant/components/device_tracker/unifi.py b/homeassistant/components/device_tracker/unifi.py index 24dd8e7db00..fbfaef83e81 100644 --- a/homeassistant/components/device_tracker/unifi.py +++ b/homeassistant/components/device_tracker/unifi.py @@ -1,7 +1,8 @@ """ -homeassistant.components.device_tracker.unifi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Device tracker platform that supports scanning a Unifi WAP controller +Support for Unifi WAP controllers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.unifi/ """ import logging import urllib @@ -17,7 +18,7 @@ CONF_PORT = 'port' def get_scanner(hass, config): - """ Sets up unifi device_tracker """ + """Setup Unifi device_tracker.""" from unifi.controller import Controller if not validate_config(config, {DOMAIN: [CONF_USERNAME, @@ -50,10 +51,12 @@ class UnifiScanner(object): """Provide device_tracker support from Unifi WAP client data.""" def __init__(self, controller): + """Initialize the scanner.""" self._controller = controller self._update() def _update(self): + """Get the clients from the device.""" try: clients = self._controller.get_clients() except urllib.error.HTTPError as ex: @@ -63,12 +66,12 @@ class UnifiScanner(object): self._clients = {client['mac']: client for client in clients} def scan_devices(self): - """ Scans for devices. """ + """Scan for devices.""" self._update() return self._clients.keys() def get_device_name(self, mac): - """ Returns the name (if known) of the device. + """Return 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. diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 6dd4e667a75..79a152e5848 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED) DOMAIN = "discovery" -REQUIREMENTS = ['netdisco==0.5.2'] +REQUIREMENTS = ['netdisco==0.5.4'] SCAN_INTERVAL = 300 # seconds @@ -55,10 +55,7 @@ def listen(hass, service, callback): def discover(hass, service, discovered=None, component=None, hass_config=None): - """Fire discovery event. - - Can ensure a component is loaded. - """ + """Fire discovery event. Can ensure a component is loaded.""" if component is not None: bootstrap.setup_component(hass, component, hass_config) @@ -73,7 +70,7 @@ def discover(hass, service, discovered=None, component=None, hass_config=None): def setup(hass, config): - """ Starts a discovery service. """ + """Start a discovery service.""" logger = logging.getLogger(__name__) from netdisco.service import DiscoveryService @@ -84,13 +81,13 @@ def setup(hass, config): lock = threading.Lock() def new_service_listener(service, info): - """ Called when a new service is found. """ + """Called when a new service is found.""" with lock: logger.info("Found new service: %s %s", service, info) component = SERVICE_HANDLERS.get(service) - # We do not know how to handle this service + # We do not know how to handle this service. if not component: return @@ -105,7 +102,7 @@ def setup(hass, config): # pylint: disable=unused-argument def start_discovery(event): - """ Start discovering. """ + """Start discovering.""" netdisco = DiscoveryService(SCAN_INTERVAL) netdisco.add_listener(new_service_listener) netdisco.start() diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py index 6fbbd3f9473..c425d9cbb23 100644 --- a/homeassistant/components/downloader.py +++ b/homeassistant/components/downloader.py @@ -1,7 +1,5 @@ """ -homeassistant.components.downloader -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to download files. +Support for functionality to download files. For more details about this component, please refer to the documentation at https://home-assistant.io/components/downloader/ @@ -28,8 +26,7 @@ CONF_DOWNLOAD_DIR = 'download_dir' # pylint: disable=too-many-branches def setup(hass, config): - """ Listens for download events to download files. """ - + """Listen for download events to download files.""" logger = logging.getLogger(__name__) if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger): @@ -50,14 +47,13 @@ def setup(hass, config): return False def download_file(service): - """ Starts thread to download file specified in the url. """ - + """Start thread to download file specified in the URL.""" if ATTR_URL not in service.data: logger.error("Service called but 'url' parameter not specified.") return def do_download(): - """ Downloads the file. """ + """Download the file.""" try: url = service.data[ATTR_URL] diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index f2172e7e5b6..21dd73ea6e6 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -1,7 +1,5 @@ """ -homeassistant.components.ecobee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Ecobee component +Support for Ecobee. For more details about this component, please refer to the documentation at https://home-assistant.io/components/ecobee/ @@ -31,12 +29,12 @@ _LOGGER = logging.getLogger(__name__) ECOBEE_CONFIG_FILE = 'ecobee.conf' _CONFIGURING = {} -# 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=180) def request_configuration(network, hass, config): - """ Request configuration steps from the user. """ + """Request configuration steps from the user.""" configurator = get_component('configurator') if 'ecobee' in _CONFIGURING: configurator.notify_errors( @@ -46,7 +44,7 @@ def request_configuration(network, hass, config): # pylint: disable=unused-argument def ecobee_configuration_callback(callback_data): - """ Actions to do when our configuration callback is called. """ + """The actions to do when our configuration callback is called.""" network.request_tokens() network.update() setup_ecobee(hass, network, config) @@ -62,7 +60,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) @@ -93,22 +91,23 @@ def setup_ecobee(hass, network, config): # pylint: disable=too-few-public-methods class EcobeeData(object): - """ Gets the latest data and update the states. """ + """Get the latest data and update the states.""" def __init__(self, config_file): + """Initialize the Ecobee data object.""" from pyecobee import Ecobee self.ecobee = Ecobee(config_file) @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Get the latest data from pyecobee. """ + """Get the latest data from pyecobee.""" self.ecobee.update() _LOGGER.info("ecobee data updated successfully.") def setup(hass, config): - """ - Setup Ecobee. + """Setup Ecobee. + Will automatically load thermostat and sensor components to support devices discovered on the network. """ diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index b5e6fffb3d9..8ba2a06130b 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,9 +1,4 @@ -""" -homeassistant.components.frontend -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Provides a frontend for Home Assistant. -""" +"""Handle the frontend for Home Assistant.""" import re import os import logging @@ -32,7 +27,7 @@ _FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) def setup(hass, config): - """ Setup serving the frontend. """ + """Setup serving the frontend.""" for url in FRONTEND_URLS: hass.http.register_path('GET', url, _handle_get_root, False) @@ -58,7 +53,7 @@ def setup(hass, config): def _handle_get_api_bootstrap(handler, path_match, data): - """ Returns all data needed to bootstrap Home Assistant. """ + """Return all data needed to bootstrap Home Assistant.""" hass = handler.server.hass handler.write_json({ @@ -70,7 +65,7 @@ def _handle_get_api_bootstrap(handler, path_match, data): def _handle_get_root(handler, path_match, data): - """ Renders the frontend. """ + """Render the frontend.""" handler.send_response(HTTP_OK) handler.send_header('Content-type', 'text/html; charset=utf-8') handler.end_headers() @@ -95,7 +90,7 @@ def _handle_get_root(handler, path_match, data): def _handle_get_service_worker(handler, path_match, data): - """ Returns service worker for the frontend. """ + """Return service worker for the frontend.""" if handler.server.development: sw_path = "home-assistant-polymer/build/service_worker.js" else: @@ -106,7 +101,7 @@ def _handle_get_service_worker(handler, path_match, data): def _handle_get_static(handler, path_match, data): - """ Returns a static file for the frontend. """ + """Return a static file for the frontend.""" req_file = util.sanitize_path(path_match.group('file')) # Strip md5 hash out @@ -120,9 +115,7 @@ def _handle_get_static(handler, path_match, data): def _handle_get_local(handler, path_match, data): - """ - Returns a static file from the hass.config.path/www for the frontend. - """ + """Return a static file from the hass.config.path/www for the frontend.""" req_file = util.sanitize_path(path_match.group('file')) path = handler.server.hass.config.path('www', req_file) diff --git a/homeassistant/components/frontend/mdi_version.py b/homeassistant/components/frontend/mdi_version.py index cf3ee39d8f2..3da4b20133d 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 = "2f4adc5d3ad6d2f73bf69ed29b7594fd" +"""DO NOT MODIFY. Auto-generated by update_mdi script.""" +VERSION = "e85dc66e1a0730e44f79ed11501cd79a" diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index c62b6c52b4d..d1c34c7e7ab 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 = "a4d021cb50ed079fcfda7369ed2f0d4a" +"""DO NOT MODIFY. Auto-generated by build_frontend script.""" +VERSION = "30bcc0eacc13a2317000824741dc9ac0" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 7dd6f68c12f..f7e6a8da0bf 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2865,12 +2865,15 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym .camera-feed { width: 100%; height: auto; + border-radius: 2px; } .caption { position: absolute; left: 0px; right: 0px; bottom: 0px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; background-color: rgba(0, 0, 0, 0.3); padding: 16px; @@ -3029,6 +3032,11 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym ha-state-icon[data-domain=binary_sensor][data-state=on], ha-state-icon[data-domain=sun][data-state=above_horizon] { color: #DCC91F; + } + + /* Color the icon if unavailable */ + ha-state-icon[data-state=unavailable] { + color: var(--disabled-text-color); } \ 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 P({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,A.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,A.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,A.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),A=n(4),P=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(130),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(134),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(82),n(40),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(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}}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(176),l=r(c),f=n(175),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(177),a=i(o),u=n(57),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(137),u=i(a),s=n(138),c=i(s),l=n(139),f=i(l),d=n(135),h=r(d),p=n(136),_=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)}Object.defineProperty(e,"__esModule",{value:!0});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(132),u=r(a),s=n(24),c=r(s);n(121),n(42),n(122),n(123),n(125),n(126),n(124),n(127),n(128),n(129),e["default"]=new o["default"]({is:"state-card-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t){t&&(0,c["default"])(this,"STATE-CARD-"+(0,u["default"])(t).toUpperCase(),{stateObj:t})}})},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"input_select":return"mdi:format-list-bulleted";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(43),a=r(o)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e,n){var r=a["default"].dom(t),i=void 0;r.lastChild&&r.lastChild.tagName===e?i=r.lastChild:(r.lastChild&&r.removeChild(r.lastChild),i=document.createElement(e)),Object.keys(n).forEach(function(t){i[t]=n[t]}),null===i.parentNode&&r.appendChild(i)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(1),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(142),u=i(a),s=n(143),c=i(s),l=n(140),f=r(l),d=n(141),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(154),a=i(o),u=n(155),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(172),u=i(a),s=n(170),c=r(s),l=n(171),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(184),u=i(a),s=n(180),c=r(s),l=n(181),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(186),u=i(a),s=n(187),c=i(s),l=n(185),f=r(l),d=n(60),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))}Object.defineProperty(e,"__esModule",{value:!0});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")}}();e["default"]=n},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}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){"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(23),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(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(69),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(19),n(92),n(91),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(){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(69),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"light":return e?"mdi:brightness-5":"mdi:brightness-7"; +case"sound":return e?"mdi:music-note-off":"mdi:music-note";case"vibration":return e?"mdi:crop-portrait":"mdi:vibrate";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(43),u=r(a),s=n(23),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(145),u=i(a),s=n(146),c=i(s),l=n(147),f=i(l),d=n(148),h=i(d),p=n(149),_=i(p),v=n(144),y=r(v),m=n(48),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)}Object.defineProperty(e,"__esModule",{value:!0});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(){g&&clearTimeout(g),_&&clearTimeout(_),S=0,p=_=m=g=b=void 0}function c(e,n){n&&clearTimeout(n),_=g=b=void 0,e&&(S=o(),v=t.apply(m,p),g||_||(p=m=void 0))}function l(){var t=e-(o()-y);0>=t||t>e?c(b,_):g=setTimeout(l,t)}function f(){return(g&&b||_&&M)&&(v=t.apply(m,p)),r(),v}function d(){c(M,g)}function h(){if(p=arguments,y=o(),m=this,b=M&&(g||!w),O===!1)var n=w&&!g;else{S||_||w||(S=y);var r=O-(y-S),i=(0>=r||r>O)&&(w||_);i?(_&&(_=clearTimeout(_)),S=y,v=t.apply(m,p)):_||(_=setTimeout(d,r))}return i&&g?g=clearTimeout(g):g||e===O||(g=setTimeout(l,e)),n&&(i=!0,v=t.apply(m,p)),!i||g||_||(p=m=void 0),v}var p,_,v,y,m,g,b,S=0,w=!1,O=!1,M=!0;if("function"!=typeof t)throw new TypeError(u);return e=a(e)||0,i(n)&&(w=!!n.leading,O="maxWait"in n&&s(a(n.maxWait)||0,e),M="trailing"in n?!!n.trailing:M),h.cancel=r,h.flush=f,h}var i=n(36),o=n(205),a=n(206),u="Expected a function",s=Math.max;t.exports=r},function(t,e,n){function r(t){var e=i(t)?s.call(t):"";return e==o||e==a}var i=n(36),o="[object Function]",a="[object GeneratorFunction]",u=Object.prototype,s=u.toString;t.exports=r},function(t,e){"use strict";function n(t){if(null===t||void 0===t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}var r=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;t.exports=Object.assign||function(t,e){for(var o,a,u=n(t),s=1;s0)for(n in tr)r=tr[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),er===!1&&(er=!0,e.updateOffset(this),er=!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(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function S(t,e){var n=!0;return u(function(){return n&&(b(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function w(t,e){nr[t]||(b(e),nr[t]=!0)}function O(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function M(t){return"[object Object]"===Object.prototype.toString.call(t)}function I(t){var e,n;for(n in t)e=t[n],O(e)?this[n]=e:this["_"+n]=e;this._config=t,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function E(t,e){var n,r=u({},t);for(n in e)a(e,n)&&(M(t[n])&&M(e[n])?(r[n]={},u(r[n],t[n]),u(r[n],e[n])):null!=e[n]?r[n]=e[n]:delete r[n]);return r}function T(t){null!=t&&this.set(t)}function D(t){return t?t.toLowerCase().replace("_","-"):t}function C(t){for(var e,n,r,i,o=0;o0;){if(r=j(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&g(i,n,!0)>=e-1)break;e--}o++}return null}function j(e){var n=null;if(!ir[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=rr._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),A(n)}catch(r){}return ir[e]}function A(t,e){var n;return t&&(n=h(e)?L(t):P(t,e),n&&(rr=n)),rr._abbr}function P(t,e){return null!==e?(e.abbr=t,null!=ir[t]?(w("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),e=E(ir[t]._config,e)):null!=e.parentLocale&&(null!=ir[e.parentLocale]?e=E(ir[e.parentLocale]._config,e):w("parentLocaleUndefined","specified parentLocale is not defined yet")),ir[t]=new T(e),A(t),ir[t]):(delete ir[t],null)}function k(t,e){if(null!=e){var n;null!=ir[t]&&(e=E(ir[t]._config,e)),n=new T(e),n.parentLocale=ir[t],ir[t]=n,A(t)}else null!=ir[t]&&(null!=ir[t].parentLocale?ir[t]=ir[t].parentLocale:null!=ir[t]&&delete ir[t]);return ir[t]}function L(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return rr;if(!r(t)){if(e=j(t))return e;t=[t]}return C(t)}function N(){return Object.keys(ir)}function R(t,e){var n=t.toLowerCase();or[n]=or[n+"s"]=or[e]=t}function x(t){return"string"==typeof t?or[t]||or[t.toLowerCase()]:void 0}function H(t){var e,n,r={};for(n in t)a(t,n)&&(e=x(n),e&&(r[e]=t[n]));return r}function Y(t,n){return function(r){return null!=r?(U(this,t,r),e.updateOffset(this,n),this):z(this,t)}}function z(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function U(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function V(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=x(t),O(this[t]))return this[t](e);return this}function G(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 F(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(cr[t]=i),e&&(cr[e[0]]=function(){return G(i.apply(this,arguments),e[1],e[2])}),n&&(cr[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function B(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function W(t){var e,n,r=t.match(ar);for(e=0,n=r.length;n>e;e++)cr[r[e]]?r[e]=cr[r[e]]:r[e]=B(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 q(t,e){return t.isValid()?(e=K(e,t.localeData()),sr[e]=sr[e]||W(e),sr[e](t)):t.localeData().invalidDate()}function K(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(ur.lastIndex=0;r>=0&&ur.test(t);)t=t.replace(ur,n),ur.lastIndex=0,r-=1;return t}function J(t,e,n){Tr[t]=O(e)?e:function(t,r){return t&&n?n:e}}function $(t,e){return a(Tr,t)?Tr[t](e._strict,e._locale):new RegExp(Z(t))}function Z(t){return X(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}))}function X(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(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 at(t,e){var n;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=m(e);else if(e=t.localeData().monthsParse(e),"number"!=typeof e)return t;return n=Math.min(t.date(),nt(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function ut(t){return null!=t?(at(this,t),e.updateOffset(this,!0),this):z(this,"Month")}function st(){return nt(this.year(),this.month())}function ct(t){return this._monthsParseExact?(a(this,"_monthsRegex")||ft.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function lt(t){return this._monthsParseExact?(a(this,"_monthsRegex")||ft.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function ft(){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]=X(r[e]),i[e]=X(i[e]),o[e]=X(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 dt(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[jr]<0||n[jr]>11?jr:n[Ar]<1||n[Ar]>nt(n[Cr],n[jr])?Ar:n[Pr]<0||n[Pr]>24||24===n[Pr]&&(0!==n[kr]||0!==n[Lr]||0!==n[Nr])?Pr:n[kr]<0||n[kr]>59?kr:n[Lr]<0||n[Lr]>59?Lr:n[Nr]<0||n[Nr]>999?Nr:-1,l(t)._overflowDayOfYear&&(Cr>e||e>Ar)&&(e=Ar),l(t)._overflowWeeks&&-1===e&&(e=Rr),l(t)._overflowWeekday&&-1===e&&(e=xr),l(t).overflow=e),t}function ht(t){var e,n,r,i,o,a,u=t._i,s=Gr.exec(u)||Fr.exec(u);if(s){for(l(t).iso=!0,e=0,n=Wr.length;n>e;e++)if(Wr[e][1].exec(s[1])){i=Wr[e][0],r=Wr[e][2]!==!1;break}if(null==i)return void(t._isValid=!1);if(s[3]){for(e=0,n=qr.length;n>e;e++)if(qr[e][1].exec(s[3])){o=(s[2]||" ")+qr[e][0];break}if(null==o)return void(t._isValid=!1)}if(!r&&null!=o)return void(t._isValid=!1);if(s[4]){if(!Br.exec(s[4]))return void(t._isValid=!1);a="Z"}t._f=i+(o||"")+(a||""),Dt(t)}else t._isValid=!1}function pt(t){var n=Kr.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(ht(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function _t(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 vt(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function yt(t){return mt(t)?366:365}function mt(t){return t%4===0&&t%100!==0||t%400===0}function gt(){return mt(this.year())}function bt(t,e,n){var r=7+e-n,i=(7+vt(t,0,r).getUTCDay()-e)%7;return-i+r-1}function St(t,e,n,r,i){var o,a,u=(7+n-r)%7,s=bt(t,r,i),c=1+7*(e-1)+u+s;return 0>=c?(o=t-1,a=yt(o)+c):c>yt(t)?(o=t+1,a=c-yt(t)):(o=t,a=c),{year:o,dayOfYear:a}}function wt(t,e,n){var r,i,o=bt(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>a?(i=t.year()-1,r=a+Ot(i,e,n)):a>Ot(t.year(),e,n)?(r=a-Ot(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function Ot(t,e,n){var r=bt(t,e,n),i=bt(t+1,e,n);return(yt(t)-r+i)/7}function Mt(t,e,n){return null!=t?t:null!=e?e:n}function It(t){var n=new Date(e.now());return t._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function Et(t){var e,n,r,i,o=[];if(!t._d){for(r=It(t),t._w&&null==t._a[Ar]&&null==t._a[jr]&&Tt(t),t._dayOfYear&&(i=Mt(t._a[Cr],r[Cr]),t._dayOfYear>yt(i)&&(l(t)._overflowDayOfYear=!0),n=vt(i,0,t._dayOfYear),t._a[jr]=n.getUTCMonth(),t._a[Ar]=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[Pr]&&0===t._a[kr]&&0===t._a[Lr]&&0===t._a[Nr]&&(t._nextDay=!0,t._a[Pr]=0),t._d=(t._useUTC?vt:_t).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Pr]=24)}}function Tt(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=Mt(e.GG,t._a[Cr],wt(Rt(),1,4).year),r=Mt(e.W,1),i=Mt(e.E,1),(1>i||i>7)&&(s=!0)):(o=t._locale._week.dow,a=t._locale._week.doy,n=Mt(e.gg,t._a[Cr],wt(Rt(),o,a).year),r=Mt(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>Ot(n,o,a)?l(t)._overflowWeeks=!0:null!=s?l(t)._overflowWeekday=!0:(u=St(n,r,i,o,a),t._a[Cr]=u.year,t._dayOfYear=u.dayOfYear)}function Dt(t){if(t._f===e.ISO_8601)return void ht(t);t._a=[],l(t).empty=!0;var n,r,i,o,a,u=""+t._i,s=u.length,c=0;for(i=K(t._f,t._locale).match(ar)||[],n=0;n0&&l(t).unusedInput.push(a),u=u.slice(u.indexOf(r)+r.length),c+=r.length),cr[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),et(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[Pr]<=12&&t._a[Pr]>0&&(l(t).bigHour=void 0),t._a[Pr]=Ct(t._locale,t._a[Pr],t._meridiem),Et(t),dt(t)}function Ct(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 jt(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 At(t){if(!t._d){var e=H(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)}),Et(t)}}function Pt(t){var e=new _(dt(kt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function kt(t){var e=t._i,n=t._f;return t._locale=t._locale||L(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new _(dt(e)):(r(n)?jt(t):n?Dt(t):i(e)?t._d=e:Lt(t),f(t)||(t._d=null),t))}function Lt(t){var n=t._i;void 0===n?t._d=new Date(e.now()):i(n)?t._d=new Date(+n):"string"==typeof n?pt(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),Et(t)):"object"==typeof n?At(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function Nt(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,Pt(o)}function Rt(t,e,n,r){return Nt(t,e,n,r,!1)}function xt(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Rt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+G(~~(t/60),2)+e+G(~~t%60,2)})}function Gt(t,e){var n=(e||"").match(t)||[],r=n[n.length-1]||[],i=(r+"").match(Qr)||["-",0,0],o=+(60*i[1])+m(i[2]);return"+"===i[0]?o:-o}function Ft(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Rt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Rt(t).local()}function Bt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Wt(t,n){var r,i=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=Gt(Mr,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=Bt(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?ce(this,re(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:Bt(this):null!=t?this:NaN}function qt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Kt(t){return this.utcOffset(0,t)}function Jt(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Bt(this),"m")),this}function $t(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Gt(Or,this._i)),this}function Zt(t){return this.isValid()?(t=t?Rt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function Xt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qt(){if(!h(this._isDSTShifted))return this._isDSTShifted;var t={};if(p(t,this),t=kt(t),t._a){var e=t._isUTC?s(t._a):Rt(t._a);this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function te(){return this.isValid()?!this._isUTC:!1}function ee(){return this.isValid()?this._isUTC:!1}function ne(){return this.isValid()?this._isUTC&&0===this._offset:!1}function re(t,e){var n,r,i,o=t,u=null;return Ut(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(u=ti.exec(t))?(n="-"===u[1]?-1:1,o={y:0,d:m(u[Ar])*n,h:m(u[Pr])*n,m:m(u[kr])*n,s:m(u[Lr])*n,ms:m(u[Nr])*n}):(u=ei.exec(t))?(n="-"===u[1]?-1:1,o={y:ie(u[2],n),M:ie(u[3],n),w:ie(u[4],n),d:ie(u[5],n),h:ie(u[6],n),m:ie(u[7],n),s:ie(u[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=ae(Rt(o.from),Rt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new zt(o),Ut(t)&&a(t,"_locale")&&(r._locale=t._locale),r}function ie(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function oe(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 ae(t,e){var n;return t.isValid()&&e.isValid()?(e=Ft(e,t),t.isBefore(e)?n=oe(t,e):(n=oe(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function ue(t){return 0>t?-1*Math.round(-1*t):Math.round(t)}function se(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(w(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=re(n,r),ce(this,i,t),this}}function ce(t,n,r,i){var o=n._milliseconds,a=ue(n._days),u=ue(n._months);t.isValid()&&(i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),a&&U(t,"Date",z(t,"Date")+a*r),u&&at(t,z(t,"Month")+u*r),i&&e.updateOffset(t,a||u))}function le(t,e){var n=t||Rt(),r=Ft(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&&(O(e[o])?e[o]():e[o]);return this.format(a||this.localeData().calendar(o,this,Rt(n)))}function fe(){return new _(this)}function de(t,e){var n=v(t)?t:Rt(t);return this.isValid()&&n.isValid()?(e=x(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e)):!1}function he(t,e){var n=v(t)?t:Rt(t);return this.isValid()&&n.isValid()?(e=x(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n):!1}function pe(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function _e(t,e){var n,r=v(t)?t:Rt(t);return this.isValid()&&r.isValid()?(e=x(e||"millisecond"),"millisecond"===e?+this===+r:(n=+r,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e))):!1}function ve(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function ye(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function me(t,e,n){var r,i,o,a;return this.isValid()?(r=Ft(t,this),r.isValid()?(i=6e4*(r.utcOffset()-this.utcOffset()),e=x(e),"year"===e||"month"===e||"quarter"===e?(a=ge(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 ge(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 be(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function Se(){var t=this.clone().utc();return 0o&&(e=o),qe.call(this,t,e,n,r,i))}function qe(t,e,n,r,i){var o=St(t,e,n,r,i),a=vt(o.year,0,o.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ke(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Je(t){return wt(t,this._week.dow,this._week.doy).week}function $e(){return this._week.dow}function Ze(){return this._week.doy}function Xe(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function Qe(t){var e=wt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function tn(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function en(t,e){return r(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]}function nn(t){return this._weekdaysShort[t.day()]}function rn(t){return this._weekdaysMin[t.day()]}function on(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=Rt([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 an(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=tn(t,this.localeData()),this.add(t-e,"d")):e}function un(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 sn(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function cn(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 ln(){return this.hours()%12||12}function fn(t,e){F(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function dn(t,e){return e._meridiemParse}function hn(t){return"p"===(t+"").toLowerCase().charAt(0)}function pn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function _n(t,e){e[Nr]=m(1e3*("0."+t))}function vn(){return this._isUTC?"UTC":""}function yn(){return this._isUTC?"Coordinated Universal Time":""}function mn(t){return Rt(1e3*t)}function gn(){return Rt.apply(null,arguments).parseZone()}function bn(t,e,n){var r=this._calendar[t];return O(r)?r.call(e,n):r}function Sn(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 wn(){return this._invalidDate}function On(t){return this._ordinal.replace("%d",t)}function Mn(t){return t}function In(t,e,n,r){var i=this._relativeTime[n];return O(i)?i(t,e,n,r):i.replace(/%d/i,t)}function En(t,e){var n=this._relativeTime[t>0?"future":"past"];return O(n)?n(e):n.replace(/%s/i,e)}function Tn(t,e,n,r){var i=L(),o=s().set(r,e);return i[n](o,t)}function Dn(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return Tn(t,e,n,i);var o,a=[];for(o=0;r>o;o++)a[o]=Tn(t,o,n,i);return a}function Cn(t,e){return Dn(t,e,"months",12,"month")}function jn(t,e){return Dn(t,e,"monthsShort",12,"month")}function An(t,e){return Dn(t,e,"weekdays",7,"day")}function Pn(t,e){return Dn(t,e,"weekdaysShort",7,"day")}function kn(t,e){return Dn(t,e,"weekdaysMin",7,"day")}function Ln(){var t=this._data;return this._milliseconds=Ii(this._milliseconds),this._days=Ii(this._days),this._months=Ii(this._months),t.milliseconds=Ii(t.milliseconds),t.seconds=Ii(t.seconds),t.minutes=Ii(t.minutes),t.hours=Ii(t.hours),t.months=Ii(t.months),t.years=Ii(t.years),this}function Nn(t,e,n,r){var i=re(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function Rn(t,e){return Nn(this,t,e,1)}function xn(t,e){return Nn(this,t,e,-1)}function Hn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Yn(){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*Hn(Un(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(zn(a)),u+=i,a-=Hn(Un(i)),r=y(u/12),u%=12,s.days=a,s.months=u,s.years=r,this}function zn(t){return 4800*t/146097}function Un(t){return 146097*t/4800}function Vn(t){var e,n,r=this._milliseconds;if(t=x(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+zn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Un(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 Gn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12)}function Fn(t){return function(){return this.as(t)}}function Bn(t){return t=x(t),this[t+"s"]()}function Wn(t){return function(){return this._data[t]}}function qn(){return y(this.days()/7)}function Kn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Jn(t,e,n){var r=re(t).abs(),i=Ui(r.as("s")),o=Ui(r.as("m")),a=Ui(r.as("h")),u=Ui(r.as("d")),s=Ui(r.as("M")),c=Ui(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,Kn.apply(null,l)}function $n(t,e){return void 0===Vi[t]?!1:void 0===e?Vi[t]:(Vi[t]=e,!0)}function Zn(t){var e=this.localeData(),n=Jn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function Xn(){var t,e,n,r=Gi(this._milliseconds)/1e3,i=Gi(this._days),o=Gi(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 Qn,tr=e.momentProperties=[],er=!1,nr={};e.suppressDeprecationWarnings=!1;var rr,ir={},or={},ar=/(\[[^\[]*\])|(\\)?([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,ur=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,sr={},cr={},lr=/\d/,fr=/\d\d/,dr=/\d{3}/,hr=/\d{4}/,pr=/[+-]?\d{6}/,_r=/\d\d?/,vr=/\d\d\d\d?/,yr=/\d\d\d\d\d\d?/,mr=/\d{1,3}/,gr=/\d{1,4}/,br=/[+-]?\d{1,6}/,Sr=/\d+/,wr=/[+-]?\d+/,Or=/Z|[+-]\d\d:?\d\d/gi,Mr=/Z|[+-]\d\d(?::?\d\d)?/gi,Ir=/[+-]?\d+(\.\d{1,3})?/,Er=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Tr={},Dr={},Cr=0,jr=1,Ar=2,Pr=3,kr=4,Lr=5,Nr=6,Rr=7,xr=8;F("M",["MM",2],"Mo",function(){return this.month()+1}),F("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),F("MMMM",0,0,function(t){return this.localeData().months(this,t)}),R("month","M"),J("M",_r),J("MM",_r,fr),J("MMM",function(t,e){return e.monthsShortRegex(t)}),J("MMMM",function(t,e){return e.monthsRegex(t)}),Q(["M","MM"],function(t,e){e[jr]=m(t)-1}),Q(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[jr]=i:l(n).invalidMonth=t});var Hr=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Yr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),zr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Ur=Er,Vr=Er,Gr=/^\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)?)?/,Fr=/^\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)?)?/,Br=/Z|[+-]\d\d(?::?\d\d)?/,Wr=[["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}/]],qr=[["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/]],Kr=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=S("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":""))}),F("Y",0,0,function(){var t=this.year();return 9999>=t?""+t:"+"+t}),F(0,["YY",2],0,function(){return this.year()%100}),F(0,["YYYY",4],0,"year"),F(0,["YYYYY",5],0,"year"),F(0,["YYYYYY",6,!0],0,"year"),R("year","y"),J("Y",wr),J("YY",_r,fr),J("YYYY",gr,hr),J("YYYYY",br,pr),J("YYYYYY",br,pr),Q(["YYYYY","YYYYYY"],Cr),Q("YYYY",function(t,n){n[Cr]=2===t.length?e.parseTwoDigitYear(t):m(t)}),Q("YY",function(t,n){n[Cr]=e.parseTwoDigitYear(t)}),Q("Y",function(t,e){e[Cr]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return m(t)+(m(t)>68?1900:2e3)};var Jr=Y("FullYear",!1);e.ISO_8601=function(){};var $r=S("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Rt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:d()}),Zr=S("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Rt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:d()}),Xr=function(){return Date.now?Date.now():+new Date};Vt("Z",":"),Vt("ZZ",""),J("Z",Mr),J("ZZ",Mr),Q(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Gt(Mr,t)});var Qr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var ti=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ei=/^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/;re.fn=zt.prototype;var ni=se(1,"add"),ri=se(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var ii=S("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)});F(0,["gg",2],0,function(){return this.weekYear()%100}),F(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ue("gggg","weekYear"),Ue("ggggg","weekYear"),Ue("GGGG","isoWeekYear"),Ue("GGGGG","isoWeekYear"),R("weekYear","gg"),R("isoWeekYear","GG"),J("G",wr),J("g",wr),J("GG",_r,fr),J("gg",_r,fr),J("GGGG",gr,hr),J("gggg",gr,hr),J("GGGGG",br,pr),J("ggggg",br,pr),tt(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=m(t)}),tt(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),F("Q",0,"Qo","quarter"),R("quarter","Q"),J("Q",lr),Q("Q",function(t,e){e[jr]=3*(m(t)-1)}),F("w",["ww",2],"wo","week"),F("W",["WW",2],"Wo","isoWeek"),R("week","w"),R("isoWeek","W"),J("w",_r),J("ww",_r,fr),J("W",_r),J("WW",_r,fr),tt(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=m(t)});var oi={dow:0,doy:6};F("D",["DD",2],"Do","date"),R("date","D"),J("D",_r),J("DD",_r,fr),J("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),Q(["D","DD"],Ar),Q("Do",function(t,e){e[Ar]=m(t.match(_r)[0],10)});var ai=Y("Date",!0);F("d",0,"do","day"),F("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),F("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),F("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),F("e",0,0,"weekday"),F("E",0,0,"isoWeekday"),R("day","d"),R("weekday","e"),R("isoWeekday","E"),J("d",_r),J("e",_r),J("E",_r),J("dd",Er),J("ddd",Er),J("dddd",Er),tt(["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}),tt(["d","e","E"],function(t,e,n,r){e[r]=m(t)});var ui="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),si="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ci="Su_Mo_Tu_We_Th_Fr_Sa".split("_");F("DDD",["DDDD",3],"DDDo","dayOfYear"),R("dayOfYear","DDD"),J("DDD",mr),J("DDDD",dr),Q(["DDD","DDDD"],function(t,e,n){n._dayOfYear=m(t)}),F("H",["HH",2],0,"hour"),F("h",["hh",2],0,ln),F("hmm",0,0,function(){return""+ln.apply(this)+G(this.minutes(),2)}),F("hmmss",0,0,function(){return""+ln.apply(this)+G(this.minutes(),2)+G(this.seconds(),2)}),F("Hmm",0,0,function(){return""+this.hours()+G(this.minutes(),2)}),F("Hmmss",0,0,function(){return""+this.hours()+G(this.minutes(),2)+G(this.seconds(),2)}),fn("a",!0),fn("A",!1),R("hour","h"),J("a",dn),J("A",dn),J("H",_r),J("h",_r),J("HH",_r,fr),J("hh",_r,fr),J("hmm",vr),J("hmmss",yr),J("Hmm",vr),J("Hmmss",yr),Q(["H","HH"],Pr),Q(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),Q(["h","hh"],function(t,e,n){e[Pr]=m(t),l(n).bigHour=!0}),Q("hmm",function(t,e,n){var r=t.length-2;e[Pr]=m(t.substr(0,r)),e[kr]=m(t.substr(r)),l(n).bigHour=!0}),Q("hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Pr]=m(t.substr(0,r)),e[kr]=m(t.substr(r,2)),e[Lr]=m(t.substr(i)),l(n).bigHour=!0}),Q("Hmm",function(t,e,n){var r=t.length-2;e[Pr]=m(t.substr(0,r)),e[kr]=m(t.substr(r))}),Q("Hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Pr]=m(t.substr(0,r)),e[kr]=m(t.substr(r,2)),e[Lr]=m(t.substr(i))});var li=/[ap]\.?m?\.?/i,fi=Y("Hours",!0);F("m",["mm",2],0,"minute"),R("minute","m"),J("m",_r),J("mm",_r,fr),Q(["m","mm"],kr);var di=Y("Minutes",!1);F("s",["ss",2],0,"second"),R("second","s"),J("s",_r),J("ss",_r,fr),Q(["s","ss"],Lr);var hi=Y("Seconds",!1);F("S",0,0,function(){return~~(this.millisecond()/100)}),F(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),F(0,["SSS",3],0,"millisecond"),F(0,["SSSS",4],0,function(){return 10*this.millisecond()}),F(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),F(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),F(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),F(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),F(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),R("millisecond","ms"),J("S",mr,lr),J("SS",mr,fr),J("SSS",mr,dr);var pi;for(pi="SSSS";pi.length<=9;pi+="S")J(pi,Sr);for(pi="S";pi.length<=9;pi+="S")Q(pi,_n);var _i=Y("Milliseconds",!1);F("z",0,0,"zoneAbbr"),F("zz",0,0,"zoneName");var vi=_.prototype;vi.add=ni,vi.calendar=le,vi.clone=fe,vi.diff=me,vi.endOf=je,vi.format=we,vi.from=Oe,vi.fromNow=Me,vi.to=Ie,vi.toNow=Ee,vi.get=V,vi.invalidAt=Ye,vi.isAfter=de,vi.isBefore=he,vi.isBetween=pe,vi.isSame=_e,vi.isSameOrAfter=ve,vi.isSameOrBefore=ye,vi.isValid=xe,vi.lang=ii,vi.locale=Te,vi.localeData=De,vi.max=Zr,vi.min=$r,vi.parsingFlags=He,vi.set=V,vi.startOf=Ce,vi.subtract=ri,vi.toArray=Le,vi.toObject=Ne,vi.toDate=ke,vi.toISOString=Se,vi.toJSON=Re,vi.toString=be,vi.unix=Pe,vi.valueOf=Ae,vi.creationData=ze,vi.year=Jr,vi.isLeapYear=gt,vi.weekYear=Ve,vi.isoWeekYear=Ge,vi.quarter=vi.quarters=Ke,vi.month=ut,vi.daysInMonth=st,vi.week=vi.weeks=Xe,vi.isoWeek=vi.isoWeeks=Qe,vi.weeksInYear=Be,vi.isoWeeksInYear=Fe,vi.date=ai,vi.day=vi.days=an,vi.weekday=un,vi.isoWeekday=sn,vi.dayOfYear=cn,vi.hour=vi.hours=fi,vi.minute=vi.minutes=di,vi.second=vi.seconds=hi,vi.millisecond=vi.milliseconds=_i,vi.utcOffset=Wt,vi.utc=Kt,vi.local=Jt,vi.parseZone=$t,vi.hasAlignedHourOffset=Zt,vi.isDST=Xt,vi.isDSTShifted=Qt,vi.isLocal=te,vi.isUtcOffset=ee,vi.isUtc=ne,vi.isUTC=ne,vi.zoneAbbr=vn,vi.zoneName=yn,vi.dates=S("dates accessor is deprecated. Use date instead.",ai),vi.months=S("months accessor is deprecated. Use month instead",ut),vi.years=S("years accessor is deprecated. Use year instead",Jr),vi.zone=S("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",qt);var yi=vi,mi={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},gi={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"},bi="Invalid date",Si="%d",wi=/\d{1,2}/,Oi={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"},Mi=T.prototype;Mi._calendar=mi,Mi.calendar=bn,Mi._longDateFormat=gi,Mi.longDateFormat=Sn,Mi._invalidDate=bi,Mi.invalidDate=wn,Mi._ordinal=Si,Mi.ordinal=On,Mi._ordinalParse=wi,Mi.preparse=Mn,Mi.postformat=Mn,Mi._relativeTime=Oi,Mi.relativeTime=In,Mi.pastFuture=En,Mi.set=I,Mi.months=rt,Mi._months=Yr,Mi.monthsShort=it,Mi._monthsShort=zr,Mi.monthsParse=ot,Mi._monthsRegex=Vr,Mi.monthsRegex=lt,Mi._monthsShortRegex=Ur,Mi.monthsShortRegex=ct,Mi.week=Je,Mi._week=oi,Mi.firstDayOfYear=Ze,Mi.firstDayOfWeek=$e,Mi.weekdays=en,Mi._weekdays=ui,Mi.weekdaysMin=rn,Mi._weekdaysMin=ci,Mi.weekdaysShort=nn,Mi._weekdaysShort=si,Mi.weekdaysParse=on,Mi.isPM=hn,Mi._meridiemParse=li,Mi.meridiem=pn,A("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=S("moment.lang is deprecated. Use moment.locale instead.",A),e.langData=S("moment.langData is deprecated. Use moment.localeData instead.",L);var Ii=Math.abs,Ei=Fn("ms"),Ti=Fn("s"),Di=Fn("m"),Ci=Fn("h"),ji=Fn("d"),Ai=Fn("w"),Pi=Fn("M"),ki=Fn("y"),Li=Wn("milliseconds"),Ni=Wn("seconds"),Ri=Wn("minutes"),xi=Wn("hours"),Hi=Wn("days"),Yi=Wn("months"),zi=Wn("years"),Ui=Math.round,Vi={s:45,m:45,h:22,d:26,M:11},Gi=Math.abs,Fi=zt.prototype;Fi.abs=Ln,Fi.add=Rn,Fi.subtract=xn,Fi.as=Vn,Fi.asMilliseconds=Ei,Fi.asSeconds=Ti,Fi.asMinutes=Di,Fi.asHours=Ci,Fi.asDays=ji,Fi.asWeeks=Ai,Fi.asMonths=Pi,Fi.asYears=ki,Fi.valueOf=Gn,Fi._bubble=Yn,Fi.get=Bn,Fi.milliseconds=Li,Fi.seconds=Ni,Fi.minutes=Ri,Fi.hours=xi,Fi.days=Hi,Fi.weeks=qn,Fi.months=Yi,Fi.years=zi,Fi.humanize=Zn,Fi.toISOString=Xn,Fi.toString=Xn,Fi.toJSON=Xn,Fi.locale=Te,Fi.localeData=De,Fi.toIsoString=S("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Xn),Fi.lang=ii,F("X",0,0,"unix"),F("x",0,0,"valueOf"),J("x",wr),J("X",Ir),Q("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),Q("x",function(t,e,n){n._d=new Date(m(t))}),e.version="2.12.0",n(Rt),e.fn=yi,e.min=Ht,e.max=Yt,e.now=Xr,e.utc=s,e.unix=mn,e.months=Cn,e.isDate=i,e.locale=A,e.invalid=d,e.duration=re,e.isMoment=v,e.weekdays=An,e.parseZone=gn,e.localeData=L,e.isDuration=Ut,e.monthsShort=jn,e.weekdaysMin=kn,e.defineLocale=P,e.updateLocale=k,e.locales=N,e.weekdaysShort=Pn,e.normalizeUnits=x,e.relativeTimeThreshold=$n,e.prototype=yi;var Bi=e;return Bi})}).call(e,n(70)(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(173),u=n(198),s=i(u),c=n(200),l=i(c),f=n(202),d=i(f),h=n(15),p=r(h),_=n(26),v=r(_),y=n(9),m=r(y),g=n(49),b=r(g),S=n(153),w=r(S),O=n(27),M=r(O),I=n(158),E=r(I),T=n(52),D=r(T),C=n(55),j=r(C),A=n(29),P=r(A),k=n(62),L=r(k),N=n(13),R=r(N),x=n(31),H=r(x),Y=n(33),z=r(Y),U=n(189),V=r(U),G=n(195),F=r(G),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:P,view:L,service:R,stream:H,sync:z,template:V,voice:F,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(81),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),a=n(24),u=r(a);n(73),n(75),n(76),e["default"]=new o["default"]({is:"ha-card-chooser",properties:{cardData:{type:Object,observer:"cardDataChanged"}},cardDataChanged:function(t){t&&(0,u["default"])(this,"HA-"+t.cardType.toUpperCase()+"-CARD",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);n(39),n(17),n(20);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(39),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(44),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(18);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(46),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(23),c=r(s),l=n(22),f=r(l),d=n(46),h=r(d);n(18);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(80),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(86),n(72),n(74);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}},observers:["updateCards(columns, states, showIntroduction)"],updateCards:function(t,e,n){var r=this;this.debounce("updateCards",function(){r.cards=r.computeCards(t,e,n)},0)},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){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(){e.mouseMoveIsThrottled=!0},100))},onMouseMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){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(){this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.drawGradient()},drawGradient:function(){var t=void 0;this.width&&this.height||(t=getComputedStyle(this));var e=this.width||parseInt(t.width,10),n=this.height||parseInt(t.height,10),r=this.context.createLinearGradient(0,0,e,0);r.addColorStop(0,"rgb(255,0,0)"),r.addColorStop(.16,"rgb(255,0,255)"),r.addColorStop(.32,"rgb(0,0,255)"),r.addColorStop(.48,"rgb(0,255,255)"),r.addColorStop(.64,"rgb(0,255,0)"),r.addColorStop(.8,"rgb(255,255,0)"),r.addColorStop(1,"rgb(255,0,0)"),this.context.fillStyle=r,this.context.fillRect(0,0,e,n);var i=this.context.createLinearGradient(0,0,0,n);i.addColorStop(0,"rgba(255,255,255,1)"),i.addColorStop(.5,"rgba(255,255,255,0)"),i.addColorStop(.5,"rgba(0,0,0,0)"),i.addColorStop(1,"rgba(0,0,0,1)"),this.context.fillStyle=i,this.context.fillRect(0,0,e,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(18),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(89),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(93);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(20),n(41),n(111);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(){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(45),f=r(l);n(88),n(98),n(105),n(104),n(106),n(99),n(100),n(102),n(103),n(101),n(107),n(95),n(94);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(47),f=r(l),d=n(45),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(84);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":""},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(90);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(83);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){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(78);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(41);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(87),n(19);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(79);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}},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(19);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(24),u=r(a),s=n(133),c=r(s);n(112),n(113),n(118),n(110),n(119),n(117),n(114),n(116),n(109),n(120),n(108),n(115),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t){t&&(0,u["default"])(this,"MORE-INFO-"+(0, +c["default"])(t).toUpperCase(),{stateObj: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=["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(20);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(21),l=r(c);n(85);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:""}},handleUnlockTap:function(){this.callService("unlock",{code:this.enteredCode})},handleLockTap:function(){this.callService("lock",{code:this.enteredCode})},stateObjChanged:function(t){var e=this;t&&(this.isLocked="locked"===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(21),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(44),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(21),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(42),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),a=n(2),u=r(a);n(7),n(17);var s=u["default"].serviceActions;e["default"]=new o["default"]({is:"state-card-script",properties:{stateObj:{type:Object}},fireScript: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(17),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){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=["off","closed","unlocked"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return"unavailable"===t.state?"display":-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(22),a=r(o),u=["configurator","input_select","media_player","rollershutter","scene","script","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(203),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(31),l=n(33),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(25),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(25),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(25),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(48),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(34),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(152),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(29),u=n(50),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(50),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(160),u=i(a),s=n(161),c=i(s),l=n(162),f=i(l),d=n(163),h=i(d),p=n(156),_=r(p),v=n(157),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)}Object.defineProperty(e,"__esModule",{value:!0});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 v(){try{var t=n(221);return W=t.runOnLoop||t.runOnContext,f()}catch(e){return p()}}function y(t,e){var n=this,r=n._state;if(r===ut&&!t||r===st&&!e)return this;var i=new this.constructor(g),o=n._result;if(r){var a=arguments[r-1];Z(function(){N(r,i,a,o)})}else A(n,i,t,e);return i}function m(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(g);return T(n,t),n}function g(){}function b(){return new TypeError("You cannot resolve a promise with itself")}function S(){return new TypeError("A promises callback cannot return that same promise.")}function w(t){try{return t.then}catch(e){return ct.error=e,ct}}function O(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function M(t,e,n){Z(function(t){var r=!1,i=O(n,e,function(n){r||(r=!0,e!==n?T(t,n):C(t,n))},function(e){r||(r=!0,j(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,j(t,i))},t)}function I(t,e){e._state===ut?C(t,e._result):e._state===st?j(t,e._result):A(e,void 0,function(e){T(t,e)},function(e){j(t,e)})}function E(t,e,n){e.constructor===t.constructor&&n===it&&constructor.resolve===ot?I(t,e):n===ct?j(t,ct.error):void 0===n?C(t,e):u(n)?M(t,e,n):C(t,e)}function T(t,e){t===e?j(t,b()):a(e)?E(t,e,w(e)):C(t,e)}function D(t){t._onerror&&t._onerror(t._result),P(t)}function C(t,e){t._state===at&&(t._result=e,t._state=ut,0!==t._subscribers.length&&Z(P,t))}function j(t,e){t._state===at&&(t._state=st,t._result=e,Z(D,t))}function A(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ut]=n,i[o+st]=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++)A(r.resolve(t[a]),void 0,e,n);return i}function Y(t){var e=this,n=new e(g);return j(n,t),n}function z(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function U(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function V(t){this._id=pt++,this._state=void 0,this._result=void 0,this._subscribers=[],g!==t&&("function"!=typeof t&&z(),this instanceof V?R(this,t):U())}function G(t,e){this._instanceConstructor=t,this.promise=new t(g),Array.isArray(e)?(this._input=e,this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?C(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&C(this.promise,this._result))):j(this.promise,this._validationError())}function F(){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=function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(q?q(_):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?l():tt?d():nt?h():void 0===X?v():p();var it=y,ot=m,at=void 0,ut=1,st=2,ct=new k,lt=new k,ft=x,dt=H,ht=Y,pt=0,_t=V;V.all=ft,V.race=dt,V.resolve=ot,V.reject=ht,V._setScheduler=s,V._setAsap=c,V._asap=Z,V.prototype={constructor:V,then:it,"catch":function(t){return this.then(null,t)}};var vt=G;G.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},G.prototype._enumerate=function(){for(var t=this.length,e=this._input,n=0;this._state===at&&t>n;n++)this._eachEntry(e[n],n)},G.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===ot){var i=w(t);if(i===it&&t._state!==at)this._settledAt(t._state,e,t._result);else if("function"!=typeof i)this._remaining--,this._result[e]=t;else if(n===_t){var o=new n(g);E(o,t,i),this._willSettleAt(o,e)}else this._willSettleAt(new n(function(e){e(t)}),e)}else this._willSettleAt(r(t),e)},G.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===at&&(this._remaining--,t===st?j(r,n):this._result[e]=n),0===this._remaining&&C(r,this._result)},G.prototype._willSettleAt=function(t,e){var n=this;A(t,void 0,function(t){n._settledAt(ut,e,t)},function(t){n._settledAt(st,e,t)})};var yt=F,mt={Promise:_t,polyfill:yt};n(219).amd?(r=function(){return mt}.call(e,n,e,o),!(void 0!==r&&(o.exports=r))):"undefined"!=typeof o&&o.exports?o.exports=mt:"undefined"!=typeof this&&(this.ES6Promise=mt),yt()}).call(this)}).call(e,n(220),function(){return this}(),n(70)(t))},function(t,e){var n=Array.isArray;t.exports=n},function(t,e){var n=Date.now;t.exports=n},function(t,e,n){function r(t){if(o(t)){var e=i(t.valueOf)?t.valueOf():t;t=o(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(u,"");var n=c.test(t);return n||l.test(t)?f(t.slice(2),n?2:8):s.test(t)?a:+t}var i=n(66),o=n(36),a=NaN,u=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,l=/^0o[0-7]+$/i,f=parseInt;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){function n(t,e,n,o){for(var a=-1,u=i(r((e-t)/(n||1)),0),s=Array(u);u--;)s[o?u:++a]=t,t+=n;return s}var r=Math.ceil,i=Math.max;t.exports=n},function(t,e,n){function r(t){return function(e,n,r){return r&&"number"!=typeof r&&o(e,n,r)&&(n=r=void 0),e=a(e),e=e===e?e:0,void 0===n?(n=e,e=0):n=a(n)||0,r=void 0===r?n>e?1:-1:a(r)||0,i(e,n,r,t)}}var i=n(208),o=n(212),a=n(217);t.exports=r},function(t,e,n){var r=n(207),i=r("length");t.exports=i},function(t,e){function n(t,e){return t="number"==typeof t||i.test(t)?+t:-1,e=null==e?r:e,t>-1&&t%1==0&&e>t}var r=9007199254740991,i=/^(?:0|[1-9]\d*)$/;t.exports=n},function(t,e,n){function r(t,e,n){if(!u(n))return!1;var r=typeof e;return("number"==r?o(n)&&a(e,n.length):"string"==r&&e in n)?i(n[e],t):!1}var i=n(213),o=n(214),a=n(211),u=n(37);t.exports=r},function(t,e){function n(t,e){return t===e||t!==t&&e!==e}t.exports=n},function(t,e,n){function r(t){return null!=t&&a(i(t))&&!o(t)}var i=n(210),o=n(68),a=n(215);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,n){var r=n(209),i=r();t.exports=i},function(t,e,n){function r(t){if(o(t)){var e=i(t.valueOf)?t.valueOf():t;t=o(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(u,"");var n=c.test(t);return n||l.test(t)?f(t.slice(2),n?2:8):s.test(t)?a:+t}var i=n(68),o=n(37),a=NaN,u=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,l=/^0o[0-7]+$/i,f=parseInt;t.exports=r},function(t,e){function n(t){throw new Error("Cannot find module '"+t+"'.")}n.keys=function(){return[]},n.resolve=n,t.exports=n,n.id=218},function(t,e){t.exports=function(){throw new Error("define cannot be used indirect")}},function(t,e){function n(){c=!1,a.length?s=a.concat(s):l=-1,s.length&&r()}function r(){if(!c){var t=setTimeout(n);c=!0;for(var e=s.length;e;){for(a=s, +s=[];++l1)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 81ae753eb06..0fed700045d 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 81ae753eb06a32bcac62cbee0981b1d24580e878 +Subproject commit 0fed700045d6faba8eda8ec713ee9e6bc763507c diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index 95a96f76758..a342dde8cea 100644 --- a/homeassistant/components/frontend/www_static/mdi.html +++ b/homeassistant/components/frontend/www_static/mdi.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/garage_door/__init__.py b/homeassistant/components/garage_door/__init__.py index 956fac5621d..aace95d8a05 100644 --- a/homeassistant/components/garage_door/__init__.py +++ b/homeassistant/components/garage_door/__init__.py @@ -33,13 +33,13 @@ _LOGGER = logging.getLogger(__name__) def is_closed(hass, entity_id=None): - """Returns if the garage door is closed based on the statemachine.""" + """Return 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.""" + """Close all or a specified garage door.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_CLOSE, data) @@ -58,7 +58,7 @@ def setup(hass, config): component.setup(config) def handle_garage_door_service(service): - """Handles calls to the garage door services.""" + """Handle calls to the garage door services.""" target_locks = component.extract_from_service(service) for item in target_locks: @@ -81,7 +81,8 @@ def setup(hass, config): class GarageDoorDevice(Entity): - """Represents a garage door.""" + """Representation of a garage door.""" + # pylint: disable=no-self-use @property def is_closed(self): @@ -98,7 +99,7 @@ class GarageDoorDevice(Entity): @property def state(self): - """Returns the state of the garage door.""" + """Return the 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 2b13b6fdc92..dad8df7782c 100644 --- a/homeassistant/components/garage_door/demo.py +++ b/homeassistant/components/garage_door/demo.py @@ -19,7 +19,9 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoGarageDoor(GarageDoorDevice): """Provides a demo garage door.""" + def __init__(self, name, state): + """Initialize the garage door.""" self._name = name self._state = state diff --git a/homeassistant/components/garage_door/wink.py b/homeassistant/components/garage_door/wink.py index b6ac8aa6cd8..c721033c696 100644 --- a/homeassistant/components/garage_door/wink.py +++ b/homeassistant/components/garage_door/wink.py @@ -13,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Sets up the Wink garage door platform.""" + """Setup the Wink garage door platform.""" import pywink if discovery_info is None: @@ -32,19 +32,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WinkGarageDoorDevice(GarageDoorDevice): - """Represents a Wink garage door.""" + """Representation of a Wink garage door.""" def __init__(self, wink): + """Initialize the garage door.""" self.wink = wink @property def unique_id(self): - """Returns the id of this wink garage door.""" + """Return 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.""" + """Return the name of the garage door if any.""" return self.wink.name() def update(self): @@ -53,11 +54,11 @@ class WinkGarageDoorDevice(GarageDoorDevice): @property def is_closed(self): - """Returns true if door is closed.""" + """Return true if door is closed.""" return self.wink.state() == 0 def close_door(self): - """Closes the door.""" + """Close the door.""" self.wink.set_state(0) def open_door(self): diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index 4e726f06bb5..c262443a94d 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -1,6 +1,5 @@ """ -Component that records all events and state changes and feeds the data to -a Graphite installation. +Component that sends data to aGraphite installation. For more details about this component, please refer to the documentation at https://home-assistant.io/components/graphite/ @@ -35,8 +34,10 @@ def setup(hass, config): class GraphiteFeeder(threading.Thread): - """Feeds data to Graphite.""" + """Feed data to Graphite.""" + def __init__(self, hass, host, port, prefix): + """Initialize the feeder.""" super(GraphiteFeeder, self).__init__(daemon=True) self._hass = hass self._host = host diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 3d157f32eea..028222e0e76 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -1,11 +1,11 @@ """ -homeassistant.components.group -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to group devices that can be turned on or off. +Provides functionality to group entities. For more details about this component, please refer to the documentation at https://home-assistant.io/components/group/ """ +import threading + import homeassistant.core as ha from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, @@ -32,7 +32,7 @@ _GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME), def _get_group_on_off(state): - """ Determine the group on/off states based on a state. """ + """Determine the group on/off states based on a state.""" for states in _GROUP_TYPES: if state in states: return states @@ -41,7 +41,7 @@ def _get_group_on_off(state): def is_on(hass, entity_id): - """ Returns if the group state is in its ON-state. """ + """Test if the group state is in its ON-state.""" state = hass.states.get(entity_id) if state: @@ -54,8 +54,7 @@ def is_on(hass, entity_id): def expand_entity_ids(hass, entity_ids): - """ Returns the given list of entity ids and expands group ids into - the entity ids it represents if found. """ + """Return entity_ids with group entity ids replaced by their members.""" found_ids = [] for entity_id in entity_ids: @@ -86,7 +85,7 @@ def expand_entity_ids(hass, entity_ids): def get_entity_ids(hass, entity_id, domain_filter=None): - """ Get the entity ids that make up this group. """ + """Get members of this group.""" entity_id = entity_id.lower() try: @@ -107,7 +106,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None): def setup(hass, config): - """ Sets up all groups found definded in the configuration. """ + """Setup all groups found definded in the configuration.""" for object_id, conf in config.get(DOMAIN, {}).items(): if not isinstance(conf, dict): conf = {CONF_ENTITIES: conf} @@ -127,12 +126,12 @@ def setup(hass, config): class Group(Entity): - """ Tracks a group of entity ids. """ + """Track a group of entity ids.""" # pylint: disable=too-many-instance-attributes, too-many-arguments - def __init__(self, hass, name, entity_ids=None, user_defined=True, icon=None, view=False, object_id=None): + """Initialize a group.""" self.hass = hass self._name = name self._state = STATE_UNKNOWN @@ -146,6 +145,7 @@ class Group(Entity): self.group_on = None self.group_off = None self._assumed_state = False + self._lock = threading.Lock() if entity_ids is not None: self.update_tracked_entity_ids(entity_ids) @@ -154,26 +154,32 @@ class Group(Entity): @property def should_poll(self): + """No need to poll because groups will update themselves.""" return False @property def name(self): + """Return the name of the group.""" return self._name @property def state(self): + """Return the state of the group.""" return self._state @property def icon(self): + """Return the icon of the group.""" return self._icon @property def hidden(self): + """If group should be hidden or not.""" return not self._user_defined or self._view @property def state_attributes(self): + """Return the state attributes for the group.""" data = { ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order, @@ -186,11 +192,11 @@ class Group(Entity): @property def assumed_state(self): - """Return True if unable to access real state of entity.""" + """Test if any member has an assumed state.""" return self._assumed_state def update_tracked_entity_ids(self, entity_ids): - """ Update the tracked entity IDs. """ + """Update the member entity IDs.""" self.stop() self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) self.group_on, self.group_off = None, None @@ -200,30 +206,30 @@ class Group(Entity): self.start() def start(self): - """ Starts the tracking. """ + """Start tracking members.""" track_state_change( self.hass, self.tracking, self._state_changed_listener) def stop(self): - """ Unregisters the group from Home Assistant. """ + """Unregister the group from Home Assistant.""" self.hass.states.remove(self.entity_id) self.hass.bus.remove_listener( ha.EVENT_STATE_CHANGED, self._state_changed_listener) def update(self): - """ Query all the tracked states and determine current group state. """ + """Query all members 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. """ + """Respond to a member state changing.""" self._update_group_state(new_state) self.update_ha_state() @property def _tracking_states(self): - """States that the group is tracking.""" + """The states that the group is tracking.""" states = [] for entity_id in self.tracking: @@ -242,49 +248,53 @@ class Group(Entity): """ # 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 + with self._lock: + states = None + gr_state = self._state + gr_on = self.group_on + gr_off = self.group_off - # We have not determined type of group yet - if gr_on is None: - if tr_state is None: - states = self._tracking_states + # We have not determined type of group yet + if gr_on is None: + if tr_state is None: + states = self._tracking_states - 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) + 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 + 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 + # We cannot determine state of the group + if gr_on is None: + return - 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 tr_state is None or (gr_state == gr_on and + tr_state.state == gr_off): + if states is None: + states = self._tracking_states - if any(state.state == gr_on for state in states): - self._state = gr_on - else: - self._state = gr_off + if any(state.state == gr_on for state in states): + self._state = gr_on + else: + self._state = gr_off - elif tr_state.state in (gr_on, gr_off): - self._state = tr_state.state + elif tr_state.state in (gr_on, gr_off): + self._state = tr_state.state - 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 + 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 - self._assumed_state = any(state.attributes.get(ATTR_ASSUMED_STATE) - for state in states) + 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 + elif tr_state.attributes.get(ATTR_ASSUMED_STATE): + self._assumed_state = True diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 578cb265a27..9151406c388 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -1,6 +1,4 @@ """ -homeassistant.components.history -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provide pre-made queries on top of the recorder component. For more details about this component, please refer to the documentation at @@ -11,7 +9,7 @@ from collections import defaultdict from datetime import timedelta from itertools import groupby -import homeassistant.components.recorder as recorder +from homeassistant.components import recorder, script import homeassistant.util.dt as dt_util from homeassistant.const import HTTP_BAD_REQUEST @@ -19,13 +17,14 @@ DOMAIN = 'history' DEPENDENCIES = ['recorder', 'http'] SIGNIFICANT_DOMAINS = ('thermostat',) +IGNORE_DOMAINS = ('zone', 'scene',) URL_HISTORY_PERIOD = re.compile( r'/api/history/period(?:/(?P\d{4}-\d{1,2}-\d{1,2})|)') def last_5_states(entity_id): - """ Return the last 5 states for entity_id. """ + """Return the last 5 states for entity_id.""" entity_id = entity_id.lower() query = """ @@ -38,17 +37,18 @@ def last_5_states(entity_id): def get_significant_states(start_time, end_time=None, entity_id=None): - """Return states changes during UTC period start_time - end_time. + """ + Return states changes during UTC period start_time - end_time. Significant states are all states where there is a state change, as well as all states from certain domains (for instance thermostat so that we get current temperature in our graphs). - """ where = """ - (domain in ({}) or last_changed=last_updated) - AND last_updated > ? - """.format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS])) + (domain IN ({}) OR last_changed=last_updated) + AND domain NOT IN ({}) AND last_updated > ? + """.format(",".join("'%s'" % x for x in SIGNIFICANT_DOMAINS), + ",".join("'%s'" % x for x in IGNORE_DOMAINS)) data = [start_time] @@ -63,15 +63,14 @@ def get_significant_states(start_time, end_time=None, entity_id=None): query = ("SELECT * FROM states WHERE {} " "ORDER BY entity_id, last_updated ASC").format(where) - states = recorder.query_states(query, data) + states = (state for state in recorder.query_states(query, data) + if _is_significant(state)) return states_to_json(states, start_time, entity_id) def state_changes_during_period(start_time, end_time=None, entity_id=None): - """ - Return states changes during UTC period start_time - end_time. - """ + """Return states changes during UTC period start_time - end_time.""" where = "last_changed=last_updated AND last_changed > ? " data = [start_time] @@ -92,7 +91,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None): def get_states(utc_point_in_time, entity_ids=None, run=None): - """ Returns the states at a specific point in time. """ + """Return the states at a specific point in time.""" if run is None: run = recorder.run_information(utc_point_in_time) @@ -121,7 +120,7 @@ def get_states(utc_point_in_time, entity_ids=None, run=None): def states_to_json(states, start_time, entity_id): - """Converts SQL results into JSON friendly data structure. + """Convert SQL results into JSON friendly data structure. This takes our state list and turns it into a JSON friendly data structure {'entity_id': [list of states], 'entity_id2': [list of states]} @@ -130,7 +129,6 @@ def states_to_json(states, start_time, entity_id): each list of states, otherwise our graphs won't start on the Y axis correctly. """ - result = defaultdict(list) entity_ids = [entity_id] if entity_id is not None else None @@ -148,7 +146,7 @@ def states_to_json(states, start_time, entity_id): def get_state(utc_point_in_time, entity_id, run=None): - """ Return a state at a specific point in time. """ + """Return a state at a specific point in time.""" states = get_states(utc_point_in_time, (entity_id,), run) return states[0] if states else None @@ -156,7 +154,7 @@ def get_state(utc_point_in_time, entity_id, run=None): # pylint: disable=unused-argument def setup(hass, config): - """ Setup history hooks. """ + """Setup the history hooks.""" hass.http.register_path( 'GET', re.compile( @@ -172,14 +170,14 @@ def setup(hass, config): # pylint: disable=unused-argument # pylint: disable=invalid-name def _api_last_5_states(handler, path_match, data): - """ Return the last 5 states for an entity id as JSON. """ + """Return the last 5 states for an entity id as JSON.""" entity_id = path_match.group('entity_id') handler.write_json(last_5_states(entity_id)) def _api_history_period(handler, path_match, data): - """ Return history over a period of time. """ + """Return history over a period of time.""" date_str = path_match.group('date') one_day = timedelta(seconds=86400) @@ -200,3 +198,13 @@ def _api_history_period(handler, path_match, data): handler.write_json( get_significant_states(start_time, end_time, entity_id).values()) + + +def _is_significant(state): + """Test if state is significant for history charts. + + Will only test for things that are not filtered out in SQL. + """ + # scripts that are not cancellable will never change state + return (state.domain != 'script' or + state.attributes.get(script.ATTR_CAN_CANCEL)) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 878d7465128..90d43d38818 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -1,6 +1,4 @@ """ -homeassistant.components.http -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ -52,7 +50,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ Sets up the HTTP API and debug interface. """ + """Set up the HTTP API and debug interface.""" conf = config.get(DOMAIN, {}) api_password = util.convert(conf.get(CONF_API_PASSWORD), str) @@ -87,15 +85,16 @@ def setup(hass, config): # pylint: disable=too-many-instance-attributes class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): - """ Handle HTTP requests in a threaded fashion. """ - # pylint: disable=too-few-public-methods + """Handle HTTP requests in a threaded fashion.""" + # pylint: disable=too-few-public-methods allow_reuse_address = True daemon_threads = True # pylint: disable=too-many-arguments def __init__(self, server_address, request_handler_class, hass, api_password, development, ssl_certificate, ssl_key): + """Initialize the server.""" super().__init__(server_address, request_handler_class) self.server_address = server_address @@ -119,9 +118,9 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.socket = context.wrap_socket(self.socket, server_side=True) def start(self): - """ Starts the HTTP server. """ + """Start the HTTP server.""" def stop_http(event): - """ Stops the HTTP server. """ + """Stop the HTTP server.""" self.shutdown() self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) @@ -140,19 +139,18 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.serve_forever() def register_path(self, method, url, callback, require_auth=True): - """ Registers a path with the server. """ + """Register a path with the server.""" self.paths.append((method, url, callback, require_auth)) def log_message(self, fmt, *args): - """ Redirect built-in log to HA logging """ + """Redirect built-in log to HA logging.""" # pylint: disable=no-self-use _LOGGER.info(fmt, *args) # pylint: disable=too-many-public-methods,too-many-locals class RequestHandler(SimpleHTTPRequestHandler): - """ - Handles incoming HTTP requests + """Handle incoming HTTP requests. We extend from SimpleHTTPRequestHandler instead of Base so we can use the guess content type methods. @@ -161,13 +159,13 @@ class RequestHandler(SimpleHTTPRequestHandler): server_version = "HomeAssistant/1.0" def __init__(self, req, client_addr, server): - """ Contructor, call the base constructor and set up session """ + """Constructor, call the base constructor and set up session.""" # Track if this was an authenticated request self.authenticated = False SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) def log_message(self, fmt, *arguments): - """ Redirect built-in log to HA logging """ + """Redirect built-in log to HA logging.""" if self.server.api_password is None: _LOGGER.info(fmt, *arguments) else: @@ -176,7 +174,7 @@ class RequestHandler(SimpleHTTPRequestHandler): if isinstance(arg, str) else arg for arg in arguments)) def _handle_request(self, method): # pylint: disable=too-many-branches - """ Does some common checks and calls appropriate method. """ + """Perform some common checks and call appropriate method.""" url = urlparse(self.path) # Read query input. parse_qs gives a list for each value, we want last @@ -254,31 +252,31 @@ class RequestHandler(SimpleHTTPRequestHandler): self.end_headers() def do_HEAD(self): # pylint: disable=invalid-name - """ HEAD request handler. """ + """HEAD request handler.""" self._handle_request('HEAD') def do_GET(self): # pylint: disable=invalid-name - """ GET request handler. """ + """GET request handler.""" self._handle_request('GET') def do_POST(self): # pylint: disable=invalid-name - """ POST request handler. """ + """POST request handler.""" self._handle_request('POST') def do_PUT(self): # pylint: disable=invalid-name - """ PUT request handler. """ + """PUT request handler.""" self._handle_request('PUT') def do_DELETE(self): # pylint: disable=invalid-name - """ DELETE request handler. """ + """DELETE request handler.""" self._handle_request('DELETE') def write_json_message(self, message, status_code=HTTP_OK): - """ Helper method to return a message to the caller. """ + """Helper method to return a message to the caller.""" self.write_json({'message': message}, status_code=status_code) def write_json(self, data=None, status_code=HTTP_OK, location=None): - """ Helper method to return JSON to the caller. """ + """Helper method to return JSON to the caller.""" self.send_response(status_code) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) @@ -295,7 +293,7 @@ class RequestHandler(SimpleHTTPRequestHandler): cls=rem.JSONEncoder).encode("UTF-8")) def write_text(self, message, status_code=HTTP_OK): - """ Helper method to return a text message to the caller. """ + """Helper method to return a text message to the caller.""" self.send_response(status_code) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN) @@ -306,7 +304,7 @@ class RequestHandler(SimpleHTTPRequestHandler): self.wfile.write(message.encode("UTF-8")) def write_file(self, path, cache_headers=True): - """ Returns a file to the user. """ + """Return a file to the user.""" try: with open(path, 'rb') as inp: self.write_file_pointer(self.guess_type(path), inp, @@ -318,10 +316,7 @@ class RequestHandler(SimpleHTTPRequestHandler): _LOGGER.exception("Unable to serve %s", path) def write_file_pointer(self, content_type, inp, cache_headers=True): - """ - Helper function to write a file pointer to the user. - Does not do error handling. - """ + """Helper function to write a file pointer to the user.""" do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '') self.send_response(HTTP_OK) @@ -354,7 +349,7 @@ class RequestHandler(SimpleHTTPRequestHandler): self.copyfile(inp, self.wfile) def set_cache_header(self): - """ Add cache headers if not in development """ + """Add cache headers if not in development.""" if self.server.development: return @@ -369,7 +364,7 @@ class RequestHandler(SimpleHTTPRequestHandler): self.date_time_string(time.time()+cache_time)) def set_session_cookie_header(self): - """ Add the header for the session cookie and return session id. """ + """Add the header for the session cookie and return session ID.""" if not self.authenticated: return None @@ -387,13 +382,13 @@ class RequestHandler(SimpleHTTPRequestHandler): return session_id def verify_session(self): - """ Verify that we are in a valid session. """ + """Verify that we are in a valid session.""" return self.get_cookie_session_id() is not None def get_cookie_session_id(self): - """ - Extracts the current session id from the - cookie or returns None if not set or invalid + """Extract the current session ID from the cookie. + + Return None if not set or invalid. """ if 'Cookie' not in self.headers: return None @@ -417,7 +412,7 @@ class RequestHandler(SimpleHTTPRequestHandler): return None def destroy_session(self): - """ Destroys session. """ + """Destroy the session.""" session_id = self.get_cookie_session_id() if session_id is None: @@ -428,27 +423,28 @@ class RequestHandler(SimpleHTTPRequestHandler): def session_valid_time(): - """ Time till when a session will be valid. """ + """Time till when a session will be valid.""" return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS) class SessionStore(object): - """ Responsible for storing and retrieving http sessions """ + """Responsible for storing and retrieving HTTP sessions.""" + def __init__(self): - """ Set up the session store """ + """Setup the session store.""" self._sessions = {} self._lock = threading.RLock() @util.Throttle(SESSION_CLEAR_INTERVAL) def _remove_expired(self): - """ Remove any expired sessions. """ + """Remove any expired sessions.""" now = date_util.utcnow() for key in [key for key, valid_time in self._sessions.items() if valid_time < now]: self._sessions.pop(key) def is_valid(self, key): - """ Return True if a valid session is given. """ + """Return True if a valid session is given.""" with self._lock: self._remove_expired() @@ -456,19 +452,19 @@ class SessionStore(object): self._sessions[key] > date_util.utcnow()) def extend_validation(self, key): - """ Extend a session validation time. """ + """Extend a session validation time.""" with self._lock: if key not in self._sessions: return self._sessions[key] = session_valid_time() def destroy(self, key): - """ Destroy a session by key. """ + """Destroy a session by key.""" with self._lock: self._sessions.pop(key, None) def create(self): - """ Creates a new session. """ + """Create a new session.""" with self._lock: session_id = util.get_random_string(20) diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py index 765cd000e2c..6694bb5d2ee 100644 --- a/homeassistant/components/ifttt.py +++ b/homeassistant/components/ifttt.py @@ -1,7 +1,5 @@ """ -homeassistant.components.ifttt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This component enable you to trigger Maker IFTTT recipes. +Support to trigger Maker IFTTT recipes. For more details about this component, please refer to the documentation at https://home-assistant.io/components/ifttt/ @@ -27,7 +25,7 @@ REQUIREMENTS = ['pyfttt==0.3'] def trigger(hass, event, value1=None, value2=None, value3=None): - """ Trigger a Maker IFTTT recipe. """ + """Trigger a Maker IFTTT recipe.""" data = { ATTR_EVENT: event, ATTR_VALUE1: value1, @@ -38,15 +36,14 @@ def trigger(hass, event, value1=None, value2=None, value3=None): def setup(hass, config): - """ Setup the ifttt service component. """ - + """Setup the IFTTT service component.""" if not validate_config(config, {DOMAIN: ['key']}, _LOGGER): return False key = config[DOMAIN]['key'] def trigger_service(call): - """ Handle ifttt trigger service calls. """ + """Handle IFTTT trigger service calls.""" event = call.data.get(ATTR_EVENT) value1 = call.data.get(ATTR_VALUE1) value2 = call.data.get(ATTR_VALUE2) diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index 749dd4bd097..082430903d0 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -31,8 +31,10 @@ CONF_USERNAME = 'username' CONF_PASSWORD = 'password' CONF_SSL = 'ssl' CONF_VERIFY_SSL = 'verify_ssl' +CONF_BLACKLIST = 'blacklist' +# pylint: disable=too-many-locals def setup(hass, config): """Setup the InfluxDB component.""" from influxdb import InfluxDBClient, exceptions @@ -52,6 +54,7 @@ def setup(hass, config): ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL) verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool, DEFAULT_VERIFY_SSL) + blacklist = conf.get(CONF_BLACKLIST, []) try: influx = InfluxDBClient(host=host, port=port, username=username, @@ -67,7 +70,8 @@ def setup(hass, config): def influx_event_listener(event): """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, ''): + if state is None or state.state in (STATE_UNKNOWN, '') \ + or state.entity_id in blacklist: return try: diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py index fc5a7856604..75d5d940f47 100644 --- a/homeassistant/components/input_boolean.py +++ b/homeassistant/components/input_boolean.py @@ -1,6 +1,4 @@ """ -homeassistant.components.input_boolean -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to keep track of user controlled booleans for within automation. For more details about this component, please refer to the documentation @@ -41,7 +39,7 @@ def turn_off(hass, entity_id): def setup(hass, config): - """ Set up input boolean. """ + """Set up input boolean.""" if not isinstance(config.get(DOMAIN), dict): _LOGGER.error('Expected %s config to be a dictionary', DOMAIN) return False @@ -68,7 +66,7 @@ def setup(hass, config): return False def toggle_service(service): - """ Handle a calls to the input boolean services. """ + """Handle a calls to the input boolean services.""" target_inputs = component.extract_from_service(service) for input_b in target_inputs: @@ -86,10 +84,10 @@ def setup(hass, config): class InputBoolean(ToggleEntity): - """ Represent a boolean input. """ + """Representation of a boolean input.""" def __init__(self, object_id, name, state, icon): - """ Initialize a boolean input. """ + """Initialize a boolean input.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name self._state = state @@ -97,22 +95,22 @@ class InputBoolean(ToggleEntity): @property def should_poll(self): - """If entitiy should be polled.""" + """If entity should be polled.""" return False @property def name(self): - """Name of the boolean input.""" + """Return name of the boolean input.""" return self._name @property def icon(self): - """Icon to be used for this entity.""" + """Returh the icon to be used for this entity.""" return self._icon @property def is_on(self): - """True if entity is on.""" + """Return true if entity is on.""" return self._state def turn_on(self, **kwargs): diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index a2fdf276bef..e7c6e280386 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -1,6 +1,4 @@ """ -homeassistant.components.input_select -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to offer a way to select an option from a list. For more details about this component, please refer to the documentation @@ -29,7 +27,7 @@ SERVICE_SELECT_OPTION = 'select_option' def select_option(hass, entity_id, option): - """ Set input_select to False. """ + """Set input_select to False.""" hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, { ATTR_ENTITY_ID: entity_id, ATTR_OPTION: option, @@ -37,7 +35,7 @@ def select_option(hass, entity_id, option): def setup(hass, config): - """ Set up input select. """ + """Setup input select.""" if not isinstance(config.get(DOMAIN), dict): _LOGGER.error('Expected %s config to be a dictionary', DOMAIN) return False @@ -77,7 +75,7 @@ def setup(hass, config): return False def select_option_service(call): - """ Handle a calls to the input select services. """ + """Handle a calls to the input select services.""" target_inputs = component.extract_from_service(call) for input_select in target_inputs: @@ -92,11 +90,11 @@ def setup(hass, config): class InputSelect(Entity): - """ Represent a select input. """ + """Representation of a select input.""" # pylint: disable=too-many-arguments def __init__(self, object_id, name, state, options, icon): - """ Initialize a select input. """ + """Initialize a select input.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name self._current_option = state @@ -105,33 +103,33 @@ class InputSelect(Entity): @property def should_poll(self): - """ If entity should be polled. """ + """If entity should be polled.""" return False @property def name(self): - """ Name of the select input. """ + """Return the name of the select input.""" return self._name @property def icon(self): - """ Icon to be used for this entity. """ + """Return the icon to be used for this entity.""" return self._icon @property def state(self): - """ State of the component. """ + """Return the state of the component.""" return self._current_option @property def state_attributes(self): - """ State attributes. """ + """Return the state attributes.""" return { ATTR_OPTIONS: self._options, } def select_option(self, option): - """ Select new option. """ + """Select new option.""" if option not in self._options: _LOGGER.warning('Invalid option: %s (possible options: %s)', option, ', '.join(self._options)) diff --git a/homeassistant/components/insteon_hub.py b/homeassistant/components/insteon_hub.py index c8f26eefd85..00f7bb5b143 100644 --- a/homeassistant/components/insteon_hub.py +++ b/homeassistant/components/insteon_hub.py @@ -1,6 +1,4 @@ """ -homeassistant.components.insteon_hub -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Insteon Hub. For more details about this component, please refer to the documentation at @@ -24,8 +22,8 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ - Setup Insteon Hub component. + """Setup Insteon Hub component. + This will automatically import associated lights. """ if not validate_config( @@ -58,24 +56,25 @@ def setup(hass, config): class InsteonToggleDevice(ToggleEntity): - """ Abstract Class for an Insteon node. """ + """An abstract Class for an Insteon node.""" def __init__(self, node): + """Initialize the device.""" self.node = node self._value = 0 @property def name(self): - """ Returns the name of the node. """ + """Return the the name of the node.""" return self.node.DeviceName @property def unique_id(self): - """ Returns the id of this insteon node. """ + """Return the ID of this insteon node.""" return self.node.DeviceID def update(self): - """ Update state of the sensor. """ + """Update state of the sensor.""" resp = self.node.send_command('get_status', wait=True) try: self._value = resp['response']['level'] @@ -84,11 +83,13 @@ class InsteonToggleDevice(ToggleEntity): @property def is_on(self): - """ Returns boolean response if the node is on. """ + """Return the boolean response if the node is on.""" return self._value != 0 def turn_on(self, **kwargs): + """Turn device on.""" self.node.send_command('on') def turn_off(self, **kwargs): + """Turn device off.""" self.node.send_command('off') diff --git a/homeassistant/components/introduction.py b/homeassistant/components/introduction.py index 540d928f7f5..59e6e7a2f3d 100644 --- a/homeassistant/components/introduction.py +++ b/homeassistant/components/introduction.py @@ -1,6 +1,4 @@ """ -homeassistant.components.introduction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component that will help guide the user taking its first steps. For more details about this component, please refer to the documentation at @@ -12,7 +10,7 @@ DOMAIN = 'introduction' def setup(hass, config=None): - """ Setup the introduction component. """ + """Setup the introduction component.""" log = logging.getLogger(__name__) log.info(""" diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 36a16db0d33..697aa4e8ea6 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -1,8 +1,5 @@ """ -homeassistant.components.isy994 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Connects to an ISY-994 controller and loads relevant components to control its -devices. Also contains the base classes for ISY Sensors, Lights, and Switches. +Support the ISY-994 controllers. For configuration details please visit the documentation for this component at https://home-assistant.io/components/isy994/ @@ -32,8 +29,8 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): - """ - Setup ISY994 component. + """Setup ISY994 component. + This will automatically import associated lights, switches, and sensors. """ import PyISY @@ -45,7 +42,7 @@ def setup(hass, config): _LOGGER): return False - # pull and parse standard configuration + # Pull and parse standard configuration. user = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] host = urlparse(config[DOMAIN][CONF_HOST]) @@ -62,24 +59,24 @@ def setup(hass, config): port = host.port addr = addr.replace(':{}'.format(port), '') - # pull and parse optional configuration + # Pull and parse optional configuration. global SENSOR_STRING global HIDDEN_STRING SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING)) HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING)) tls_version = config[DOMAIN].get(CONF_TLS_VER, None) - # connect to ISY controller + # Connect to ISY controller. global ISY ISY = PyISY.ISY(addr, port, user, password, use_https=https, tls_ver=tls_version, log=_LOGGER) if not ISY.connected: return False - # listen for HA stop to disconnect + # Listen for HA stop to disconnect. hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop) - # Load components for the devices in the ISY controller that we support + # Load components for the devices in the ISY controller that we support. for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), ('light', DISCOVER_LIGHTS), ('switch', DISCOVER_SWITCHES))): @@ -94,12 +91,12 @@ def setup(hass, config): def stop(event): - """ Cleanup the ISY subscription. """ + """Cleanup the ISY subscription.""" ISY.auto_update = False class ISYDeviceABC(ToggleEntity): - """ Abstract Class for an ISY device. """ + """An abstract Class for an ISY device.""" _attrs = {} _onattrs = [] @@ -109,6 +106,7 @@ class ISYDeviceABC(ToggleEntity): _name = None def __init__(self, node): + """Initialize the device.""" # setup properties self.node = node @@ -117,35 +115,35 @@ class ISYDeviceABC(ToggleEntity): subscribe('changed', self.on_update) def __del__(self): - """ cleanup subscriptions because it is the right thing to do. """ + """Cleanup subscriptions because it is the right thing to do.""" self._change_handler.unsubscribe() @property def domain(self): - """ Returns the domain of the entity. """ + """Return the domain of the entity.""" return self._domain @property def dtype(self): - """ Returns the data type of the entity (binary or analog). """ + """Return the data type of the entity (binary or analog).""" if self._dtype in ['analog', 'binary']: return self._dtype return 'binary' if self.unit_of_measurement is None else 'analog' @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False @property def value(self): - """ Returns the unclean value from the controller. """ + """Return the unclean value from the controller.""" # pylint: disable=protected-access return self.node.status._val @property def state_attributes(self): - """ Returns the state attributes for the node. """ + """Return the state attributes for the node.""" attr = {} for name, prop in self._attrs.items(): attr[name] = getattr(self, prop) @@ -153,61 +151,61 @@ class ISYDeviceABC(ToggleEntity): return attr def _attr_filter(self, attr): - """ Placeholder for attribute filters. """ + """A Placeholder for attribute filters.""" # pylint: disable=no-self-use return attr @property def unique_id(self): - """ Returns the id of this ISY sensor. """ + """Return the ID of this ISY sensor.""" # pylint: disable=protected-access return self.node._id @property def raw_name(self): - """ Returns the unclean node name. """ + """Return the unclean node name.""" return str(self._name) \ if self._name is not None else str(self.node.name) @property def name(self): - """ Returns the cleaned name of the node. """ + """Return the cleaned name of the node.""" return self.raw_name.replace(HIDDEN_STRING, '').strip() \ .replace('_', ' ') @property def hidden(self): - """ Suggestion if the entity should be hidden from UIs. """ + """Suggestion if the entity should be hidden from UIs.""" return HIDDEN_STRING in self.raw_name def update(self): - """ Update state of the sensor. """ + """Update state of the sensor.""" # ISY objects are automatically updated by the ISY's event stream pass def on_update(self, event): - """ Handles the update received event. """ + """Handle the update received event.""" self.update_ha_state() @property def is_on(self): - """ Returns boolean response if the node is on. """ + """Return a boolean response if the node is on.""" return bool(self.value) @property def is_open(self): - """ Returns boolean respons if the node is open. On = Open. """ + """Return boolean response if the node is open. On = Open.""" return self.is_on @property def state(self): - """ Returns the state of the node. """ + """Return the state of the node.""" if len(self._states) > 0: return self._states[0] if self.is_on else self._states[1] return self.value def turn_on(self, **kwargs): - """ Turns the device on. """ + """Turn the device on.""" if self.domain is not 'sensor': attrs = [kwargs.get(name) for name in self._onattrs] self.node.on(*attrs) @@ -215,7 +213,7 @@ class ISYDeviceABC(ToggleEntity): _LOGGER.error('ISY cannot turn on sensors.') def turn_off(self, **kwargs): - """ Turns the device off. """ + """Turn the device off.""" if self.domain is not 'sensor': self.node.off() else: @@ -223,7 +221,7 @@ class ISYDeviceABC(ToggleEntity): @property def unit_of_measurement(self): - """ Returns the defined units of measurement or None. """ + """Return the defined units of measurement or None.""" try: return self.node.units except AttributeError: diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index b8c1e5f44d5..eb801fae252 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -1,6 +1,4 @@ """ -homeassistant.components.keyboard -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to emulate keyboard presses on host machine. For more details about this component, please refer to the documentation at @@ -16,37 +14,37 @@ REQUIREMENTS = ['pyuserinput==0.1.9'] def volume_up(hass): - """ Press the keyboard button for volume up. """ + """Press the keyboard button for volume up.""" hass.services.call(DOMAIN, SERVICE_VOLUME_UP) def volume_down(hass): - """ Press the keyboard button for volume down. """ + """Press the keyboard button for volume down.""" hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN) def volume_mute(hass): - """ Press the keyboard button for muting volume. """ + """Press the keyboard button for muting volume.""" hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE) def media_play_pause(hass): - """ Press the keyboard button for play/pause. """ + """Press the keyboard button for play/pause.""" hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE) def media_next_track(hass): - """ Press the keyboard button for next track. """ + """Press the keyboard button for next track.""" hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK) def media_prev_track(hass): - """ Press the keyboard button for prev track. """ + """Press the keyboard button for prev track.""" hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK) def setup(hass, config): - """ Listen for keyboard events. """ + """Listen for keyboard events.""" import pykeyboard keyboard = pykeyboard.PyKeyboard() diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index cc00cd6be42..357a6156c3a 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to interact with lights. For more details about this component, please refer to the documentation at @@ -30,26 +28,26 @@ ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights') ENTITY_ID_FORMAT = DOMAIN + ".{}" -# integer that represents transition time in seconds to make change +# Integer that represents transition time in seconds to make change. ATTR_TRANSITION = "transition" -# lists holding color values +# Lists holding color values ATTR_RGB_COLOR = "rgb_color" ATTR_XY_COLOR = "xy_color" ATTR_COLOR_TEMP = "color_temp" -# int with value 0 .. 255 representing brightness of the light +# int with value 0 .. 255 representing brightness of the light. ATTR_BRIGHTNESS = "brightness" -# String representing a profile (built-in ones or external defined) +# String representing a profile (built-in ones or external defined). ATTR_PROFILE = "profile" -# If the light should flash, can be FLASH_SHORT or FLASH_LONG +# If the light should flash, can be FLASH_SHORT or FLASH_LONG. ATTR_FLASH = "flash" FLASH_SHORT = "short" FLASH_LONG = "long" -# Apply an effect to the light, can be EFFECT_COLORLOOP +# Apply an effect to the light, can be EFFECT_COLORLOOP. ATTR_EFFECT = "effect" EFFECT_COLORLOOP = "colorloop" EFFECT_RANDOM = "random" @@ -57,7 +55,7 @@ EFFECT_WHITE = "white" LIGHT_PROFILES_FILE = "light_profiles.csv" -# Maps discovered services to their platforms +# Maps discovered services to their platforms. DISCOVERY_PLATFORMS = { wemo.DISCOVER_LIGHTS: 'wemo', wink.DISCOVER_LIGHTS: 'wink', @@ -79,9 +77,8 @@ _LOGGER = logging.getLogger(__name__) def is_on(hass, entity_id=None): - """ Returns if the lights are on based on the statemachine. """ + """Return if the lights are on based on the statemachine.""" entity_id = entity_id or ENTITY_ID_ALL_LIGHTS - return hass.states.is_state(entity_id, STATE_ON) @@ -89,7 +86,7 @@ def is_on(hass, entity_id=None): def turn_on(hass, entity_id=None, transition=None, brightness=None, rgb_color=None, xy_color=None, color_temp=None, profile=None, flash=None, effect=None): - """ Turns all or specified light on. """ + """Turn all or specified light on.""" data = { key: value for key, value in [ (ATTR_ENTITY_ID, entity_id), @@ -108,7 +105,7 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None, def turn_off(hass, entity_id=None, transition=None): - """ Turns all or specified light off. """ + """Turn all or specified light off.""" data = { key: value for key, value in [ (ATTR_ENTITY_ID, entity_id), @@ -120,7 +117,7 @@ def turn_off(hass, entity_id=None, transition=None): def toggle(hass, entity_id=None, transition=None): - """ Toggles all or specified light. """ + """Toggle all or specified light.""" data = { key: value for key, value in [ (ATTR_ENTITY_ID, entity_id), @@ -133,8 +130,7 @@ def toggle(hass, entity_id=None, transition=None): # pylint: disable=too-many-branches, too-many-locals, too-many-statements def setup(hass, config): - """ Exposes light control via statemachine and services. """ - + """Expose light control via statemachine and services.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, GROUP_NAME_ALL_LIGHTS) @@ -168,7 +164,7 @@ def setup(hass, config): return False def handle_light_service(service): - """ Hande a turn light on or off service call. """ + """Hande a turn light on or off service call.""" # Get and validate data dat = service.data @@ -197,11 +193,11 @@ def setup(hass, config): light.update_ha_state(True) return - # Processing extra data for turn light on request + # Processing extra data for turn light on request. # We process the profile first so that we get the desired # behavior that extra service data attributes overwrite - # profile values + # profile values. profile = profiles.get(dat.get(ATTR_PROFILE)) if profile: @@ -215,10 +211,10 @@ def setup(hass, config): if ATTR_XY_COLOR in dat: try: - # xy_color should be a list containing 2 floats + # xy_color should be a list containing 2 floats. xycolor = dat.get(ATTR_XY_COLOR) - # Without this check, a xycolor with value '99' would work + # Without this check, a xycolor with value '99' would work. if not isinstance(xycolor, str): params[ATTR_XY_COLOR] = [float(val) for val in xycolor] @@ -263,7 +259,7 @@ def setup(hass, config): if light.should_poll: light.update_ha_state(True) - # Listen for light on and light off service calls + # Listen for light on and light off service calls. descriptions = load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service, @@ -279,32 +275,32 @@ def setup(hass, config): class Light(ToggleEntity): - """ Represents a light within Home Assistant. """ - # pylint: disable=no-self-use + """Representation of a light.""" + # pylint: disable=no-self-use @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" return None @property def xy_color(self): - """ XY color value [float, float]. """ + """Return the XY color value [float, float].""" return None @property def rgb_color(self): - """ RGB color value [int, int, int] """ + """Return the RGB color value [int, int, int].""" return None @property def color_temp(self): - """ CT color value in mirads. """ + """Return the CT color value in mirads.""" return None @property def state_attributes(self): - """ Returns optional state attributes. """ + """Return optional state attributes.""" data = {} if self.is_on: diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/light/blinksticklight.py index 5e2f026aa90..9e42a41cb91 100644 --- a/homeassistant/components/light/blinksticklight.py +++ b/homeassistant/components/light/blinksticklight.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.blinksticklight -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Blinkstick lights. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ REQUIREMENTS = ["blinkstick==1.1.7"] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add device specified by serial number. """ + """Add device specified by serial number.""" from blinkstick import blinkstick stick = blinkstick.find_by_serial(config['serial']) @@ -27,9 +25,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class BlinkStickLight(Light): - """ Represents a BlinkStick light. """ + """Representation of a BlinkStick light.""" def __init__(self, stick, name): + """Initialize the light.""" self._stick = stick self._name = name self._serial = stick.get_serial() @@ -37,30 +36,30 @@ class BlinkStickLight(Light): @property def should_poll(self): - """ Polling needed. """ + """Polling needed.""" return True @property def name(self): - """ The name of the light. """ + """Return the name of the light.""" return self._name @property def rgb_color(self): - """ Read back the color of the light. """ + """Read back the color of the light.""" return self._rgb_color @property def is_on(self): - """ Check whether any of the LEDs colors are non-zero. """ + """Check whether any of the LEDs colors are non-zero.""" return sum(self._rgb_color) > 0 def update(self): - """ Read back the device state """ + """Read back the device state.""" self._rgb_color = self._stick.get_color() def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" if ATTR_RGB_COLOR in kwargs: self._rgb_color = kwargs[ATTR_RGB_COLOR] else: @@ -71,5 +70,5 @@ class BlinkStickLight(Light): blue=self._rgb_color[2]) def turn_off(self, **kwargs): - """ Turn the device off """ + """Turn the device off.""" self._stick.turn_off() diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index b4a3515d29b..ddca6e0a9b5 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -27,9 +27,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoLight(Light): - """Provides a demo light.""" + """Provide a demo light.""" + # pylint: disable=too-many-arguments def __init__(self, name, state, rgb=None, ct=None, brightness=180): + """Initialize the light.""" self._name = name self._state = state self._rgb = rgb or random.choice(LIGHT_COLORS) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index a183d1b533b..0c2bf8b0411 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.hue -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Hue lights. For more details about this platform, please refer to the documentation at @@ -36,7 +34,7 @@ _LOGGER = logging.getLogger(__name__) def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE): - """ Attempt to detect host based on existing configuration. """ + """Attempt to detect host based on existing configuration.""" path = hass.config.path(filename) if not os.path.isfile(path): @@ -53,7 +51,7 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE): def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Gets the Hue lights. """ + """Setup the Hue lights.""" filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE) if discovery_info is not None: host = urlparse(discovery_info[1]).hostname @@ -75,7 +73,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_bridge(host, hass, add_devices_callback, filename): - """ Setup a phue bridge based on host parameter. """ + """Setup a phue bridge based on host parameter.""" import phue try: @@ -106,7 +104,7 @@ def setup_bridge(host, hass, add_devices_callback, filename): @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_lights(): - """ Updates the Hue light objects with latest info from the bridge. """ + """Update the Hue light objects with latest info from the bridge.""" try: api = bridge.get_api() except socket.error: @@ -144,7 +142,7 @@ def setup_bridge(host, hass, add_devices_callback, filename): def request_configuration(host, hass, add_devices_callback, filename): - """ Request configuration steps from the user. """ + """Request configuration steps from the user.""" configurator = get_component('configurator') # We got an error if this method is called while we are configuring @@ -156,7 +154,7 @@ def request_configuration(host, hass, add_devices_callback, filename): # pylint: disable=unused-argument def hue_configuration_callback(data): - """ Actions to do when our configuration callback is called. """ + """The actions to do when our configuration callback is called.""" setup_bridge(host, hass, add_devices_callback, filename) _CONFIGURING[host] = configurator.request_config( @@ -169,11 +167,12 @@ def request_configuration(host, hass, add_devices_callback, filename): class HueLight(Light): - """ Represents a Hue light """ + """Representation of a Hue light.""" # pylint: disable=too-many-arguments def __init__(self, light_id, info, bridge, update_lights, bridge_type='hue'): + """Initialize the light.""" self.light_id = light_id self.info = info self.bridge = bridge @@ -182,39 +181,38 @@ class HueLight(Light): @property def unique_id(self): - """ Returns the id of this Hue light """ + """Return the ID of this Hue light.""" return "{}.{}".format( self.__class__, self.info.get('uniqueid', self.name)) @property def name(self): - """ Get the mame of the Hue light. """ + """Return the mame of the Hue light.""" return self.info.get('name', DEVICE_DEFAULT_NAME) @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" return self.info['state'].get('bri') @property def xy_color(self): - """ XY color value. """ + """Return the XY color value.""" return self.info['state'].get('xy') @property def color_temp(self): - """ CT color value. """ + """Return the CT color value.""" return self.info['state'].get('ct') @property def is_on(self): - """ True if device is on. """ + """Return true if device is on.""" self.update_lights() - return self.info['state']['reachable'] and self.info['state']['on'] def turn_on(self, **kwargs): - """ Turn the specified or all lights on. """ + """Turn the specified or all lights on.""" command = {'on': True} if ATTR_TRANSITION in kwargs: @@ -254,7 +252,7 @@ class HueLight(Light): self.bridge.set_light(self.light_id, command) def turn_off(self, **kwargs): - """ Turn the specified or all lights off. """ + """Turn the specified or all lights off.""" command = {'on': False} if ATTR_TRANSITION in kwargs: @@ -265,5 +263,5 @@ class HueLight(Light): self.bridge.set_light(self.light_id, command) def update(self): - """ Synchronize state with bridge. """ + """Synchronize state with bridge.""" self.update_lights(no_throttle=True) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index 7eb112eccd6..93e6e490e80 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.hyperion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Hyperion remotes. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ REQUIREMENTS = [] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Sets up a Hyperion server remote """ + """Setup a Hyperion server remote.""" host = config.get(CONF_HOST, None) port = config.get("port", 19444) device = Hyperion(host, port) @@ -30,9 +28,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class Hyperion(Light): - """ Represents a Hyperion remote """ + """Representation of a Hyperion remote.""" def __init__(self, host, port): + """Initialize the light.""" self._host = host self._port = port self._name = host @@ -41,21 +40,21 @@ class Hyperion(Light): @property def name(self): - """ Return the hostname of the server. """ + """Return the hostname of the server.""" return self._name @property def rgb_color(self): - """ Last RGB color value set. """ + """Return last RGB color value set.""" return self._rgb_color @property def is_on(self): - """ True if the device is online. """ + """Return true if the device is online.""" return self._is_available def turn_on(self, **kwargs): - """ Turn the lights on. """ + """Turn the lights on.""" if self._is_available: if ATTR_RGB_COLOR in kwargs: self._rgb_color = kwargs[ATTR_RGB_COLOR] @@ -64,16 +63,16 @@ class Hyperion(Light): "color": self._rgb_color}) def turn_off(self, **kwargs): - """ Disconnect the remote. """ + """Disconnect the remote.""" self.json_request({"command": "clearall"}) def update(self): - """ Ping the remote. """ + """Ping the remote.""" # just see if the remote port is open self._is_available = self.json_request() def setup(self): - """ Get the hostname of the remote. """ + """Get the hostname of the remote.""" response = self.json_request({"command": "serverinfo"}) if response: self._name = response["info"]["hostname"] @@ -82,7 +81,7 @@ class Hyperion(Light): return False def json_request(self, request=None, wait_for_response=False): - """ Communicate with the json server. """ + """Communicate with the JSON server.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) @@ -93,7 +92,7 @@ class Hyperion(Light): return False if not request: - # no communication needed, simple presence detection returns True + # No communication needed, simple presence detection returns True sock.close() return True @@ -101,11 +100,11 @@ class Hyperion(Light): try: buf = sock.recv(4096) except socket.timeout: - # something is wrong, assume it's offline + # Something is wrong, assume it's offline sock.close() return False - # read until a newline or timeout + # Read until a newline or timeout buffering = True while buffering: if "\n" in str(buf, "utf-8"): diff --git a/homeassistant/components/light/insteon_hub.py b/homeassistant/components/light/insteon_hub.py index 6ce17b5622a..bf7e915f8fb 100644 --- a/homeassistant/components/light/insteon_hub.py +++ b/homeassistant/components/light/insteon_hub.py @@ -1,14 +1,14 @@ """ -homeassistant.components.light.insteon -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Insteon Hub lights. -""" +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/insteon_hub/ +""" from homeassistant.components.insteon_hub import INSTEON, InsteonToggleDevice def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Insteon Hub light platform. """ + """Setup the Insteon Hub light platform.""" devs = [] for device in INSTEON.devices: if device.DeviceCategory == "Switched Lighting Control": diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 6d76ea45c58..f7261540ddd 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.isy994 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for ISY994 lights. For more details about this platform, please refer to the documentation at @@ -15,15 +13,15 @@ from homeassistant.const import STATE_OFF, STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the ISY994 platform. """ + """Setup the ISY994 platform.""" logger = logging.getLogger(__name__) devs = [] - # 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 dimmable nodes + # Import dimmable nodes for (path, node) in ISY.nodes: if node.dimmable and SENSOR_STRING not in node.name: if HIDDEN_STRING in path: @@ -34,7 +32,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ISYLightDevice(ISYDeviceABC): - """ Represents as ISY light. """ + """Representation of a ISY light.""" _domain = 'light' _dtype = 'analog' @@ -43,7 +41,7 @@ class ISYLightDevice(ISYDeviceABC): _states = [STATE_ON, STATE_OFF] def _attr_filter(self, attr): - """ Filter brightness out of entity while off. """ + """Filter brightness out of entity while off.""" if ATTR_BRIGHTNESS in attr and not self.is_on: del attr[ATTR_BRIGHTNESS] return attr diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 5d58ca50811..257bfc9e408 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -1,7 +1,5 @@ """ -homeassistant.components.light.lifx -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LIFX platform that implements lights +Support for the LIFX platform that implements lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.lifx/ @@ -31,8 +29,11 @@ TEMP_MAX_HASS = 500 # home assistant maximum temperature class LIFX(): + """Representation of a LIFX light.""" + def __init__(self, add_devices_callback, server_addr=None, broadcast_addr=None): + """Initialize the light.""" import liffylights self._devices = [] @@ -47,6 +48,7 @@ class LIFX(): broadcast_addr) def find_bulb(self, ipaddr): + """Search for bulbs.""" bulb = None for device in self._devices: if device.ipaddr == ipaddr: @@ -56,6 +58,7 @@ class LIFX(): # pylint: disable=too-many-arguments def on_device(self, ipaddr, name, power, hue, sat, bri, kel): + """Initialize the light.""" bulb = self.find_bulb(ipaddr) if bulb is None: @@ -74,6 +77,7 @@ class LIFX(): # pylint: disable=too-many-arguments def on_color(self, ipaddr, hue, sat, bri, kel): + """Initialize the light.""" bulb = self.find_bulb(ipaddr) if bulb is not None: @@ -81,6 +85,7 @@ class LIFX(): bulb.update_ha_state() def on_power(self, ipaddr, power): + """Initialize the light.""" bulb = self.find_bulb(ipaddr) if bulb is not None: @@ -89,28 +94,30 @@ class LIFX(): # pylint: disable=unused-argument def poll(self, now): + """Initialize the light.""" self.probe() def probe(self, address=None): + """Initialize the light.""" self._liffylights.probe(address) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Set up platform. """ + """Setup the LIFX platform.""" server_addr = config.get(CONF_SERVER, None) broadcast_addr = config.get(CONF_BROADCAST, None) lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr) - # register our poll service + # Register our poll service track_time_change(hass, lifx_library.poll, second=[10, 40]) lifx_library.probe() def convert_rgb_to_hsv(rgb): - """ Convert HASS RGB values to HSV values. """ + """Convert Home Assistant RGB values to HSV values.""" red, green, blue = [_ / BYTE_MAX for _ in rgb] hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue) @@ -122,10 +129,12 @@ def convert_rgb_to_hsv(rgb): # pylint: disable=too-many-instance-attributes class LIFXLight(Light): - """ Provides LIFX light. """ + """Representation of a LIFX light.""" + # pylint: disable=too-many-arguments def __init__(self, liffy, ipaddr, name, power, hue, saturation, brightness, kelvin): + """Initialize the light.""" _LOGGER.debug("LIFXLight: %s %s", ipaddr, name) @@ -137,58 +146,50 @@ class LIFXLight(Light): @property def should_poll(self): - """ No polling needed for LIFX light. """ + """No polling needed for LIFX light.""" return False @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def ipaddr(self): - """ Returns the ip of the device. """ + """Return the IP address of the device.""" return self._ip @property def rgb_color(self): - """ Returns RGB value. """ + """Return the RGB value.""" _LOGGER.debug("rgb_color: [%d %d %d]", self._rgb[0], self._rgb[1], self._rgb[2]) - return self._rgb @property def brightness(self): - """ Returns brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" brightness = int(self._bri / (BYTE_MAX + 1)) - - _LOGGER.debug("brightness: %d", - brightness) - + _LOGGER.debug("brightness: %d", brightness) return brightness @property def color_temp(self): - """ Returns color temperature. """ + """Return the color temperature.""" temperature = int(TEMP_MIN_HASS + (TEMP_MAX_HASS - TEMP_MIN_HASS) * (self._kel - TEMP_MIN) / (TEMP_MAX - TEMP_MIN)) - _LOGGER.debug("color_temp: %d", - temperature) - + _LOGGER.debug("color_temp: %d", temperature) return temperature @property def is_on(self): - """ True if device is on. """ - _LOGGER.debug("is_on: %d", - self._power) - + """Return true if device is on.""" + _LOGGER.debug("is_on: %d", self._power) return self._power != 0 def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" if ATTR_TRANSITION in kwargs: fade = kwargs[ATTR_TRANSITION] * 1000 else: @@ -225,30 +226,26 @@ class LIFXLight(Light): brightness, kelvin, fade) def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" if ATTR_TRANSITION in kwargs: fade = kwargs[ATTR_TRANSITION] * 1000 else: fade = 0 - _LOGGER.debug("turn_off: %s %d", - self._ip, fade) - + _LOGGER.debug("turn_off: %s %d", self._ip, fade) self._liffylights.set_power(self._ip, 0, fade) def set_name(self, name): - """ Set name. """ + """Set name of the light.""" self._name = name def set_power(self, power): - """ Set power state value. """ - _LOGGER.debug("set_power: %d", - power) - + """Set power state value.""" + _LOGGER.debug("set_power: %d", power) self._power = (power != 0) def set_color(self, hue, sat, bri, kel): - """ Set color state values. """ + """Set color state values.""" self._hue = hue self._sat = sat self._bri = bri diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index 5110c3a3570..5242746dc42 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.limitlessled -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for LimitlessLED bulbs. For more details about this platform, please refer to the documentation at @@ -23,7 +21,7 @@ WHITE = [255, 255, 255] def rewrite_legacy(config): - """ Rewrite legacy configuration to new format. """ + """Rewrite legacy configuration to new format.""" bridges = config.get('bridges', [config]) new_bridges = [] for bridge_conf in bridges: @@ -49,7 +47,7 @@ def rewrite_legacy(config): def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Gets the LimitlessLED lights. """ + """Setup the LimitlessLED lights.""" from limitlessled.bridge import Bridge # Two legacy configuration formats are supported to @@ -71,15 +69,15 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): def state(new_state): - """ State decorator. + """State decorator. Specify True (turn on) or False (turn off). """ def decorator(function): - """ Decorator function. """ + """Decorator function.""" # pylint: disable=no-member,protected-access def wrapper(self, **kwargs): - """ Wrap a group state change. """ + """Wrap a group state change.""" from limitlessled.pipeline import Pipeline pipeline = Pipeline() transition_time = DEFAULT_TRANSITION @@ -104,9 +102,10 @@ def state(new_state): class LimitlessLEDGroup(Light): - """ LimitessLED group. """ + """Representation of a LimitessLED group.""" + def __init__(self, group): - """ Initialize a group. """ + """Initialize a group.""" self.group = group self.repeating = False self._is_on = False @@ -114,7 +113,7 @@ class LimitlessLEDGroup(Light): @staticmethod def factory(group): - """ Produce LimitlessLEDGroup objects. """ + """Produce LimitlessLEDGroup objects.""" from limitlessled.group.rgbw import RgbwGroup from limitlessled.group.white import WhiteGroup if isinstance(group, WhiteGroup): @@ -124,38 +123,36 @@ class LimitlessLEDGroup(Light): @property def should_poll(self): - """ No polling needed. - - LimitlessLED state cannot be fetched. - """ + """No polling needed.""" return False @property def name(self): - """ Returns the name of the group. """ + """Return the name of the group.""" return self.group.name @property def is_on(self): - """ True if device is on. """ + """Return true if device is on.""" return self._is_on @property def brightness(self): - """ Brightness property. """ + """Return the brightness property.""" return self._brightness @state(False) def turn_off(self, transition_time, pipeline, **kwargs): - """ Turn off a group. """ + """Turn off a group.""" if self.is_on: pipeline.transition(transition_time, brightness=0.0).off() class LimitlessLEDWhiteGroup(LimitlessLEDGroup): - """ LimitlessLED White group. """ + """Representation of a LimitlessLED White group.""" + def __init__(self, group): - """ Initialize White group. """ + """Initialize White group.""" super().__init__(group) # Initialize group with known values. self.group.on = True @@ -167,12 +164,12 @@ class LimitlessLEDWhiteGroup(LimitlessLEDGroup): @property def color_temp(self): - """ Temperature property. """ + """Return the temperature property.""" return self._temperature @state(True) def turn_on(self, transition_time, pipeline, **kwargs): - """ Turn on (or adjust property of) a group. """ + """Turn on (or adjust property of) a group.""" # Check arguments. if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] @@ -187,9 +184,10 @@ class LimitlessLEDWhiteGroup(LimitlessLEDGroup): class LimitlessLEDRGBWGroup(LimitlessLEDGroup): - """ LimitlessLED RGBW group. """ + """Representation of a LimitlessLED RGBW group.""" + def __init__(self, group): - """ Initialize RGBW group. """ + """Initialize RGBW group.""" super().__init__(group) # Initialize group with known values. self.group.on = True @@ -201,12 +199,12 @@ class LimitlessLEDRGBWGroup(LimitlessLEDGroup): @property def rgb_color(self): - """ Color property. """ + """Return the color property.""" return self._color @state(True) def turn_on(self, transition_time, pipeline, **kwargs): - """ Turn on (or adjust property of) a group. """ + """Turn on (or adjust property of) a group.""" from limitlessled.presets import COLORLOOP # Check arguments. if ATTR_BRIGHTNESS in kwargs: @@ -239,43 +237,31 @@ class LimitlessLEDRGBWGroup(LimitlessLEDGroup): def _from_hass_temperature(temperature): - """ Convert Home Assistant color temperature - units to percentage. - """ + """Convert Home Assistant color temperature units to percentage.""" return (temperature - 154) / 346 def _to_hass_temperature(temperature): - """ Convert percentage to Home Assistant - color temperature units. - """ + """Convert percentage to Home Assistant color temperature units.""" return int(temperature * 346) + 154 def _from_hass_brightness(brightness): - """ Convert Home Assistant brightness units - to percentage. - """ + """Convert Home Assistant brightness units to percentage.""" return brightness / 255 def _to_hass_brightness(brightness): - """ Convert percentage to Home Assistant - brightness units. - """ + """Convert percentage to Home Assistant brightness units.""" return int(brightness * 255) def _from_hass_color(color): - """ Convert Home Assistant RGB list - to Color tuple. - """ + """Convert Home Assistant RGB list to Color tuple.""" from limitlessled import Color return Color(*tuple(color)) def _to_hass_color(color): - """ Convert from Color tuple to - Home Assistant RGB list. - """ + """Convert from Color tuple to Home Assistant RGB list.""" return list([int(c) for c in color]) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index a1ad6ea7e52..f55cc310192 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -11,6 +11,7 @@ import homeassistant.components.mqtt as mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) from homeassistant.helpers.template import render_with_possible_json_value +from homeassistant.util import convert _LOGGER = logging.getLogger(__name__) @@ -26,37 +27,36 @@ DEPENDENCIES = ['mqtt'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Add MQTT Light.""" - if config.get('command_topic') is None: _LOGGER.error("Missing required variable: command_topic") return False add_devices_callback([MqttLight( hass, - config.get('name', DEFAULT_NAME), - {key: config.get(key) for key in + convert(config.get('name'), str, DEFAULT_NAME), + {key: convert(config.get(key), str) for key in (typ + topic for typ in ('', 'brightness_', 'rgb_') for topic in ('state_topic', 'command_topic'))}, - {key: config.get(key + '_value_template') + {key: convert(config.get(key + '_value_template'), str) for key in ('state', 'brightness', 'rgb')}, - config.get('qos', DEFAULT_QOS), + convert(config.get('qos'), int, DEFAULT_QOS), { - 'on': config.get('payload_on', DEFAULT_PAYLOAD_ON), - 'off': config.get('payload_off', DEFAULT_PAYLOAD_OFF) + 'on': convert(config.get('payload_on'), str, DEFAULT_PAYLOAD_ON), + 'off': convert(config.get('payload_off'), str, DEFAULT_PAYLOAD_OFF) }, - config.get('optimistic', DEFAULT_OPTIMISTIC), - config.get('brightness_scale', DEFAULT_BRIGHTNESS_SCALE) + convert(config.get('optimistic'), bool, DEFAULT_OPTIMISTIC), + convert(config.get('brightness_scale'), int, DEFAULT_BRIGHTNESS_SCALE) )]) class MqttLight(Light): - """Provides a MQTT light.""" + """MQTT light.""" # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__(self, hass, name, topic, templates, qos, payload, optimistic, brightness_scale): - + """Initialize MQTT light.""" self._hass = hass self._name = name self._topic = topic @@ -98,6 +98,8 @@ class MqttLight(Light): mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], brightness_received, self._qos) self._brightness = 255 + elif self._topic["brightness_command_topic"] is not None: + self._brightness = 255 else: self._brightness = None @@ -111,17 +113,19 @@ class MqttLight(Light): mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], rgb_received, self._qos) self._rgb = [255, 255, 255] + if self._topic["rgb_command_topic"] is not None: + self._rgb = [255, 255, 255] else: self._rgb = None @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 RGB color value.""" return self._rgb @property @@ -131,17 +135,17 @@ class MqttLight(Light): @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_on(self): - """True if device is on.""" + """Return true if device is on.""" return self._state @property def assumed_state(self): - """Return True if we do optimistic updates.""" + """Return true if we do optimistic updates.""" return self._optimistic def turn_on(self, **kwargs): diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 906f06e6ab4..bd74ef350f8 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -56,7 +56,6 @@ class MySensorsLight(Light): """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 instance attributes.""" @@ -75,27 +74,27 @@ class MySensorsLight(Light): @property def should_poll(self): - """MySensor gateway pushes its state to HA.""" + """No polling needed.""" return False @property def name(self): - """The name of this entity.""" + """Return the name of this entity.""" 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 [int, int, int].""" + """Return the 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 the white value in RGBW, value between 0..255.""" return self._white @property @@ -113,12 +112,17 @@ class MySensorsLight(Light): @property def available(self): - """Return True if entity is available.""" + """Return true if entity is available.""" return self.value_type in self._values + @property + def assumed_state(self): + """Return true if unable to access real state of entity.""" + return self.gateway.optimistic + @property def is_on(self): - """True if device is on.""" + """Return true if device is on.""" return self._state def _turn_on_light(self): diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 60d30645e9f..79c4640a55a 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.rfxtrx -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for RFXtrx lights. For more details about this platform, please refer to the documentation at @@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Setup the RFXtrx platform. """ + """Setup the RFXtrx platform.""" import RFXtrx as rfxtrxmod lights = [] @@ -47,7 +45,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): add_devices_callback(lights) def light_update(event): - """ Callback for light updates from the RFXtrx gateway. """ + """Callback for light updates from the RFXtrx gateway.""" if not isinstance(event.device, rfxtrxmod.LightingDevice) or \ not event.device.known_to_be_dimmable: return @@ -120,8 +118,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class RfxtrxLight(Light): - """ Provides a RFXtrx light. """ + """Represenation of a RFXtrx light.""" + def __init__(self, name, event, datas, signal_repetitions): + """Initialize the light.""" self._name = name self._event = event self._state = datas[ATTR_STATE] @@ -131,27 +131,27 @@ class RfxtrxLight(Light): @property def should_poll(self): - """ No polling needed for a light. """ + """No polling needed for a light.""" return False @property def name(self): - """ Returns the name of the light if any. """ + """Return the name of the light if any.""" return self._name @property def should_fire_event(self): - """ Returns is the device must fire event""" + """Return true if the device must fire event.""" return self._should_fire_event @property def is_on(self): - """ True if light is on. """ + """Return true if light is on.""" return self._state @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" return self._brightness @property @@ -160,28 +160,25 @@ class RfxtrxLight(Light): return True def turn_on(self, **kwargs): - """ Turn the light on. """ + """Turn the light on.""" brightness = kwargs.get(ATTR_BRIGHTNESS) if not self._event: return - if brightness is None: - self._brightness = 100 + self._brightness = 255 for _ in range(self.signal_repetitions): self._event.device.send_on(rfxtrx.RFXOBJECT.transport) else: - self._brightness = ((brightness + 4) * 100 // 255 - 1) + self._brightness = brightness + _brightness = (brightness * 100 // 255) for _ in range(self.signal_repetitions): self._event.device.send_dim(rfxtrx.RFXOBJECT.transport, - self._brightness) - - self._brightness = (self._brightness * 255 // 100) + _brightness) self._state = True self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the light off. """ - + """Turn the light off.""" if not self._event: return diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index e8d104242a5..31c07513136 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.scsgate -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for SCSGate lights. For more details about this platform, please refer to the documentation at @@ -16,8 +14,7 @@ DEPENDENCIES = ['scsgate'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add the SCSGate swiches defined inside of the configuration file. """ - + """Add the SCSGate swiches defined inside of the configuration file.""" devices = config.get('devices') lights = [] logger = logging.getLogger(__name__) @@ -42,8 +39,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class SCSGateLight(Light): - """ Provides a SCSGate light. """ + """representation of a SCSGate light.""" + def __init__(self, scs_id, name, logger): + """Initialize the light.""" self._name = name self._scs_id = scs_id self._toggled = False @@ -51,26 +50,26 @@ class SCSGateLight(Light): @property def scs_id(self): - """ SCS ID """ + """Return the SCS ID.""" return self._scs_id @property def should_poll(self): - """ No polling needed for a SCSGate light. """ + """No polling needed for a SCSGate light.""" 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 is_on(self): - """ True if light is on. """ + """Return true if light is on.""" return self._toggled def turn_on(self, **kwargs): - """ Turn the device on. """ + """Turn the device on.""" from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( @@ -82,7 +81,7 @@ class SCSGateLight(Light): self.update_ha_state() def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( @@ -94,7 +93,7 @@ class SCSGateLight(Light): self.update_ha_state() def process_event(self, message): - """ Handle a SCSGate message related with this light """ + """Handle a SCSGate message related with this light.""" if self._toggled == message.toggled: self._logger.info( "Light %s, ignoring message %s because state already active", diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index c0f59abc10b..8d54ddb1604 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.tellstick -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Tellstick lights. For more details about this platform, please refer to the documentation at @@ -15,8 +13,7 @@ SIGNAL_REPETITIONS = 1 # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return Tellstick lights. """ - + """Setup Tellstick lights.""" import tellcore.telldus as telldus from tellcore.library import DirectCallbackDispatcher import tellcore.constants as tellcore_constants @@ -32,7 +29,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): lights.append(TellstickLight(switch, signal_repetitions)) def _device_event_callback(id_, method, data, cid): - """ Called from the TelldusCore library to update one device """ + """Called from the TelldusCore library to update one device.""" for light_device in lights: if light_device.tellstick_device.id == id_: # Execute the update in another thread @@ -42,7 +39,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): callback_id = core.register_device_event(_device_event_callback) def unload_telldus_lib(event): - """ Un-register the callback bindings """ + """Un-register the callback bindings.""" if callback_id is not None: core.unregister_callback(callback_id) @@ -52,9 +49,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class TellstickLight(Light): - """ Represents a Tellstick light. """ + """Representation of a Tellstick light.""" def __init__(self, tellstick_device, signal_repetitions): + """Initialize the light.""" import tellcore.constants as tellcore_constants self.tellstick_device = tellstick_device @@ -70,28 +68,28 @@ class TellstickLight(Light): @property def name(self): - """ Returns the name of the switch if any. """ + """Return the name of the switch if any.""" return self.tellstick_device.name @property def is_on(self): - """ True if switch is on. """ + """Return true if switch is on.""" return self._brightness > 0 @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Return the brightness of this light between 0..255.""" return self._brightness def turn_off(self, **kwargs): - """ Turns the switch off. """ + """Turn the switch off.""" for _ in range(self.signal_repetitions): self.tellstick_device.turn_off() self._brightness = 0 self.update_ha_state() def turn_on(self, **kwargs): - """ Turns the switch on. """ + """Turn the switch on.""" brightness = kwargs.get(ATTR_BRIGHTNESS) if brightness is None: @@ -104,7 +102,7 @@ class TellstickLight(Light): self.update_ha_state() def update(self): - """ Update state of the light. """ + """Update state of the light.""" import tellcore.constants as tellcore_constants last_command = self.tellstick_device.last_sent_command( @@ -123,5 +121,10 @@ class TellstickLight(Light): @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False + + @property + def assumed_state(self): + """Tellstick devices are always assumed state.""" + return True diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 179afa423ee..99e60b1921e 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.vera -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Vera lights. 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 setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return Vera lights. """ + """Setup Vera lights.""" import pyvera as veraApi base_url = config.get('vera_controller_url') @@ -40,7 +38,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): 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() @@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): 'On/Off Switch', 'Dimmable Switch']) 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 @@ -69,9 +67,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class VeraLight(Light): - """ Represents a Vera Light, including dimmable. """ + """Representation of a Vera Light, including dimmable.""" def __init__(self, vera_device, controller, extra_data=None): + """Initialize the light.""" self.vera_device = vera_device self.extra_data = extra_data self.controller = controller @@ -89,16 +88,17 @@ class VeraLight(Light): @property def name(self): - """ Get the mame of the switch. """ + """Return the name of the light.""" return self._name @property def brightness(self): - """Brightness of the light.""" + """Return the brightness of the light.""" if self.vera_device.is_dimmable: return self.vera_device.get_brightness() def turn_on(self, **kwargs): + """Turn the light on.""" if ATTR_BRIGHTNESS in kwargs and self.vera_device.is_dimmable: self.vera_device.set_brightness(kwargs[ATTR_BRIGHTNESS]) else: @@ -108,12 +108,14 @@ class VeraLight(Light): self.update_ha_state(True) def turn_off(self, **kwargs): + """Turn the light off.""" self.vera_device.switch_off() self._state = STATE_OFF self.update_ha_state() @property def device_state_attributes(self): + """Return the state attributes.""" attr = {} if self.vera_device.has_battery: @@ -138,16 +140,16 @@ class VeraLight(Light): @property def should_poll(self): - """ Tells Home Assistant not to poll this entity. """ + """No polling needed.""" return False @property def is_on(self): - """ True if device is on. """ + """Return true if device is on.""" return self._state == STATE_ON def update(self): - """ Called by the vera device callback to update state. """ + """Called by the vera device callback to update state.""" if self.vera_device.is_switched_on(): self._state = STATE_ON else: diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index 61b3d4585f2..c533b3acf13 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.wemo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Belkin WeMo lights. For more details about this component, please refer to the documentation at @@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Probe WeMo bridges and register connected lights.""" + """Setup WeMo bridges and register connected lights.""" import pywemo.discovery as discovery if discovery_info is not None: @@ -40,8 +38,7 @@ def setup_bridge(bridge, add_devices_callback): @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_lights(): - """Updates the WeMo led objects with latest info from the bridge.""" - + """Update the WeMo led objects with latest info from the bridge.""" bridge.bridge_get_lights() new_lights = [] @@ -61,9 +58,10 @@ def setup_bridge(bridge, add_devices_callback): class WemoLight(Light): - """Represents a WeMo light""" + """Representation of a WeMo light.""" def __init__(self, bridge, light_id, info, update_lights): + """Initialize the light.""" self.bridge = bridge self.light_id = light_id self.info = info @@ -71,18 +69,18 @@ class WemoLight(Light): @property def unique_id(self): - """Returns the id of this light""" + """Return the ID of this light.""" deviceid = self.bridge.light_get_id(self.info) return "{}.{}".format(self.__class__, deviceid) @property def name(self): - """Get the name of the light.""" + """Return the name of the light.""" return self.bridge.light_name(self.info) @property def brightness(self): - """Brightness of this light between 0..255.""" + """Return the brightness of this light between 0..255.""" state = self.bridge.light_get_state(self.info) return int(state['dim']) diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 560d08119df..f268864f350 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.wink -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Wink lights. For more details about this platform, please refer to the documentation at @@ -15,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return Wink lights. """ + """Setup Wink lights.""" import pywink token = config.get(CONF_ACCESS_TOKEN) @@ -34,46 +32,46 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class WinkLight(Light): - """ Represents a Wink light. """ + """Representation of a Wink light.""" def __init__(self, wink): + """Initialize the light.""" self.wink = wink @property def unique_id(self): - """ Returns the id of this Wink switch. """ + """Return the ID of this Wink light.""" return "{}.{}".format(self.__class__, self.wink.device_id()) @property def name(self): - """ Returns the name of the light if any. """ + """Return the name of the light if any.""" return self.wink.name() @property def is_on(self): - """ True if light is on. """ + """Return true if light is on.""" return self.wink.state() @property def brightness(self): - """Brightness of the light.""" + """Return the brightness of the light.""" return int(self.wink.brightness() * 255) # pylint: disable=too-few-public-methods def turn_on(self, **kwargs): - """ Turns the switch on. """ + """Turn the switch on.""" brightness = kwargs.get(ATTR_BRIGHTNESS) if brightness is not None: self.wink.set_state(True, brightness=brightness / 255) - else: self.wink.set_state(True) def turn_off(self): - """ Turns the switch off. """ + """Turn the switch off.""" self.wink.set_state(False) def update(self): - """ Update state of the light. """ + """Update state of the light.""" self.wink.update_state() diff --git a/homeassistant/components/light/zigbee.py b/homeassistant/components/light/zigbee.py index 5b6fe7fdc40..1ab6a0b265a 100644 --- a/homeassistant/components/light/zigbee.py +++ b/homeassistant/components/light/zigbee.py @@ -1,7 +1,5 @@ """ -homeassistant.components.light.zigbee -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Contains functionality to use a ZigBee device as a light. +Functionality to use a ZigBee device as a light. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.zigbee/ @@ -14,15 +12,13 @@ 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([ ZigBeeLight(hass, ZigBeeDigitalOutConfig(config)) ]) class ZigBeeLight(ZigBeeDigitalOut, Light): - """ - Use multiple inheritance to turn an instance of ZigBeeDigitalOut into a - Light. - """ + """Use ZigBeeDigitalOut as light.""" + pass diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 86cc7543073..7693692cac1 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -1,6 +1,4 @@ """ -homeassistant.components.light.zwave -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Z-Wave lights. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ from homeassistant.const import STATE_OFF, STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): - """ Find and add Z-Wave lights. """ + """Find and add Z-Wave lights.""" if discovery_info is None or NETWORK is None: return @@ -37,10 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def brightness_state(value): - """ - Returns the brightness and state according to the current data of given - value. - """ + """Return the brightness and state.""" if value.data > 0: return (value.data / 99) * 255, STATE_ON else: @@ -48,9 +43,11 @@ def brightness_state(value): class ZwaveDimmer(ZWaveDeviceEntity, Light): - """ Provides a Z-Wave dimmer. """ + """Representation of a Z-Wave dimmer.""" + # pylint: disable=too-many-arguments def __init__(self, value): + """Initialize the light.""" from openzwave.network import ZWaveNetwork from pydispatch import dispatcher @@ -66,7 +63,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light): self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) 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: return @@ -89,17 +86,16 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light): @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 is_on(self): - """ True if device is on. """ + """Return true if device is on.""" return self._state == STATE_ON def turn_on(self, **kwargs): - """ Turn the device on. """ - + """Turn the device on.""" if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] @@ -111,6 +107,6 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light): self._state = STATE_ON def turn_off(self, **kwargs): - """ Turn the device off. """ + """Turn the device off.""" if self._value.node.set_dimmer(self._value.value_id, 0): self._state = STATE_OFF diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 74d73d129e5..6d21ae310d8 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.lock -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to interface with various locks that can be controlled remotely. For more details about this component, please refer to the documentation @@ -15,8 +13,8 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import ( - STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, - ATTR_ENTITY_ID) + ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, + STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) from homeassistant.components import (group, verisure, wink) DOMAIN = 'lock' @@ -27,10 +25,6 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks') ENTITY_ID_FORMAT = DOMAIN + '.{}' -ATTR_LOCKED = "locked" -ATTR_CODE = 'code' -ATTR_CODE_FORMAT = 'code_format' - MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) # Maps discovered services to their platforms @@ -43,13 +37,13 @@ _LOGGER = logging.getLogger(__name__) def is_locked(hass, entity_id=None): - """ Returns if the lock is locked based on the statemachine. """ + """Return if the lock is locked based on the statemachine.""" entity_id = entity_id or ENTITY_ID_ALL_LOCKS return hass.states.is_state(entity_id, STATE_LOCKED) def lock(hass, entity_id=None, code=None): - """ Locks all or specified locks. """ + """Lock all or specified locks.""" data = {} if code: data[ATTR_CODE] = code @@ -60,7 +54,7 @@ def lock(hass, entity_id=None, code=None): def unlock(hass, entity_id=None, code=None): - """ Unlocks all or specified locks. """ + """Unlock all or specified locks.""" data = {} if code: data[ATTR_CODE] = code @@ -71,14 +65,14 @@ def unlock(hass, entity_id=None, code=None): def setup(hass, config): - """ Track states and offer events for locks. """ + """Track states and offer events for locks.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, GROUP_NAME_ALL_LOCKS) component.setup(config) def handle_lock_service(service): - """ Handles calls to the lock services. """ + """Handle calls to the lock services.""" target_locks = component.extract_from_service(service) if ATTR_CODE not in service.data: @@ -106,30 +100,30 @@ def setup(hass, config): class LockDevice(Entity): - """ Represents a lock within Home Assistant. """ - # pylint: disable=no-self-use + """Representation of a lock.""" + # pylint: disable=no-self-use @property def code_format(self): - """ regex for code format or None if no code is required. """ + """Regex for code format or None if no code is required.""" return None @property def is_locked(self): - """ Is the lock locked or unlocked. """ + """Return true if the lock is locked.""" return None def lock(self, **kwargs): - """ Locks the lock. """ + """Lock the lock.""" raise NotImplementedError() def unlock(self, **kwargs): - """ Unlocks the lock. """ + """Unlock the lock.""" raise NotImplementedError() @property def state_attributes(self): - """ Return the state attributes. """ + """Return the state attributes.""" if self.code_format is None: return None state_attr = { @@ -139,6 +133,7 @@ class LockDevice(Entity): @property def state(self): + """Return the state.""" locked = self.is_locked if locked is None: return STATE_UNKNOWN diff --git a/homeassistant/components/lock/demo.py b/homeassistant/components/lock/demo.py index 319ee760e57..06366429e6c 100644 --- a/homeassistant/components/lock/demo.py +++ b/homeassistant/components/lock/demo.py @@ -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): - """Setup the demo lock platform. """ + """Setup the demo lock platform.""" add_devices_callback([ DemoLock('Front Door', STATE_LOCKED), DemoLock('Kitchen Door', STATE_UNLOCKED) @@ -18,8 +18,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class DemoLock(LockDevice): - """Provides a demo lock.""" + """Representation of a demo lock.""" + def __init__(self, name, state): + """Initialize the lock.""" self._name = name self._state = state diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py new file mode 100644 index 00000000000..a68a3303de6 --- /dev/null +++ b/homeassistant/components/lock/mqtt.py @@ -0,0 +1,119 @@ +""" +Support for MQTT locks. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/lock.mqtt/ +""" +import logging + +import homeassistant.components.mqtt as mqtt +from homeassistant.components.lock import LockDevice +from homeassistant.const import CONF_VALUE_TEMPLATE +from homeassistant.helpers import template + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = "MQTT Lock" +DEFAULT_PAYLOAD_LOCK = "LOCK" +DEFAULT_PAYLOAD_UNLOCK = "UNLOCK" +DEFAULT_QOS = 0 +DEFAULT_OPTIMISTIC = False +DEFAULT_RETAIN = False + +DEPENDENCIES = ['mqtt'] + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Setup the MQTT lock.""" + if config.get('command_topic') is None: + _LOGGER.error("Missing required variable: command_topic") + return False + + add_devices_callback([MqttLock( + hass, + config.get('name', DEFAULT_NAME), + config.get('state_topic'), + config.get('command_topic'), + config.get('qos', DEFAULT_QOS), + config.get('retain', DEFAULT_RETAIN), + config.get('payload_lock', DEFAULT_PAYLOAD_LOCK), + config.get('payload_unlock', DEFAULT_PAYLOAD_UNLOCK), + config.get('optimistic', DEFAULT_OPTIMISTIC), + config.get(CONF_VALUE_TEMPLATE))]) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class MqttLock(LockDevice): + """Represents a lock that can be toggled using MQTT.""" + + def __init__(self, hass, name, state_topic, command_topic, qos, retain, + payload_lock, payload_unlock, optimistic, value_template): + """Initialize the lock.""" + self._state = False + self._hass = hass + self._name = name + self._state_topic = state_topic + self._command_topic = command_topic + self._qos = qos + self._retain = retain + self._payload_lock = payload_lock + self._payload_unlock = payload_unlock + self._optimistic = optimistic + + def message_received(topic, payload, qos): + """A new MQTT message has been received.""" + if value_template is not None: + payload = template.render_with_possible_json_value( + hass, value_template, payload) + if payload == self._payload_lock: + self._state = True + self.update_ha_state() + elif payload == self._payload_unlock: + self._state = False + self.update_ha_state() + + if self._state_topic is None: + # Force into optimistic mode. + self._optimistic = True + else: + mqtt.subscribe(hass, self._state_topic, message_received, + self._qos) + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """The name of the lock.""" + return self._name + + @property + def is_locked(self): + """Return true if lock is locked.""" + return self._state + + @property + def assumed_state(self): + """Return true if we do optimistic updates.""" + return self._optimistic + + def lock(self, **kwargs): + """Lock the device.""" + mqtt.publish(self.hass, self._command_topic, self._payload_lock, + self._qos, self._retain) + if self._optimistic: + # Optimistically assume that switch has changed state. + self._state = True + self.update_ha_state() + + def unlock(self, **kwargs): + """Unlock the device.""" + mqtt.publish(self.hass, self._command_topic, self._payload_unlock, + self._qos, self._retain) + if self._optimistic: + # Optimistically assume that switch has changed state. + self._state = False + self.update_ha_state() diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/lock/verisure.py index 2542e51bacc..a905b6f2d19 100644 --- a/homeassistant/components/lock/verisure.py +++ b/homeassistant/components/lock/verisure.py @@ -1,6 +1,4 @@ """ -homeassistant.components.lock.verisure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interfaces with Verisure locks. For more details about this platform, please refer to the documentation at @@ -10,51 +8,51 @@ import logging from homeassistant.components.verisure import HUB as hub from homeassistant.components.lock import LockDevice -from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED +from homeassistant.const import ( + ATTR_CODE, STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED) _LOGGER = logging.getLogger(__name__) -ATTR_CODE = 'code' def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Verisure platform. """ - + """Setup the Verisure platform.""" 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) # pylint: disable=abstract-method class VerisureDoorlock(LockDevice): - """ Represents a Verisure doorlock status. """ + """Representation of a Verisure doorlock.""" def __init__(self, device_id): + """Initialize the lock.""" self._id = device_id self._state = STATE_UNKNOWN self._digits = int(hub.config.get('code_digits', '4')) @property def name(self): - """ Returns the name of the device. """ + """Return the name of the lock.""" return 'Lock {}'.format(self._id) @property def state(self): - """ Returns the state of the device. """ + """Return the state of the lock.""" return self._state @property def code_format(self): - """ Six digit code required. """ + """Return the required six digit code.""" return '^\\d{%s}$' % self._digits def update(self): - """ Update lock status """ + """Update lock status.""" hub.update_locks() if hub.lock_status[self._id].status == 'unlocked': @@ -68,18 +66,18 @@ class VerisureDoorlock(LockDevice): @property def is_locked(self): - """ True if device is locked. """ + """Return true if lock is locked.""" return hub.lock_status[self._id].status def unlock(self, **kwargs): - """ Send unlock command. """ + """Send unlock command.""" hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED') _LOGGER.info('verisure doorlock unlocking') hub.my_pages.lock.wait_while_pending() self.update() def lock(self, **kwargs): - """ Send lock command. """ + """Send lock command.""" hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED') _LOGGER.info('verisure doorlock locking') hub.my_pages.lock.wait_while_pending() diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 77037c4b205..a78713afce1 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -1,6 +1,4 @@ """ -homeassistant.components.lock.wink -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Wink locks. For more details about this platform, please refer to the documentation at @@ -15,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.2'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Wink platform. """ + """Setup the Wink platform.""" import pywink if discovery_info is None: @@ -33,34 +31,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WinkLockDevice(LockDevice): - """ Represents a Wink lock. """ + """Representation of a Wink lock.""" def __init__(self, wink): + """Initialize the lock.""" self.wink = wink @property def unique_id(self): - """ Returns the id of this wink lock """ + """Return the id of this wink lock.""" return "{}.{}".format(self.__class__, self.wink.device_id()) @property def name(self): - """ Returns the name of the lock if any. """ + """Return the name of the lock if any.""" return self.wink.name() def update(self): - """ Update the state of the lock. """ + """Update the state of the lock.""" self.wink.update_state() @property def is_locked(self): - """ True if device is locked. """ + """Return true if device is locked.""" return self.wink.state() def lock(self, **kwargs): - """ Lock the device. """ + """Lock the device.""" self.wink.set_state(True) def unlock(self, **kwargs): - """ Unlock the device. """ + """Unlock the device.""" self.wink.set_state(False) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 95fbcc9a793..d26b3373cb8 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -41,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.""" + """Add an entry to the logbook.""" data = { ATTR_NAME: name, ATTR_MESSAGE: message @@ -55,8 +55,7 @@ def log_entry(hass, name, message, domain=None, entity_id=None): def setup(hass, config): - """Listens for download events to download files.""" - + """Listen for download events to download files.""" def log_message(service): """Handle sending notification message service calls.""" message = service.data.get(ATTR_MESSAGE) @@ -105,6 +104,7 @@ class Entry(object): # pylint: disable=too-many-arguments, too-few-public-methods def __init__(self, when=None, name=None, message=None, domain=None, entity_id=None): + """Initialize the entry.""" self.when = when self.name = name self.message = message @@ -123,8 +123,7 @@ class Entry(object): def humanify(events): - """ - Generator that converts a list of events into Entry objects. + """Generator that converts a list of events into Entry objects. Will try to group events if possible: - if 2+ sensor updates in GROUP_BY_MINUTES, show last @@ -237,7 +236,6 @@ def _entry_message_from_state(domain, state): """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 - if domain == 'device_tracker': if state.state == STATE_NOT_HOME: return 'is away' diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger.py index a0d769e3d82..ed17e7520d0 100644 --- a/homeassistant/components/logger.py +++ b/homeassistant/components/logger.py @@ -1,6 +1,4 @@ """ -homeassistant.components.logger -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component that will help set the level of logging for components. For more details about this component, please refer to the documentation at @@ -27,16 +25,17 @@ LOGGER_LOGS = 'logs' class HomeAssistantLogFilter(logging.Filter): - """ A log filter. """ - # pylint: disable=no-init,too-few-public-methods + """A log filter.""" + # pylint: disable=no-init,too-few-public-methods def __init__(self, logfilter): + """Initialize the filter.""" super().__init__() self.logfilter = logfilter def filter(self, record): - + """A filter to use.""" # Log with filtered severity if LOGGER_LOGS in self.logfilter: for filtername in self.logfilter[LOGGER_LOGS]: @@ -50,8 +49,7 @@ class HomeAssistantLogFilter(logging.Filter): def setup(hass, config=None): - """ Setup the logger component. """ - + """Setup the logger component.""" logfilter = dict() # Set default log severity diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 32bf57108f7..70a29c6b919 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -1,6 +1,4 @@ """ -homeassistant.components.media_player -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Component to interface with various media players. For more details about this component, please refer to the documentation at @@ -110,45 +108,48 @@ ATTR_TO_PROPERTY = [ def is_on(hass, entity_id=None): - """ Returns true if specified media player entity_id is on. - Will check all media player if no entity_id specified. """ + """ + Return true if specified media player entity_id is on. + + Check all media player if no entity_id specified. + """ entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) return any(not hass.states.is_state(entity_id, STATE_OFF) for entity_id in entity_ids) def turn_on(hass, entity_id=None): - """ Will turn on specified media player or all. """ + """Turn on specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None): - """ Will turn off specified media player or all. """ + """Turn off specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) def toggle(hass, entity_id=None): - """ Will toggle specified media player or all. """ + """Toggle specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TOGGLE, data) def volume_up(hass, entity_id=None): - """ Send the media player the command for volume up. """ + """Send the media player the command for volume up.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data) def volume_down(hass, entity_id=None): - """ Send the media player the command for volume down. """ + """Send the media player the command for volume down.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data) def mute_volume(hass, mute, entity_id=None): - """ Send the media player the command for volume down. """ + """Send the media player the command for muting the volume.""" data = {ATTR_MEDIA_VOLUME_MUTED: mute} if entity_id: @@ -158,7 +159,7 @@ def mute_volume(hass, mute, entity_id=None): def set_volume_level(hass, volume, entity_id=None): - """ Send the media player the command for volume down. """ + """Send the media player the command for setting the volume.""" data = {ATTR_MEDIA_VOLUME_LEVEL: volume} if entity_id: @@ -168,45 +169,46 @@ def set_volume_level(hass, volume, entity_id=None): def media_play_pause(hass, entity_id=None): - """ Send the media player the command for play/pause. """ + """Send the media player the command for play/pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data) def media_play(hass, entity_id=None): - """ Send the media player the command for play/pause. """ + """Send the media player the command for play/pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data) def media_pause(hass, entity_id=None): - """ Send the media player the command for play/pause. """ + """Send the media player the command for pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data) def media_next_track(hass, entity_id=None): - """ Send the media player the command for next track. """ + """Send the media player the command for next track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data) def media_previous_track(hass, entity_id=None): - """ Send the media player the command for prev track. """ + """Send the media player the command for prev track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data) def media_seek(hass, position, entity_id=None): - """ Send the media player the command to seek in current playing media. """ + """Send the media player the command to seek in current playing media.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data[ATTR_MEDIA_SEEK_POSITION] = position hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data) def play_media(hass, media_type, media_id, entity_id=None): - """ Send the media player the command for playing media. """ - data = {"media_type": media_type, "media_id": media_id} + """Send the media player the command for playing media.""" + data = {ATTR_MEDIA_CONTENT_TYPE: media_type, + ATTR_MEDIA_CONTENT_ID: media_id} if entity_id: data[ATTR_ENTITY_ID] = entity_id @@ -215,7 +217,7 @@ def play_media(hass, media_type, media_id, entity_id=None): def setup(hass, config): - """ Track states and offer events for media_players. """ + """Track states and offer events for media_players.""" component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS) @@ -226,7 +228,7 @@ def setup(hass, config): os.path.join(os.path.dirname(__file__), 'services.yaml')) def media_player_service_handler(service): - """ Maps services to methods on MediaPlayerDevice. """ + """Map services to methods on MediaPlayerDevice.""" target_players = component.extract_from_service(service) method = SERVICE_TO_METHOD[service.service] @@ -242,7 +244,7 @@ def setup(hass, config): descriptions.get(service)) def volume_set_service(service): - """ Set specified volume on the media player. """ + """Set specified volume on the media player.""" target_players = component.extract_from_service(service) if ATTR_MEDIA_VOLUME_LEVEL not in service.data: @@ -260,7 +262,7 @@ def setup(hass, config): descriptions.get(SERVICE_VOLUME_SET)) def volume_mute_service(service): - """ Mute (true) or unmute (false) the media player. """ + """Mute (true) or unmute (false) the media player.""" target_players = component.extract_from_service(service) if ATTR_MEDIA_VOLUME_MUTED not in service.data: @@ -278,7 +280,7 @@ def setup(hass, config): descriptions.get(SERVICE_VOLUME_MUTE)) def media_seek_service(service): - """ Seek to a position. """ + """Seek to a position.""" target_players = component.extract_from_service(service) if ATTR_MEDIA_SEEK_POSITION not in service.data: @@ -296,9 +298,9 @@ def setup(hass, config): descriptions.get(SERVICE_MEDIA_SEEK)) def play_media_service(service): - """ Plays specified media_id on the media player. """ - media_type = service.data.get('media_type') - media_id = service.data.get('media_id') + """Play specified media_id on the media player.""" + media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) + media_id = service.data.get(ATTR_MEDIA_CONTENT_ID) if media_type is None: return @@ -320,206 +322,207 @@ def setup(hass, config): class MediaPlayerDevice(Entity): - """ ABC for media player devices. """ + """ABC for media player devices.""" + # pylint: disable=too-many-public-methods,no-self-use # Implement these for your media player @property def state(self): - """ State of the player. """ + """State of the player.""" return STATE_UNKNOWN @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" return None @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" return None @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return None @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return None @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" return None @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" return None @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" return None @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Artist of current playing media, music track only.""" return None @property def media_album_name(self): - """ Album name of current playing media. (Music track only) """ + """Album name of current playing media, music track only.""" return None @property def media_album_artist(self): - """ Album arist of current playing media. (Music track only) """ + """Album artist of current playing media, music track only.""" return None @property def media_track(self): - """ Track number of current playing media. (Music track only) """ + """Track number of current playing media, music track only.""" return None @property def media_series_title(self): - """ Series title of current playing media. (TV Show only)""" + """Title of series of current playing media, TV show only.""" return None @property def media_season(self): - """ Season of current playing media. (TV Show only) """ + """Season of current playing media, TV show only.""" return None @property def media_episode(self): - """ Episode of current playing media. (TV Show only) """ + """Episode of current playing media, TV show only.""" return None @property def media_channel(self): - """ Channel currently playing. """ + """Channel currently playing.""" return None @property def media_playlist(self): - """ Title of Playlist currently playing. """ + """Title of Playlist currently playing.""" return None @property def app_id(self): - """ ID of the current running app. """ + """ID of the current running app.""" return None @property def app_name(self): - """ Name of the current running app. """ + """Name of the current running app.""" return None @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag media commands that are supported.""" return 0 def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" raise NotImplementedError() def turn_off(self): - """ turn the media player off. """ + """Turn the media player off.""" raise NotImplementedError() def mute_volume(self, mute): - """ mute the volume. """ + """Mute the volume.""" raise NotImplementedError() def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" raise NotImplementedError() def media_play(self): - """ Send play commmand. """ + """Send play commmand.""" raise NotImplementedError() def media_pause(self): - """ Send pause command. """ + """Send pause command.""" raise NotImplementedError() def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" raise NotImplementedError() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" raise NotImplementedError() def media_seek(self, position): - """ Send seek command. """ + """Send seek command.""" raise NotImplementedError() def play_media(self, media_type, media_id): - """ Plays a piece of media. """ + """Play a piece of media.""" raise NotImplementedError() # No need to overwrite these. @property def support_pause(self): - """ Boolean if pause is supported. """ + """Boolean if pause is supported.""" return bool(self.supported_media_commands & SUPPORT_PAUSE) @property def support_seek(self): - """ Boolean if seek is supported. """ + """Boolean if seek is supported.""" return bool(self.supported_media_commands & SUPPORT_SEEK) @property def support_volume_set(self): - """ Boolean if setting volume is supported. """ + """Boolean if setting volume is supported.""" return bool(self.supported_media_commands & SUPPORT_VOLUME_SET) @property def support_volume_mute(self): - """ Boolean if muting volume is supported. """ + """Boolean if muting volume is supported.""" return bool(self.supported_media_commands & SUPPORT_VOLUME_MUTE) @property def support_previous_track(self): - """ Boolean if previous track command supported. """ + """Boolean if previous track command supported.""" return bool(self.supported_media_commands & SUPPORT_PREVIOUS_TRACK) @property def support_next_track(self): - """ Boolean if next track command supported. """ + """Boolean if next track command supported.""" return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK) @property def support_play_media(self): - """ Boolean if play media command supported. """ + """Boolean if play media command supported.""" return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA) def toggle(self): - """ Toggles the power on the media player. """ + """Toggle the power on the media player.""" if self.state in [STATE_OFF, STATE_IDLE]: self.turn_on() else: self.turn_off() def volume_up(self): - """ volume_up media player. """ + """Turn volume up for media player.""" if self.volume_level < 1: self.set_volume_level(min(1, self.volume_level + .1)) def volume_down(self): - """ volume_down media player. """ + """Turn volume down for media player.""" if self.volume_level > 0: self.set_volume_level(max(0, self.volume_level - .1)) def media_play_pause(self): - """ media_play_pause media player. """ + """Play or pause the media player.""" if self.state == STATE_PLAYING: self.media_pause() else: @@ -532,7 +535,7 @@ class MediaPlayerDevice(Entity): @property def state_attributes(self): - """ Return the state attributes. """ + """Return the state attributes.""" if self.state == STATE_OFF: state_attr = { ATTR_SUPPORTED_MEDIA_COMMANDS: self.supported_media_commands, diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 19b1dd46d3c..022a2d2d762 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.chromecast -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to interact with Cast devices on the network. +Provide functionality to interact with Cast devices on the network. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.cast/ @@ -18,7 +16,7 @@ from homeassistant.const import ( CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) -REQUIREMENTS = ['pychromecast==0.7.1'] +REQUIREMENTS = ['pychromecast==0.7.2'] CONF_IGNORE_CEC = 'ignore_cec' CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ @@ -31,7 +29,7 @@ DEFAULT_PORT = 8009 # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the cast platform. """ + """Setup the cast platform.""" import pychromecast logger = logging.getLogger(__name__) @@ -70,12 +68,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class CastDevice(MediaPlayerDevice): - """ Represents a Cast device on the network. """ + """Representation of a Cast device on the network.""" # pylint: disable=abstract-method # pylint: disable=too-many-public-methods - def __init__(self, host, port): + """Initialize the Cast device.""" import pychromecast self.cast = pychromecast.Chromecast(host, port) @@ -86,22 +84,20 @@ class CastDevice(MediaPlayerDevice): self.cast_status = self.cast.status self.media_status = self.cast.media_controller.status - # Entity properties and methods - @property def should_poll(self): + """No polling needed.""" return False @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self.cast.device.friendly_name # MediaPlayerDevice properties and methods - @property def state(self): - """ State of the player. """ + """Return the state of the player.""" if self.media_status is None: return STATE_UNKNOWN elif self.media_status.player_is_playing: @@ -117,22 +113,22 @@ class CastDevice(MediaPlayerDevice): @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" return self.cast_status.volume_level if self.cast_status else None @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" return self.cast_status.volume_muted if self.cast_status else None @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return self.media_status.content_id if self.media_status else None @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" if self.media_status is None: return None elif self.media_status.media_is_tvshow: @@ -145,12 +141,12 @@ class CastDevice(MediaPlayerDevice): @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" return self.media_status.duration if self.media_status else None @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" if self.media_status is None: return None @@ -160,61 +156,61 @@ class CastDevice(MediaPlayerDevice): @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" return self.media_status.title if self.media_status else None @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Artist of current playing media (Music track only).""" return self.media_status.artist if self.media_status else None @property def media_album(self): - """ Album of current playing media. (Music track only) """ + """Album of current playing media (Music track only).""" return self.media_status.album_name if self.media_status else None @property def media_album_artist(self): - """ Album arist of current playing media. (Music track only) """ + """Album arist of current playing media (Music track only).""" return self.media_status.album_artist if self.media_status else None @property def media_track(self): - """ Track number of current playing media. (Music track only) """ + """Track number of current playing media (Music track only).""" return self.media_status.track if self.media_status else None @property def media_series_title(self): - """ Series title of current playing media. (TV Show only)""" + """The title of the series of current playing media (TV Show only).""" return self.media_status.series_title if self.media_status else None @property def media_season(self): - """ Season of current playing media. (TV Show only) """ + """Season of current playing media (TV Show only).""" return self.media_status.season if self.media_status else None @property def media_episode(self): - """ Episode of current playing media. (TV Show only) """ + """Episode of current playing media (TV Show only).""" return self.media_status.episode if self.media_status else None @property def app_id(self): - """ ID of the current running app. """ + """Return the ID of the current running app.""" return self.cast.app_id @property def app_name(self): - """ Name of the current running app. """ + """Name of the current running app.""" return self.cast.app_display_name @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_CAST def turn_on(self): - """ Turns on the ChromeCast. """ + """Turn on the ChromeCast.""" # The only way we can turn the Chromecast is on is by launching an app if not self.cast.status or not self.cast.status.is_active_input: import pychromecast @@ -226,49 +222,48 @@ class CastDevice(MediaPlayerDevice): CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED) def turn_off(self): - """ Turns Chromecast off. """ + """Turn Chromecast off.""" self.cast.quit_app() def mute_volume(self, mute): - """ mute the volume. """ + """Mute the volume.""" self.cast.set_volume_muted(mute) def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" self.cast.set_volume(volume) def media_play(self): - """ Send play commmand. """ + """Send play commmand.""" self.cast.media_controller.play() def media_pause(self): - """ Send pause command. """ + """Send pause command.""" self.cast.media_controller.pause() def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" self.cast.media_controller.rewind() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self.cast.media_controller.skip() def media_seek(self, position): - """ Seek the media to a specific location. """ + """Seek the media to a specific location.""" self.cast.media_controller.seek(position) def play_media(self, media_type, media_id): - """ Plays media from a URL """ + """Play media from a URL.""" self.cast.media_controller.play_media(media_id, media_type) - # implementation of chromecast status_listener methods - + # Implementation of chromecast status_listener methods def new_cast_status(self, status): - """ Called when a new cast status is received. """ + """Called when a new cast status is received.""" self.cast_status = status self.update_ha_state() def new_media_status(self, status): - """ Called when a new media status is received. """ + """Called when a new media status is received.""" self.media_status = status self.update_ha_state() diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 04fe7bb49c3..913d41c27ba 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -14,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): - """Setup the media palyer demo platform.""" + """Setup the media player demo platform.""" add_devices([ DemoYoutubePlayer( 'Living Room', 'eyU3bRy2x44', @@ -39,10 +39,12 @@ NETFLIX_PLAYER_SUPPORT = \ class AbstractDemoPlayer(MediaPlayerDevice): - """A demo media players""" + """A demo media players.""" + # We only implement the methods that we support # pylint: disable=abstract-method def __init__(self, name): + """Initialize the demo device.""" self._name = name self._player_state = STATE_PLAYING self._volume_level = 1.0 @@ -106,9 +108,11 @@ class AbstractDemoPlayer(MediaPlayerDevice): class DemoYoutubePlayer(AbstractDemoPlayer): """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): + """Initialize the demo device.""" super().__init__(name) self.youtube_id = youtube_id self._media_title = media_title @@ -125,7 +129,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): @property def media_duration(self): - """ Return the duration of current playing media in seconds.""" + """Return the duration of current playing media in seconds.""" return 360 @property @@ -145,7 +149,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): @property def supported_media_commands(self): - """Flags of media commands that are supported.""" + """Flag of media commands that are supported.""" return YOUTUBE_PLAYER_SUPPORT def play_media(self, media_type, media_id): @@ -156,6 +160,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): class DemoMusicPlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" + # We only implement the methods that we support # pylint: disable=abstract-method tracks = [ @@ -181,6 +186,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): ] def __init__(self): + """Initialize the demo device.""" super().__init__('Walkman') self._cur_track = 0 @@ -222,14 +228,12 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_track(self): - """ - Return the track number of current playing media (Music track only). - """ + """Return the track number of current media (Music track only).""" return self._cur_track + 1 @property def supported_media_commands(self): - """Flags of media commands that are supported.""" + """Flag of media commands that are supported.""" support = MUSIC_PLAYER_SUPPORT if self._cur_track > 0: @@ -255,9 +259,11 @@ class DemoMusicPlayer(AbstractDemoPlayer): class DemoTVShowPlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" + # We only implement the methods that we support # pylint: disable=abstract-method def __init__(self): + """Initialize the demo device.""" super().__init__('Lounge room') self._cur_episode = 1 self._episode_count = 13 diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/media_player/denon.py index 2853dda90ac..b4bcc9ae5ba 100644 --- a/homeassistant/components/media_player/denon.py +++ b/homeassistant/components/media_player/denon.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.denon -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to Denon Network Receivers. +Support for Denon Network Receivers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.denon/ @@ -23,7 +21,7 @@ SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Denon platform. """ + """Setup the Denon platform.""" if not config.get(CONF_HOST): _LOGGER.error( "Missing required configuration items in %s: %s", @@ -43,11 +41,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DenonDevice(MediaPlayerDevice): - """ Represents a Denon device. """ + """Representation of a Denon device.""" # pylint: disable=too-many-public-methods, abstract-method - def __init__(self, name, host): + """Initialize the Denon device.""" self._name = name self._host = host self._pwstate = "PWSTANDBY" @@ -57,18 +55,19 @@ class DenonDevice(MediaPlayerDevice): @classmethod def telnet_request(cls, telnet, command): - """ Executes `command` and returns the response. """ + """Execute `command` and return the response.""" telnet.write(command.encode("ASCII") + b"\r") return telnet.read_until(b"\r", timeout=0.2).decode("ASCII").strip() def telnet_command(self, command): - """ Establishes a telnet connection and sends `command`. """ + """Establish a telnet connection and sends `command`.""" telnet = telnetlib.Telnet(self._host) telnet.write(command.encode("ASCII") + b"\r") telnet.read_very_eager() # skip response telnet.close() def update(self): + """Get the latest details from the device.""" try: telnet = telnetlib.Telnet(self._host) except ConnectionRefusedError: @@ -88,12 +87,12 @@ class DenonDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self._pwstate == "PWSTANDBY": return STATE_OFF if self._pwstate == "PWON": @@ -103,60 +102,61 @@ class DenonDevice(MediaPlayerDevice): @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" return self._volume @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" return self._muted @property def media_title(self): - """ Current media source. """ + """Current media source.""" return self._mediasource @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_DENON def turn_off(self): - """ turn_off media player. """ + """Turn off media player.""" self.telnet_command("PWSTANDBY") def volume_up(self): - """ volume_up media player. """ + """Volume up media player.""" self.telnet_command("MVUP") def volume_down(self): - """ volume_down media player. """ + """Volume down media player.""" self.telnet_command("MVDOWN") def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" # 60dB max self.telnet_command("MV" + str(round(volume * 60)).zfill(2)) def mute_volume(self, mute): - """ mute (true) or unmute (false) media player. """ + """Mute (true) or unmute (false) media player.""" self.telnet_command("MU" + ("ON" if mute else "OFF")) def media_play(self): - """ media_play media player. """ + """Play media media player.""" self.telnet_command("NS9A") def media_pause(self): - """ media_pause media player. """ + """Pause media player.""" self.telnet_command("NS9B") def media_next_track(self): - """ Send next track command. """ + """Send the next track command.""" self.telnet_command("NS9D") def media_previous_track(self): + """Send the previous track command.""" self.telnet_command("NS9E") def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" self.telnet_command("PWON") diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index e151f1d516f..02b456a207c 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.firetv -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to interact with FireTV devices. +Support for functionality to interact with FireTV devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.firetv/ @@ -31,7 +29,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the FireTV platform. """ + """Setup the FireTV platform.""" host = config.get('host', 'localhost:5556') device_id = config.get('device', 'default') try: @@ -54,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class FireTV(object): - """ firetv-server client. + """The firetv-server client. Should a native Python 3 ADB module become available, python-firetv can support Python 3, it can be added as a dependency, and this class can be @@ -65,12 +63,13 @@ class FireTV(object): """ def __init__(self, host, device_id): + """Initialize the FireTV server.""" self.host = host self.device_id = device_id @property def state(self): - """ Get the device state. An exception means UNKNOWN state. """ + """Get the device state. An exception means UNKNOWN state.""" try: response = requests.get( DEVICE_STATE_URL.format( @@ -85,7 +84,7 @@ class FireTV(object): return STATE_UNKNOWN def action(self, action_id): - """ Perform an action on the device. """ + """Perform an action on the device.""" try: requests.get( DEVICE_ACTION_URL.format( @@ -101,37 +100,37 @@ class FireTV(object): class FireTVDevice(MediaPlayerDevice): - """ Represents an Amazon Fire TV device on the network. """ + """Representation of an Amazon Fire TV device on the network.""" # pylint: disable=abstract-method - def __init__(self, host, device, name): + """Initialize the FireTV device.""" self._firetv = FireTV(host, device) self._name = name self._state = STATE_UNKNOWN @property def name(self): - """ Get the device name. """ + """Return the device name.""" return self._name @property def should_poll(self): - """ Device should be polled. """ + """Device should be polled.""" return True @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_FIRETV @property def state(self): - """ State of the player. """ + """Return the state of the player.""" return self._state def update(self): - """ Update device state. """ + """Get the latest date and update device state.""" self._state = { 'idle': STATE_IDLE, 'off': STATE_OFF, @@ -142,37 +141,37 @@ class FireTVDevice(MediaPlayerDevice): }.get(self._firetv.state, STATE_UNKNOWN) def turn_on(self): - """ Turns on the device. """ + """Turn on the device.""" self._firetv.action('turn_on') def turn_off(self): - """ Turns off the device. """ + """Turn off the device.""" self._firetv.action('turn_off') def media_play(self): - """ Send play command. """ + """Send play command.""" self._firetv.action('media_play') def media_pause(self): - """ Send pause command. """ + """Send pause command.""" self._firetv.action('media_pause') def media_play_pause(self): - """ Send play/pause command. """ + """Send play/pause command.""" self._firetv.action('media_play_pause') def volume_up(self): - """ Send volume up command. """ + """Send volume up command.""" self._firetv.action('volume_up') def volume_down(self): - """ Send volume down command. """ + """Send volume down command.""" self._firetv.action('volume_down') def media_previous_track(self): - """ Send previous track command (results in rewind). """ + """Send previous track command (results in rewind).""" self._firetv.action('media_previous') def media_next_track(self): - """ Send next track command (results in fast-forward). """ + """Send next track command (results in fast-forward).""" self._firetv.action('media_next') diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index 584a692bd57..9418d1c5703 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.itunes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to iTunes API. +Support for interfacing to iTunes API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.itunes/ @@ -30,22 +28,23 @@ DOMAIN = 'itunes' class Itunes(object): - """ itunes-api client. """ + """iTunes API client.""" def __init__(self, host, port): + """Initialize the iTunes device.""" self.host = host self.port = port @property def _base_url(self): - """ Returns the base url for endpoints. """ + """Return the base url for endpoints.""" if self.port: return self.host + ":" + str(self.port) else: return self.host def _request(self, method, path, params=None): - """ Makes the actual request and returns the parsed response. """ + """Make the actual request and returns the parsed response.""" url = self._base_url + path try: @@ -65,39 +64,39 @@ class Itunes(object): return {'player_state': 'offline'} def _command(self, named_command): - """ Makes a request for a controlling command. """ + """Make a request for a controlling command.""" return self._request('PUT', '/' + named_command) def now_playing(self): - """ Returns the current state. """ + """Return the current state.""" return self._request('GET', '/now_playing') def set_volume(self, level): - """ Sets the volume and returns the current state, level 0-100. """ + """Set the volume and returns the current state, level 0-100.""" return self._request('PUT', '/volume', {'level': level}) def set_muted(self, muted): - """ Mutes and returns the current state, muted True or False. """ + """Mute and returns the current state, muted True or False.""" return self._request('PUT', '/mute', {'muted': muted}) def play(self): - """ Sets playback to play and returns the current state. """ + """Set playback to play and returns the current state.""" return self._command('play') def pause(self): - """ Sets playback to paused and returns the current state. """ + """Set playback to paused and returns the current state.""" return self._command('pause') def next(self): - """ Skips to the next track and returns the current state. """ + """Skip to the next track and returns the current state.""" return self._command('next') def previous(self): - """ Skips back and returns the current state. """ + """Skip back and returns the current state.""" return self._command('previous') def play_playlist(self, playlist_id_or_name): - """ Sets a playlist to be current and returns the current state. """ + """Set a playlist to be current and returns the current state.""" response = self._request('GET', '/playlists') playlists = response.get('playlists', []) @@ -111,25 +110,25 @@ class Itunes(object): return self._request('PUT', path) def artwork_url(self): - """ Returns a URL of the current track's album art. """ + """Return a URL of the current track's album art.""" return self._base_url + '/artwork' def airplay_devices(self): - """ Returns a list of AirPlay devices. """ + """Return a list of AirPlay devices.""" return self._request('GET', '/airplay_devices') def airplay_device(self, device_id): - """ Returns an AirPlay device. """ + """Return an AirPlay device.""" return self._request('GET', '/airplay_devices/' + device_id) def toggle_airplay_device(self, device_id, toggle): - """ Toggles airplay device on or off, id, toggle True or False. """ + """Toggle airplay device on or off, id, toggle True or False.""" command = 'on' if toggle else 'off' path = '/airplay_devices/' + device_id + '/' + command return self._request('PUT', path) def set_volume_airplay_device(self, device_id, level): - """ Sets volume, returns current state of device, id,level 0-100. """ + """Set volume, returns current state of device, id,level 0-100.""" path = '/airplay_devices/' + device_id + '/volume' return self._request('PUT', path, {'level': level}) @@ -137,8 +136,7 @@ class Itunes(object): # pylint: disable=unused-argument, abstract-method # pylint: disable=too-many-instance-attributes def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the itunes platform. """ - + """Setup the itunes platform.""" add_devices([ ItunesDevice( config.get('name', 'iTunes'), @@ -150,10 +148,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ItunesDevice(MediaPlayerDevice): - """ Represents a iTunes-API instance. """ + """Representation of an iTunes API instance.""" # pylint: disable=too-many-public-methods def __init__(self, name, host, port, add_devices): + """Initialize the iTunes device.""" self._name = name self._host = host self._port = port @@ -176,7 +175,7 @@ class ItunesDevice(MediaPlayerDevice): self.update() def update_state(self, state_hash): - """ Update all the state properties with the passed in dictionary. """ + """Update all the state properties with the passed in dictionary.""" self.player_state = state_hash.get('player_state', None) self.current_volume = state_hash.get('volume', 0) @@ -189,13 +188,12 @@ class ItunesDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ - + """Return the state of the device.""" if self.player_state == 'offline' or self.player_state is None: return 'offline' @@ -211,7 +209,7 @@ class ItunesDevice(MediaPlayerDevice): return STATE_PLAYING def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" now_playing = self.client.now_playing() self.update_state(now_playing) @@ -239,28 +237,27 @@ class ItunesDevice(MediaPlayerDevice): @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" return self.muted @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" return self.current_volume/100.0 @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return self.content_id @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_image_url(self): - """ Image url of current playing media. """ - + """Image url of current playing media.""" if self.player_state in (STATE_PLAYING, STATE_IDLE, STATE_PAUSED) and \ self.current_title is not None: return self.client.artwork_url() @@ -270,76 +267,74 @@ class ItunesDevice(MediaPlayerDevice): @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" return self.current_title @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Artist of current playing media (Music track only).""" return self.current_artist @property def media_album_name(self): - """ Album of current playing media. (Music track only) """ + """Album of current playing media (Music track only).""" return self.current_album @property def media_playlist(self): - """ Title of the currently playing playlist. """ + """Title of the currently playing playlist.""" return self.current_playlist @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_ITUNES def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" response = self.client.set_volume(int(volume * 100)) self.update_state(response) def mute_volume(self, mute): - """ mute (true) or unmute (false) media player. """ + """Mute (true) or unmute (false) media player.""" response = self.client.set_muted(mute) self.update_state(response) def media_play(self): - """ media_play media player. """ + """Send media_play command to media player.""" response = self.client.play() self.update_state(response) def media_pause(self): - """ media_pause media player. """ + """Send media_pause command to media player.""" response = self.client.pause() self.update_state(response) def media_next_track(self): - """ media_next media player. """ + """Send media_next command to media player.""" response = self.client.next() self.update_state(response) def media_previous_track(self): - """ media_previous media player. """ + """Send media_previous command media player.""" response = self.client.previous() self.update_state(response) def play_media(self, media_type, media_id): - """ play_media media player. """ + """Send the play_media command to the media player.""" if media_type == MEDIA_TYPE_PLAYLIST: response = self.client.play_playlist(media_id) self.update_state(response) class AirPlayDevice(MediaPlayerDevice): - """ Represents an AirPlay device via an iTunes-API instance. """ + """Representation an AirPlay device via an iTunes API instance.""" # pylint: disable=too-many-public-methods - def __init__(self, device_id, client): + """Initialize the AirPlay device.""" self._id = device_id - self.client = client - self.device_name = "AirPlay" self.kind = None self.active = False @@ -350,8 +345,7 @@ class AirPlayDevice(MediaPlayerDevice): self.player_state = None def update_state(self, state_hash): - """ Update all the state properties with the passed in dictionary. """ - + """Update all the state properties with the passed in dictionary.""" if 'player_state' in state_hash: self.player_state = state_hash.get('player_state', None) @@ -379,12 +373,12 @@ class AirPlayDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self.device_name @property def icon(self): - """ Icon to use in the frontend, if any. """ + """Return the icon to use in the frontend, if any.""" if self.selected is True: return "mdi:volume-high" else: @@ -392,44 +386,45 @@ class AirPlayDevice(MediaPlayerDevice): @property def state(self): - """ Returns the state of the device. """ - + """Return the state of the device.""" if self.selected is True: return STATE_ON else: return STATE_OFF def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" @property def volume_level(self): + """Return the volume.""" return float(self.volume)/100.0 @property def media_content_type(self): + """Flag of media content that is supported.""" return MEDIA_TYPE_MUSIC @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_AIRPLAY def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" volume = int(volume * 100) response = self.client.set_volume_airplay_device(self._id, volume) self.update_state(response) def turn_on(self): - """ Select AirPlay. """ + """Select AirPlay.""" self.update_state({"selected": True}) self.update_ha_state() response = self.client.toggle_airplay_device(self._id, True) self.update_state(response) def turn_off(self): - """ Deselect AirPlay. """ + """Deselect AirPlay.""" self.update_state({"selected": False}) self.update_ha_state() response = self.client.toggle_airplay_device(self._id, False) diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index f7a7e9cf53a..6184608a9e8 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.kodi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to the XBMC/Kodi JSON-RPC API +Support for interfacing with 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/ @@ -23,8 +21,7 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the kodi platform. """ - + """Setup the Kodi platform.""" url = '{}:{}'.format(config.get('host'), config.get('port', '8080')) jsonrpc_url = config.get('url') # deprecated @@ -42,11 +39,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class KodiDevice(MediaPlayerDevice): - """ Represents a XBMC/Kodi device. """ + """Representation of a XBMC/Kodi device.""" # pylint: disable=too-many-public-methods, abstract-method - def __init__(self, name, url, auth=None): + """Initialize the Kodi device.""" import jsonrpc_requests self._name = name self._url = url @@ -61,11 +58,11 @@ class KodiDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name def _get_players(self): - """ Returns the active player objects or None """ + """Return the active player objects or None.""" import jsonrpc_requests try: return self._server.Player.GetActivePlayers() @@ -76,7 +73,7 @@ class KodiDevice(MediaPlayerDevice): @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self._players is None: return STATE_OFF @@ -89,7 +86,7 @@ class KodiDevice(MediaPlayerDevice): return STATE_PLAYING def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" self._players = self._get_players() if self._players is not None and len(self._players) > 0: @@ -117,31 +114,31 @@ class KodiDevice(MediaPlayerDevice): @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" if self._app_properties is not None: return self._app_properties['volume'] / 100.0 @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" if self._app_properties is not None: return self._app_properties['muted'] @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" if self._item is not None: return self._item.get('uniqueid', None) @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" if self._players is not None and len(self._players) > 0: return self._players[0]['type'] @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" if self._properties is not None: total_time = self._properties['totaltime'] @@ -152,12 +149,12 @@ class KodiDevice(MediaPlayerDevice): @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" if self._item is not None: return self._get_image_url() def _get_image_url(self): - """ Helper function that parses the thumbnail URLs used by Kodi. """ + """Helper function that parses the thumbnail URLs used by Kodi.""" url_components = urllib.parse.urlparse(self._item['thumbnail']) if url_components.scheme == 'image': @@ -167,7 +164,7 @@ class KodiDevice(MediaPlayerDevice): @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" # find a string we can use as a title if self._item is not None: return self._item.get( @@ -180,36 +177,36 @@ class KodiDevice(MediaPlayerDevice): @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_KODI def turn_off(self): - """ turn_off media player. """ + """Turn off media player.""" self._server.System.Shutdown() self.update_ha_state() def volume_up(self): - """ volume_up media player. """ + """Volume up the media player.""" assert self._server.Input.ExecuteAction('volumeup') == 'OK' self.update_ha_state() def volume_down(self): - """ volume_down media player. """ + """Volume down the media player.""" assert self._server.Input.ExecuteAction('volumedown') == 'OK' self.update_ha_state() def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" self._server.Application.SetVolume(int(volume * 100)) self.update_ha_state() def mute_volume(self, mute): - """ mute (true) or unmute (false) media player. """ + """Mute (true) or unmute (false) media player.""" self._server.Application.SetMute(mute) self.update_ha_state() def _set_play_state(self, state): - """ Helper method for play/pause/toggle. """ + """Helper method for play/pause/toggle.""" players = self._get_players() if len(players) != 0: @@ -218,19 +215,19 @@ class KodiDevice(MediaPlayerDevice): self.update_ha_state() def media_play_pause(self): - """ media_play_pause media player. """ + """Pause media on media player.""" self._set_play_state('toggle') def media_play(self): - """ media_play media player. """ + """Play media.""" self._set_play_state(True) def media_pause(self): - """ media_pause media player. """ + """Pause the media player.""" self._set_play_state(False) def _goto(self, direction): - """ Helper method used for previous/next track. """ + """Helper method used for previous/next track.""" players = self._get_players() if len(players) != 0: @@ -239,18 +236,18 @@ class KodiDevice(MediaPlayerDevice): self.update_ha_state() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self._goto('next') def media_previous_track(self): - """ Send next track command. """ + """Send next track command.""" # first seek to position 0, Kodi seems to go to the beginning # of the current track current track is not at the beginning self.media_seek(0) self._goto('previous') def media_seek(self, position): - """ Send seek command. """ + """Send seek command.""" players = self._get_players() time = {} diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index a4a6c0cac61..526cfe8b45b 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.mpd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to interact with a Music Player Daemon. +Support to interact with a Music Player Daemon. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.mpd/ @@ -24,8 +22,7 @@ SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the MPD platform. """ - + """Setup the MPD platform.""" daemon = config.get('server', None) port = config.get('port', 6600) location = config.get('location', 'MPD') @@ -64,12 +61,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MpdDevice(MediaPlayerDevice): - """ Represents a MPD server. """ + """Representation of a MPD server.""" # MPD confuses pylint # pylint: disable=no-member, abstract-method - def __init__(self, server, port, location, password): + """Initialize the MPD device.""" import mpd self.server = server @@ -85,6 +82,7 @@ class MpdDevice(MediaPlayerDevice): self.update() def update(self): + """Get the latest data and update the state.""" import mpd try: self.status = self.client.status() @@ -100,12 +98,12 @@ class MpdDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the media state. """ + """Return the media state.""" if self.status['state'] == 'play': return STATE_PLAYING elif self.status['state'] == 'pause': @@ -115,23 +113,23 @@ class MpdDevice(MediaPlayerDevice): @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return self.currentsong['id'] @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" # Time does not exist for streams return self.currentsong.get('time') @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" name = self.currentsong.get('name', None) title = self.currentsong.get('title', None) @@ -146,61 +144,62 @@ class MpdDevice(MediaPlayerDevice): @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Artist of current playing media (Music track only).""" return self.currentsong.get('artist') @property def media_album_name(self): - """ Album of current playing media. (Music track only) """ + """Album of current playing media (Music track only).""" return self.currentsong.get('album') @property def volume_level(self): + """Return the volume level.""" return int(self.status['volume'])/100 @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_MPD def turn_off(self): - """ Service to send the MPD the command to stop playing. """ + """Service to send the MPD the command to stop playing.""" self.client.stop() def turn_on(self): - """ Service to send the MPD the command to start playing. """ + """Service to send the MPD the command to start playing.""" self.client.play() def set_volume_level(self, volume): - """ Sets volume """ + """Set volume of media player.""" self.client.setvol(int(volume * 100)) def volume_up(self): - """ Service to send the MPD the command for volume up. """ + """Service to send the MPD the command for volume up.""" current_volume = int(self.status['volume']) if current_volume <= 100: self.client.setvol(current_volume + 5) def volume_down(self): - """ Service to send the MPD the command for volume down. """ + """Service to send the MPD the command for volume down.""" current_volume = int(self.status['volume']) if current_volume >= 0: self.client.setvol(current_volume - 5) def media_play(self): - """ Service to send the MPD the command for play/pause. """ + """Service to send the MPD the command for play/pause.""" self.client.pause(0) def media_pause(self): - """ Service to send the MPD the command for play/pause. """ + """Service to send the MPD the command for play/pause.""" self.client.pause(1) def media_next_track(self): - """ Service to send the MPD the command for next track. """ + """Service to send the MPD the command for next track.""" self.client.next() def media_previous_track(self): - """ Service to send the MPD the command for previous track. """ + """Service to send the MPD the command for previous track.""" self.client.previous() diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 0d68d7e9c30..b472b6c8eba 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.plex -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to the Plex API. +Support to interface with the Plex API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.plex/ @@ -35,7 +33,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK def config_from_file(filename, config=None): - """ Small configuration file management function. """ + """Small configuration file management function.""" if config: # We're writing configuration try: @@ -61,8 +59,7 @@ def config_from_file(filename, config=None): # pylint: disable=abstract-method def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Sets up the plex platform. """ - + """Setup the Plex platform.""" config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) if len(config): # Setup a configured PlexServer @@ -85,7 +82,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-branches def setup_plexserver(host, token, hass, add_devices_callback): - """ Setup a plexserver based on host parameter. """ + """Setup a plexserver based on host parameter.""" import plexapi.server import plexapi.exceptions @@ -119,7 +116,7 @@ def setup_plexserver(host, token, hass, add_devices_callback): @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_devices(): - """ Updates the devices objects. """ + """Update the devices objects.""" try: devices = plexserver.clients() except plexapi.exceptions.BadRequest: @@ -145,7 +142,7 @@ def setup_plexserver(host, token, hass, add_devices_callback): @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_sessions(): - """ Updates the sessions objects. """ + """Update the sessions objects.""" try: sessions = plexserver.sessions() except plexapi.exceptions.BadRequest: @@ -161,7 +158,7 @@ def setup_plexserver(host, token, hass, add_devices_callback): def request_configuration(host, hass, add_devices_callback): - """ Request configuration steps from the user. """ + """Request configuration steps from the user.""" configurator = get_component('configurator') # We got an error if this method is called while we are configuring @@ -172,7 +169,7 @@ def request_configuration(host, hass, add_devices_callback): return def plex_configuration_callback(data): - """ Actions to do when our configuration callback is called. """ + """The actions to do when our configuration callback is called.""" setup_plexserver(host, data.get('token'), hass, add_devices_callback) _CONFIGURING[host] = configurator.request_config( @@ -185,33 +182,34 @@ def request_configuration(host, hass, add_devices_callback): class PlexClient(MediaPlayerDevice): - """ Represents a Plex device. """ + """Representation of a Plex device.""" # pylint: disable=too-many-public-methods, attribute-defined-outside-init def __init__(self, device, plex_sessions, update_devices, update_sessions): + """Initialize the Plex device.""" self.plex_sessions = plex_sessions self.update_devices = update_devices self.update_sessions = update_sessions self.set_device(device) def set_device(self, device): - """ Sets the device property. """ + """Set the device property.""" self.device = device @property def unique_id(self): - """ Returns the id of this plex client """ + """Return the id of this plex client.""" return "{}.{}".format( self.__class__, self.device.machineIdentifier or self.device.name) @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self.device.name or DEVICE_DEFAULT_NAME @property def session(self): - """ Returns the session, if any. """ + """Return the session, if any.""" if self.device.machineIdentifier not in self.plex_sessions: return None @@ -219,7 +217,7 @@ class PlexClient(MediaPlayerDevice): @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self.session: state = self.session.player.state if state == 'playing': @@ -235,18 +233,19 @@ class PlexClient(MediaPlayerDevice): return STATE_UNKNOWN def update(self): + """Get the latest details.""" self.update_devices(no_throttle=True) self.update_sessions(no_throttle=True) @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" if self.session is not None: return self.session.ratingKey @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" if self.session is None: return None media_type = self.session.type @@ -258,61 +257,61 @@ class PlexClient(MediaPlayerDevice): @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" if self.session is not None: return self.session.duration @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" if self.session is not None: return self.session.thumbUrl @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" # find a string we can use as a title if self.session is not None: return self.session.title @property def media_season(self): - """ Season of curent playing media (TV Show only). """ + """Season of curent playing media (TV Show only).""" from plexapi.video import Show if isinstance(self.session, Show): return self.session.seasons()[0].index @property def media_series_title(self): - """ Series title of current playing media (TV Show only). """ + """The title of the series of current playing media (TV Show only).""" from plexapi.video import Show if isinstance(self.session, Show): return self.session.grandparentTitle @property def media_episode(self): - """ Episode of current playing media (TV Show only). """ + """Episode of current playing media (TV Show only).""" from plexapi.video import Show if isinstance(self.session, Show): return self.session.index @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_PLEX def media_play(self): - """ media_play media player. """ + """Send play command.""" self.device.play() def media_pause(self): - """ media_pause media player. """ + """Send pause command.""" self.device.pause() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self.device.skipNext() def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" self.device.skipPrevious() diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index f7dc7f2a15b..65a243e473a 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.denon -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to Samsung TV with a Laninterface. +Support for interface with an Samsung TV. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.samsungtv/ @@ -31,8 +29,7 @@ SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Samsung TV platform. """ - + """Setup the Samsung TV platform.""" # Validate that all required config options are given if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_HOST]}, _LOGGER): return False @@ -55,10 +52,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=abstract-method class SamsungTVDevice(MediaPlayerDevice): - """ Represents a Samsung TV. """ + """Representation of a Samsung TV.""" # pylint: disable=too-many-public-methods def __init__(self, name, config): + """Initialize the samsung device.""" from samsungctl import Remote # Save a reference to the imported class self._remote_class = Remote @@ -72,12 +70,12 @@ class SamsungTVDevice(MediaPlayerDevice): self._config = config def update(self): + """Retrieve the latest data.""" # Send an empty key to see if we are still connected return self.send_key('KEY_POWER') def get_remote(self): - """ Creates or Returns a remote control instance """ - + """Create or return a remote control instance.""" if self._remote is None: # We need to create a new instance to reconnect. self._remote = self._remote_class(self._config) @@ -85,7 +83,7 @@ class SamsungTVDevice(MediaPlayerDevice): return self._remote def send_key(self, key): - """ Sends a key to the tv and handles exceptions """ + """Send a key to the tv and handles exceptions.""" try: self.get_remote().control(key) self._state = STATE_ON @@ -106,62 +104,65 @@ class SamsungTVDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): + """Return the state of the device.""" return self._state @property def is_volume_muted(self): - """ Boolean if volume is currently muted. """ + """Boolean if volume is currently muted.""" return self._muted @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_SAMSUNGTV def turn_off(self): - """ turn_off media player. """ + """Turn off media player.""" self.send_key("KEY_POWEROFF") def volume_up(self): - """ volume_up media player. """ + """Volume up the media player.""" self.send_key("KEY_VOLUP") def volume_down(self): - """ volume_down media player. """ + """Volume down media player.""" self.send_key("KEY_VOLDOWN") def mute_volume(self, mute): + """Send mute command.""" self.send_key("KEY_MUTE") def media_play_pause(self): - """ Simulate play pause media player. """ + """Simulate play pause media player.""" if self._playing: self.media_pause() else: self.media_play() def media_play(self): - """ media_play media player. """ + """Send play command.""" self._playing = True self.send_key("KEY_PLAY") def media_pause(self): - """ media_pause media player. """ + """Send media pause command to media player.""" self._playing = False self.send_key("KEY_PAUSE") def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self.send_key("KEY_FF") def media_previous_track(self): + """Send the previous track command.""" self.send_key("KEY_REWIND") def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" self.send_key("KEY_POWERON") diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index 5c8bd24a79d..6bdbe9c6183 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.snapcast -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides functionality to interact with Snapcast clients. +Support for interacting with Snapcast clients. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.snapcast/ @@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Snapcast platform. """ + """Setup the Snapcast platform.""" import snapcast.control host = config.get('host') port = config.get('port', snapcast.control.CONTROL_PORT) @@ -39,44 +37,44 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SnapcastDevice(MediaPlayerDevice): - """ Represents a Snapcast client device. """ + """Representation of a Snapcast client device.""" # pylint: disable=abstract-method - def __init__(self, client): + """Initialize the Snapcast device.""" self._client = client @property def name(self): - """ Device name. """ + """Return the name of the device.""" return self._client.identifier @property def volume_level(self): - """ Volume level. """ + """Return the volume level.""" return self._client.volume / 100 @property def is_volume_muted(self): - """ Volume muted. """ + """Volume muted.""" return self._client.muted @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_SNAPCAST @property def state(self): - """ State of the player. """ + """Return the state of the player.""" if self._client.connected: return STATE_ON return STATE_OFF def mute_volume(self, mute): - """ Mute status. """ + """Send the mute command.""" self._client.muted = mute def set_volume_level(self, volume): - """ Volume level. """ + """Set the volume level.""" self._client.volume = round(volume * 100) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 70b7d911cca..129bb42a737 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.sonos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to Sonos players (via SoCo) +Support to interface with Sonos players (via SoCo). For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.sonos/ @@ -34,7 +32,7 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Sonos platform. """ + """Setup the Sonos platform.""" import soco import socket @@ -67,15 +65,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): 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 - """ + """Decorator for coordinator. + 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 """ + """Decorator wrapper.""" if args[0].is_coordinator: return func(*args, **kwargs) else: @@ -89,10 +86,11 @@ def only_if_coordinator(func): # pylint: disable=too-many-instance-attributes, too-many-public-methods # pylint: disable=abstract-method class SonosDevice(MediaPlayerDevice): - """ Represents a Sonos device. """ + """Representation of a Sonos device.""" # pylint: disable=too-many-arguments def __init__(self, hass, player): + """Initialize the Sonos device.""" self.hass = hass super(SonosDevice, self).__init__() self._player = player @@ -100,25 +98,26 @@ class SonosDevice(MediaPlayerDevice): @property def should_poll(self): + """No polling needed.""" return True def update_sonos(self, now): - """ Updates state, called by track_utc_time_change. """ + """Update state, called by track_utc_time_change.""" self.update_ha_state(True) @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def unique_id(self): - """ Returns a unique id. """ + """Return a unique ID.""" return "{}.{}".format(self.__class__, self._player.uid) @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if self._status == 'PAUSED_PLAYBACK': return STATE_PAUSED if self._status == 'PLAYING': @@ -129,11 +128,11 @@ class SonosDevice(MediaPlayerDevice): @property def is_coordinator(self): - """ Returns true if player is a coordinator """ + """Return true if player is a coordinator.""" return self._player.is_coordinator def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" self._name = self._player.get_speaker_info()['zone_name'].replace( ' (R)', '').replace(' (L)', '') self._status = self._player.get_current_transport_info().get( @@ -142,26 +141,27 @@ class SonosDevice(MediaPlayerDevice): @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" return self._player.volume / 100.0 @property def is_volume_muted(self): + """Return true if volume is muted.""" return self._player.mute @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return self._trackinfo.get('title', None) @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" dur = self._trackinfo.get('duration', '0:00') # If the speaker is playing from the "line-in" source, getting @@ -175,13 +175,13 @@ class SonosDevice(MediaPlayerDevice): @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" if 'album_art' in self._trackinfo: return self._trackinfo['album_art'] @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" if 'artist' in self._trackinfo and 'title' in self._trackinfo: return '{artist} - {title}'.format( artist=self._trackinfo['artist'], @@ -192,60 +192,60 @@ class SonosDevice(MediaPlayerDevice): @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_SONOS @only_if_coordinator def turn_off(self): - """ Turn off media player. """ + """Turn off media player.""" self._player.pause() @only_if_coordinator def volume_up(self): - """ Volume up media player. """ + """Volume up media player.""" self._player.volume += 1 @only_if_coordinator def volume_down(self): - """ Volume down media player. """ + """Volume down media player.""" self._player.volume -= 1 @only_if_coordinator def set_volume_level(self, volume): - """ Set volume level, range 0..1. """ + """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. """ + """Mute (true) or unmute (false) media player.""" self._player.mute = mute @only_if_coordinator def media_play(self): - """ Send paly command. """ + """Send paly command.""" self._player.play() @only_if_coordinator def media_pause(self): - """ Send pause command. """ + """Send pause command.""" self._player.pause() @only_if_coordinator def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self._player.next() @only_if_coordinator def media_previous_track(self): - """ Send next track command. """ + """Send next track command.""" self._player.previous() @only_if_coordinator def media_seek(self, position): - """ Send seek command. """ + """Send seek command.""" self._player.seek(str(datetime.timedelta(seconds=int(position)))) @only_if_coordinator def turn_on(self): - """ Turn the media player on. """ + """Turn the media player on.""" self._player.play() diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 457e1bc539a..f22d9f20f08 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.squeezebox -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides an interface to the Logitech SqueezeBox API +Support for interfacing to the Logitech SqueezeBox API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.squeezebox/ @@ -26,7 +24,7 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \ def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the squeezebox platform. """ + """Setup the squeezebox platform.""" if not config.get(CONF_HOST): _LOGGER.error( "Missing required configuration items in %s: %s", @@ -49,9 +47,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class LogitechMediaServer(object): - """ Represents a Logitech media server. """ + """Representation of a Logitech media server.""" def __init__(self, host, port, username, password): + """Initialize the Logitech device.""" self.host = host self.port = port self._username = username @@ -60,7 +59,7 @@ class LogitechMediaServer(object): self.init_success = True if self.http_port else False def _get_http_port(self): - """ Get http port from media server, it is used to get cover art. """ + """Get http port from media server, it is used to get cover art.""" http_port = None try: http_port = self.query('pref', 'httpport', '?') @@ -80,7 +79,7 @@ class LogitechMediaServer(object): return def create_players(self): - """ Create a list of SqueezeBoxDevices connected to the LMS. """ + """Create a list of SqueezeBoxDevices connected to the LMS.""" players = [] count = self.query('player', 'count', '?') for index in range(0, int(count)): @@ -90,7 +89,7 @@ class LogitechMediaServer(object): return players def query(self, *parameters): - """ Send request and await response from server. """ + """Send request and await response from server.""" telnet = telnetlib.Telnet(self.host, self.port) if self._username and self._password: telnet.write('login {username} {password}\n'.format( @@ -107,7 +106,7 @@ class LogitechMediaServer(object): return urllib.parse.unquote(response) def get_player_status(self, player): - """ Get ithe status of a player. """ + """Get ithe status of a player.""" # (title) : Song title # Requested Information # a (artist): Artist name 'artist' @@ -133,10 +132,11 @@ class LogitechMediaServer(object): # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-public-methods class SqueezeBoxDevice(MediaPlayerDevice): - """ Represents a SqueezeBox device. """ + """Representation of a SqueezeBox device.""" # pylint: disable=too-many-arguments, abstract-method def __init__(self, lms, player_id): + """Initialize the SqeezeBox device.""" super(SqueezeBoxDevice, self).__init__() self._lms = lms self._id = player_id @@ -145,12 +145,12 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def name(self): - """ Returns the name of the device. """ + """Return the name of the device.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if 'power' in self._status and self._status['power'] == '0': return STATE_OFF if 'mode' in self._status: @@ -163,40 +163,41 @@ class SqueezeBoxDevice(MediaPlayerDevice): return STATE_UNKNOWN def update(self): - """ Retrieve latest state. """ + """Retrieve latest state.""" self._status = self._lms.get_player_status(self._id) @property def volume_level(self): - """ Volume level of the media player (0..1). """ + """Volume level of the media player (0..1).""" if 'mixer volume' in self._status: return int(float(self._status['mixer volume'])) / 100.0 @property def is_volume_muted(self): + """Return true if volume is muted.""" if 'mixer volume' in self._status: return self._status['mixer volume'].startswith('-') @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" if 'current_title' in self._status: return self._status['current_title'] @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" if 'duration' in self._status: return int(float(self._status['duration'])) @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" if 'artwork_url' in self._status: media_url = self._status['artwork_url'] elif 'id' in self._status: @@ -214,7 +215,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" if 'artist' in self._status and 'title' in self._status: return '{artist} - {title}'.format( artist=self._status['artist'], @@ -225,67 +226,67 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag of media commands that are supported.""" return SUPPORT_SQUEEZEBOX def turn_off(self): - """ turn_off media player. """ + """Turn off media player.""" self._lms.query(self._id, 'power', '0') self.update_ha_state() def volume_up(self): - """ volume_up media player. """ + """Volume up media player.""" self._lms.query(self._id, 'mixer', 'volume', '+5') self.update_ha_state() def volume_down(self): - """ volume_down media player. """ + """Volume down media player.""" self._lms.query(self._id, 'mixer', 'volume', '-5') self.update_ha_state() def set_volume_level(self, volume): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" volume_percent = str(int(volume*100)) self._lms.query(self._id, 'mixer', 'volume', volume_percent) self.update_ha_state() def mute_volume(self, mute): - """ mute (true) or unmute (false) media player. """ + """Mute (true) or unmute (false) media player.""" mute_numeric = '1' if mute else '0' self._lms.query(self._id, 'mixer', 'muting', mute_numeric) self.update_ha_state() def media_play_pause(self): - """ media_play_pause media player. """ + """Send pause command to media player.""" self._lms.query(self._id, 'pause') self.update_ha_state() def media_play(self): - """ media_play media player. """ + """Send play command to media player.""" self._lms.query(self._id, 'play') self.update_ha_state() def media_pause(self): - """ media_pause media player. """ + """Send pause command to media player.""" self._lms.query(self._id, 'pause', '1') self.update_ha_state() def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self._lms.query(self._id, 'playlist', 'index', '+1') self.update_ha_state() def media_previous_track(self): - """ Send next track command. """ + """Send next track command.""" self._lms.query(self._id, 'playlist', 'index', '-1') self.update_ha_state() def media_seek(self, position): - """ Send seek command. """ + """Send seek command.""" self._lms.query(self._id, 'time', position) self.update_ha_state() def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" self._lms.query(self._id, 'power', '1') self.update_ha_state() diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 2bc4a8506f6..0c37a372328 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -1,7 +1,5 @@ """ -homeassistant.components.media_player.universal -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Combines multiple media players into one for a universal controller. +Combination of multiple media players into one for a universal controller. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.universal/ @@ -45,9 +43,8 @@ REQUIREMENTS = [] _LOGGER = logging.getLogger(__name__) -# pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ sets up the universal media players """ + """Setup the universal media players.""" if not validate_config(config): return @@ -61,10 +58,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def validate_config(config): - """ validate universal media player configuration """ + """Validate universal media player configuration.""" del config[CONF_PLATFORM] - # validate name + # Validate name if CONF_NAME not in config: _LOGGER.error('Universal Media Player configuration requires name') return False @@ -87,7 +84,7 @@ def validate_config(config): def validate_children(config): - """ validate children """ + """Validate children.""" if CONF_CHILDREN not in config: _LOGGER.info( 'No children under Universal Media Player (%s)', config[CONF_NAME]) @@ -101,7 +98,7 @@ def validate_children(config): def validate_commands(config): - """ validate commands """ + """Validate commands.""" if CONF_COMMANDS not in config: config[CONF_COMMANDS] = {} elif not isinstance(config[CONF_COMMANDS], dict): @@ -113,7 +110,7 @@ def validate_commands(config): def validate_attributes(config): - """ validate attributes """ + """Validate attributes.""" if CONF_ATTRS not in config: config[CONF_ATTRS] = {} elif not isinstance(config[CONF_ATTRS], dict): @@ -131,10 +128,11 @@ def validate_attributes(config): class UniversalMediaPlayer(MediaPlayerDevice): - """ Represents a universal media player in HA """ - # pylint: disable=too-many-public-methods + """Representation of an universal media player.""" + # pylint: disable=too-many-public-methods def __init__(self, hass, name, children, commands, attributes): + """Initialize the Universal media device.""" # pylint: disable=too-many-arguments self.hass = hass self._name = name @@ -144,7 +142,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): self._child_state = None def on_dependency_update(*_): - """ update ha state when dependencies update """ + """Update ha state when dependencies update.""" self.update_ha_state(True) depend = copy(children) @@ -154,7 +152,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): track_state_change(hass, depend, on_dependency_update) def _entity_lkp(self, entity_id, state_attr=None): - """ Looks up an entity state from hass """ + """Look up an entity state.""" state_obj = self.hass.states.get(entity_id) if state_obj is None: @@ -165,7 +163,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): return state_obj.state def _override_or_child_attr(self, attr_name): - """ returns either the override or the active child for attr_name """ + """Return either the override or the active child for attr_name.""" if attr_name in self._attrs: return self._entity_lkp(self._attrs[attr_name][0], self._attrs[attr_name][1]) @@ -173,13 +171,13 @@ class UniversalMediaPlayer(MediaPlayerDevice): return self._child_attr(attr_name) def _child_attr(self, attr_name): - """ returns the active child's attr """ + """Return the active child's attributes.""" active_child = self._child_state return active_child.attributes.get(attr_name) if active_child else None def _call_service(self, service_name, service_data=None, allow_override=False): - """ calls either a specified or active child's service """ + """Call either a specified or active child's service.""" if allow_override and service_name in self._cmds: call_from_config( self.hass, self._cmds[service_name], blocking=True) @@ -196,12 +194,12 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def should_poll(self): - """ Indicates whether HA should poll for updates """ + """No polling needed.""" return False @property def master_state(self): - """ gets the master state from entity or none """ + """Return the master state for entity or None.""" if CONF_STATE in self._attrs: master_state = self._entity_lkp(self._attrs[CONF_STATE][0], self._attrs[CONF_STATE][1]) @@ -211,17 +209,16 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def name(self): - """ name of universal player """ + """Return the name of universal player.""" return self._name @property def state(self): - """ - Current state of media player + """Current state of media player. Off if master state is off - ELSE Status of first active child - ELSE master state or off + else Status of first active child + else master state or off """ master_state = self.master_state # avoid multiple lookups if master_state == STATE_OFF: @@ -235,98 +232,98 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def volume_level(self): - """ Volume level of entity specified in attributes or active child """ + """Volume level of entity specified in attributes or active child.""" return self._child_attr(ATTR_MEDIA_VOLUME_LEVEL) @property def is_volume_muted(self): - """ boolean if volume is muted """ + """Boolean if volume is muted.""" return self._override_or_child_attr(ATTR_MEDIA_VOLUME_MUTED) \ in [True, STATE_ON] @property def media_content_id(self): - """ Content ID of current playing media. """ + """Content ID of current playing media.""" return self._child_attr(ATTR_MEDIA_CONTENT_ID) @property def media_content_type(self): - """ Content type of current playing media. """ + """Content type of current playing media.""" return self._child_attr(ATTR_MEDIA_CONTENT_TYPE) @property def media_duration(self): - """ Duration of current playing media in seconds. """ + """Duration of current playing media in seconds.""" return self._child_attr(ATTR_MEDIA_DURATION) @property def media_image_url(self): - """ Image url of current playing media. """ + """Image url of current playing media.""" return self._child_attr(ATTR_ENTITY_PICTURE) @property def media_title(self): - """ Title of current playing media. """ + """Title of current playing media.""" return self._child_attr(ATTR_MEDIA_TITLE) @property def media_artist(self): - """ Artist of current playing media. (Music track only) """ + """Artist of current playing media (Music track only).""" return self._child_attr(ATTR_MEDIA_ARTIST) @property def media_album_name(self): - """ Album name of current playing media. (Music track only) """ + """Album name of current playing media (Music track only).""" return self._child_attr(ATTR_MEDIA_ALBUM_NAME) @property def media_album_artist(self): - """ Album arist of current playing media. (Music track only) """ + """Album artist of current playing media (Music track only).""" return self._child_attr(ATTR_MEDIA_ALBUM_ARTIST) @property def media_track(self): - """ Track number of current playing media. (Music track only) """ + """Track number of current playing media (Music track only).""" return self._child_attr(ATTR_MEDIA_TRACK) @property def media_series_title(self): - """ Series title of current playing media. (TV Show only)""" + """The title of the series of current playing media (TV Show only).""" return self._child_attr(ATTR_MEDIA_SERIES_TITLE) @property def media_season(self): - """ Season of current playing media. (TV Show only) """ + """Season of current playing media (TV Show only).""" return self._child_attr(ATTR_MEDIA_SEASON) @property def media_episode(self): - """ Episode of current playing media. (TV Show only) """ + """Episode of current playing media (TV Show only).""" return self._child_attr(ATTR_MEDIA_EPISODE) @property def media_channel(self): - """ Channel currently playing. """ + """Channel currently playing.""" return self._child_attr(ATTR_MEDIA_CHANNEL) @property def media_playlist(self): - """ Title of Playlist currently playing. """ + """Title of Playlist currently playing.""" return self._child_attr(ATTR_MEDIA_PLAYLIST) @property def app_id(self): - """ ID of the current running app. """ + """ID of the current running app.""" return self._child_attr(ATTR_APP_ID) @property def app_name(self): - """ Name of the current running app. """ + """Name of the current running app.""" return self._child_attr(ATTR_APP_NAME) @property def supported_media_commands(self): - """ Flags of media commands that are supported. """ + """Flag media commands that are supported.""" flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0 if SERVICE_TURN_ON in self._cmds: @@ -347,69 +344,70 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def device_state_attributes(self): - """ Extra attributes a device wants to expose. """ + """Return device specific state attributes.""" active_child = self._child_state return {ATTR_ACTIVE_CHILD: active_child.entity_id} \ if active_child else {} def turn_on(self): - """ turn the media player on. """ + """Turn the media player on.""" self._call_service(SERVICE_TURN_ON, allow_override=True) def turn_off(self): - """ turn the media player off. """ + """Turn the media player off.""" self._call_service(SERVICE_TURN_OFF, allow_override=True) def mute_volume(self, is_volume_muted): - """ mute the volume. """ + """Mute the volume.""" data = {ATTR_MEDIA_VOLUME_MUTED: is_volume_muted} self._call_service(SERVICE_VOLUME_MUTE, data, allow_override=True) def set_volume_level(self, volume_level): - """ set volume level, range 0..1. """ + """Set volume level, range 0..1.""" data = {ATTR_MEDIA_VOLUME_LEVEL: volume_level} self._call_service(SERVICE_VOLUME_SET, data) def media_play(self): - """ Send play commmand. """ + """Send play commmand.""" self._call_service(SERVICE_MEDIA_PLAY) def media_pause(self): - """ Send pause command. """ + """Send pause command.""" self._call_service(SERVICE_MEDIA_PAUSE) def media_previous_track(self): - """ Send previous track command. """ + """Send previous track command.""" self._call_service(SERVICE_MEDIA_PREVIOUS_TRACK) def media_next_track(self): - """ Send next track command. """ + """Send next track command.""" self._call_service(SERVICE_MEDIA_NEXT_TRACK) def media_seek(self, position): - """ Send seek command. """ + """Send seek command.""" data = {ATTR_MEDIA_SEEK_POSITION: position} self._call_service(SERVICE_MEDIA_SEEK, data) def play_media(self, media_type, media_id): - """ Plays a piece of media. """ - data = {'media_type': media_type, 'media_id': media_id} + """Play a piece of media.""" + data = {ATTR_MEDIA_CONTENT_TYPE: media_type, + ATTR_MEDIA_CONTENT_ID: media_id} self._call_service(SERVICE_PLAY_MEDIA, data) def volume_up(self): - """ volume_up media player. """ + """Turn volume up for media player.""" self._call_service(SERVICE_VOLUME_UP, allow_override=True) def volume_down(self): - """ volume_down media player. """ + """Turn volume down for media player.""" self._call_service(SERVICE_VOLUME_DOWN, allow_override=True) def media_play_pause(self): - """ media_play_pause media player. """ + """Play or pause the media player.""" self._call_service(SERVICE_MEDIA_PLAY_PAUSE) def update(self): - """ event to trigger a state update in HA """ + """Update state in HA.""" for child_name in self._children: child_state = self.hass.states.get(child_name) if child_state and child_state.state not in OFF_STATES: diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 0c625968f14..01f4e72ca0d 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -1,7 +1,5 @@ """ -homeassistant.components.modbus -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Modbus component, using pymodbus (python3 branch). +Support for Modbus. For more details about this component, please refer to the documentation at https://home-assistant.io/components/modbus/ @@ -38,8 +36,7 @@ TYPE = None def setup(hass, config): - """ Setup Modbus component. """ - + """Setup Modbus component.""" # Modbus connection type # pylint: disable=global-statement, import-error global TYPE @@ -69,15 +66,14 @@ def setup(hass, config): return False def stop_modbus(event): - """ Stop Modbus service. """ + """Stop Modbus service.""" NETWORK.close() def start_modbus(event): - """ Start Modbus service. """ + """Start Modbus service.""" NETWORK.connect() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) - # Tells the bootstrapper that the component was successfully initialized return True diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py index fe4c8c546ca..a807487c90f 100644 --- a/homeassistant/components/mqtt_eventstream.py +++ b/homeassistant/components/mqtt_eventstream.py @@ -62,10 +62,7 @@ def setup(hass, config): # 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 and fire them on this hass instance.""" event = json.loads(payload) event_type = event.get('event_type') event_data = event.get('event_data') diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 0510dc6cece..2d3c8eb1f53 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -1,9 +1,5 @@ """ -homeassistant.components.mysensors. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -MySensors component that connects to a MySensors gateway via pymysensors -API. +Connect to a MySensors gateway via pymysensors API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mysensors/ diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index c5daf7e0734..efc51c771b6 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -1,7 +1,5 @@ """ -homeassistant.components.thermostat.nest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Adds support for Nest thermostats. +Support for Nest thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/thermostat.nest/ @@ -18,7 +16,7 @@ NEST = None # pylint: disable=unused-argument def setup(hass, config): - """ Sets up the nest thermostat. """ + """Setup the Nest thermostat component.""" global NEST logger = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 2b1ece959ac..85086aa15cc 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -45,7 +45,7 @@ def send_message(hass, message, title=None): def setup(hass, config): - """Sets up notify services.""" + """Setup the notify services.""" success = False descriptions = load_yaml_config_file( @@ -91,11 +91,11 @@ def setup(hass, config): # pylint: disable=too-few-public-methods class BaseNotificationService(object): - """Provides an ABC for notification services.""" + """An abstract class for notification services.""" def send_message(self, message, **kwargs): - """ - Send a message. + """Send a message. + kwargs can contain ATTR_TITLE to specify a title. """ raise NotImplementedError diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index 4852c7312d1..df77560c22b 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -1,7 +1,5 @@ """ -homeassistant.components.notify.command_line -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -command_line notification service. +Support for command line notification services. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.command_line/ @@ -16,8 +14,7 @@ _LOGGER = logging.getLogger(__name__) def get_service(hass, config): - """ Get the Command Line notification service. """ - + """Get the Command Line notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['command']}, _LOGGER): @@ -30,14 +27,14 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class CommandLineNotificationService(BaseNotificationService): - """ Implements notification service for the Command Line service. """ + """Implement the notification service for the Command Line service.""" def __init__(self, command): + """Initialize the service.""" self.command = command def send_message(self, message="", **kwargs): - """ Send a message to a command_line. """ - + """Send a message to a command line.""" try: proc = subprocess.Popen(self.command, universal_newlines=True, stdin=subprocess.PIPE, shell=True) diff --git a/homeassistant/components/notify/demo.py b/homeassistant/components/notify/demo.py index d4fbf08f7b9..c051bce020d 100644 --- a/homeassistant/components/notify/demo.py +++ b/homeassistant/components/notify/demo.py @@ -16,13 +16,13 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class DemoNotificationService(BaseNotificationService): - """Implements demo notification service.""" + """Implement demo notification service.""" + def __init__(self, hass): + """Initialize the service.""" self.hass = hass def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" title = kwargs.get(ATTR_TITLE) - self.hass.bus.fire(EVENT_NOTIFY, {"title": title, "message": message}) diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/notify/file.py index 5f9befa9510..f9b186e59ad 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/notify/file.py @@ -1,7 +1,5 @@ """ -homeassistant.components.notify.file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -File notification service. +Support for file notification. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.file/ @@ -18,8 +16,7 @@ _LOGGER = logging.getLogger(__name__) def get_service(hass, config): - """ Get the file notification service. """ - + """Get the file notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['filename', 'timestamp']}, @@ -34,15 +31,15 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class FileNotificationService(BaseNotificationService): - """ Implements notification service for the File service. """ + """Implement the notification service for the File service.""" def __init__(self, hass, filename, add_timestamp): + """Initialize the service.""" self.filepath = os.path.join(hass.config.config_dir, filename) self.add_timestamp = add_timestamp def send_message(self, message="", **kwargs): - """ Send a message to a file. """ - + """Send a message to a file.""" with open(self.filepath, 'a') as file: if os.stat(self.filepath).st_size == 0: title = '{} notifications (Log started: {})\n{}\n'.format( diff --git a/homeassistant/components/notify/free_mobile.py b/homeassistant/components/notify/free_mobile.py index 10ba96e2e73..e12cc5893b8 100644 --- a/homeassistant/components/notify/free_mobile.py +++ b/homeassistant/components/notify/free_mobile.py @@ -1,7 +1,5 @@ """ -homeassistant.components.notify.free_mobile -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Free Mobile SMS platform for notify component. +Support for thr Free Mobile SMS platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.free_mobile/ @@ -17,8 +15,7 @@ REQUIREMENTS = ['freesms==0.1.0'] def get_service(hass, config): - """ Get the Free Mobile SMS notification service. """ - + """Get the Free Mobile SMS notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_USERNAME, CONF_ACCESS_TOKEN]}, @@ -31,14 +28,15 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class FreeSMSNotificationService(BaseNotificationService): - """ Implements notification service for the Free Mobile SMS service. """ + """Implement a notification service for the Free Mobile SMS service.""" def __init__(self, username, access_token): + """Initialize the service.""" from freesms import FreeClient self.free_client = FreeClient(username, access_token) def send_message(self, message="", **kwargs): - """ Send a message to the Free Mobile user cell. """ + """Send a message to the Free Mobile user cell.""" resp = self.free_client.send_sms(message) if resp.status_code == 400: diff --git a/homeassistant/components/notify/googlevoice.py b/homeassistant/components/notify/googlevoice.py index c66ec5e99a3..021496fa00b 100644 --- a/homeassistant/components/notify/googlevoice.py +++ b/homeassistant/components/notify/googlevoice.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.googlevoice -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Google Voice SMS platform for notify component. For more details about this platform, please refer to the documentation at @@ -20,8 +18,7 @@ REQUIREMENTS = ['https://github.com/w1ll1am23/pygooglevoice-sms/archive/' def get_service(hass, config): - """ Get the Google Voice SMS notification service. """ - + """Get the Google Voice SMS notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_USERNAME, CONF_PASSWORD]}, @@ -34,17 +31,17 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class GoogleVoiceSMSNotificationService(BaseNotificationService): - """ Implements notification service for the Google Voice SMS service. """ + """Implement the notification service for the Google Voice SMS service.""" def __init__(self, username, password): + """Initialize the service.""" from googlevoicesms import Voice self.voice = Voice() self.username = username self.password = password def send_message(self, message="", **kwargs): - """ Send SMS to specified target user cell. """ - + """Send SMS to specified target user cell.""" targets = kwargs.get(ATTR_TARGET) if not targets: diff --git a/homeassistant/components/notify/instapush.py b/homeassistant/components/notify/instapush.py index d3dd05d7e54..028afb32468 100644 --- a/homeassistant/components/notify/instapush.py +++ b/homeassistant/components/notify/instapush.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.instapush -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instapush notification service. For more details about this platform, please refer to the documentation at @@ -21,8 +19,7 @@ _RESOURCE = 'https://api.instapush.im/v1/' def get_service(hass, config): - """ Get the instapush notification service. """ - + """Get the instapush notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_API_KEY, 'app_secret', @@ -58,9 +55,10 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class InstapushNotificationService(BaseNotificationService): - """ Implements notification service for Instapush. """ + """Implement the notification service for Instapush.""" def __init__(self, api_key, app_secret, event, tracker): + """Initialize the service.""" self._api_key = api_key self._app_secret = app_secret self._event = event @@ -71,10 +69,8 @@ class InstapushNotificationService(BaseNotificationService): 'Content-Type': 'application/json'} def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" title = kwargs.get(ATTR_TITLE) - data = {"event": self._event, "trackers": {self._tracker: title + " : " + message}} diff --git a/homeassistant/components/notify/nma.py b/homeassistant/components/notify/nma.py index 5133bbf287d..f37f5ca8bd0 100644 --- a/homeassistant/components/notify/nma.py +++ b/homeassistant/components/notify/nma.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.nma -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ NMA (Notify My Android) notification service. For more details about this platform, please refer to the documentation at @@ -21,8 +19,7 @@ _RESOURCE = 'https://www.notifymyandroid.com/publicapi/' def get_service(hass, config): - """ Get the NMA notification service. """ - + """Get the NMA notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_API_KEY]}, _LOGGER): @@ -41,14 +38,14 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class NmaNotificationService(BaseNotificationService): - """ Implements notification service for NMA. """ + """Implement the notification service for NMA.""" def __init__(self, api_key): + """Initialize the service.""" self._api_key = api_key def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" data = { "apikey": self._api_key, "application": 'home-assistant', diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index c7fbf5478e3..1baee8e5287 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.pushbullet -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PushBullet platform for notify component. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ REQUIREMENTS = ['pushbullet.py==0.9.0'] # pylint: disable=unused-argument def get_service(hass, config): - """ Get the PushBullet notification service. """ + """Get the PushBullet notification service.""" from pushbullet import PushBullet from pushbullet import InvalidKeyError @@ -39,16 +37,16 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class PushBulletNotificationService(BaseNotificationService): - """ Implements notification service for Pushbullet. """ + """Implement the notification service for Pushbullet.""" def __init__(self, pb): + """Initialize the service.""" self.pushbullet = pb self.pbtargets = {} self.refresh() def refresh(self): - """ - Refresh devices, contacts, etc + """Refresh devices, contacts, etc. pbtargets stores all targets available from this pushbullet instance into a dict. These are PB objects!. It sacrifices a bit of memory @@ -67,8 +65,8 @@ class PushBulletNotificationService(BaseNotificationService): } def send_message(self, message=None, **kwargs): - """ - Send a message to a specified target. + """Send a message to a specified target. + If no target specified, a 'normal' push will be sent to all devices linked to the PB account. Email is special, these are assumed to always exist. We use a special diff --git a/homeassistant/components/notify/pushetta.py b/homeassistant/components/notify/pushetta.py index d2c797d2fd7..234c8978452 100644 --- a/homeassistant/components/notify/pushetta.py +++ b/homeassistant/components/notify/pushetta.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.pushetta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pushetta platform for notify component. For more details about this platform, please refer to the documentation at @@ -19,8 +17,7 @@ REQUIREMENTS = ['pushetta==1.0.15'] def get_service(hass, config): - """ Get the Pushetta notification service. """ - + """Get the Pushetta notification service.""" from pushetta import Pushetta, exceptions if not validate_config({DOMAIN: config}, @@ -44,20 +41,17 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class PushettaNotificationService(BaseNotificationService): - """ Implements notification service for Pushetta. """ + """Implement the notification service for Pushetta.""" def __init__(self, api_key, channel_name): - + """Initialize the service.""" from pushetta import Pushetta - self._api_key = api_key self._channel_name = channel_name self.pushetta = Pushetta(self._api_key) def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" title = kwargs.get(ATTR_TITLE) - self.pushetta.pushMessage(self._channel_name, "{} {}".format(title, message)) diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index 6eaa1b91643..b202f38fa7c 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.pushover -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pushover platform for notify component. For more details about this platform, please refer to the documentation at @@ -19,8 +17,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-variable def get_service(hass, config): - """ Get the pushover notification service. """ - + """Get the Pushover notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['user_key', CONF_API_KEY]}, _LOGGER): @@ -40,9 +37,10 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class PushoverNotificationService(BaseNotificationService): - """ Implements notification service for Pushover. """ + """Implement the notification service for Pushover.""" def __init__(self, user_key, api_token): + """Initialize the service.""" from pushover import Client self._user_key = user_key self._api_token = api_token @@ -50,7 +48,7 @@ class PushoverNotificationService(BaseNotificationService): self._user_key, api_token=self._api_token) def send_message(self, message="", **kwargs): - """ Send a message to a user. """ + """Send a message to a user.""" from pushover import RequestError try: diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/notify/rest.py index 7f5582fd0fe..4c2c069a4b9 100644 --- a/homeassistant/components/notify/rest.py +++ b/homeassistant/components/notify/rest.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.rest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ REST platform for notify component. For more details about this platform, please refer to the documentation at @@ -23,8 +21,7 @@ DEFAULT_TARGET_PARAM_NAME = None def get_service(hass, config): - """ Get the REST notification service. """ - + """Get the REST notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['resource', ]}, _LOGGER): @@ -45,10 +42,11 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods, too-many-arguments class RestNotificationService(BaseNotificationService): - """ Implements notification service for REST. """ + """Implement the notification service for REST.""" def __init__(self, resource, method, message_param_name, title_param_name, target_param_name): + """Initialize the service.""" self._resource = resource self._method = method.upper() self._message_param_name = message_param_name @@ -56,8 +54,7 @@ class RestNotificationService(BaseNotificationService): self._target_param_name = target_param_name def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" data = { self._message_param_name: message } diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/notify/sendgrid.py index f56bbb59d3e..ac3fc3deaab 100644 --- a/homeassistant/components/notify/sendgrid.py +++ b/homeassistant/components/notify/sendgrid.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.sendgrid -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SendGrid notification service. For more details about this platform, please refer to the documentation at @@ -17,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def get_service(hass, config): - """ Get the SendGrid notification service """ + """Get the SendGrid notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['api_key', 'sender', 'recipient']}, _LOGGER): @@ -31,9 +29,10 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class SendgridNotificationService(BaseNotificationService): - """ Implements the notification service for email via Sendgrid. """ + """Implement the notification service for email via Sendgrid.""" def __init__(self, api_key, sender, recipient): + """Initialize the service.""" self.api_key = api_key self.sender = sender self.recipient = recipient @@ -42,7 +41,7 @@ class SendgridNotificationService(BaseNotificationService): self._sg = SendGridClient(self.api_key) def send_message(self, message='', **kwargs): - """ Send an email to a user via SendGrid. """ + """Send an email to a user via SendGrid.""" subject = kwargs.get(ATTR_TITLE) from sendgrid import Mail diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 75034aebb32..48c1578efe3 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.slack -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Slack platform for notify component. For more details about this platform, please refer to the documentation at @@ -18,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-variable def get_service(hass, config): - """ Get the slack notification service. """ + """Get the Slack notification service.""" import slacker if not validate_config({DOMAIN: config}, @@ -39,22 +37,21 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class SlackNotificationService(BaseNotificationService): - """ Implements notification service for Slack. """ + """Implement the notification service for Slack.""" def __init__(self, default_channel, api_token): + """Initialize the service.""" from slacker import Slacker - self._default_channel = default_channel self._api_token = api_token self.slack = Slacker(self._api_token) self.slack.auth.test() def send_message(self, message="", **kwargs): - """ Send a message to a user. """ + """Send a message to a user.""" import slacker channel = kwargs.get('channel', self._default_channel) - try: self.slack.chat.post_message(channel, message) except slacker.Error: diff --git a/homeassistant/components/notify/smtp.py b/homeassistant/components/notify/smtp.py index 8ee48ed9644..7664753f2ee 100644 --- a/homeassistant/components/notify/smtp.py +++ b/homeassistant/components/notify/smtp.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.smtp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Mail (SMTP) notification service. For more details about this platform, please refer to the documentation at @@ -18,8 +16,7 @@ _LOGGER = logging.getLogger(__name__) def get_service(hass, config): - """ Get the mail notification service. """ - + """Get the mail notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['recipient']}, _LOGGER): @@ -74,11 +71,12 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods, too-many-instance-attributes class MailNotificationService(BaseNotificationService): - """ Implements notification service for E-Mail messages. """ + """Implement the notification service for E-Mail messages.""" # pylint: disable=too-many-arguments def __init__(self, server, port, sender, starttls, username, password, recipient, debug): + """Initialize the service.""" self._server = server self._port = port self._sender = sender @@ -90,8 +88,7 @@ class MailNotificationService(BaseNotificationService): self.tries = 2 def connect(self): - """ Connect/Authenticate to SMTP Server """ - + """Connect/authenticate to SMTP Server.""" mail = smtplib.SMTP(self._server, self._port, timeout=5) mail.set_debuglevel(self.debug) mail.ehlo_or_helo_if_needed() @@ -103,8 +100,7 @@ class MailNotificationService(BaseNotificationService): return mail def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" mail = self.connect() subject = kwargs.get(ATTR_TITLE) diff --git a/homeassistant/components/notify/syslog.py b/homeassistant/components/notify/syslog.py index b568ce7d41c..381a92394c3 100644 --- a/homeassistant/components/notify/syslog.py +++ b/homeassistant/components/notify/syslog.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.syslog -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Syslog notification service. For more details about this platform, please refer to the documentation at @@ -69,16 +67,17 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class SyslogNotificationService(BaseNotificationService): - """ Implements syslog notification service. """ + """Implement the syslog notification service.""" # pylint: disable=too-many-arguments def __init__(self, facility, option, priority): + """Initialize the service.""" self._facility = facility self._option = option self._priority = priority def send_message(self, message="", **kwargs): - """ Send a message to a user. """ + """Send a message to a user.""" import syslog title = kwargs.get(ATTR_TITLE) diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py index 727ba578765..a5c46376ef1 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/notify/telegram.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.telegram -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Telegram platform for notify component. For more details about this platform, please refer to the documentation at @@ -20,7 +18,7 @@ REQUIREMENTS = ['python-telegram-bot==3.2.0'] def get_service(hass, config): - """ Get the Telegram notification service. """ + """Get the Telegram notification service.""" import telegram if not validate_config({DOMAIN: config}, @@ -41,9 +39,10 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class TelegramNotificationService(BaseNotificationService): - """ Implements notification service for Telegram. """ + """Implement the notification service for Telegram.""" def __init__(self, api_key, chat_id): + """Initialize the service.""" import telegram self._api_key = api_key @@ -51,7 +50,7 @@ class TelegramNotificationService(BaseNotificationService): self.bot = telegram.Bot(token=self._api_key) def send_message(self, message="", **kwargs): - """ Send a message to a user. """ + """Send a message to a user.""" import telegram title = kwargs.get(ATTR_TITLE) diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/notify/twitter.py index e0770381d72..45c54fd88de 100644 --- a/homeassistant/components/notify/twitter.py +++ b/homeassistant/components/notify/twitter.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.twitter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Twitter platform for notify component. For more details about this platform, please refer to the documentation at @@ -21,8 +19,7 @@ CONF_ACCESS_TOKEN_SECRET = "access_token_secret" def get_service(hass, config): - """ Get the Twitter notification service. """ - + """Get the Twitter notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_CONSUMER_KEY, CONF_CONSUMER_SECRET, CONF_ACCESS_TOKEN, @@ -38,16 +35,17 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class TwitterNotificationService(BaseNotificationService): - """ Implements notification service for the Twitter service. """ + """Implement notification service for the Twitter service.""" def __init__(self, consumer_key, consumer_secret, access_token_key, access_token_secret): + """Initialize the service.""" from TwitterAPI import TwitterAPI self.api = TwitterAPI(consumer_key, consumer_secret, access_token_key, access_token_secret) def send_message(self, message="", **kwargs): - """ Tweet some message. """ + """Tweet some message.""" resp = self.api.request('statuses/update', {'status': message}) if resp.status_code != 200: import json diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index 9b3bec0b195..2e609b120da 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -1,6 +1,4 @@ """ -homeassistant.components.notify.xmpp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Jabber (XMPP) notification service. For more details about this platform, please refer to the documentation at @@ -18,8 +16,7 @@ _LOGGER = logging.getLogger(__name__) def get_service(hass, config): - """ Get the Jabber (XMPP) notification service. """ - + """Get the Jabber (XMPP) notification service.""" if not validate_config({DOMAIN: config}, {DOMAIN: ['sender', 'password', 'recipient']}, _LOGGER): @@ -32,16 +29,16 @@ def get_service(hass, config): # pylint: disable=too-few-public-methods class XmppNotificationService(BaseNotificationService): - """ Implements notification service for Jabber (XMPP). """ + """Implement the notification service for Jabber (XMPP).""" def __init__(self, sender, password, recipient): + """Initialize the service.""" self._sender = sender self._password = password self._recipient = recipient def send_message(self, message="", **kwargs): - """ Send a message to a user. """ - + """Send a message to a user.""" title = kwargs.get(ATTR_TITLE) data = "{}: {}".format(title, message) if title else message @@ -50,13 +47,14 @@ class XmppNotificationService(BaseNotificationService): def send_message(sender, password, recipient, message): - """ Send a message over XMPP. """ + """Send a message over XMPP.""" import sleekxmpp class SendNotificationBot(sleekxmpp.ClientXMPP): - """ Service for sending Jabber (XMPP) messages. """ + """Service for sending Jabber (XMPP) messages.""" def __init__(self): + """Initialize the Jabber Bot.""" super(SendNotificationBot, self).__init__(sender, password) logging.basicConfig(level=logging.ERROR) @@ -69,14 +67,14 @@ def send_message(sender, password, recipient, message): self.process() def start(self, event): - """ Starts the communication and sends the message. """ + """Start the communication and sends the message.""" self.send_presence() self.get_roster() self.send_message(mto=recipient, mbody=message, mtype='chat') self.disconnect(wait=True) def check_credentials(self, event): - """" Disconnect from the server if credentials are invalid. """ + """"Disconnect from the server if credentials are invalid.""" self.disconnect() SendNotificationBot() diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index ba52dce91e0..5880df639bd 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -1,6 +1,6 @@ """ -homeassistant.components.proximity -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for tracking the proximity of a device. + Component to monitor the proximity of devices to a particular zone and the direction of travel. @@ -32,13 +32,13 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements - """ Get the zones and offsets from configuration.yaml. """ + """Get the zones and offsets from configuration.yaml.""" ignored_zones = [] if 'ignored_zones' in config[DOMAIN]: for variable in config[DOMAIN]['ignored_zones']: ignored_zones.append(variable) - # Get the devices from configuration.yaml + # Get the devices from configuration.yaml. if 'devices' not in config[DOMAIN]: _LOGGER.error('devices not found in config') return False @@ -47,10 +47,10 @@ def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements for variable in config[DOMAIN]['devices']: proximity_devices.append(variable) - # Get the direction of travel tolerance from configuration.yaml + # Get the direction of travel tolerance from configuration.yaml. tolerance = config[DOMAIN].get('tolerance', DEFAULT_TOLERANCE) - # Get the zone to monitor proximity to from configuration.yaml + # Get the zone to monitor proximity to from configuration.yaml. proximity_zone = config[DOMAIN].get('zone', DEFAULT_PROXIMITY_ZONE) entity_id = DOMAIN + '.' + proximity_zone @@ -59,7 +59,7 @@ def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements state = hass.states.get(proximity_zone) zone_friendly_name = (state.name).lower() - # set the default values + # Set the default values. dist_to_zone = 'not set' dir_of_travel = 'not set' nearest = 'not set' @@ -71,7 +71,7 @@ def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements proximity.update_ha_state() - # Main command to monitor proximity of devices + # Main command to monitor proximity of devices. track_state_change(hass, proximity_devices, proximity.check_proximity_state_change) @@ -79,11 +79,13 @@ def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements class Proximity(Entity): # pylint: disable=too-many-instance-attributes - """ Represents a Proximity. """ + """Representation of a Proximity.""" + + # pylint: disable=too-many-arguments def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel, nearest, ignored_zones, proximity_devices, tolerance, proximity_zone): - # pylint: disable=too-many-arguments + """Initialize the proximity.""" self.hass = hass self.friendly_name = zone_friendly_name self.dist_to = dist_to @@ -101,25 +103,25 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes @property def state(self): - """ Returns the state. """ + """Return the state.""" return self.dist_to @property def unit_of_measurement(self): - """ Unit of measurement of this entity. """ + """Return the unit of measurement of this entity.""" return "km" @property def state_attributes(self): - """ Returns the state attributes. """ + """Return the state attributes.""" return { ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest, } + # pylint: disable=too-many-branches,too-many-statements,too-many-locals def check_proximity_state_change(self, entity, old_state, new_state): - # pylint: disable=too-many-branches,too-many-statements,too-many-locals - """ Function to perform the proximity checking. """ + """Function to perform the proximity checking.""" entity_name = new_state.name devices_to_calculate = False devices_in_zone = '' @@ -128,21 +130,21 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes proximity_latitude = zone_state.attributes.get('latitude') proximity_longitude = zone_state.attributes.get('longitude') - # Check for devices in the monitored zone + # Check for devices in the monitored zone. for device in self.proximity_devices: device_state = self.hass.states.get(device) if device_state.state not in self.ignored_zones: devices_to_calculate = True - # Check the location of all devices + # Check the location of all devices. if (device_state.state).lower() == (self.friendly_name).lower(): device_friendly = device_state.name if devices_in_zone != '': devices_in_zone = devices_in_zone + ', ' devices_in_zone = devices_in_zone + device_friendly - # No-one to track so reset the entity + # No-one to track so reset the entity. if not devices_to_calculate: self.dist_to = 'not set' self.dir_of_travel = 'not set' @@ -150,7 +152,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes self.update_ha_state() return - # At least one device is in the monitored zone so update the entity + # At least one device is in the monitored zone so update the entity. if devices_in_zone != '': self.dist_to = 0 self.dir_of_travel = 'arrived' @@ -158,32 +160,33 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes self.update_ha_state() return - # We can't check proximity because latitude and longitude don't exist + # We can't check proximity because latitude and longitude don't exist. if 'latitude' not in new_state.attributes: return - # Collect distances to the zone for all devices + # Collect distances to the zone for all devices. distances_to_zone = {} for device in self.proximity_devices: - # Ignore devices in an ignored zone + # Ignore devices in an ignored zone. device_state = self.hass.states.get(device) if device_state.state in self.ignored_zones: continue - # Ignore devices if proximity cannot be calculated + # Ignore devices if proximity cannot be calculated. if 'latitude' not in device_state.attributes: continue - # Calculate the distance to the proximity zone + # Calculate the distance to the proximity zone. dist_to_zone = distance(proximity_latitude, proximity_longitude, device_state.attributes['latitude'], device_state.attributes['longitude']) - # Add the device and distance to a dictionary + # Add the device and distance to a dictionary. distances_to_zone[device] = round(dist_to_zone / 1000, 1) - # Loop through each of the distances collected and work out the closest + # Loop through each of the distances collected and work out the + # closest. closest_device = '' dist_to_zone = 1000000 @@ -192,7 +195,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes closest_device = device dist_to_zone = distances_to_zone[device] - # If the closest device is one of the other devices + # If the closest device is one of the other devices. if closest_device != entity: self.dist_to = round(distances_to_zone[closest_device]) self.dir_of_travel = 'unknown' @@ -202,7 +205,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes return # Stop if we cannot calculate the direction of travel (i.e. we don't - # have a previous state and a current LAT and LONG) + # have a previous state and a current LAT and LONG). if old_state is None or 'latitude' not in old_state.attributes: self.dist_to = round(distances_to_zone[entity]) self.dir_of_travel = 'unknown' @@ -213,7 +216,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes # Reset the variables distance_travelled = 0 - # Calculate the distance travelled + # Calculate the distance travelled. old_distance = distance(proximity_latitude, proximity_longitude, old_state.attributes['latitude'], old_state.attributes['longitude']) diff --git a/homeassistant/components/recorder.py b/homeassistant/components/recorder.py index eb30a930cae..07825821880 100644 --- a/homeassistant/components/recorder.py +++ b/homeassistant/components/recorder.py @@ -1,6 +1,6 @@ """ -homeassistant.components.recorder -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for recording details. + Component that records all events and state changes. Allows other components to query this database. @@ -35,14 +35,14 @@ _LOGGER = logging.getLogger(__name__) def query(sql_query, arguments=None): - """ Query the database. """ + """Query the database.""" _verify_instance() return _INSTANCE.query(sql_query, arguments) def query_states(state_query, arguments=None): - """ Query the database and return a list of states. """ + """Query the database and return a list of states.""" return [ row for row in (row_to_state(row) for row in query(state_query, arguments)) @@ -50,7 +50,7 @@ def query_states(state_query, arguments=None): def query_events(event_query, arguments=None): - """ Query the database and return a list of states. """ + """Query the database and return a list of states.""" return [ row for row in (row_to_event(row) for row in query(event_query, arguments)) @@ -58,7 +58,7 @@ def query_events(event_query, arguments=None): def row_to_state(row): - """ Convert a database row to a state. """ + """Convert a database row to a state.""" try: return State( row[1], row[2], json.loads(row[3]), @@ -71,7 +71,7 @@ def row_to_state(row): def row_to_event(row): - """ Convert a databse row to an event. """ + """Convert a databse row to an event.""" try: return Event(row[1], json.loads(row[2]), EventOrigin(row[3]), dt_util.utc_from_timestamp(row[5])) @@ -82,8 +82,9 @@ def row_to_event(row): def run_information(point_in_time=None): - """ - Returns information about current run or the run that covers point_in_time. + """Return information about current run. + + There is also the run that covers point_in_time. """ _verify_instance() @@ -98,7 +99,7 @@ def run_information(point_in_time=None): def setup(hass, config): - """ Setup the recorder. """ + """Setup the recorder.""" # pylint: disable=global-statement global _INSTANCE @@ -108,8 +109,10 @@ def setup(hass, config): class RecorderRun(object): - """ Represents a recorder run. """ + """Representation of arecorder run.""" + def __init__(self, row=None): + """Initialize the recorder run.""" self.end = None if row is None: @@ -124,8 +127,8 @@ class RecorderRun(object): self.closed_incorrect = bool(row[3]) def entity_ids(self, point_in_time=None): - """ - Return the entity ids that existed in this run. + """Return the entity ids that existed in this run. + Specify point_in_time if you want to know which existed at that point in time inside the run. """ @@ -142,15 +145,18 @@ class RecorderRun(object): @property def where_after_start_run(self): - """ - Returns SQL WHERE clause to select rows created after the start of the - run. + """Return SQL WHERE clause. + + Selection of the rows created after the start of the run. """ return "created >= {} ".format(_adapt_datetime(self.start)) @property def where_limit_to_run(self): - """ Return a SQL WHERE clause to limit results to this run. """ + """Return a SQL WHERE clause. + + For limiting the results to this run. + """ where = self.where_after_start_run if self.end is not None: @@ -160,8 +166,10 @@ class RecorderRun(object): class Recorder(threading.Thread): - """ Threaded recorder class """ + """A threaded recorder class.""" + def __init__(self, hass): + """Initialize the recorder.""" threading.Thread.__init__(self) self.hass = hass @@ -173,7 +181,7 @@ class Recorder(threading.Thread): self.utc_offset = dt_util.now().utcoffset().total_seconds() def start_recording(event): - """ Start recording. """ + """Start recording.""" self.start() hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_recording) @@ -181,7 +189,7 @@ class Recorder(threading.Thread): hass.bus.listen(MATCH_ALL, self.event_listener) def run(self): - """ Start processing events to save. """ + """Start processing events to save.""" self._setup_connection() self._setup_run() @@ -208,19 +216,16 @@ class Recorder(threading.Thread): self.queue.task_done() def event_listener(self, event): - """ - Listens for new events on the EventBus and puts them in the process - queue. - """ + """Listen for new events and put them in the process queue.""" self.queue.put(event) def shutdown(self, event): - """ Tells the recorder to shut down. """ + """Tell the recorder to shut down.""" self.queue.put(self.quit_object) self.block_till_done() def record_state(self, entity_id, state, event_id): - """ Save a state to the database. """ + """Save a state to the database.""" now = dt_util.utcnow() # State got deleted @@ -251,7 +256,7 @@ class Recorder(threading.Thread): info) def record_event(self, event): - """ Save an event to the database. """ + """Save an event to the database.""" info = ( event.event_type, json.dumps(event.data, cls=JSONEncoder), str(event.origin), dt_util.utcnow(), event.time_fired, @@ -264,7 +269,7 @@ class Recorder(threading.Thread): ") VALUES (?, ?, ?, ?, ?, ?)", info, RETURN_LASTROWID) def query(self, sql_query, data=None, return_value=None): - """ Query the database. """ + """Query the database.""" try: with self.conn, self.lock: _LOGGER.debug("Running query %s", sql_query) @@ -292,11 +297,11 @@ class Recorder(threading.Thread): return [] def block_till_done(self): - """ Blocks till all events processed. """ + """Block till all events processed.""" self.queue.join() def _setup_connection(self): - """ Ensure database is ready to fly. """ + """Ensure database is ready to fly.""" db_path = self.hass.config.path(DB_FILE) self.conn = sqlite3.connect(db_path, check_same_thread=False) self.conn.row_factory = sqlite3.Row @@ -305,15 +310,15 @@ class Recorder(threading.Thread): # without the STOP event being fired. atexit.register(self._close_connection) - # Have datetime objects be saved as integers + # Have datetime objects be saved as integers. sqlite3.register_adapter(date, _adapt_datetime) sqlite3.register_adapter(datetime, _adapt_datetime) - # Validate we are on the correct schema or that we have to migrate + # Validate we are on the correct schema or that we have to migrate. cur = self.conn.cursor() def save_migration(migration_id): - """ Save and commit a migration to the database. """ + """Save and commit a migration to the database.""" cur.execute('INSERT INTO schema_version VALUES (?, ?)', (migration_id, dt_util.utcnow())) self.conn.commit() @@ -324,7 +329,7 @@ class Recorder(threading.Thread): migration_id = cur.fetchone()[0] or 0 except sqlite3.OperationalError: - # The table does not exist + # The table does not exist. cur.execute('CREATE TABLE schema_version (' 'migration_id integer primary key, performed integer)') migration_id = 0 @@ -399,7 +404,7 @@ class Recorder(threading.Thread): save_migration(3) if migration_id < 4: - # We had a bug where we did not save utc offset for recorder runs + # We had a bug where we did not save utc offset for recorder runs. cur.execute( """UPDATE recorder_runs SET utc_offset=? WHERE utc_offset IS NULL""", [self.utc_offset]) @@ -412,15 +417,15 @@ class Recorder(threading.Thread): save_migration(4) if migration_id < 5: - # Add domain so that thermostat graphs look right + # Add domain so that thermostat graphs look right. try: cur.execute(""" ALTER TABLE states ADD COLUMN domain text """) except sqlite3.OperationalError: - # We had a bug in this migration for a while on dev - # Without this, dev-users will have to throw away their db + # We had a bug in this migration for a while on dev. + # Without this, dev-users will have to throw away their db. pass # TravisCI has Python compiled against an old version of SQLite3 @@ -429,13 +434,13 @@ class Recorder(threading.Thread): "instr", 2, lambda string, substring: string.find(substring) + 1) - # populate domain with defaults + # Populate domain with defaults. cur.execute(""" UPDATE states set domain=substr(entity_id, 0, instr(entity_id, '.')) """) - # add indexes we are going to use a lot on selects + # Add indexes we are going to use a lot on selects. cur.execute(""" CREATE INDEX states__state_changes ON states (last_changed, last_updated, entity_id)""") @@ -445,13 +450,13 @@ class Recorder(threading.Thread): save_migration(5) def _close_connection(self): - """ Close connection to the database. """ + """Close connection to the database.""" _LOGGER.info("Closing database") atexit.unregister(self._close_connection) self.conn.close() def _setup_run(self): - """ Log the start of the current run. """ + """Log the start of the current run.""" if self.query("""UPDATE recorder_runs SET end=?, closed_incorrect=1 WHERE end IS NULL""", (self.recording_start, ), return_value=RETURN_ROWCOUNT): @@ -464,18 +469,18 @@ class Recorder(threading.Thread): (self.recording_start, dt_util.utcnow(), self.utc_offset)) def _close_run(self): - """ Save end time for current run. """ + """Save end time for current run.""" self.query( "UPDATE recorder_runs SET end=? WHERE start=?", (dt_util.utcnow(), self.recording_start)) def _adapt_datetime(datetimestamp): - """ Turn a datetime into an integer for in the DB. """ + """Turn a datetime into an integer for in the DB.""" return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp() def _verify_instance(): - """ Throws error if recorder not initialized. """ + """Throw error if recorder not initialized.""" if _INSTANCE is None: raise RuntimeError("Recorder not initialized.") diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index acf119f7561..cfc6f1bab5a 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rfxtrx -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides support for RFXtrx components. +Support for RFXtrx components. For more details about this component, please refer to the documentation at https://home-assistant.io/components/rfxtrx/ @@ -10,8 +8,8 @@ import logging from homeassistant.util import slugify -REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip' + - '#pyRFXtrx==0.4'] +REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/' + + 'archive/0.5.zip#pyRFXtrx==0.5'] DOMAIN = "rfxtrx" @@ -22,6 +20,7 @@ ATTR_NAME = 'name' ATTR_PACKETID = 'packetid' ATTR_FIREEVENT = 'fire_event' ATTR_DATA_TYPE = 'data_type' +ATTR_DUMMY = "dummy" EVENT_BUTTON_PRESSED = 'button_pressed' @@ -32,12 +31,10 @@ RFXOBJECT = None def setup(hass, config): - """ Setup the RFXtrx component. """ - + """Setup the RFXtrx component.""" # Declare the Handle event def handle_receive(event): - """ Callback all subscribers for RFXtrx gateway. """ - + """Callback all subscribers for RFXtrx gateway.""" # Log RFXCOM event if not event.device.id_string: return @@ -47,33 +44,39 @@ def setup(hass, config): _LOGGER.info("Receive RFXCOM event from %s => %s", event.device, entity_name) - # Callback to HA registered components + # Callback to HA registered components. for subscriber in RECEIVED_EVT_SUBSCRIBERS: subscriber(event) - # Try to load the RFXtrx module + # Try to load the RFXtrx module. import RFXtrx as rfxtrxmod - # Init the rfxtrx module + # Init the rfxtrx module. global RFXOBJECT if ATTR_DEVICE not in config[DOMAIN]: - _LOGGER.exception( - "can found device parameter in %s YAML configuration section", + _LOGGER.error( + "can not find device parameter in %s YAML configuration section", DOMAIN ) return False device = config[DOMAIN][ATTR_DEVICE] debug = config[DOMAIN].get(ATTR_DEBUG, False) + dummy_connection = config[DOMAIN].get(ATTR_DUMMY, False) - RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug) + if dummy_connection: + RFXOBJECT =\ + rfxtrxmod.Core(device, handle_receive, debug=debug, + transport_protocol=rfxtrxmod.DummyTransport2) + else: + RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug) return True def get_rfx_object(packetid): - """ Return the RFXObject with the packetid. """ + """Return the RFXObject with the packetid.""" import RFXtrx as rfxtrxmod binarypacket = bytearray.fromhex(packetid) diff --git a/homeassistant/components/rollershutter/__init__.py b/homeassistant/components/rollershutter/__init__.py index 517ebf97b25..109302708b2 100644 --- a/homeassistant/components/rollershutter/__init__.py +++ b/homeassistant/components/rollershutter/__init__.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rollershutter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Rollershutter component. +Support for Roller shutters. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter/ @@ -36,38 +34,38 @@ ATTR_CURRENT_POSITION = 'current_position' def is_open(hass, entity_id=None): - """ Returns if the rollershutter is open based on the statemachine. """ + """Return if the roller shutter is open based on the statemachine.""" entity_id = entity_id or ENTITY_ID_ALL_ROLLERSHUTTERS return hass.states.is_state(entity_id, STATE_OPEN) def move_up(hass, entity_id=None): - """ Move up all or specified rollershutter. """ + """Move up all or specified roller shutter.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_MOVE_UP, data) def move_down(hass, entity_id=None): - """ Move down all or specified rollershutter. """ + """Move down all or specified roller shutter.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_MOVE_DOWN, data) def stop(hass, entity_id=None): - """ Stops all or specified rollershutter. """ + """Stop all or specified roller shutter.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None hass.services.call(DOMAIN, SERVICE_STOP, data) def setup(hass, config): - """ Track states and offer events for rollershutters. """ + """Track states and offer events for roller shutters.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, GROUP_NAME_ALL_ROLLERSHUTTERS) component.setup(config) def handle_rollershutter_service(service): - """ Handles calls to the rollershutter services. """ + """Handle calls to the roller shutter services.""" target_rollershutters = component.extract_from_service(service) for rollershutter in target_rollershutters: @@ -98,20 +96,20 @@ def setup(hass, config): class RollershutterDevice(Entity): - """ Represents a rollershutter within Home Assistant. """ - # pylint: disable=no-self-use + """Representation a rollers hutter.""" + # pylint: disable=no-self-use @property def current_position(self): - """ - Return current position of rollershutter. + """Return current position of roller shutter. + None is unknown, 0 is closed, 100 is fully open. """ raise NotImplementedError() @property def state(self): - """ Returns the state of the rollershutter. """ + """Return the state of the roller shutter.""" current = self.current_position if current is None: @@ -121,7 +119,7 @@ class RollershutterDevice(Entity): @property def state_attributes(self): - """ Return the state attributes. """ + """Return the state attributes.""" current = self.current_position if current is None: @@ -132,13 +130,13 @@ class RollershutterDevice(Entity): } def move_up(self, **kwargs): - """ Move the rollershutter down. """ + """Move the roller shutter down.""" raise NotImplementedError() def move_down(self, **kwargs): - """ Move the rollershutter up. """ + """Move the roller shutter up.""" raise NotImplementedError() def stop(self, **kwargs): - """ Stop the rollershutter. """ + """Stop the roller shutter.""" raise NotImplementedError() diff --git a/homeassistant/components/rollershutter/command_line.py b/homeassistant/components/rollershutter/command_line.py index 1808901ef3a..148c93335ac 100644 --- a/homeassistant/components/rollershutter/command_line.py +++ b/homeassistant/components/rollershutter/command_line.py @@ -15,8 +15,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup rollershutter controlled by shell commands.""" - + """Setup roller shutter controlled by shell commands.""" rollershutters = config.get('rollershutters', {}) devices = [] @@ -35,15 +34,15 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class CommandRollershutter(RollershutterDevice): - """ Represents a rollershutter - can be controlled using shell cmd. """ + """Representation a command line roller shutter.""" # pylint: disable=too-many-arguments def __init__(self, hass, name, command_up, command_down, command_stop, command_state, value_template): - + """Initialize the roller shutter.""" self._hass = hass self._name = name - self._state = None # Unknown + self._state = None self._command_up = command_up self._command_down = command_down self._command_stop = command_stop @@ -52,7 +51,7 @@ class CommandRollershutter(RollershutterDevice): @staticmethod def _move_rollershutter(command): - """ Execute the actual commands. """ + """Execute the actual commands.""" _LOGGER.info('Running command: %s', command) success = (subprocess.call(command, shell=True) == 0) @@ -64,7 +63,7 @@ class CommandRollershutter(RollershutterDevice): @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: @@ -75,31 +74,31 @@ class CommandRollershutter(RollershutterDevice): @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 rollershutter. """ + """Return the name of the roller shutter.""" return self._name @property def current_position(self): - """ - Return current position of rollershutter. + """Return current position of roller shutter. + None is unknown, 0 is closed, 100 is fully open. """ return self._state def _query_state(self): - """ Query for state. """ + """Query for the 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. """ + """Update device state.""" if self._command_state: payload = str(self._query_state()) if self._value_template: @@ -108,13 +107,13 @@ class CommandRollershutter(RollershutterDevice): self._state = int(payload) def move_up(self, **kwargs): - """ Move the rollershutter up. """ + """Move the roller shutter up.""" self._move_rollershutter(self._command_up) def move_down(self, **kwargs): - """ Move the rollershutter down. """ + """Move the roller shutter down.""" self._move_rollershutter(self._command_down) def stop(self, **kwargs): - """ Stop the device. """ + """Stop the device.""" self._move_rollershutter(self._command_stop) diff --git a/homeassistant/components/rollershutter/demo.py b/homeassistant/components/rollershutter/demo.py index 2910f102d11..ebdc3907a59 100644 --- a/homeassistant/components/rollershutter/demo.py +++ b/homeassistant/components/rollershutter/demo.py @@ -18,9 +18,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoRollershutter(RollershutterDevice): - """Represents a roller shutter.""" + """Representation of a demo roller shutter.""" + # pylint: disable=no-self-use def __init__(self, hass, name, position): + """Initialize the roller shutter.""" self.hass = hass self._name = name self._position = position @@ -29,7 +31,7 @@ class DemoRollershutter(RollershutterDevice): @property def name(self): - """Returns the name of the roller shutter.""" + """Return the name of the roller shutter.""" return self._name @property @@ -59,7 +61,7 @@ class DemoRollershutter(RollershutterDevice): self._moving_up = False def stop(self, **kwargs): - """Stops the roller shutter.""" + """Stop the roller shutter.""" if self._listener is not None: self.hass.bus.remove_listener(EVENT_TIME_CHANGED, self._listener) self._listener = None diff --git a/homeassistant/components/rollershutter/mqtt.py b/homeassistant/components/rollershutter/mqtt.py index d5d5a7acb97..45ca9f6d631 100644 --- a/homeassistant/components/rollershutter/mqtt.py +++ b/homeassistant/components/rollershutter/mqtt.py @@ -24,7 +24,6 @@ DEFAULT_PAYLOAD_STOP = "STOP" def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Add MQTT Rollershutter.""" - if config.get('command_topic') is None: _LOGGER.error("Missing required variable: command_topic") return False @@ -43,9 +42,11 @@ 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.""" + """Representation of a roller shutter that can be controlled using MQTT.""" + def __init__(self, hass, name, state_topic, command_topic, qos, payload_up, payload_down, payload_stop, value_template): + """Initialize the roller shutter.""" self._state = None self._hass = hass self._name = name @@ -80,24 +81,24 @@ class MqttRollershutter(RollershutterDevice): @property def name(self): - """The name of the rollershutter.""" + """Return the name of the roller shutter.""" return self._name @property def current_position(self): - """ - Return current position of rollershutter. + """Return current position of roller shutter. + None is unknown, 0 is closed, 100 is fully open. """ return self._state def move_up(self, **kwargs): - """Move the rollershutter up.""" + """Move the roller shutter up.""" mqtt.publish(self.hass, self._command_topic, self._payload_up, self._qos) def move_down(self, **kwargs): - """Move the rollershutter down.""" + """Move the roller shutter down.""" mqtt.publish(self.hass, self._command_topic, self._payload_down, self._qos) diff --git a/homeassistant/components/rollershutter/scsgate.py b/homeassistant/components/rollershutter/scsgate.py index 55261a2a617..078173e1924 100644 --- a/homeassistant/components/rollershutter/scsgate.py +++ b/homeassistant/components/rollershutter/scsgate.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rollershutter.scsgate -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to configure a SCSGate rollershutter. +Allow to configure a SCSGate roller shutter. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rollershutter.scsgate/ @@ -15,8 +13,7 @@ DEPENDENCIES = ['scsgate'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Add the SCSGate swiches defined inside of the configuration file. """ - + """Setup the SCSGate roller shutter.""" devices = config.get('devices') rollershutters = [] logger = logging.getLogger(__name__) @@ -42,57 +39,59 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class SCSGateRollerShutter(RollershutterDevice): - """ Represents a rollershutter that can be controlled using SCSGate. """ + """Representation of SCSGate rollershutter.""" + def __init__(self, scs_id, name, logger): + """Initialize the roller shutter.""" self._scs_id = scs_id self._name = name self._logger = logger @property def scs_id(self): - """ SCSGate ID """ + """Return the SCSGate ID.""" return self._scs_id @property def should_poll(self): - """ No polling needed """ + """No polling needed.""" return False @property def name(self): - """ The name of the rollershutter. """ + """Return the name of the roller shutter.""" return self._name @property def current_position(self): - """ - Return current position of rollershutter. + """Return current position of roller shutter. + None is unknown, 0 is closed, 100 is fully open. """ return None def move_up(self, **kwargs): - """ Move the rollershutter up. """ + """Move the roller shutter up.""" from scsgate.tasks import RaiseRollerShutterTask scsgate.SCSGATE.append_task( RaiseRollerShutterTask(target=self._scs_id)) def move_down(self, **kwargs): - """ Move the rollershutter down. """ + """Move the rollers hutter down.""" from scsgate.tasks import LowerRollerShutterTask scsgate.SCSGATE.append_task( LowerRollerShutterTask(target=self._scs_id)) def stop(self, **kwargs): - """ Stop the device. """ + """Stop the device.""" from scsgate.tasks import HaltRollerShutterTask scsgate.SCSGATE.append_task(HaltRollerShutterTask(target=self._scs_id)) def process_event(self, message): - """ Handle a SCSGate message related with this rollershutter """ + """Handle a SCSGate message related with this roller shutter.""" self._logger.debug( "Rollershutter %s, got message %s", self._scs_id, message.toggled) diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py index 899d51c4cde..1b302eb1839 100644 --- a/homeassistant/components/rpi_gpio.py +++ b/homeassistant/components/rpi_gpio.py @@ -1,7 +1,5 @@ """ -homeassistant.components.rpi_gpio -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows to control the GPIO pins of a Raspberry Pi. +Support for controlling GPIO pins of a Raspberry Pi. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/rpi_gpio/ @@ -19,15 +17,15 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=no-member def setup(hass, config): - """ Sets up the Raspberry PI GPIO component. """ + """Setup the Raspberry PI GPIO component.""" import RPi.GPIO as GPIO def cleanup_gpio(event): - """ Stuff to do before stop home assistant. """ + """Stuff to do before stopping.""" GPIO.cleanup() def prepare_gpio(event): - """ Stuff to do when home assistant starts. """ + """Stuff to do when home assistant starts.""" hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) @@ -36,32 +34,32 @@ def setup(hass, config): def setup_output(port): - """ Setup a GPIO as output. """ + """Setup a GPIO as output.""" import RPi.GPIO as GPIO GPIO.setup(port, GPIO.OUT) def setup_input(port, pull_mode): - """ Setup a GPIO as input. """ + """Setup a GPIO as input.""" import RPi.GPIO as GPIO GPIO.setup(port, GPIO.IN, GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP) def write_output(port, value): - """ Write a value to a GPIO. """ + """Write a value to a GPIO.""" import RPi.GPIO as GPIO GPIO.output(port, value) def read_input(port): - """ Read a value from a GPIO. """ + """Read a value from a GPIO.""" import RPi.GPIO as GPIO return GPIO.input(port) def edge_detect(port, event_callback, bounce): - """ Adds detection for RISING and FALLING events. """ + """Add detection for RISING and FALLING events.""" import RPi.GPIO as GPIO GPIO.add_event_detect( port, diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py deleted file mode 100644 index 494c224c416..00000000000 --- a/homeassistant/components/scene.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -homeassistant.components.scene -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Allows users to set and activate scenes. - -For more details about this component, please refer to the documentation at -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 - -DOMAIN = 'scene' -DEPENDENCIES = ['group'] -STATE = 'scening' - -CONF_ENTITIES = "entities" - -SceneConfig = namedtuple('SceneConfig', ['name', 'states']) - - -def activate(hass, entity_id=None): - """ Activate a scene. """ - data = {} - - if entity_id: - data[ATTR_ENTITY_ID] = entity_id - - hass.services.call(DOMAIN, SERVICE_TURN_ON, data) - - -def setup(hass, config): - """ Sets up scenes. """ - - logger = logging.getLogger(__name__) - - scene_configs = config.get(DOMAIN) - - if not isinstance(scene_configs, list) or \ - any(not isinstance(item, dict) for item in scene_configs): - logger.error('Scene config should be a list of dictionaries') - return False - - component = EntityComponent(logger, DOMAIN, hass) - - component.add_entities(Scene(hass, _process_config(scene_config)) - for scene_config in scene_configs) - - def handle_scene_service(service): - """ Handles calls to the switch services. """ - target_scenes = component.extract_from_service(service) - - for scene in target_scenes: - scene.activate() - - hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service) - - return True - - -def _process_config(scene_config): - """ Process passed in config into a format to work with. """ - name = scene_config.get('name') - - states = {} - c_entities = dict(scene_config.get(CONF_ENTITIES, {})) - - for entity_id in c_entities: - if isinstance(c_entities[entity_id], dict): - entity_attrs = c_entities[entity_id].copy() - state = entity_attrs.pop('state', None) - attributes = entity_attrs - else: - state = c_entities[entity_id] - attributes = {} - - # YAML translates 'on' to a boolean - # http://yaml.org/type/bool.html - if isinstance(state, bool): - state = STATE_ON if state else STATE_OFF - else: - state = str(state) - - states[entity_id.lower()] = State(entity_id, state, attributes) - - return SceneConfig(name, states) - - -class Scene(Entity): - """ A scene is a group of entities and the states we want them to be. """ - - def __init__(self, hass, scene_config): - self.hass = hass - self.scene_config = scene_config - - self.update() - - @property - def should_poll(self): - return False - - @property - def name(self): - return self.scene_config.name - - @property - def state(self): - return STATE - - @property - def entity_ids(self): - """ Entity IDs part of this scene. """ - return self.scene_config.states.keys() - - @property - def state_attributes(self): - """ Scene state attributes. """ - return { - ATTR_ENTITY_ID: list(self.entity_ids), - } - - def activate(self): - """ Activates scene. Tries to get entities into requested state. """ - reproduce_state(self.hass, self.scene_config.states.values(), True) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py new file mode 100644 index 00000000000..03bb7c265c3 --- /dev/null +++ b/homeassistant/components/scene/__init__.py @@ -0,0 +1,84 @@ +""" +Allow users to set and activate scenes. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/scene/ +""" +import logging +from collections import namedtuple + +from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_ON, CONF_PLATFORM) +from homeassistant.helpers import extract_domain_configs +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent + +DOMAIN = 'scene' +DEPENDENCIES = ['group'] +STATE = 'scening' + +CONF_ENTITIES = "entities" + +SceneConfig = namedtuple('SceneConfig', ['name', 'states']) + + +def activate(hass, entity_id=None): + """Activate a scene.""" + data = {} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_TURN_ON, data) + + +def setup(hass, config): + """Setup scenes.""" + logger = logging.getLogger(__name__) + + # You are not allowed to mutate the original config so make a copy + config = dict(config) + + for config_key in extract_domain_configs(config, DOMAIN): + platform_config = config[config_key] + if not isinstance(platform_config, list): + platform_config = [platform_config] + + if not any(CONF_PLATFORM in entry for entry in platform_config): + platform_config = [{'platform': 'homeassistant', 'states': entry} + for entry in platform_config] + + config[config_key] = platform_config + + component = EntityComponent(logger, DOMAIN, hass) + + component.setup(config) + + def handle_scene_service(service): + """Handle calls to the switch services.""" + target_scenes = component.extract_from_service(service) + + for scene in target_scenes: + scene.activate() + + hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service) + + return True + + +class Scene(Entity): + """A scene is a group of entities and the states we want them to be.""" + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def state(self): + """Return the state of the scene.""" + return STATE + + def activate(self): + """Activate scene. Try to get entities into requested state.""" + raise NotImplementedError diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py new file mode 100644 index 00000000000..e507c664bef --- /dev/null +++ b/homeassistant/components/scene/homeassistant.py @@ -0,0 +1,86 @@ +""" +Allow users to set and activate scenes. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/scene/ +""" +from collections import namedtuple + +from homeassistant.components.scene import Scene +from homeassistant.const import ( + ATTR_ENTITY_ID, STATE_OFF, STATE_ON) +from homeassistant.core import State +from homeassistant.helpers.state import reproduce_state + +DEPENDENCIES = ['group'] +STATE = 'scening' + +CONF_ENTITIES = "entities" + +SceneConfig = namedtuple('SceneConfig', ['name', 'states']) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup home assistant scene entries.""" + scene_config = config.get("states") + + if not isinstance(scene_config, list): + scene_config = [scene_config] + + add_devices(HomeAssistantScene(hass, _process_config(scene)) + for scene in scene_config) + + return True + + +def _process_config(scene_config): + """Process passed in config into a format to work with.""" + name = scene_config.get('name') + + states = {} + c_entities = dict(scene_config.get(CONF_ENTITIES, {})) + + for entity_id in c_entities: + if isinstance(c_entities[entity_id], dict): + entity_attrs = c_entities[entity_id].copy() + state = entity_attrs.pop('state', None) + attributes = entity_attrs + else: + state = c_entities[entity_id] + attributes = {} + + # YAML translates 'on' to a boolean + # http://yaml.org/type/bool.html + if isinstance(state, bool): + state = STATE_ON if state else STATE_OFF + else: + state = str(state) + + states[entity_id.lower()] = State(entity_id, state, attributes) + + return SceneConfig(name, states) + + +class HomeAssistantScene(Scene): + """A scene is a group of entities and the states we want them to be.""" + + def __init__(self, hass, scene_config): + """Initialize the scene.""" + self.hass = hass + self.scene_config = scene_config + + @property + def name(self): + """Return the name of the scene.""" + return self.scene_config.name + + @property + def device_state_attributes(self): + """Return the scene state attributes.""" + return { + ATTR_ENTITY_ID: list(self.scene_config.states.keys()), + } + + def activate(self): + """Activate scene. Try to get entities into requested state.""" + reproduce_state(self.hass, self.scene_config.states.values(), True) diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py new file mode 100644 index 00000000000..0557dec5cc2 --- /dev/null +++ b/homeassistant/components/scene/hunterdouglas_powerview.py @@ -0,0 +1,69 @@ +""" +Support for Powerview scenes from a Powerview hub. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/scene/ +""" +import logging + +from homeassistant.components.scene import Scene + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = [ + 'https://github.com/sander76/powerviewApi/' + 'archive/master.zip#powerviewApi==0.2'] + +HUB_ADDRESS = 'address' + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the powerview scenes stored in a Powerview hub.""" + import powerview + + hub_address = config.get(HUB_ADDRESS) + + _pv = powerview.PowerView(hub_address) + try: + _scenes = _pv.get_scenes() + _rooms = _pv.get_rooms() + except ConnectionError: + _LOGGER.exception("error connecting to powerview " + "hub with ip address: %s", hub_address) + return False + add_devices(PowerViewScene(hass, scene, _rooms, _pv) + for scene in _scenes['sceneData']) + + return True + + +class PowerViewScene(Scene): + """Representation of a Powerview scene.""" + + def __init__(self, hass, scene_data, room_data, pv_instance): + """Initialize the scene.""" + self.pv_instance = pv_instance + self.hass = hass + self.scene_data = scene_data + self._sync_room_data(room_data) + + def _sync_room_data(self, room_data): + """Sync the room data.""" + room = next((room for room in room_data["roomData"] + if room["id"] == self.scene_data["roomId"]), None) + if room is not None: + self.scene_data["roomName"] = room["name"] + + @property + def name(self): + """Return the name of the scene.""" + return self.scene_data["name"] + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return {"roomName": self.scene_data["roomName"]} + + def activate(self): + """Activate the scene. Tries to get entities into requested state.""" + self.pv_instance.activate_scene(self.scene_data["id"]) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 1ad07a9a5bd..071921426bb 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -1,6 +1,6 @@ """ -homeassistant.components.script -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for scripts. + Scripts are a sequence of actions that can be triggered manually by the user or automatically based upon automation events, etc. @@ -45,34 +45,33 @@ _LOGGER = logging.getLogger(__name__) def is_on(hass, entity_id): - """ Returns if the switch is on based on the statemachine. """ + """Return if the switch is on based on the statemachine.""" return hass.states.is_state(entity_id, STATE_ON) def turn_on(hass, entity_id): - """ Turn script on. """ + """Turn script on.""" _, object_id = split_entity_id(entity_id) hass.services.call(DOMAIN, object_id) def turn_off(hass, entity_id): - """ Turn script on. """ + """Turn script on.""" hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}) def toggle(hass, entity_id): - """ Toggles script. """ + """Toggle the script.""" hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id}) def setup(hass, config): - """ Load the scripts from the configuration. """ - + """Load the scripts from the configuration.""" component = EntityComponent(_LOGGER, DOMAIN, hass) def service_handler(service): - """ Execute a service call to script.