diff --git a/.coveragerc b/.coveragerc index c8e59e55357..9656508982a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -77,6 +77,9 @@ omit = homeassistant/components/octoprint.py homeassistant/components/*/octoprint.py + homeassistant/components/opencv.py + homeassistant/components/*/opencv.py + homeassistant/components/qwikswitch.py homeassistant/components/*/qwikswitch.py @@ -154,6 +157,13 @@ omit = homeassistant/components/tado.py homeassistant/components/*/tado.py + homeassistant/components/zha/__init__.py + homeassistant/components/zha/const.py + homeassistant/components/*/zha.py + + homeassistant/components/eight_sleep.py + homeassistant/components/*/eight_sleep.py + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/concord232.py homeassistant/components/alarm_control_panel/nx584.py @@ -165,6 +175,7 @@ omit = homeassistant/components/binary_sensor/flic.py homeassistant/components/binary_sensor/hikvision.py homeassistant/components/binary_sensor/iss.py + homeassistant/components/binary_sensor/pilight.py homeassistant/components/binary_sensor/ping.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py @@ -182,15 +193,18 @@ omit = homeassistant/components/climate/oem.py homeassistant/components/climate/proliphix.py homeassistant/components/climate/radiotherm.py + homeassistant/components/climate/sensibo.py homeassistant/components/cover/garadget.py homeassistant/components/cover/homematic.py homeassistant/components/cover/myq.py + homeassistant/components/cover/opengarage.py homeassistant/components/cover/rpi_gpio.py homeassistant/components/cover/scsgate.py homeassistant/components/cover/wink.py homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py + homeassistant/components/device_tracker/automatic.py homeassistant/components/device_tracker/bbox.py homeassistant/components/device_tracker/bluetooth_le_tracker.py homeassistant/components/device_tracker/bluetooth_tracker.py @@ -201,6 +215,7 @@ omit = homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/linksys_ap.py homeassistant/components/device_tracker/luci.py + homeassistant/components/device_tracker/mikrotik.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/ping.py @@ -222,10 +237,13 @@ omit = homeassistant/components/foursquare.py homeassistant/components/hdmi_cec.py homeassistant/components/ifttt.py + homeassistant/components/image_processing/dlib_face_detect.py + homeassistant/components/image_processing/dlib_face_identify.py homeassistant/components/joaoapps_join.py homeassistant/components/keyboard.py homeassistant/components/keyboard_remote.py homeassistant/components/light/avion.py + homeassistant/components/light/blinkt.py homeassistant/components/light/blinksticklight.py homeassistant/components/light/decora.py homeassistant/components/light/flux_led.py @@ -238,6 +256,7 @@ omit = homeassistant/components/light/osramlightify.py homeassistant/components/light/rpi_gpio_pwm.py homeassistant/components/light/piglow.py + homeassistant/components/light/sensehat.py homeassistant/components/light/tikteck.py homeassistant/components/light/tradfri.py homeassistant/components/light/x10.py @@ -335,6 +354,7 @@ omit = homeassistant/components/sensor/broadlink.py homeassistant/components/sensor/dublin_bus_transport.py homeassistant/components/sensor/coinmarketcap.py + homeassistant/components/sensor/cert_expiry.py homeassistant/components/sensor/comed_hourly_pricing.py homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/crimereports.py @@ -350,6 +370,7 @@ omit = homeassistant/components/sensor/eddystone_temperature.py homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/emoncms.py + homeassistant/components/sensor/envirophat.py homeassistant/components/sensor/fastdotcom.py homeassistant/components/sensor/fedex.py homeassistant/components/sensor/fido.py @@ -391,6 +412,7 @@ omit = homeassistant/components/sensor/pi_hole.py homeassistant/components/sensor/plex.py homeassistant/components/sensor/pocketcasts.py + homeassistant/components/sensor/pushbullet.py homeassistant/components/sensor/pvoutput.py homeassistant/components/sensor/qnap.py homeassistant/components/sensor/sabnzbd.py diff --git a/Dockerfile b/Dockerfile index 579229d154a..f0ceb982982 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,4 +27,4 @@ RUN pip3 install --no-cache-dir -r requirements_all.txt && \ # Copy source COPY . . -CMD [ "python", "-m", "homeassistant", "--config", "/config" ] \ No newline at end of file +CMD [ "python", "-m", "homeassistant", "--config", "/config" ] diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 7035b26f670..0b07e5aa6f6 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,4 +1,4 @@ -"""Starts home assistant.""" +"""Start Home Assistant.""" from __future__ import print_function import argparse @@ -277,7 +277,7 @@ def cmdline() -> List[str]: def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> Optional[int]: - """Setup HASS and run.""" + """Set up HASS and run.""" from homeassistant import bootstrap # Run a simple daemon runner process on Windows to handle restarts diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 7f8c586fd80..eeda1db51fc 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,4 +1,4 @@ -"""Provides methods to bootstrap a home assistant instance.""" +"""Provide methods to bootstrap a Home Assistant instance.""" import asyncio import logging import logging.handlers @@ -206,7 +206,7 @@ def async_from_config_file(config_path: str, @core.callback def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False, log_rotate_days=None) -> None: - """Setup the logging. + """Set up the logging. This method must be run in the event loop. """ @@ -214,12 +214,12 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False, fmt = ("%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s") colorfmt = "%(log_color)s{}%(reset)s".format(fmt) - datefmt = '%y-%m-%d %H:%M:%S' + datefmt = '%Y-%m-%d %H:%M:%S' - # suppress overly verbose logs from libraries that aren't helpful - logging.getLogger("requests").setLevel(logging.WARNING) - logging.getLogger("urllib3").setLevel(logging.WARNING) - logging.getLogger("aiohttp.access").setLevel(logging.WARNING) + # Suppress overly verbose logs from libraries that aren't helpful + logging.getLogger('requests').setLevel(logging.WARNING) + logging.getLogger('urllib3').setLevel(logging.WARNING) + logging.getLogger('aiohttp.access').setLevel(logging.WARNING) try: from colorlog import ColoredFormatter @@ -274,7 +274,7 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False, else: _LOGGER.error( - 'Unable to setup error log %s (access denied)', err_log_path) + "Unable to setup error log %s (access denied)", err_log_path) def mount_local_lib_path(config_dir: str) -> str: diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 2bb7af23662..c372004e310 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -102,10 +102,10 @@ def reload_core_config(hass): @asyncio.coroutine def async_setup(hass, config): - """Setup general services related to Home Assistant.""" + """Set up general services related to Home Assistant.""" @asyncio.coroutine def async_handle_turn_service(service): - """Method to handle calls to homeassistant.turn_on/off.""" + """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/alarmdecoder.py b/homeassistant/components/alarm_control_panel/alarmdecoder.py index f176a87827b..f54774b8923 100644 --- a/homeassistant/components/alarm_control_panel/alarmdecoder.py +++ b/homeassistant/components/alarm_control_panel/alarmdecoder.py @@ -25,7 +25,7 @@ DEPENDENCIES = ['alarmdecoder'] @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Perform the setup for AlarmDecoder alarm panels.""" + """Set up for AlarmDecoder alarm panels.""" _LOGGER.debug("AlarmDecoderAlarmPanel: setup") device = AlarmDecoderAlarmPanel("Alarm Panel", hass) @@ -44,7 +44,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): self._name = name self._state = STATE_UNKNOWN - _LOGGER.debug("AlarmDecoderAlarm: Setting up panel") + _LOGGER.debug("Setting up panel") @asyncio.coroutine def async_added_to_hass(self): @@ -78,12 +78,12 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property def code_format(self): - """Regex for code format or None if no code is required.""" + """Return the regex for code format or None if no code is required.""" return '^\\d{4,6}$' @property @@ -94,26 +94,23 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): @asyncio.coroutine def async_alarm_disarm(self, code=None): """Send disarm command.""" - _LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: %s", code) + _LOGGER.debug("alarm_disarm: %s", code) if code: - _LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: sending %s1", - str(code)) + _LOGGER.debug("alarm_disarm: sending %s1", str(code)) self.hass.data[DATA_AD].send("{!s}1".format(code)) @asyncio.coroutine def async_alarm_arm_away(self, code=None): """Send arm away command.""" - _LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: %s", code) + _LOGGER.debug("alarm_arm_away: %s", code) if code: - _LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: sending %s2", - str(code)) + _LOGGER.debug("alarm_arm_away: sending %s2", str(code)) self.hass.data[DATA_AD].send("{!s}2".format(code)) @asyncio.coroutine def async_alarm_arm_home(self, code=None): """Send arm home command.""" - _LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: %s", code) + _LOGGER.debug("alarm_arm_home: %s", code) if code: - _LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: sending %s3", - str(code)) + _LOGGER.debug("alarm_arm_home: sending %s3", str(code)) self.hass.data[DATA_AD].send("{!s}3".format(code)) diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index 6e99ba67257..75d3bc9922d 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -1,5 +1,4 @@ """ - Interfaces with Alarm.com alarm control panels. For more details about this platform, please refer to the documentation at @@ -33,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a Alarm.com control panel.""" + """Set up a Alarm.com control panel.""" name = config.get(CONF_NAME) code = config.get(CONF_CODE) username = config.get(CONF_USERNAME) diff --git a/homeassistant/components/alarm_control_panel/concord232.py b/homeassistant/components/alarm_control_panel/concord232.py index 18a492d6c12..167b7909fe6 100755 --- a/homeassistant/components/alarm_control_panel/concord232.py +++ b/homeassistant/components/alarm_control_panel/concord232.py @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class Concord232Alarm(alarm.AlarmControlPanel): - """Represents the Concord232-based alarm panel.""" + """Representation of the Concord232-based alarm panel.""" def __init__(self, hass, url, name): """Initialize the Concord232 alarm panel.""" @@ -79,7 +79,7 @@ class Concord232Alarm(alarm.AlarmControlPanel): @property def code_format(self): - """The characters if code is defined.""" + """Return the characters if code is defined.""" return '[0-9]{4}([0-9]{2})?' @property diff --git a/homeassistant/components/alarm_control_panel/demo.py b/homeassistant/components/alarm_control_panel/demo.py index ccbe3e72e3c..8ebf0a93c38 100644 --- a/homeassistant/components/alarm_control_panel/demo.py +++ b/homeassistant/components/alarm_control_panel/demo.py @@ -8,7 +8,7 @@ import homeassistant.components.alarm_control_panel.manual as manual def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo alarm control panel platform.""" + """Set up the Demo alarm control panel platform.""" add_devices([ manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10, False), ]) diff --git a/homeassistant/components/alarm_control_panel/envisalink.py b/homeassistant/components/alarm_control_panel/envisalink.py index 25f9257f393..34919a9db79 100644 --- a/homeassistant/components/alarm_control_panel/envisalink.py +++ b/homeassistant/components/alarm_control_panel/envisalink.py @@ -104,7 +104,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): @callback def _update_callback(self, partition): - """Update HA state, if needed.""" + """Update Home Assistant state, if needed.""" if partition is None or int(partition) == self._partition_number: self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index cc67795d713..ba932a1c372 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -39,7 +39,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the manual alarm platform.""" + """Set up the manual alarm platform.""" add_devices([ManualAlarm( hass, config[CONF_NAME], @@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ManualAlarm(alarm.AlarmControlPanel): """ - Represents an alarm status. + Representation of an alarm status. When armed, will be pending for 'pending_time', after that armed. When triggered, will be pending for 'trigger_time'. After that will be @@ -62,7 +62,7 @@ class ManualAlarm(alarm.AlarmControlPanel): def __init__(self, hass, name, code, pending_time, trigger_time, disarm_after_trigger): - """Initalize the manual alarm panel.""" + """Init the manual alarm panel.""" self._state = STATE_ALARM_DISARMED self._hass = hass self._name = name @@ -75,7 +75,7 @@ class ManualAlarm(alarm.AlarmControlPanel): @property def should_poll(self): - """No polling needed.""" + """Return the plling state.""" return False @property @@ -166,5 +166,5 @@ class ManualAlarm(alarm.AlarmControlPanel): """Validate given code.""" check = self._code is None or code == self._code if not check: - _LOGGER.warning('Invalid code given for %s', state) + _LOGGER.warning("Invalid code given for %s", state) return check diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index b22f50b6575..33bfe464eea 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the MQTT platform.""" + """Set up the MQTT Alarm Control Panel platform.""" async_add_devices([MqttAlarm( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), @@ -62,7 +62,7 @@ class MqttAlarm(alarm.AlarmControlPanel): def __init__(self, name, state_topic, command_topic, qos, payload_disarm, payload_arm_home, payload_arm_away, code): - """Initalize the MQTT alarm panel.""" + """Init the MQTT Alarm Control Panel.""" self._state = STATE_UNKNOWN self._name = name self._state_topic = state_topic @@ -80,11 +80,11 @@ class MqttAlarm(alarm.AlarmControlPanel): """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Run when 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): - _LOGGER.warning('Received unexpected payload: %s', payload) + _LOGGER.warning("Received unexpected payload: %s", payload) return self._state = payload self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index b7b3beec72d..81a8b02cc64 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup nx584 platform.""" + """Set up the nx584 platform.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -42,15 +42,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: add_devices([NX584Alarm(hass, url, name)]) except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to NX584: %s', str(ex)) + _LOGGER.error("Unable to connect to NX584: %s", str(ex)) return False class NX584Alarm(alarm.AlarmControlPanel): - """Represents the NX584-based alarm panel.""" + """Representation of a NX584-based alarm panel.""" def __init__(self, hass, url, name): - """Initalize the nx584 alarm panel.""" + """Init the nx584 alarm panel.""" from nx584 import client self._hass = hass self._name = name @@ -69,7 +69,7 @@ class NX584Alarm(alarm.AlarmControlPanel): @property def code_format(self): - """The characters if code is defined.""" + """Return che characters if code is defined.""" return '[0-9]{4}([0-9]{2})?' @property @@ -83,20 +83,19 @@ class NX584Alarm(alarm.AlarmControlPanel): part = self._alarm.list_partitions()[0] zones = self._alarm.list_zones() except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to %(host)s: %(reason)s', + _LOGGER.error("Unable to connect to %(host)s: %(reason)s", dict(host=self._url, reason=ex)) self._state = STATE_UNKNOWN zones = [] except IndexError: - _LOGGER.error('nx584 reports no partitions') + _LOGGER.error("nx584 reports no partitions") self._state = STATE_UNKNOWN zones = [] bypassed = False for zone in zones: if zone['bypassed']: - _LOGGER.debug('Zone %(zone)s is bypassed, ' - 'assuming HOME', + _LOGGER.debug("Zone %(zone)s is bypassed, assuming HOME", dict(zone=zone['number'])) bypassed = True break diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index 6d8e6be1f5c..985d219865d 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -123,25 +123,25 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): if not self._validate_code(code, 'disarming'): return self.simplisafe.set_state('off') - _LOGGER.info('SimpliSafe alarm disarming') + _LOGGER.info("SimpliSafe alarm disarming") def alarm_arm_home(self, code=None): """Send arm home command.""" if not self._validate_code(code, 'arming home'): return self.simplisafe.set_state('home') - _LOGGER.info('SimpliSafe alarm arming home') + _LOGGER.info("SimpliSafe alarm arming home") def alarm_arm_away(self, code=None): """Send arm away command.""" if not self._validate_code(code, 'arming away'): return self.simplisafe.set_state('away') - _LOGGER.info('SimpliSafe alarm arming away') + _LOGGER.info("SimpliSafe alarm arming away") def _validate_code(self, code, state): """Validate given code.""" check = self._code is None or code == self._code if not check: - _LOGGER.warning('Wrong code entered for %s', state) + _LOGGER.warning("Wrong code entered for %s", state) return check diff --git a/homeassistant/components/alarm_control_panel/totalconnect.py b/homeassistant/components/alarm_control_panel/totalconnect.py index a69e260c053..13925d7bd02 100644 --- a/homeassistant/components/alarm_control_panel/totalconnect.py +++ b/homeassistant/components/alarm_control_panel/totalconnect.py @@ -1,15 +1,20 @@ -"""Interfaces with TotalConnect alarm control panels.""" +""" +Interfaces with TotalConnect alarm control panels. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/alarm_control_panel.totalconnect/ +""" import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_NAME) -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['total_connect_client==0.7'] @@ -25,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup a TotalConnect control panel.""" + """Set up a TotalConnect control panel.""" name = config.get(CONF_NAME) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -41,13 +46,13 @@ class TotalConnect(alarm.AlarmControlPanel): """Initialize the TotalConnect status.""" from total_connect_client import TotalConnectClient - _LOGGER.debug('Setting up TotalConnect...') + _LOGGER.debug("Setting up TotalConnect...") self._name = name self._username = username self._password = password self._state = STATE_UNKNOWN - self._client = TotalConnectClient.TotalConnectClient(username, - password) + self._client = TotalConnectClient.TotalConnectClient( + username, password) @property def name(self): diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index c1a394fe462..32f28865a41 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Verisure platform.""" + """Set up the Verisure platform.""" alarms = [] if int(hub.config.get(CONF_ALARM, 1)): hub.update_alarms() @@ -29,10 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureAlarm(alarm.AlarmControlPanel): - """Represent a Verisure alarm status.""" + """Representation of a Verisure alarm status.""" def __init__(self, device_id): - """Initalize the Verisure alarm panel.""" + """Initialize the Verisure alarm panel.""" self._id = device_id self._state = STATE_UNKNOWN self._digits = hub.config.get(CONF_CODE_DIGITS) @@ -55,12 +55,12 @@ class VerisureAlarm(alarm.AlarmControlPanel): @property def code_format(self): - """The code format as regex.""" + """Return the code format as regex.""" return '^\\d{%s}$' % self._digits @property def changed_by(self): - """Last change triggered by.""" + """Return the last change triggered by.""" return self._changed_by def update(self): @@ -75,24 +75,23 @@ class VerisureAlarm(alarm.AlarmControlPanel): self._state = STATE_ALARM_ARMED_AWAY elif hub.alarm_status[self._id].status != 'pending': _LOGGER.error( - 'Unknown alarm state %s', - hub.alarm_status[self._id].status) + "Unknown alarm state %s", hub.alarm_status[self._id].status) self._changed_by = hub.alarm_status[self._id].name def alarm_disarm(self, code=None): """Send disarm command.""" hub.my_pages.alarm.set(code, 'DISARMED') - _LOGGER.info('verisure alarm disarming') + _LOGGER.info("Verisure alarm disarming") hub.my_pages.alarm.wait_while_pending() def alarm_arm_home(self, code=None): """Send arm home command.""" hub.my_pages.alarm.set(code, 'ARMED_HOME') - _LOGGER.info('verisure alarm arming home') + _LOGGER.info("Verisure alarm arming home") hub.my_pages.alarm.wait_while_pending() def alarm_arm_away(self, code=None): """Send arm away command.""" hub.my_pages.alarm.set(code, 'ARMED_AWAY') - _LOGGER.info('verisure alarm arming away') + _LOGGER.info("Verisure alarm arming away") hub.my_pages.alarm.wait_while_pending() diff --git a/homeassistant/components/alarm_control_panel/wink.py b/homeassistant/components/alarm_control_panel/wink.py index c489b53c9c0..12dca97dd81 100644 --- a/homeassistant/components/alarm_control_panel/wink.py +++ b/homeassistant/components/alarm_control_panel/wink.py @@ -16,11 +16,12 @@ from homeassistant.components.wink import WinkDevice, DOMAIN _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['wink'] + STATE_ALARM_PRIVACY = 'Private' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink platform.""" + """Set up the Wink platform.""" import pywink for camera in pywink.get_cameras(): diff --git a/homeassistant/components/alarmdecoder.py b/homeassistant/components/alarmdecoder.py index ec99f2381e5..d4948429b81 100644 --- a/homeassistant/components/alarmdecoder.py +++ b/homeassistant/components/alarmdecoder.py @@ -8,11 +8,10 @@ import asyncio import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.config_validation as cv from homeassistant.core import callback from homeassistant.const import EVENT_HOMEASSISTANT_STOP - from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -24,19 +23,16 @@ DOMAIN = 'alarmdecoder' DATA_AD = 'alarmdecoder' - CONF_DEVICE = 'device' -CONF_DEVICE_TYPE = 'type' -CONF_DEVICE_HOST = 'host' -CONF_DEVICE_PORT = 'port' -CONF_DEVICE_PATH = 'path' CONF_DEVICE_BAUD = 'baudrate' - -CONF_ZONES = 'zones' +CONF_DEVICE_HOST = 'host' +CONF_DEVICE_PATH = 'path' +CONF_DEVICE_PORT = 'port' +CONF_DEVICE_TYPE = 'type' +CONF_PANEL_DISPLAY = 'panel_display' CONF_ZONE_NAME = 'name' CONF_ZONE_TYPE = 'type' - -CONF_PANEL_DISPLAY = 'panel_display' +CONF_ZONES = 'zones' DEFAULT_DEVICE_TYPE = 'socket' DEFAULT_DEVICE_HOST = 'localhost' @@ -87,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Common setup for AlarmDecoder devices.""" + """Set up for the AlarmDecoder devices.""" from alarmdecoder import AlarmDecoder from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice) @@ -106,28 +102,28 @@ def async_setup(hass, config): sync_connect = asyncio.Future(loop=hass.loop) def handle_open(device): - """Callback for a successful connection.""" - _LOGGER.info("Established a connection with the alarmdecoder.") + """Handle the successful connection.""" + _LOGGER.info("Established a connection with the alarmdecoder") hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder) sync_connect.set_result(True) @callback def stop_alarmdecoder(event): - """Callback to handle shutdown alarmdecoder.""" - _LOGGER.debug("Shutting down alarmdecoder.") + """Handle the shutdown of AlarmDecoder.""" + _LOGGER.debug("Shutting down alarmdecoder") controller.close() @callback def handle_message(sender, message): - """Callback to handle message from alarmdecoder.""" + """Handle message from AlarmDecoder.""" async_dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message) def zone_fault_callback(sender, zone): - """Callback to handle zone fault from alarmdecoder.""" + """Handle zone fault from AlarmDecoder.""" async_dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone) def zone_restore_callback(sender, zone): - """Callback to handle zone restore from alarmdecoder.""" + """Handle zone restore from AlarmDecoder.""" async_dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone) controller = False @@ -157,15 +153,16 @@ def async_setup(hass, config): if not result: return False - hass.async_add_job(async_load_platform(hass, 'alarm_control_panel', DOMAIN, - conf, config)) + hass.async_add_job( + async_load_platform(hass, 'alarm_control_panel', DOMAIN, conf, + config)) if zones: hass.async_add_job(async_load_platform( hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config)) if display: - hass.async_add_job(async_load_platform(hass, 'sensor', DOMAIN, - conf, config)) + hass.async_add_job(async_load_platform( + hass, 'sensor', DOMAIN, conf, config)) return True diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 5c3d944aad4..5f59f760d0b 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -141,7 +141,7 @@ def reload(hass): @asyncio.coroutine def async_setup(hass, config): - """Setup the automation.""" + """Set up the automation.""" component = EntityComponent(_LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_AUTOMATIONS) @@ -400,7 +400,7 @@ def _async_get_action(hass, config, name): @asyncio.coroutine def action(entity_id, variables): - """Action to be executed.""" + """Execute an action.""" _LOGGER.info('Executing %s', name) logbook.async_log_entry( hass, name, 'has been triggered', DOMAIN, entity_id) @@ -430,7 +430,7 @@ def _async_process_if(hass, config, p_config): @asyncio.coroutine def _async_process_trigger(hass, config, trigger_configs, name, action): - """Setup the triggers. + """Set up the triggers. This method is a coroutine. """ diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 0ff10665eb3..ba8e67e9213 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -13,8 +13,8 @@ from homeassistant.core import callback, CoreState from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START from homeassistant.helpers import config_validation as cv -CONF_EVENT_TYPE = "event_type" -CONF_EVENT_DATA = "event_data" +CONF_EVENT_TYPE = 'event_type' +CONF_EVENT_DATA = 'event_data' _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 0222ef02c26..6b8ee577a09 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -31,7 +31,7 @@ def async_trigger(hass, config, action): if event == EVENT_SHUTDOWN: @callback def hass_shutdown(event): - """Called when Home Assistant is shutting down.""" + """Execute when Home Assistant is shutting down.""" hass.async_run_job(action, { 'trigger': { 'platform': 'homeassistant', diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 1f55ef67f25..576e9e60186 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -14,11 +14,11 @@ from homeassistant.helpers.event import ( async_track_state_change, async_track_point_in_utc_time) import homeassistant.helpers.config_validation as cv -CONF_ENTITY_ID = "entity_id" -CONF_FROM = "from" -CONF_TO = "to" -CONF_STATE = "state" -CONF_FOR = "for" +CONF_ENTITY_ID = 'entity_id' +CONF_FROM = 'from' +CONF_TO = 'to' +CONF_STATE = 'state' +CONF_FOR = 'for' TRIGGER_SCHEMA = vol.All( vol.Schema({ diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index 0adcd5f8272..1ca714026a2 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -14,9 +14,9 @@ from homeassistant.const import CONF_AFTER, CONF_PLATFORM from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change -CONF_HOURS = "hours" -CONF_MINUTES = "minutes" -CONF_SECONDS = "seconds" +CONF_HOURS = 'hours' +CONF_MINUTES = 'minutes' +CONF_SECONDS = 'seconds' _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index c2a0e4d094d..61d846582cb 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -14,8 +14,8 @@ from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers import ( condition, config_validation as cv, location) -EVENT_ENTER = "enter" -EVENT_LEAVE = "leave" +EVENT_ENTER = 'enter' +EVENT_LEAVE = 'leave' DEFAULT_EVENT = EVENT_ENTER TRIGGER_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py index 89692a1e1e1..5d3954b4c87 100644 --- a/homeassistant/components/bbb_gpio.py +++ b/homeassistant/components/bbb_gpio.py @@ -37,14 +37,14 @@ def setup(hass, config): # noqa: F821 def setup_output(pin): - """Setup a GPIO as output.""" + """Set up a GPIO as output.""" # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO GPIO.setup(pin, GPIO.OUT) def setup_input(pin, pull_mode): - """Setup a GPIO as input.""" + """Set up a GPIO as input.""" # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO GPIO.setup(pin, GPIO.IN, # noqa: F821 diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index ab49c13770a..8f2b6bc59b3 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -57,7 +57,7 @@ class BinarySensorDevice(Entity): @property def is_on(self): - """Return True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return None @property diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py index e6292128710..495feaf64ab 100644 --- a/homeassistant/components/binary_sensor/alarmdecoder.py +++ b/homeassistant/components/binary_sensor/alarmdecoder.py @@ -11,7 +11,6 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import (STATE_ON, STATE_OFF, STATE_OPEN, STATE_CLOSED) from homeassistant.components.alarmdecoder import (ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, @@ -27,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup AlarmDecoder binary sensor devices.""" + """Set up the AlarmDecoder binary sensor devices.""" configured_zones = discovery_info[CONF_ZONES] devices = [] @@ -36,10 +35,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): device_config_data = ZONE_SCHEMA(configured_zones[zone_num]) zone_type = device_config_data[CONF_ZONE_TYPE] zone_name = device_config_data[CONF_ZONE_NAME] - device = AlarmDecoderBinarySensor(hass, - zone_num, - zone_name, - zone_type) + device = AlarmDecoderBinarySensor( + hass, zone_num, zone_name, zone_type) devices.append(device) async_add_devices(devices) @@ -58,7 +55,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): self._name = zone_name self._type = zone_type - _LOGGER.debug('AlarmDecoderBinarySensor: Setup up zone: ' + zone_name) + _LOGGER.debug("Setup up zone: %s", self._name) @asyncio.coroutine def async_added_to_hass(self): @@ -69,14 +66,6 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): async_dispatcher_connect( self.hass, SIGNAL_ZONE_RESTORE, self._restore_callback) - @property - def state(self): - """Return the state of the binary sensor.""" - if self._type == 'opening': - return STATE_OPEN if self.is_on else STATE_CLOSED - - return STATE_ON if self.is_on else STATE_OFF - @property def name(self): """Return the name of the entity.""" diff --git a/homeassistant/components/binary_sensor/android_ip_webcam.py b/homeassistant/components/binary_sensor/android_ip_webcam.py index 4b9b4af24af..b1940f432ae 100644 --- a/homeassistant/components/binary_sensor/android_ip_webcam.py +++ b/homeassistant/components/binary_sensor/android_ip_webcam.py @@ -15,7 +15,7 @@ DEPENDENCIES = ['android_ip_webcam'] @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup IP Webcam binary sensors.""" + """Set up the IP Webcam binary sensors.""" if discovery_info is None: return @@ -28,7 +28,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): - """Represents an IP Webcam binary sensor.""" + """Representation of an IP Webcam binary sensor.""" def __init__(self, name, host, ipcam, sensor): """Initialize the binary sensor.""" @@ -47,7 +47,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): @property def is_on(self): - """True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return self._state @asyncio.coroutine diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index 05d0749b9ef..620b7fcc5de 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -21,7 +21,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup an Online Status binary sensor.""" + """Set up an Online Status binary sensor.""" add_entities((OnlineStatus(config, apcupsd.DATA),)) diff --git a/homeassistant/components/binary_sensor/bbb_gpio.py b/homeassistant/components/binary_sensor/bbb_gpio.py index dd960defaa8..785b178969f 100644 --- a/homeassistant/components/binary_sensor/bbb_gpio.py +++ b/homeassistant/components/binary_sensor/bbb_gpio.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Beaglebone Black GPIO devices.""" + """Set up the Beaglebone Black GPIO devices.""" pins = config.get(CONF_PINS) binary_sensors = [] @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BBBGPIOBinarySensor(BinarySensorDevice): - """Represent a binary sensor that uses Beaglebone Black GPIO.""" + """Representation of a binary sensor that uses Beaglebone Black GPIO.""" def __init__(self, pin, params): """Initialize the Beaglebone Black binary sensor.""" diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/binary_sensor/blink.py index 1e95d4d466b..4d8617b3811 100644 --- a/homeassistant/components/binary_sensor/blink.py +++ b/homeassistant/components/binary_sensor/blink.py @@ -24,7 +24,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BlinkCameraMotionSensor(BinarySensorDevice): - """A representation of a Blink binary sensor.""" + """Representation of a Blink binary sensor.""" def __init__(self, name, data): """Initialize the sensor.""" diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py index 845a58ee918..38f362fb1bb 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -18,7 +18,6 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['bloomsky'] -# These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { 'Rain': 'moisture', 'Night': None, @@ -31,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available BloomSky weather binary sensors.""" + """Set up the available BloomSky weather binary sensors.""" bloomsky = get_component('bloomsky') # Default needed in case of discovery sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES) @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class BloomSkySensor(BinarySensorDevice): - """Represent a single binary sensor in a BloomSky device.""" + """Representation of a single binary sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): """Initialize a BloomSky binary sensor.""" @@ -55,7 +54,7 @@ class BloomSkySensor(BinarySensorDevice): @property def name(self): - """The name of the BloomSky device and this sensor.""" + """Return the name of the BloomSky device and this sensor.""" return self._name @property diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index e08ebbe18f8..6ed0e40409a 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Command line Binary Sensor.""" + """Set up the Command line Binary Sensor.""" name = config.get(CONF_NAME) command = config.get(CONF_COMMAND) payload_off = config.get(CONF_PAYLOAD_OFF) @@ -56,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class CommandBinarySensor(BinarySensorDevice): - """Represent a command line binary sensor.""" + """Representation of a command line binary sensor.""" def __init__(self, hass, data, name, device_class, payload_on, payload_off, value_template): diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index 38ec0a74f97..fc8c0b81edf 100755 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -67,8 +67,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if zone['number'] not in exclude: sensors.append( Concord232ZoneSensor( - hass, client, zone, zone_types.get(zone['number'], - get_opening_type(zone))) + hass, client, zone, zone_types.get( + zone['number'], get_opening_type(zone)) + ) ) add_devices(sensors) @@ -77,7 +78,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def get_opening_type(zone): - """Helper function to try to guess sensor type from name.""" + """Return the result of the type guessing from name.""" if 'MOTION' in zone['name']: return 'motion' if 'KEY' in zone['name']: @@ -123,7 +124,7 @@ class Concord232ZoneSensor(BinarySensorDevice): return bool(self._zone['state'] == 'Normal') def update(self): - """"Get updated stats from API.""" + """Get updated stats from API.""" last_update = datetime.datetime.now() - self._client.last_zone_update _LOGGER.debug("Zone: %s ", self._zone) if last_update > datetime.timedelta(seconds=1): diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index 9dbae2fdb49..10077c60ed1 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -8,7 +8,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo binary sensor platform.""" + """Set up the Demo binary sensor platform.""" add_devices([ DemoBinarySensor('Basement Floor Wet', False, 'moisture'), DemoBinarySensor('Movement Backyard', True, 'motion'), @@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoBinarySensor(BinarySensorDevice): - """A Demo binary sensor.""" + """representation of a Demo binary sensor.""" def __init__(self, name, state, device_class): """Initialize the demo sensor.""" diff --git a/homeassistant/components/binary_sensor/ecobee.py b/homeassistant/components/binary_sensor/ecobee.py index b2a5d21386b..d14a1124390 100644 --- a/homeassistant/components/binary_sensor/ecobee.py +++ b/homeassistant/components/binary_sensor/ecobee.py @@ -13,7 +13,7 @@ ECOBEE_CONFIG_FILE = 'ecobee.conf' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Ecobee sensors.""" + """Set up the Ecobee sensors.""" if discovery_info is None: return data = ecobee.NETWORK diff --git a/homeassistant/components/binary_sensor/eight_sleep.py b/homeassistant/components/binary_sensor/eight_sleep.py new file mode 100644 index 00000000000..a6d4476f047 --- /dev/null +++ b/homeassistant/components/binary_sensor/eight_sleep.py @@ -0,0 +1,69 @@ +""" +Support for Eight Sleep binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.eight_sleep/ +""" +import logging +import asyncio + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.eight_sleep import ( + DATA_EIGHT, EightSleepHeatEntity, CONF_BINARY_SENSORS, NAME_MAP) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['eight_sleep'] + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the eight sleep binary sensor.""" + if discovery_info is None: + return + + name = 'Eight' + sensors = discovery_info[CONF_BINARY_SENSORS] + eight = hass.data[DATA_EIGHT] + + all_sensors = [] + + for sensor in sensors: + all_sensors.append(EightHeatSensor(name, eight, sensor)) + + async_add_devices(all_sensors, True) + + +class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice): + """Representation of a Eight Sleep heat-based sensor.""" + + def __init__(self, name, eight, sensor): + """Initialize the sensor.""" + super().__init__(eight) + + self._sensor = sensor + self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) + self._name = '{} {}'.format(name, self._mapped_name) + self._state = None + + self._side = self._sensor.split('_')[0] + self._userid = self._eight.fetch_userid(self._side) + self._usrobj = self._eight.users[self._userid] + + _LOGGER.debug("Presence Sensor: %s, Side: %s, User: %s", + self._sensor, self._side, self._userid) + + @property + def name(self): + """Return the name of the sensor, if any.""" + return self._name + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._state + + @asyncio.coroutine + def async_update(self): + """Retrieve latest state.""" + self._state = self._usrobj.bed_presence diff --git a/homeassistant/components/binary_sensor/enocean.py b/homeassistant/components/binary_sensor/enocean.py index be01f63e657..358abb434fd 100644 --- a/homeassistant/components/binary_sensor/enocean.py +++ b/homeassistant/components/binary_sensor/enocean.py @@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Binary Sensor platform fo EnOcean.""" + """Set up the Binary Sensor platform for EnOcean.""" dev_id = config.get(CONF_ID) devname = config.get(CONF_NAME) device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) @@ -44,7 +44,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): def __init__(self, dev_id, devname, device_class): """Initialize the EnOcean binary sensor.""" enocean.EnOceanDevice.__init__(self) - self.stype = "listener" + self.stype = 'listener' self.dev_id = dev_id self.which = -1 self.onoff = -1 @@ -53,7 +53,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): @property def name(self): - """The default name for the binary sensor.""" + """Return the default name for the binary sensor.""" return self.devname @property @@ -80,7 +80,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): elif value2 == 0x10: self.which = 1 self.onoff = 1 - self.hass.bus.fire('button_pressed', {"id": self.dev_id, + self.hass.bus.fire('button_pressed', {'id': self.dev_id, 'pushed': value, 'which': self.which, 'onoff': self.onoff}) diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/binary_sensor/envisalink.py index 22a3256f9fe..5fbc1eb90a1 100644 --- a/homeassistant/components/binary_sensor/envisalink.py +++ b/homeassistant/components/binary_sensor/envisalink.py @@ -15,13 +15,14 @@ from homeassistant.components.envisalink import ( SIGNAL_ZONE_UPDATE) from homeassistant.const import ATTR_LAST_TRIP_TIME -DEPENDENCIES = ['envisalink'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['envisalink'] + @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup Envisalink binary sensor devices.""" + """Set up the Envisalink binary sensor devices.""" configured_zones = discovery_info['zones'] devices = [] diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/binary_sensor/ffmpeg_motion.py index 418a6342172..1bbf39dd6e0 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_motion.py +++ b/homeassistant/components/binary_sensor/ffmpeg_motion.py @@ -48,23 +48,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Create the binary sensor.""" + """Set up the FFmpeg binary moition sensor.""" manager = hass.data[DATA_FFMPEG] - # check source if not manager.async_run_test(config.get(CONF_INPUT)): return - # generate sensor object entity = FFmpegMotion(hass, manager, config) async_add_devices([entity]) class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice): - """A binary sensor which use ffmpeg for noise detection.""" + """A binary sensor which use FFmpeg for noise detection.""" def __init__(self, config): - """Constructor for binary sensor noise detection.""" + """Init for the binary sensor noise detection.""" super().__init__(config.get(CONF_INITIAL_STATE)) self._state = False @@ -79,7 +77,7 @@ class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice): @property def is_on(self): - """True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return self._state @property @@ -89,10 +87,10 @@ class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice): class FFmpegMotion(FFmpegBinarySensor): - """A binary sensor which use ffmpeg for noise detection.""" + """A binary sensor which use FFmpeg for noise detection.""" def __init__(self, hass, manager, config): - """Initialize ffmpeg motion binary sensor.""" + """Initialize FFmpeg motion binary sensor.""" from haffmpeg import SensorMotion super().__init__(config) @@ -125,4 +123,4 @@ class FFmpegMotion(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return "motion" + return 'motion' diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/binary_sensor/ffmpeg_noise.py index c3400150f74..db7647d9b2c 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_noise.py +++ b/homeassistant/components/binary_sensor/ffmpeg_noise.py @@ -45,23 +45,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Create the binary sensor.""" + """Set up the FFmpeg noise binary sensor.""" manager = hass.data[DATA_FFMPEG] - # check source if not manager.async_run_test(config.get(CONF_INPUT)): return - # generate sensor object entity = FFmpegNoise(hass, manager, config) async_add_devices([entity]) class FFmpegNoise(FFmpegBinarySensor): - """A binary sensor which use ffmpeg for noise detection.""" + """A binary sensor which use FFmpeg for noise detection.""" def __init__(self, hass, manager, config): - """Initialize ffmpeg noise binary sensor.""" + """Initialize FFmpeg noise binary sensor.""" from haffmpeg import SensorNoise super().__init__(config) @@ -77,14 +75,12 @@ class FFmpegNoise(FFmpegBinarySensor): if entity_ids is not None and self.entity_id not in entity_ids: return - # init config self.ffmpeg.set_options( time_duration=self._config.get(CONF_DURATION), time_reset=self._config.get(CONF_RESET), peak=self._config.get(CONF_PEAK), ) - # run yield from self.ffmpeg.open_sensor( input_source=self._config.get(CONF_INPUT), output_dest=self._config.get(CONF_OUTPUT), @@ -94,4 +90,4 @@ class FFmpegNoise(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return "sound" + return 'sound' diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/binary_sensor/flic.py index 2079d6a1ce8..f78ee75ae25 100644 --- a/homeassistant/components/binary_sensor/flic.py +++ b/homeassistant/components/binary_sensor/flic.py @@ -110,7 +110,7 @@ def start_scanning(config, add_entities, client): def setup_button(hass, config, add_entities, client, address): - """Setup single button device.""" + """Set up a single button device.""" timeout = config.get(CONF_TIMEOUT) ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES) button = FlicButton(hass, client, address, timeout, ignored_click_types) diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py index 9a7a1dcf546..61e69e991b3 100644 --- a/homeassistant/components/binary_sensor/hikvision.py +++ b/homeassistant/components/binary_sensor/hikvision.py @@ -67,7 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup Hikvision binary sensor devices.""" + """Set up the Hikvision binary sensor devices.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -77,16 +77,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): customize = config.get(CONF_CUSTOMIZE) if config.get(CONF_SSL): - protocol = "https" + protocol = 'https' else: - protocol = "http" + protocol = 'http' url = '{}://{}'.format(protocol, host) data = HikvisionData(hass, url, port, name, username, password) if data.sensors is None: - _LOGGER.error('Hikvision event stream has no data, unable to setup.') + _LOGGER.error("Hikvision event stream has no data, unable to setup") return False entities = [] @@ -104,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ignore = custom.get(CONF_IGNORED) delay = custom.get(CONF_DELAY) - _LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s', + _LOGGER.debug("Entity: %s - %s, Options - Ignore: %s, Delay: %s", data.name, sensor_name, ignore, delay) if not ignore: entities.append(HikvisionBinarySensor( @@ -126,8 +126,8 @@ class HikvisionData(object): self._password = password # Establish camera - self.camdata = HikCamera(self._url, self._port, - self._username, self._password) + self.camdata = HikCamera( + self._url, self._port, self._username, self._password) if self._name is None: self._name = self.camdata.get_name @@ -251,7 +251,7 @@ class HikvisionBinarySensor(BinarySensorDevice): # Set timer to wait until updating the state def _delay_update(now): """Timer callback for sensor update.""" - _LOGGER.debug('%s Called delayed (%ssec) update.', + _LOGGER.debug("%s Called delayed (%ssec) update", self._name, self._delay) self.schedule_update_ha_state() self._timer = None diff --git a/homeassistant/components/binary_sensor/homematic.py b/homeassistant/components/binary_sensor/homematic.py index 1ae7fde706a..17d08265a5b 100644 --- a/homeassistant/components/binary_sensor/homematic.py +++ b/homeassistant/components/binary_sensor/homematic.py @@ -14,22 +14,22 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] SENSOR_TYPES_CLASS = { - "Remote": None, - "ShutterContact": "opening", - "MaxShutterContact": "opening", - "IPShutterContact": "opening", - "Smoke": "smoke", - "SmokeV2": "smoke", - "Motion": "motion", - "MotionV2": "motion", - "RemoteMotion": None, - "WeatherSensor": None, - "TiltSensor": None, + 'Remote': None, + 'ShutterContact': 'opening', + 'MaxShutterContact': 'opening', + 'IPShutterContact': 'opening', + 'Smoke': 'smoke', + 'SmokeV2': 'smoke', + 'Motion': 'motion', + 'MotionV2': 'motion', + 'RemoteMotion': None, + 'WeatherSensor': None, + 'TiltSensor': None, } def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Homematic binary sensor platform.""" + """Set up the Homematic binary sensor platform.""" if discovery_info is None: return @@ -56,8 +56,8 @@ class HMBinarySensor(HMDevice, BinarySensorDevice): def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" # If state is MOTION (RemoteMotion works only) - if self._state == "MOTION": - return "motion" + if self._state == 'MOTION': + return 'motion' return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None) def _init_data_struct(self): diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py index 03cc7e6bd9b..448ceae8636 100644 --- a/homeassistant/components/binary_sensor/insteon_plm.py +++ b/homeassistant/components/binary_sensor/insteon_plm.py @@ -67,7 +67,7 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice): def is_on(self): """Return the boolean response if the node is on.""" sensorstate = self._plm.get_device_attr(self._address, 'sensorstate') - _LOGGER.info('sensor state for %s is %s', self._address, sensorstate) + _LOGGER.info("Sensor state for %s is %s", self._address, sensorstate) return bool(sensorstate) @property @@ -83,5 +83,5 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice): @callback def async_binarysensor_update(self, message): """Receive notification from transport that new data exists.""" - _LOGGER.info('Received update calback from PLM for %s', self._address) + _LOGGER.info("Received update calback from PLM for %s", self._address) self._hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py index 8f4cb1637b4..fd6269e3630 100644 --- a/homeassistant/components/binary_sensor/isy994.py +++ b/homeassistant/components/binary_sensor/isy994.py @@ -12,7 +12,6 @@ import homeassistant.components.isy994 as isy from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers.typing import ConfigType - _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { @@ -27,9 +26,9 @@ STATES = [STATE_OFF, STATE_ON, 'true', 'false'] # pylint: disable=unused-argument def setup_platform(hass, config: ConfigType, add_devices: Callable[[list], None], discovery_info=None): - """Setup the ISY994 binary sensor platform.""" + """Set up the ISY994 binary sensor platform.""" if isy.ISY is None or not isy.ISY.connected: - _LOGGER.error('A connection has not been made to the ISY controller.') + _LOGGER.error("A connection has not been made to the ISY controller") return False devices = [] diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/binary_sensor/knx.py index 304dad9d71b..87f8a30d78c 100644 --- a/homeassistant/components/binary_sensor/knx.py +++ b/homeassistant/components/binary_sensor/knx.py @@ -11,7 +11,7 @@ DEPENDENCIES = ['knx'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the KNX binary sensor platform.""" + """Set up the KNX binary sensor platform.""" add_devices([KNXSwitch(hass, KNXConfig(config))]) diff --git a/homeassistant/components/binary_sensor/maxcube.py b/homeassistant/components/binary_sensor/maxcube.py index 77448fd6adc..cf2be6baed5 100644 --- a/homeassistant/components/binary_sensor/maxcube.py +++ b/homeassistant/components/binary_sensor/maxcube.py @@ -4,7 +4,6 @@ Support for MAX! Window Shutter via MAX! Cube. For more details about this platform, please refer to the documentation https://home-assistant.io/components/maxcube/ """ - import logging from homeassistant.components.binary_sensor import BinarySensorDevice @@ -15,27 +14,24 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Iterate through all MAX! Devices and add window shutters to HASS.""" + """Iterate through all MAX! Devices and add window shutters.""" cube = hass.data[MAXCUBE_HANDLE].cube - - # List of devices devices = [] for device in cube.devices: - # Create device name by concatenating room name + device name - name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name) + name = "{} {}".format( + cube.room_by_id(device.room_id).name, device.name) # Only add Window Shutters if cube.is_windowshutter(device): - # add device to HASS devices.append(MaxCubeShutter(hass, name, device.rf_address)) - if len(devices) > 0: + if devices: add_devices(devices) class MaxCubeShutter(BinarySensorDevice): - """MAX! Cube BinarySensor device.""" + """Representation of a MAX! Cube Binary Sensor device.""" def __init__(self, hass, name, rf_address): """Initialize MAX! Cube BinarySensorDevice.""" @@ -47,7 +43,7 @@ class MaxCubeShutter(BinarySensorDevice): @property def should_poll(self): - """Polling is required.""" + """Return the polling state.""" return True @property @@ -68,9 +64,5 @@ class MaxCubeShutter(BinarySensorDevice): def update(self): """Get latest data from MAX! Cube.""" self._cubehandle.update() - - # Get the device we want to update device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Update our internal state self._state = device.is_open diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index d43c348f116..54e4cefb230 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -16,9 +16,9 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] -CONF_COIL = "coil" -CONF_COILS = "coils" -CONF_SLAVE = "slave" +CONF_COIL = 'coil' +CONF_COILS = 'coils' +CONF_SLAVE = 'slave' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COILS): [{ @@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Modbus binary sensors.""" + """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): sensors.append(ModbusCoilSensor( diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index d8467a6cbfe..fe19523c5b2 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -79,7 +79,7 @@ class MqttBinarySensor(BinarySensorDevice): """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle a new received MQTT message.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) @@ -95,7 +95,7 @@ class MqttBinarySensor(BinarySensorDevice): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index 3a0b09c0074..767ed858ec7 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -16,7 +16,7 @@ DEPENDENCIES = [] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for sensors.""" + """Set up the MySensors platform for sensors.""" # Only act if loaded via mysensors by discovery event. # Otherwise gateway is not setup. if discovery_info is None: diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index 4689bc59082..4089f3a2eaf 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -16,15 +16,18 @@ DEPENDENCIES = ['nest'] BINARY_TYPES = ['online'] -CLIMATE_BINARY_TYPES = ['fan', - 'is_using_emergency_heat', - 'is_locked', - 'has_leaf'] +CLIMATE_BINARY_TYPES = [ + 'fan', + 'is_using_emergency_heat', + 'is_locked', + 'has_leaf', +] CAMERA_BINARY_TYPES = [ 'motion_detected', 'sound_detected', - 'person_detected'] + 'person_detected', +] _BINARY_TYPES_DEPRECATED = [ 'hvac_ac_state', @@ -34,7 +37,8 @@ _BINARY_TYPES_DEPRECATED = [ 'hvac_heat_x3_state', 'hvac_alt_heat_state', 'hvac_alt_heat_x2_state', - 'hvac_emer_heat_state'] + 'hvac_emer_heat_state', +] _VALID_BINARY_SENSOR_TYPES = BINARY_TYPES + CLIMATE_BINARY_TYPES \ + CAMERA_BINARY_TYPES @@ -43,7 +47,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Nest binary sensors.""" + """Set up the Nest binary sensors.""" if discovery_info is None: return @@ -93,7 +97,7 @@ class NestBinarySensor(NestSensor, BinarySensorDevice): @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): diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index ee7bc53c4cb..70887e9391d 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.netatmo/. """ import logging + import voluptuous as vol from homeassistant.components.binary_sensor import ( @@ -16,10 +17,9 @@ from homeassistant.loader import get_component from homeassistant.const import CONF_TIMEOUT, CONF_OFFSET from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ["netatmo"] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['netatmo'] # These are the available sensors mapped to binary_sensor class WELCOME_SENSOR_TYPES = { @@ -34,8 +34,8 @@ PRESENCE_SENSOR_TYPES = { "Outdoor vehicle": "motion" } TAG_SENSOR_TYPES = { - "Tag Vibration": 'vibration', - "Tag Open": 'opening' + "Tag Vibration": "vibration", + "Tag Open": "opening" } CONF_HOME = 'home' @@ -61,7 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup access to Netatmo binary sensor.""" + """Set up the access to Netatmo binary sensor.""" netatmo = get_component('netatmo') home = config.get(CONF_HOME, None) timeout = config.get(CONF_TIMEOUT, 15) @@ -85,35 +85,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for camera_name in data.get_camera_names(): camera_type = data.get_camera_type(camera=camera_name, home=home) - if camera_type == "NACamera": + if camera_type == 'NACamera': if CONF_CAMERAS in config: if config[CONF_CAMERAS] != [] and \ camera_name not in config[CONF_CAMERAS]: continue for variable in welcome_sensors: - add_devices([NetatmoBinarySensor(data, camera_name, - module_name, home, timeout, - offset, camera_type, - variable)]) - if camera_type == "NOC": + add_devices([NetatmoBinarySensor( + data, camera_name, module_name, home, timeout, + offset, camera_type, variable)]) + if camera_type == 'NOC': if CONF_CAMERAS in config: if config[CONF_CAMERAS] != [] and \ camera_name not in config[CONF_CAMERAS]: continue for variable in presence_sensors: - add_devices([NetatmoBinarySensor(data, camera_name, - module_name, home, timeout, - offset, camera_type, - variable)]) + add_devices([NetatmoBinarySensor( + data, camera_name, module_name, home, timeout, offset, + camera_type, variable)]) for module_name in data.get_module_names(camera_name): for variable in tag_sensors: camera_type = None - add_devices([NetatmoBinarySensor(data, camera_name, - module_name, home, - timeout, offset, - camera_type, - variable)]) + add_devices([NetatmoBinarySensor( + data, camera_name, module_name, home, timeout, offset, + camera_type, variable)]) class NetatmoBinarySensor(BinarySensorDevice): @@ -121,7 +117,7 @@ class NetatmoBinarySensor(BinarySensorDevice): def __init__(self, data, camera_name, module_name, home, timeout, offset, camera_type, sensor): - """Setup for access to the Netatmo camera events.""" + """Set up for access to the Netatmo camera events.""" self._data = data self._camera_name = camera_name self._module_name = module_name @@ -129,23 +125,23 @@ class NetatmoBinarySensor(BinarySensorDevice): self._timeout = timeout self._offset = offset if home: - self._name = home + ' / ' + camera_name + self._name = '{} / {}'.format(home, camera_name) else: self._name = camera_name if module_name: self._name += ' / ' + module_name self._sensor_name = sensor self._name += ' ' + sensor - camera_id = data.camera_data.cameraByName(camera=camera_name, - home=home)['id'] - self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(self._name, - camera_id) + camera_id = data.camera_data.cameraByName( + camera=camera_name, home=home)['id'] + self._unique_id = "Netatmo_binary_sensor {0} - {1}".format( + self._name, camera_id) self._cameratype = camera_type self.update() @property def name(self): - """The name of the Netatmo device and this sensor.""" + """Return the name of the Netatmo device and this sensor.""" return self._name @property @@ -156,9 +152,9 @@ class NetatmoBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - if self._cameratype == "NACamera": + if self._cameratype == 'NACamera': return WELCOME_SENSOR_TYPES.get(self._sensor_name) - elif self._cameratype == "NOC": + elif self._cameratype == 'NOC': return PRESENCE_SENSOR_TYPES.get(self._sensor_name) else: return TAG_SENSOR_TYPES.get(self._sensor_name) @@ -173,51 +169,44 @@ class NetatmoBinarySensor(BinarySensorDevice): self._data.update() self._data.update_event() - if self._cameratype == "NACamera": + if self._cameratype == 'NACamera': if self._sensor_name == "Someone known": self._state =\ - self._data.camera_data.someoneKnownSeen(self._home, - self._camera_name, - self._timeout*60) + self._data.camera_data.someoneKnownSeen( + self._home, self._camera_name, self._timeout*60) elif self._sensor_name == "Someone unknown": self._state =\ self._data.camera_data.someoneUnknownSeen( self._home, self._camera_name, self._timeout*60) elif self._sensor_name == "Motion": self._state =\ - self._data.camera_data.motionDetected(self._home, - self._camera_name, - self._timeout*60) - elif self._cameratype == "NOC": + self._data.camera_data.motionDetected( + self._home, self._camera_name, self._timeout*60) + elif self._cameratype == 'NOC': if self._sensor_name == "Outdoor motion": self._state =\ self._data.camera_data.outdoormotionDetected( self._home, self._camera_name, self._offset) elif self._sensor_name == "Outdoor human": self._state =\ - self._data.camera_data.humanDetected(self._home, - self._camera_name, - self._offset) + self._data.camera_data.humanDetected( + self._home, self._camera_name, self._offset) elif self._sensor_name == "Outdoor animal": self._state =\ - self._data.camera_data.animalDetected(self._home, - self._camera_name, - self._offset) + self._data.camera_data.animalDetected( + self._home, self._camera_name, self._offset) elif self._sensor_name == "Outdoor vehicle": self._state =\ - self._data.camera_data.carDetected(self._home, - self._camera_name, - self._offset) + self._data.camera_data.carDetected( + self._home, self._camera_name, self._offset) if self._sensor_name == "Tag Vibration": self._state =\ - self._data.camera_data.moduleMotionDetected(self._home, - self._module_name, - self._camera_name, - self._timeout*60) + self._data.camera_data.moduleMotionDetected( + self._home, self._module_name, self._camera_name, + self._timeout*60) elif self._sensor_name == "Tag Open": self._state =\ - self._data.camera_data.moduleOpened(self._home, - self._module_name, - self._camera_name) + self._data.camera_data.moduleOpened( + self._home, self._module_name, self._camera_name) else: return None diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index 6ffcb7b0cf3..4dff263f79a 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the NX584 binary sensor platform.""" + """Set up the NX584 binary sensor platform.""" from nx584 import client as nx584_client host = config.get(CONF_HOST) @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): client = nx584_client.Client('http://{}:{}'.format(host, port)) zones = client.list_zones() except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to NX584: %s', str(ex)) + _LOGGER.error("Unable to connect to NX584: %s", str(ex)) return False version = [int(v) for v in client.get_version().split('.')] diff --git a/homeassistant/components/binary_sensor/octoprint.py b/homeassistant/components/binary_sensor/octoprint.py index 3fd92d1ceb1..6e278ccfccf 100644 --- a/homeassistant/components/binary_sensor/octoprint.py +++ b/homeassistant/components/binary_sensor/octoprint.py @@ -9,14 +9,12 @@ import logging import requests import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, STATE_ON, STATE_OFF, CONF_MONITORED_CONDITIONS) +from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['octoprint'] @@ -38,22 +36,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available OctoPrint binary sensors.""" + """Set up the available OctoPrint binary sensors.""" octoprint = get_component('octoprint') name = config.get(CONF_NAME) - monitored_conditions = config.get(CONF_MONITORED_CONDITIONS, - SENSOR_TYPES.keys()) + monitored_conditions = config.get( + CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys()) devices = [] for octo_type in monitored_conditions: - new_sensor = OctoPrintBinarySensor(octoprint.OCTOPRINT, - octo_type, - SENSOR_TYPES[octo_type][2], - name, - SENSOR_TYPES[octo_type][3], - SENSOR_TYPES[octo_type][0], - SENSOR_TYPES[octo_type][1], - 'flags') + new_sensor = OctoPrintBinarySensor( + octoprint.OCTOPRINT, octo_type, SENSOR_TYPES[octo_type][2], + name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0], + SENSOR_TYPES[octo_type][1], 'flags') devices.append(new_sensor) add_devices(devices) @@ -85,18 +79,10 @@ class OctoPrintBinarySensor(BinarySensorDevice): """Return the name of the sensor.""" return self._name - @property - def state(self): - """Return the state of the sensor.""" - return self.is_on - @property def is_on(self): """Return true if binary sensor is on.""" - if self._state: - return STATE_ON - else: - return STATE_OFF + return bool(self._state) @property def device_class(self): @@ -106,10 +92,9 @@ class OctoPrintBinarySensor(BinarySensorDevice): def update(self): """Update state of sensor.""" try: - self._state = self.api.update(self.sensor_type, - self.api_endpoint, - self.api_group, - self.api_tool) + self._state = self.api.update( + self.sensor_type, self.api_endpoint, self.api_group, + self.api_tool) except requests.exceptions.ConnectionError: # Error calling the api, already logged in api.update() return diff --git a/homeassistant/components/binary_sensor/pilight.py b/homeassistant/components/binary_sensor/pilight.py new file mode 100644 index 00000000000..d0774ae12e6 --- /dev/null +++ b/homeassistant/components/binary_sensor/pilight.py @@ -0,0 +1,186 @@ +""" +Support for Pilight binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.pilight/ +""" +import datetime +import logging + +import voluptuous as vol +from homeassistant.components import pilight +from homeassistant.components.binary_sensor import ( + PLATFORM_SCHEMA, + BinarySensorDevice, +) +from homeassistant.const import ( + CONF_DISARM_AFTER_TRIGGER, + CONF_NAME, + CONF_PAYLOAD, + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON +) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import track_point_in_time +from homeassistant.util import dt as dt_util + + +_LOGGER = logging.getLogger(__name__) + +CONF_VARIABLE = 'variable' + +DEFAULT_NAME = 'Pilight Binary Sensor' +DEPENDENCIES = ['pilight'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_VARIABLE): cv.string, + vol.Required(CONF_PAYLOAD): vol.Schema(dict), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string, + vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Pilight Binary Sensor.""" + disarm = config.get(CONF_DISARM_AFTER_TRIGGER) + if disarm: + add_devices([PilightTriggerSensor( + hass=hass, + name=config.get(CONF_NAME), + variable=config.get(CONF_VARIABLE), + payload=config.get(CONF_PAYLOAD), + on_value=config.get(CONF_PAYLOAD_ON), + off_value=config.get(CONF_PAYLOAD_OFF), + )]) + else: + add_devices([PilightBinarySensor( + hass=hass, + name=config.get(CONF_NAME), + variable=config.get(CONF_VARIABLE), + payload=config.get(CONF_PAYLOAD), + on_value=config.get(CONF_PAYLOAD_ON), + off_value=config.get(CONF_PAYLOAD_OFF), + )]) + + +class PilightBinarySensor(BinarySensorDevice): + """Representation of a binary sensor that can be updated using Pilight.""" + + def __init__(self, hass, name, variable, payload, on_value, off_value): + """Initialize the sensor.""" + self._state = False + self._hass = hass + self._name = name + self._variable = variable + self._payload = payload + self._on_value = on_value + self._off_value = off_value + + hass.bus.listen(pilight.EVENT, self._handle_code) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self._state + + def _handle_code(self, call): + """Handle received code by the pilight-daemon. + + If the code matches the defined playload + of this sensor the sensor state is changed accordingly. + """ + # Check if received code matches defined playoad + # True if payload is contained in received code dict + payload_ok = True + for key in self._payload: + if key not in call.data: + payload_ok = False + continue + if self._payload[key] != call.data[key]: + payload_ok = False + # Read out variable if payload ok + if payload_ok: + if self._variable not in call.data: + return + value = call.data[self._variable] + self._state = (value == self._on_value) + self.schedule_update_ha_state() + + +class PilightTriggerSensor(BinarySensorDevice): + """Representation of a binary sensor that can be updated using Pilight.""" + + def __init__( + self, + hass, + name, + variable, + payload, + on_value, + off_value, + rst_dly_sec=30): + """Initialize the sensor.""" + self._state = False + self._hass = hass + self._name = name + self._variable = variable + self._payload = payload + self._on_value = on_value + self._off_value = off_value + self._reset_delay_sec = rst_dly_sec + self._delay_after = None + self._hass = hass + + hass.bus.listen(pilight.EVENT, self._handle_code) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self._state + + def _reset_state(self, call): + self._state = False + self._delay_after = None + self.schedule_update_ha_state() + + def _handle_code(self, call): + """Handle received code by the pilight-daemon. + + If the code matches the defined playload + of this sensor the sensor state is changed accordingly. + """ + # Check if received code matches defined playoad + # True if payload is contained in received code dict + payload_ok = True + for key in self._payload: + if key not in call.data: + payload_ok = False + continue + if self._payload[key] != call.data[key]: + payload_ok = False + # Read out variable if payload ok + if payload_ok: + if self._variable not in call.data: + return + value = call.data[self._variable] + self._state = (value == self._on_value) + if self._delay_after is None: + self._delay_after = dt_util.utcnow() + datetime.timedelta( + seconds=self._reset_delay_sec) + track_point_in_time( + self._hass, self._reset_state, + self._delay_after) + self.schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py index cee5b81f00d..4a23b2e42ca 100644 --- a/homeassistant/components/binary_sensor/ping.py +++ b/homeassistant/components/binary_sensor/ping.py @@ -35,6 +35,9 @@ SCAN_INTERVAL = timedelta(minutes=5) PING_MATCHER = re.compile( r'(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)') +WIN32_PING_MATCHER = re.compile( + r'(?P\d+)ms.+(?P\d+)ms.+(?P\d+)ms') + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -102,7 +105,7 @@ class PingData(object): if sys.platform == 'win32': self._ping_cmd = [ - 'ping', '-n', str(self._count), '-w 1000', self._ip_address] + 'ping', '-n', str(self._count), '-w', '1000', self._ip_address] else: self._ping_cmd = [ 'ping', '-n', '-q', '-c', str(self._count), '-W1', @@ -114,13 +117,23 @@ class PingData(object): self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: out = pinger.communicate() - match = PING_MATCHER.search(str(out).split('\n')[-1]) - rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() - return { - 'min': rtt_min, - 'avg': rtt_avg, - 'max': rtt_max, - 'mdev': rtt_mdev} + _LOGGER.debug("Output is %s", str(out)) + if sys.platform == 'win32': + match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1]) + rtt_min, rtt_avg, rtt_max = match.groups() + return { + 'min': rtt_min, + 'avg': rtt_avg, + 'max': rtt_max, + 'mdev': ''} + else: + match = PING_MATCHER.search(str(out).split('\n')[-1]) + rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() + return { + 'min': rtt_min, + 'avg': rtt_avg, + 'max': rtt_max, + 'mdev': rtt_mdev} except (subprocess.CalledProcessError, AttributeError): return False diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 2e5de7e2f27..abdbc8251c7 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the REST binary sensor.""" + """Set up the REST binary sensor.""" name = config.get(CONF_NAME) resource = config.get(CONF_RESOURCE) method = config.get(CONF_METHOD) @@ -114,8 +114,8 @@ class RestBinarySensor(BinarySensorDevice): try: return bool(int(response)) except ValueError: - return {"true": True, "on": True, "open": True, - "yes": True}.get(response.lower(), False) + return {'true': True, 'on': True, 'open': True, + 'yes': True}.get(response.lower(), False) def update(self): """Get the latest data from REST API and updates the state.""" diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index eaf9ee737e5..2322b1bf498 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -41,10 +41,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """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) + """Set up the Raspberry PI GPIO devices.""" + pull_mode = config.get(CONF_PULL_MODE) + bouncetime = config.get(CONF_BOUNCETIME) + invert_logic = config.get(CONF_INVERT_LOGIC) binary_sensors = [] ports = config.get('ports') diff --git a/homeassistant/components/binary_sensor/sleepiq.py b/homeassistant/components/binary_sensor/sleepiq.py index f55eba82dbe..3a6c27db386 100644 --- a/homeassistant/components/binary_sensor/sleepiq.py +++ b/homeassistant/components/binary_sensor/sleepiq.py @@ -11,7 +11,7 @@ DEPENDENCIES = ['sleepiq'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SleepIQ sensors.""" + """Set up the SleepIQ sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 12a96a5492f..cfaa8057798 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -27,5 +27,5 @@ class TcpBinarySensor(BinarySensorDevice, TcpSensor): @property def is_on(self): - """True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return self._state == self._config[CONF_VALUE_ON] diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 396f591923b..989f8b358af 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup template binary sensors.""" + """Set up template binary sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -57,15 +57,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): sensors.append( BinarySensorTemplate( - hass, - device, - friendly_name, - device_class, - value_template, + hass, device, friendly_name, device_class, value_template, entity_ids) ) if not sensors: - _LOGGER.error('No sensors added') + _LOGGER.error("No sensors added") return False async_add_devices(sensors, True) @@ -79,8 +75,8 @@ class BinarySensorTemplate(BinarySensorDevice): value_template, entity_ids): """Initialize the Template binary sensor.""" self.hass = hass - self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device, - hass=hass) + self.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, device, hass=hass) self._name = friendly_name self._device_class = device_class self._template = value_template @@ -96,7 +92,7 @@ class BinarySensorTemplate(BinarySensorDevice): @callback def template_bsensor_state_listener(entity, old_state, new_state): - """Called when the target device changes state.""" + """Handle the target device state changes.""" self.hass.async_add_job(self.async_update_ha_state(True)) @callback @@ -139,8 +135,8 @@ class BinarySensorTemplate(BinarySensorDevice): if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning - _LOGGER.warning('Could not render template %s,' - ' the state is unknown.', self._name) + _LOGGER.warning("Could not render template %s, " + "the state is unknown", self._name) return - _LOGGER.error('Could not render template %s: %s', self._name, ex) + _LOGGER.error("Could not render template %s: %s", self._name, ex) self._state = False diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py index c97ba17b874..b4891906a01 100644 --- a/homeassistant/components/binary_sensor/threshold.py +++ b/homeassistant/components/binary_sensor/threshold.py @@ -77,7 +77,7 @@ class ThresholdSensor(BinarySensorDevice): # pylint: disable=invalid-name def async_threshold_sensor_state_listener( entity, old_state, new_state): - """Called when the sensor changes state.""" + """Handle sensor state changes.""" if new_state.state == STATE_UNKNOWN: return diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index 2c2a0563452..82585acfc38 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -6,22 +6,18 @@ https://home-assistant.io/components/sensor.trend/ """ import asyncio import logging + import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, - ENTITY_ID_FORMAT, - PLATFORM_SCHEMA, + BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( - ATTR_FRIENDLY_NAME, - ATTR_ENTITY_ID, - CONF_SENSOR_CLASS, - CONF_DEVICE_CLASS, - STATE_UNKNOWN,) + ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_SENSOR_CLASS, + CONF_DEVICE_CLASS, STATE_UNKNOWN,) from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import track_state_change @@ -47,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the trend sensors.""" + """Set up the trend sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -60,13 +56,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors.append( SensorTrend( - hass, - device, - friendly_name, - entity_id, - attribute, - device_class, - invert) + hass, device, friendly_name, entity_id, attribute, + device_class, invert) ) if not sensors: _LOGGER.error("No sensors added") @@ -82,8 +73,8 @@ class SensorTrend(BinarySensorDevice): target_entity, attribute, device_class, invert): """Initialize the sensor.""" self._hass = hass - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, - hass=hass) + self.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._target_entity = target_entity self._attribute = attribute @@ -95,7 +86,7 @@ class SensorTrend(BinarySensorDevice): @callback def trend_sensor_state_listener(entity, old_state, new_state): - """Called when the target device changes state.""" + """Handle the target device state changes.""" self.from_state = old_state self.to_state = new_state hass.async_add_job(self.async_update_ha_state(True)) diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py index e72e17a9bfe..2c7c398d91a 100644 --- a/homeassistant/components/binary_sensor/volvooncall.py +++ b/homeassistant/components/binary_sensor/volvooncall.py @@ -3,7 +3,6 @@ Support for VOC. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.volvooncall/ - """ import logging @@ -14,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Volvo sensors.""" + """Set up the Volvo sensors.""" if discovery_info is None: return add_devices([VolvoSensor(hass, *discovery_info)]) @@ -28,7 +27,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice): """Return True if the binary sensor is on.""" val = getattr(self.vehicle, self._attribute) if self._attribute == 'bulb_failures': - return len(val) > 0 + return bool(val) elif self._attribute in ['doors', 'windows']: return any([val[key] for key in val if 'Open' in key]) else: diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py index 58bd411d758..1ec9e703eab 100644 --- a/homeassistant/components/binary_sensor/wemo.py +++ b/homeassistant/components/binary_sensor/wemo.py @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class WemoBinarySensor(BinarySensorDevice): - """Represents a WeMo binary sensor.""" + """Representation a WeMo binary sensor.""" def __init__(self, device): """Initialize the WeMo sensor.""" @@ -41,10 +41,8 @@ class WemoBinarySensor(BinarySensorDevice): wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) def _update_callback(self, _device, _type, _params): - """Called by the Wemo device callback to update state.""" - _LOGGER.info( - 'Subscription update for %s', - _device) + """Handle state changes.""" + _LOGGER.info("Subscription update for %s", _device) updated = self.wemo.subscription_update(_type, _params) self._update(force_update=(not updated)) @@ -60,7 +58,7 @@ class WemoBinarySensor(BinarySensorDevice): @property def unique_id(self): """Return the id of this WeMo device.""" - return "{}.{}".format(self.__class__, self.wemo.serialnumber) + return '{}.{}'.format(self.__class__, self.wemo.serialnumber) @property def name(self): @@ -69,7 +67,7 @@ class WemoBinarySensor(BinarySensorDevice): @property def is_on(self): - """True if sensor is on.""" + """Return true if sensor is on.""" return self._state def update(self): @@ -80,5 +78,5 @@ class WemoBinarySensor(BinarySensorDevice): try: self._state = self.wemo.get_state(force_update) except AttributeError as err: - _LOGGER.warning('Could not update status for %s (%s)', - self.name, err) + _LOGGER.warning( + "Could not update status for %s (%s)", self.name, err) diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index 797ef9649b6..3f77d1d6081 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -16,23 +16,23 @@ DEPENDENCIES = ['wink'] # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { - "opened": "opening", - "brightness": "light", - "vibration": "vibration", - "loudness": "sound", - "noise": "sound", - "capturing_audio": "sound", - "liquid_detected": "moisture", - "motion": "motion", - "presence": "occupancy", - "co_detected": "gas", - "smoke_detected": "smoke", - "capturing_video": None + 'opened': 'opening', + 'brightness': 'light', + 'vibration': 'vibration', + 'loudness': 'sound', + 'noise': 'sound', + 'capturing_audio': 'sound', + 'liquid_detected': 'moisture', + 'motion': 'motion', + 'presence': 'occupancy', + 'co_detected': 'gas', + 'smoke_detected': 'smoke', + 'capturing_video': None } def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink binary sensor platform.""" + """Set up the Wink binary sensor platform.""" import pywink for sensor in pywink.get_sensors(): @@ -83,7 +83,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if camera_sensor.capability() in SENSOR_TYPES: add_devices([WinkBinarySensorDevice(camera_sensor, hass)]) except AttributeError: - _LOGGER.info("Device isn't a sensor, skipping.") + _LOGGER.info("Device isn't a sensor, skipping") class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity): diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py index c25ea81922b..81cc8fd8798 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/binary_sensor/workday.py @@ -11,10 +11,9 @@ import datetime import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, WEEKDAYS) +from homeassistant.const import CONF_NAME, WEEKDAYS import homeassistant.util.dt as dt_util -from homeassistant.helpers.entity import Entity +from homeassistant.components.binary_sensor import BinarySensorDevice import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -66,15 +65,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None): obj_holidays = getattr(holidays, country)(years=year) if province: - if province not in obj_holidays.PROVINCES and \ - province not in obj_holidays.STATES: + # 'state' and 'prov' are not interchangeable, so need to make + # sure we use the right one + if (hasattr(obj_holidays, "PROVINCES") and + province in obj_holidays.PROVINCES): + obj_holidays = getattr(holidays, country)(prov=province, + years=year) + elif (hasattr(obj_holidays, "STATES") and + province in obj_holidays.STATES): + obj_holidays = getattr(holidays, country)(state=province, + years=year) + else: _LOGGER.error("There is no province/state %s in country %s", province, country) return False - else: - year = datetime.datetime.now().year - obj_holidays = getattr(holidays, country)(prov=province, - years=year) _LOGGER.debug("Found the following holidays for your configuration:") for date, name in sorted(obj_holidays.items()): @@ -92,7 +96,7 @@ def day_to_string(day): return None -class IsWorkdaySensor(Entity): +class IsWorkdaySensor(BinarySensorDevice): """Implementation of a Workday sensor.""" def __init__(self, obj_holidays, workdays, excludes, name): @@ -101,7 +105,7 @@ class IsWorkdaySensor(Entity): self._obj_holidays = obj_holidays self._workdays = workdays self._excludes = excludes - self._state = STATE_UNKNOWN + self._state = None @property def name(self): @@ -109,7 +113,7 @@ class IsWorkdaySensor(Entity): return self._name @property - def state(self): + def is_on(self): """Return the state of the device.""" return self._state @@ -135,14 +139,14 @@ class IsWorkdaySensor(Entity): def async_update(self): """Get date and look whether it is a holiday.""" # Default is no workday - self._state = STATE_OFF + self._state = False # Get iso day of the week (1 = Monday, 7 = Sunday) day = datetime.datetime.today().isoweekday() - 1 day_of_week = day_to_string(day) if self.is_include(day_of_week, dt_util.now()): - self._state = STATE_ON + self._state = True if self.is_exclude(day_of_week, dt_util.now()): - self._state = STATE_OFF + self._state = False diff --git a/homeassistant/components/binary_sensor/zha.py b/homeassistant/components/binary_sensor/zha.py new file mode 100644 index 00000000000..1d895fbf3a7 --- /dev/null +++ b/homeassistant/components/binary_sensor/zha.py @@ -0,0 +1,89 @@ +""" +Binary sensors on Zigbee Home Automation networks. + +For more details on this platform, please refer to the documentation +at https://home-assistant.io/components/binary_sensor.zha/ +""" +import asyncio +import logging + +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components import zha + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zha'] + +# ZigBee Cluster Library Zone Type to Home Assistant device class +CLASS_MAPPING = { + 0x000d: 'motion', + 0x0015: 'opening', + 0x0028: 'smoke', + 0x002a: 'moisture', + 0x002b: 'gas', + 0x002d: 'vibration', +} + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the Zigbee Home Automation binary sensors.""" + discovery_info = zha.get_discovery_info(hass, discovery_info) + if discovery_info is None: + return + + from bellows.zigbee.zcl.clusters.security import IasZone + + clusters = discovery_info['clusters'] + + device_class = None + cluster = [c for c in clusters if isinstance(c, IasZone)][0] + if discovery_info['new_join']: + yield from cluster.bind() + ieee = cluster.endpoint.device.application.ieee + yield from cluster.write_attributes({'cie_addr': ieee}) + + try: + zone_type = yield from cluster['zone_type'] + device_class = CLASS_MAPPING.get(zone_type, None) + except Exception: # pylint: disable=broad-except + # If we fail to read from the device, use a non-specific class + pass + + sensor = BinarySensor(device_class, **discovery_info) + async_add_devices([sensor]) + + +class BinarySensor(zha.Entity, BinarySensorDevice): + """THe ZHA Binary Sensor.""" + + _domain = DOMAIN + + def __init__(self, device_class, **kwargs): + """Initialize the ZHA binary sensor.""" + super().__init__(**kwargs) + self._device_class = device_class + from bellows.zigbee.zcl.clusters.security import IasZone + self._ias_zone_cluster = self._clusters[IasZone.cluster_id] + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + if self._state == 'unknown': + return False + return bool(self._state) + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return self._device_class + + def cluster_command(self, aps_frame, tsn, command_id, args): + """Handle commands received to this cluster.""" + if command_id == 0: + self._state = args[0] & 3 + _LOGGER.debug("Updated alarm state: %s", self._state) + self.schedule_update_ha_state() + elif command_id == 1: + _LOGGER.debug("Enroll requested") + self.hass.add_job(self._ias_zone_cluster.enroll_response(0, 0)) diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 935d4b4bb3f..659d82f809b 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ZigBee binary sensor platform.""" + """Set up the ZigBee binary sensor platform.""" add_devices( [ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 5fd9c39ef2a..cc5fb3ed572 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -20,7 +20,7 @@ DEPENDENCIES = [] def get_device(values, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" device_mapping = workaround.get_device_mapping(values.primary) if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT: # Default the multiplier to 4 @@ -45,12 +45,12 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): self._state = self.values.primary.data def update_properties(self): - """Callback on data changes for node values.""" + """Handle data changes for node values.""" self._state = self.values.primary.data @property def is_on(self): - """Return True if the binary sensor is on.""" + """Return true if the binary sensor is on.""" return self._state @property @@ -69,7 +69,7 @@ class ZWaveTriggerSensor(ZWaveBinarySensor): self.invalidate_after = None def update_properties(self): - """Called when a value for this entity's node has changed.""" + """Handle value changes for this entity's node.""" self._state = self.values.primary.data # only allow this value to be true for re_arm secs if not self.hass: @@ -83,7 +83,7 @@ class ZWaveTriggerSensor(ZWaveBinarySensor): @property def is_on(self): - """Return True if movement has happened within the rearm time.""" + """Return true if movement has happened within the rearm time.""" return self._state and \ (self.invalidate_after is None or self.invalidate_after > dt_util.utcnow()) diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index 13225773b3a..aff1c14b252 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -35,7 +35,7 @@ CONFIG_SCHEMA = vol.Schema({ # pylint: disable=unused-argument def setup(hass, config): - """Setup BloomSky component.""" + """Set up the BloomSky component.""" api_key = config[DOMAIN][CONF_API_KEY] global BLOOMSKY @@ -67,9 +67,8 @@ class BloomSky(object): def refresh_devices(self): """Use the API to retrieve a list of devices.""" _LOGGER.debug("Fetching BloomSky update") - response = requests.get(self.API_URL, - headers={"Authorization": self._api_key}, - timeout=10) + response = requests.get( + self.API_URL, headers={"Authorization": self._api_key}, timeout=10) if response.status_code == 401: raise RuntimeError("Invalid API_KEY") elif response.status_code != 200: diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 70477198ea0..2ccd591db2c 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -7,12 +7,10 @@ https://home-assistant.io/components/calendar/ import asyncio import logging from datetime import timedelta - import re -from homeassistant.components.google import (CONF_OFFSET, - CONF_DEVICE_ID, - CONF_NAME) +from homeassistant.components.google import ( + CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.config_validation import time_period_str from homeassistant.helpers.entity import Entity, generate_entity_id @@ -22,16 +20,18 @@ from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(seconds=60) DOMAIN = 'calendar' + ENTITY_ID_FORMAT = DOMAIN + '.{}' +SCAN_INTERVAL = timedelta(seconds=60) + @asyncio.coroutine def async_setup(hass, config): """Track states and offer events for calendars.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DOMAIN) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN) yield from component.async_setup(config) return True @@ -55,9 +55,8 @@ class CalendarEventDevice(Entity): self._name = data.get(CONF_NAME) self.dev_id = data.get(CONF_DEVICE_ID) self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, - self.dev_id, - hass=hass) + self.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, self.dev_id, hass=hass) self._cal_data = { 'all_day': False, @@ -87,7 +86,7 @@ class CalendarEventDevice(Entity): @property def device_state_attributes(self): - """State Attributes for HA.""" + """Return the device state attributes.""" start = self._cal_data.get('start', None) end = self._cal_data.get('end', None) start = start.strftime(DATE_STR_FORMAT) if start is not None else None diff --git a/homeassistant/components/calendar/demo.py b/homeassistant/components/calendar/demo.py index 279119a1ff5..9f6ad70b58f 100755 --- a/homeassistant/components/calendar/demo.py +++ b/homeassistant/components/calendar/demo.py @@ -10,7 +10,7 @@ from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo binary sensor platform.""" + """Set up the Demo Calendar platform.""" calendar_data_future = DemoGoogleCalendarDataFuture() calendar_data_current = DemoGoogleCalendarDataCurrent() add_devices([ @@ -27,8 +27,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DemoGoogleCalendarData(object): - """Setup base class for data.""" - + """Representation of a Demo Calendar element.""" + # pylint: disable=no-self-use def update(self): """Return true so entity knows we have new data.""" @@ -36,7 +36,7 @@ class DemoGoogleCalendarData(object): class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): - """Setup future data event.""" + """Representation of a Demo Calendar for a future event.""" def __init__(self): """Set the event to a future event.""" @@ -55,7 +55,7 @@ class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData): - """Create a current event we're in the middle of.""" + """Representation of a Demo Calendar for a current event.""" def __init__(self): """Set the event data.""" @@ -74,9 +74,9 @@ class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData): class DemoGoogleCalendar(CalendarEventDevice): - """A Demo binary sensor.""" + """Representation of a Demo Calendar element.""" def __init__(self, hass, calendar_data, data): - """The same as a google calendar but without the api calls.""" + """Initialize Google Calendar but without the API calls.""" self.data = calendar_data super().__init__(hass, data) diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 8f9de90a1b1..26c2c251afb 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -9,25 +9,24 @@ import logging from datetime import timedelta from homeassistant.components.calendar import CalendarEventDevice -from homeassistant.components.google import (CONF_CAL_ID, CONF_ENTITIES, - CONF_TRACK, TOKEN_FILE, - GoogleCalendarService) +from homeassistant.components.google import ( + CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE, + GoogleCalendarService) from homeassistant.util import Throttle, dt +_LOGGER = logging.getLogger(__name__) + DEFAULT_GOOGLE_SEARCH_PARAMS = { 'orderBy': 'startTime', 'maxResults': 1, 'singleEvents': True, } -# Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_devices, disc_info=None): - """Setup the calendar platform for event devices.""" + """Set up the calendar platform for event devices.""" if disc_info is None: return @@ -55,7 +54,7 @@ class GoogleCalendarData(object): """Class to utilize calendar service object to get next event.""" def __init__(self, calendar_service, calendar_id, search=None): - """Setup how we are going to search the google calendar.""" + """Set up how we are going to search the google calendar.""" self.calendar_service = calendar_service self.calendar_id = calendar_id self.search = search diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 388a9fce39b..f6238d6ae23 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -76,7 +76,7 @@ def async_get_image(hass, entity_id, timeout=10): @asyncio.coroutine def async_setup(hass, config): - """Setup the camera component.""" + """Set up the camera component.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) hass.http.register_view(CameraImageView(component.entities)) @@ -121,12 +121,12 @@ class Camera(Entity): @property def brand(self): - """Camera brand.""" + """Return the camera brand.""" return None @property def model(self): - """Camera model.""" + """Return the camera model.""" return None def camera_image(self): @@ -191,7 +191,7 @@ class Camera(Entity): @property def state(self): - """Camera state.""" + """Return the camera state.""" if self.is_recording: return STATE_RECORDING elif self.is_streaming: @@ -201,7 +201,7 @@ class Camera(Entity): @property def state_attributes(self): - """Camera state attributes.""" + """Return the camera state attributes.""" attr = { 'access_token': self.access_tokens[-1], } @@ -233,7 +233,7 @@ class CameraView(HomeAssistantView): @asyncio.coroutine def get(self, request, entity_id): - """Start a get request.""" + """Start a GET request.""" camera = self.entities.get(entity_id) if camera is None: @@ -251,15 +251,15 @@ class CameraView(HomeAssistantView): @asyncio.coroutine def handle(self, request, camera): - """Hanlde the camera request.""" + """Handle the camera request.""" raise NotImplementedError() class CameraImageView(CameraView): """Camera view to serve an image.""" - url = "/api/camera_proxy/{entity_id}" - name = "api:camera:image" + url = '/api/camera_proxy/{entity_id}' + name = 'api:camera:image' @asyncio.coroutine def handle(self, request, camera): @@ -277,8 +277,8 @@ class CameraImageView(CameraView): class CameraMjpegStream(CameraView): """Camera View to serve an MJPEG stream.""" - url = "/api/camera_proxy_stream/{entity_id}" - name = "api:camera:stream" + url = '/api/camera_proxy_stream/{entity_id}' + name = 'api:camera:stream' @asyncio.coroutine def handle(self, request, camera): diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py index 59616b0ded2..bca4fafec4f 100644 --- a/homeassistant/components/camera/blink.py +++ b/homeassistant/components/camera/blink.py @@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup a Blink Camera.""" + """Set up a Blink Camera.""" if discovery_info is None: return @@ -49,19 +49,19 @@ class BlinkCamera(Camera): @property def name(self): - """A camera name.""" + """Return the camera name.""" return self._name @Throttle(MIN_TIME_BETWEEN_UPDATES) def request_image(self): - """An image request from Blink servers.""" + """Request a new image from Blink servers.""" _LOGGER.info("Requesting new image from blink servers") image_url = self.check_for_motion() header = self.data.cameras[self._name].header self.response = requests.get(image_url, headers=header, stream=True) def check_for_motion(self): - """A method to check if motion has been detected since last update.""" + """Check if motion has been detected since last update.""" self.data.refresh() notifs = self.data.cameras[self._name].notifications if notifs > self.notifications: diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/camera/bloomsky.py index 7137c73c299..c3b4775b593 100644 --- a/homeassistant/components/camera/bloomsky.py +++ b/homeassistant/components/camera/bloomsky.py @@ -16,7 +16,7 @@ DEPENDENCIES = ['bloomsky'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup access to BloomSky cameras.""" + """Set up access to BloomSky cameras.""" bloomsky = get_component('bloomsky') for device in bloomsky.BLOOMSKY.devices.values(): add_devices([BloomSkyCamera(bloomsky.BLOOMSKY, device)]) @@ -26,14 +26,14 @@ class BloomSkyCamera(Camera): """Representation of the images published from the BloomSky's camera.""" def __init__(self, bs, device): - """Setup for access to the BloomSky camera images.""" + """Initialize access to the BloomSky camera images.""" super(BloomSkyCamera, self).__init__() self._name = device['DeviceName'] self._id = device['DeviceID'] self._bloomsky = bs self._url = "" self._last_url = "" - # _last_image will store images as they are downloaded so that the + # 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. self._last_image = "" diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index 5e451c48b40..158f6c11751 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -11,7 +11,7 @@ from homeassistant.components.camera import Camera def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo camera platform.""" + """Set up the Demo camera platform.""" add_devices([ DemoCamera('Demo camera') ]) @@ -29,8 +29,8 @@ class DemoCamera(Camera): """Return a faked still image response.""" now = dt_util.utcnow() - image_path = os.path.join(os.path.dirname(__file__), - 'demo_{}.jpg'.format(now.second % 4)) + image_path = os.path.join( + os.path.dirname(__file__), 'demo_{}.jpg'.format(now.second % 4)) with open(image_path, 'rb') as file: return file.read() diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index d4c7b54fc7f..8ca72a09261 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -16,10 +16,10 @@ from homeassistant.components.ffmpeg import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream) -DEPENDENCIES = ['ffmpeg'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['ffmpeg'] DEFAULT_NAME = 'FFmpeg' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a FFmpeg Camera.""" + """Set up a FFmpeg camera.""" if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)): return async_add_devices([FFmpegCamera(hass, config)]) diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index c1f9513d2c6..86061aa3d1d 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup a Foscam IP Camera.""" + """Set up a Foscam IP Camera.""" add_devices([FoscamCamera(config)]) @@ -60,7 +60,7 @@ class FoscamCamera(Camera): ) self._name = device_info.get(CONF_NAME) - _LOGGER.info('Using the following URL for %s: %s', + _LOGGER.info("Using the following URL for %s: %s", self._name, uri_template.format('***', '***')) def camera_image(self): diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index a330836f32b..83164c55230 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a generic IP Camera.""" + """Set up a generic IP Camera.""" async_add_devices([GenericCamera(hass, config)]) @@ -85,8 +85,8 @@ class GenericCamera(Camera): try: url = self._still_image_url.async_render() except TemplateError as err: - _LOGGER.error('Error parsing template %s: %s', - self._still_image_url, err) + _LOGGER.error( + "Error parsing template %s: %s", self._still_image_url, err) return self._last_image if url == self._last_url and self._limit_refetch: @@ -100,7 +100,7 @@ class GenericCamera(Camera): response = requests.get(url, timeout=10, auth=self._auth) return response.content except requests.exceptions.RequestException as error: - _LOGGER.error('Error getting camera image: %s', error) + _LOGGER.error("Error getting camera image: %s", error) return self._last_image self._last_image = yield from self.hass.loop.run_in_executor( @@ -114,10 +114,10 @@ class GenericCamera(Camera): url, auth=self._auth) self._last_image = yield from response.read() except asyncio.TimeoutError: - _LOGGER.error('Timeout getting camera image') + _LOGGER.error("Timeout getting camera image") return self._last_image except aiohttp.ClientError as err: - _LOGGER.error('Error getting new camera image: %s', err) + _LOGGER.error("Error getting new camera image: %s", err) return self._last_image self._last_url = url diff --git a/homeassistant/components/camera/local_file.py b/homeassistant/components/camera/local_file.py index 85438820393..1f3a71c9d05 100644 --- a/homeassistant/components/camera/local_file.py +++ b/homeassistant/components/camera/local_file.py @@ -26,7 +26,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Camera.""" + """Set up the Camera that works with local files.""" file_path = config[CONF_FILE_PATH] # check filepath given is readable @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class LocalFile(Camera): - """Local camera.""" + """Representation of a local file camera.""" def __init__(self, name, file_path): """Initialize Local File Camera component.""" diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index a158c36152e..1e9859fe7c2 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a MJPEG IP Camera.""" + """Set up a MJPEG IP Camera.""" if discovery_info: config = PLATFORM_SCHEMA(discovery_info) async_add_devices([MjpegCamera(hass, config)]) @@ -102,10 +102,10 @@ class MjpegCamera(Camera): return image except asyncio.TimeoutError: - _LOGGER.error('Timeout getting camera image') + _LOGGER.error("Timeout getting camera image") except aiohttp.ClientError as err: - _LOGGER.error('Error getting new camera image: %s', err) + _LOGGER.error("Error getting new camera image: %s", err) def camera_image(self): """Return a still image response from the camera.""" diff --git a/homeassistant/components/camera/mqtt.py b/homeassistant/components/camera/mqtt.py index 39fbdc7fd9f..8d72ec35a28 100755 --- a/homeassistant/components/camera/mqtt.py +++ b/homeassistant/components/camera/mqtt.py @@ -32,17 +32,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Camera.""" + """Set up the MQTT Camera.""" topic = config[CONF_TOPIC] async_add_devices([MqttCamera(config[CONF_NAME], topic)]) class MqttCamera(Camera): - """MQTT camera.""" + """representation of a MQTT camera.""" def __init__(self, name, topic): - """Initialize Local File Camera component.""" + """Initialize the MQTT Camera.""" super().__init__() self._name = name @@ -61,13 +61,13 @@ class MqttCamera(Camera): return self._name def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe MQTT events. This method must be run in the event loop and returns a coroutine. """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" self._last_image = payload return mqtt.async_subscribe( diff --git a/homeassistant/components/camera/neato.py b/homeassistant/components/camera/neato.py index d6eafc36859..33bd00caa6b 100644 --- a/homeassistant/components/camera/neato.py +++ b/homeassistant/components/camera/neato.py @@ -1,5 +1,5 @@ """ -Camera that loads a picture from a local file. +Camera that loads a picture from Neato. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.neato/ @@ -18,12 +18,12 @@ DEPENDENCIES = ['neato'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Camera.""" + """Set up the Neato Camera.""" dev = [] for robot in hass.data[NEATO_ROBOTS]: if 'maps' in robot.traits: dev.append(NeatoCleaningMap(hass, robot)) - _LOGGER.debug('Adding robots for cleaning maps %s', dev) + _LOGGER.debug("Adding robots for cleaning maps %s", dev) add_devices(dev, True) @@ -34,7 +34,7 @@ class NeatoCleaningMap(Camera): """Initialize Neato cleaning map.""" super().__init__() self.robot = robot - self._robot_name = self.robot.name + ' Cleaning Map' + self._robot_name = '{} {}'.format(self.robot.name, 'Cleaning Map') self._robot_serial = self.robot.serial self.neato = hass.data[NEATO_LOGIN] self._image_url = None @@ -53,7 +53,7 @@ class NeatoCleaningMap(Camera): map_data = self.hass.data[NEATO_MAP_DATA] image_url = map_data[self._robot_serial]['maps'][0]['url'] if image_url == self._image_url: - _LOGGER.debug('The map image_url is the same as old') + _LOGGER.debug("The map image_url is the same as old") return image = self.neato.download_map(image_url) self._image = image.read() diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 6ede7c5a162..e5f22cced16 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup access to Netatmo cameras.""" + """Set up access to Netatmo cameras.""" netatmo = get_component('netatmo') home = config.get(CONF_HOME) verify_ssl = config.get(CONF_VERIFY_SSL, True) @@ -55,7 +55,7 @@ class NetatmoCamera(Camera): """Representation of the images published from a Netatmo camera.""" def __init__(self, data, camera_name, home, camera_type, verify_ssl): - """Setup for access to the Netatmo camera images.""" + """Set up for access to the Netatmo camera images.""" super(NetatmoCamera, self).__init__() self._data = data self._camera_name = camera_name @@ -64,10 +64,10 @@ class NetatmoCamera(Camera): self._name = home + ' / ' + camera_name else: self._name = camera_name - camera_id = data.camera_data.cameraByName(camera=camera_name, - home=home)['id'] - self._unique_id = "Welcome_camera {0} - {1}".format(self._name, - camera_id) + camera_id = data.camera_data.cameraByName( + camera=camera_name, home=home)['id'] + self._unique_id = "Welcome_camera {0} - {1}".format( + self._name, camera_id) self._vpnurl, self._localurl = self._data.camera_data.cameraUrls( camera=camera_name ) @@ -83,13 +83,13 @@ class NetatmoCamera(Camera): response = requests.get('{0}/live/snapshot_720.jpg'.format( self._vpnurl), timeout=10, verify=self._verify_ssl) else: - _LOGGER.error('Welcome VPN url is None') + _LOGGER.error("Welcome VPN URL is None") self._data.update() (self._vpnurl, self._localurl) = \ self._data.camera_data.cameraUrls(camera=self._camera_name) return None except requests.exceptions.RequestException as error: - _LOGGER.error('Welcome url changed: %s', error) + _LOGGER.error("Welcome URL changed: %s", error) self._data.update() (self._vpnurl, self._localurl) = \ self._data.camera_data.cameraUrls(camera=self._camera_name) @@ -103,12 +103,12 @@ class NetatmoCamera(Camera): @property def brand(self): - """Camera brand.""" + """Return the camera brand.""" return "Netatmo" @property def model(self): - """Camera model.""" + """Return the camera model.""" if self._cameratype == "NOC": return "Presence" elif self._cameratype == "NACamera": diff --git a/homeassistant/components/camera/rpi_camera.py b/homeassistant/components/camera/rpi_camera.py index b42c62e56d0..6e3d3622a3f 100644 --- a/homeassistant/components/camera/rpi_camera.py +++ b/homeassistant/components/camera/rpi_camera.py @@ -62,7 +62,7 @@ def kill_raspistill(*args): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Raspberry Camera.""" + """Set up the Raspberry Camera.""" if shutil.which("raspistill") is None: _LOGGER.error("'raspistill' was not found") return False diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 378d75ac26d..de61535b336 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -59,7 +59,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a Synology IP Camera.""" + """Set up a Synology IP Camera.""" verify_ssl = config.get(CONF_VERIFY_SSL) timeout = config.get(CONF_TIMEOUT) websession_init = async_get_clientsession(hass, verify_ssl) @@ -140,16 +140,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): snapshot_path = camera['snapshot_path'] device = SynologyCamera( - hass, - websession, - config, - camera_id, - camera['name'], - snapshot_path, - streaming_path, - camera_path, - auth_path, - timeout + hass, websession, config, camera_id, camera['name'], + snapshot_path, streaming_path, camera_path, auth_path, timeout ) devices.append(device) diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index c5252209996..3840a8a90b1 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -41,13 +41,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: cameras = nvrconn.index() except nvr.NotAuthorized: - _LOGGER.error('Authorization failure while connecting to NVR') + _LOGGER.error("Authorization failure while connecting to NVR") return False except nvr.NvrError: - _LOGGER.error('NVR refuses to talk to me') + _LOGGER.error("NVR refuses to talk to me") return False except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to NVR: %s', str(ex)) + _LOGGER.error("Unable to connect to NVR: %s", str(ex)) return False identifier = nvrconn.server_version >= (3, 2, 0) and 'id' or 'uuid' @@ -113,7 +113,7 @@ class UnifiVideoCamera(Camera): store = uvc_store.get_info_store() password = store.get_camera_password(self._uuid) if password is None: - _LOGGER.debug('Logging into camera %(name)s with default password', + _LOGGER.debug("Logging into camera %(name)s with default password", dict(name=self._name)) password = 'ubnt' @@ -125,11 +125,10 @@ class UnifiVideoCamera(Camera): camera = None for addr in addrs: try: - camera = client_cls(addr, - caminfo['username'], - password) + camera = client_cls( + addr, caminfo['username'], password) camera.login() - _LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s', + _LOGGER.debug("Logged into UVC camera %(name)s via %(addr)s", dict(name=self._name, addr=addr)) self._connect_addr = addr break @@ -140,7 +139,7 @@ class UnifiVideoCamera(Camera): except uvc_camera.CameraAuthError: pass if not self._connect_addr: - _LOGGER.error('Unable to login to camera') + _LOGGER.error("Unable to login to camera") return None self._camera = camera @@ -157,14 +156,14 @@ class UnifiVideoCamera(Camera): try: return self._camera.get_snapshot() except uvc_camera.CameraConnectError: - _LOGGER.error('Unable to contact camera') + _LOGGER.error("Unable to contact camera") except uvc_camera.CameraAuthError: if retry: self._login() return _get_image(retry=False) else: - _LOGGER.error('Unable to log into camera, unable ' - 'to get snapshot') + _LOGGER.error( + "Unable to log into camera, unable to get snapshot") raise return _get_image() diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/camera/verisure.py index 6e613b72298..1c2e7e382fe 100644 --- a/homeassistant/components/camera/verisure.py +++ b/homeassistant/components/camera/verisure.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Camera.""" + """Set up the Verisure Camera.""" if not int(hub.config.get(CONF_SMARTCAM, 1)): return False directory_path = hass.config.config_dir @@ -33,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureSmartcam(Camera): - """Local camera.""" + """Representation of a Verisure camera.""" def __init__(self, hass, device_id, directory_path): """Initialize Verisure File Camera component.""" @@ -50,9 +50,9 @@ class VerisureSmartcam(Camera): """Return image response.""" self.check_imagelist() if not self._image: - _LOGGER.debug('No image to display') + _LOGGER.debug("No image to display") return - _LOGGER.debug('Trying to open %s', self._image) + _LOGGER.debug("Trying to open %s", self._image) with open(self._image, 'rb') as file: return file.read() @@ -64,35 +64,30 @@ class VerisureSmartcam(Camera): return images = hub.smartcam_dict[self._device_id] new_image_id = images[0] - _LOGGER.debug('self._device_id=%s, self._images=%s, ' - 'self._new_image_id=%s', self._device_id, + _LOGGER.debug("self._device_id=%s, self._images=%s, " + "self._new_image_id=%s", self._device_id, images, new_image_id) if (new_image_id == '-1' or self._image_id == new_image_id): - _LOGGER.debug('The image is the same, or loading image_id') + _LOGGER.debug("The image is the same, or loading image_id") return - _LOGGER.debug('Download new image %s', new_image_id) - hub.my_pages.smartcam.download_image(self._device_id, - new_image_id, - self._directory_path) - _LOGGER.debug('Old image_id=%s', self._image_id) + _LOGGER.debug("Download new image %s", new_image_id) + hub.my_pages.smartcam.download_image( + self._device_id, new_image_id, self._directory_path) + _LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image(self) self._image_id = new_image_id - self._image = os.path.join(self._directory_path, - '{}{}'.format( - self._image_id, - '.jpg')) + self._image = os.path.join( + self._directory_path, '{}{}'.format(self._image_id, '.jpg')) def delete_image(self, event): """Delete an old image.""" - remove_image = os.path.join(self._directory_path, - '{}{}'.format( - self._image_id, - '.jpg')) + remove_image = os.path.join( + self._directory_path, '{}{}'.format(self._image_id, '.jpg')) try: os.remove(remove_image) - _LOGGER.debug('Deleting old image %s', remove_image) + _LOGGER.debug("Deleting old image %s", remove_image) except OSError as error: if error.errno != errno.ENOENT: raise diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py index e419564a887..5410833761b 100644 --- a/homeassistant/components/camera/zoneminder.py +++ b/homeassistant/components/camera/zoneminder.py @@ -51,21 +51,21 @@ def _get_image_url(hass, monitor, mode): @asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup ZoneMinder cameras.""" + """Set up the ZoneMinder cameras.""" cameras = [] monitors = zoneminder.get_state('api/monitors.json') if not monitors: - _LOGGER.warning('Could not fetch monitors from ZoneMinder') + _LOGGER.warning("Could not fetch monitors from ZoneMinder") return for i in monitors['monitors']: monitor = i['Monitor'] if monitor['Function'] == 'None': - _LOGGER.info('Skipping camera %s', monitor['Id']) + _LOGGER.info("Skipping camera %s", monitor['Id']) continue - _LOGGER.info('Initializing camera %s', monitor['Id']) + _LOGGER.info("Initializing camera %s", monitor['Id']) device_info = { CONF_NAME: monitor['Name'], @@ -75,7 +75,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): cameras.append(ZoneMinderCamera(hass, device_info, monitor)) if not cameras: - _LOGGER.warning('No active cameras found') + _LOGGER.warning("No active cameras found") return async_add_devices(cameras) @@ -97,18 +97,18 @@ class ZoneMinderCamera(MjpegCamera): def update(self): """Update our recording state from the ZM API.""" - _LOGGER.debug('Updating camera state for monitor %i', self._monitor_id) + _LOGGER.debug("Updating camera state for monitor %i", self._monitor_id) status_response = zoneminder.get_state( 'api/monitors/alarm/id:%i/command:status.json' % self._monitor_id ) if not status_response: - _LOGGER.warning('Could not get status for monitor %i', + _LOGGER.warning("Could not get status for monitor %i", self._monitor_id) return - if status_response['success'] is False: - _LOGGER.warning('Alarm status API call failed for monitor %i', + if not status_response.get("success", False): + _LOGGER.warning("Alarm status API call failed for monitor %i", self._monitor_id) return diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 2a0bd2eb5c1..2e2dfbef8ca 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -23,45 +23,46 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS) -DOMAIN = "climate" +DOMAIN = 'climate' -ENTITY_ID_FORMAT = DOMAIN + ".{}" +ENTITY_ID_FORMAT = DOMAIN + '.{}' SCAN_INTERVAL = timedelta(seconds=60) -SERVICE_SET_AWAY_MODE = "set_away_mode" -SERVICE_SET_AUX_HEAT = "set_aux_heat" -SERVICE_SET_TEMPERATURE = "set_temperature" -SERVICE_SET_FAN_MODE = "set_fan_mode" -SERVICE_SET_HOLD_MODE = "set_hold_mode" -SERVICE_SET_OPERATION_MODE = "set_operation_mode" -SERVICE_SET_SWING_MODE = "set_swing_mode" -SERVICE_SET_HUMIDITY = "set_humidity" +SERVICE_SET_AWAY_MODE = 'set_away_mode' +SERVICE_SET_AUX_HEAT = 'set_aux_heat' +SERVICE_SET_TEMPERATURE = 'set_temperature' +SERVICE_SET_FAN_MODE = 'set_fan_mode' +SERVICE_SET_HOLD_MODE = 'set_hold_mode' +SERVICE_SET_OPERATION_MODE = 'set_operation_mode' +SERVICE_SET_SWING_MODE = 'set_swing_mode' +SERVICE_SET_HUMIDITY = 'set_humidity' -STATE_HEAT = "heat" -STATE_COOL = "cool" -STATE_IDLE = "idle" -STATE_AUTO = "auto" -STATE_DRY = "dry" -STATE_FAN_ONLY = "fan_only" +STATE_HEAT = 'heat' +STATE_COOL = 'cool' +STATE_IDLE = 'idle' +STATE_AUTO = 'auto' +STATE_DRY = 'dry' +STATE_FAN_ONLY = 'fan_only' -ATTR_CURRENT_TEMPERATURE = "current_temperature" -ATTR_MAX_TEMP = "max_temp" -ATTR_MIN_TEMP = "min_temp" -ATTR_TARGET_TEMP_HIGH = "target_temp_high" -ATTR_TARGET_TEMP_LOW = "target_temp_low" -ATTR_AWAY_MODE = "away_mode" -ATTR_AUX_HEAT = "aux_heat" -ATTR_FAN_MODE = "fan_mode" -ATTR_FAN_LIST = "fan_list" -ATTR_CURRENT_HUMIDITY = "current_humidity" -ATTR_HUMIDITY = "humidity" -ATTR_MAX_HUMIDITY = "max_humidity" -ATTR_MIN_HUMIDITY = "min_humidity" -ATTR_HOLD_MODE = "hold_mode" -ATTR_OPERATION_MODE = "operation_mode" -ATTR_OPERATION_LIST = "operation_list" -ATTR_SWING_MODE = "swing_mode" -ATTR_SWING_LIST = "swing_list" +ATTR_CURRENT_TEMPERATURE = 'current_temperature' +ATTR_MAX_TEMP = 'max_temp' +ATTR_MIN_TEMP = 'min_temp' +ATTR_TARGET_TEMP_HIGH = 'target_temp_high' +ATTR_TARGET_TEMP_LOW = 'target_temp_low' +ATTR_TARGET_TEMP_STEP = 'target_temp_step' +ATTR_AWAY_MODE = 'away_mode' +ATTR_AUX_HEAT = 'aux_heat' +ATTR_FAN_MODE = 'fan_mode' +ATTR_FAN_LIST = 'fan_list' +ATTR_CURRENT_HUMIDITY = 'current_humidity' +ATTR_HUMIDITY = 'humidity' +ATTR_MAX_HUMIDITY = 'max_humidity' +ATTR_MIN_HUMIDITY = 'min_humidity' +ATTR_HOLD_MODE = 'hold_mode' +ATTR_OPERATION_MODE = 'operation_mode' +ATTR_OPERATION_LIST = 'operation_list' +ATTR_SWING_MODE = 'swing_mode' +ATTR_SWING_LIST = 'swing_list' # The degree of precision for each platform PRECISION_WHOLE = 1 @@ -208,7 +209,7 @@ def set_swing_mode(hass, swing_mode, entity_id=None): @asyncio.coroutine def async_setup(hass, config): - """Setup climate devices.""" + """Set up climate devices.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) yield from component.async_setup(config) @@ -273,7 +274,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_aux_heat_set_service(service): - """Set auxillary heater on target climate devices.""" + """Set auxiliary heater on target climate devices.""" target_climate = component.async_extract_from_service(service) aux_heat = service.data.get(ATTR_AUX_HEAT) @@ -419,6 +420,10 @@ class ClimateDevice(Entity): ATTR_TEMPERATURE: self._convert_for_display(self.target_temperature), } + + if self.target_temperature_step is not None: + data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step + target_temp_high = self.target_temperature_high if target_temp_high is not None: data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display( @@ -467,12 +472,12 @@ class ClimateDevice(Entity): @property def unit_of_measurement(self): - """The unit of measurement to display.""" + """Return the unit of measurement to display.""" return self.hass.config.units.temperature_unit @property def temperature_unit(self): - """The unit of measurement used by the platform.""" + """Return the unit of measurement used by the platform.""" raise NotImplementedError @property @@ -492,7 +497,7 @@ class ClimateDevice(Entity): @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" return None @property @@ -505,6 +510,11 @@ class ClimateDevice(Entity): """Return the temperature we try to reach.""" return None + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + return None + @property def target_temperature_high(self): """Return the highbound target temperature we try to reach.""" @@ -537,7 +547,7 @@ class ClimateDevice(Entity): @property def fan_list(self): - """List of available fan modes.""" + """Return the list of available fan modes.""" return None @property @@ -547,7 +557,7 @@ class ClimateDevice(Entity): @property def swing_list(self): - """List of available swing modes.""" + """Return the list of available swing modes.""" return None def set_temperature(self, **kwargs): @@ -695,8 +705,8 @@ class ClimateDevice(Entity): if temp is None or not isinstance(temp, Number): return temp if self.temperature_unit != self.unit_of_measurement: - temp = convert_temperature(temp, self.temperature_unit, - self.unit_of_measurement) + temp = convert_temperature( + temp, self.temperature_unit, self.unit_of_measurement) # Round in the units appropriate if self.precision == PRECISION_HALVES: return round(temp * 2) / 2.0 diff --git a/homeassistant/components/climate/demo.py b/homeassistant/components/climate/demo.py index 9830daff69c..24b40af7eb1 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/climate/demo.py @@ -10,14 +10,14 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo climate devices.""" + """Set up the Demo climate devices.""" add_devices([ - DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, None, 77, - "Auto Low", None, None, "Auto", "heat", None, None, None), - DemoClimate("Hvac", 21, TEMP_CELSIUS, True, None, 22, "On High", - 67, 54, "Off", "cool", False, None, None), - DemoClimate("Ecobee", None, TEMP_CELSIUS, None, None, 23, "Auto Low", - None, None, "Auto", "auto", None, 24, 21) + DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77, + 'Auto Low', None, None, 'Auto', 'heat', None, None, None), + DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High', + 67, 54, 'Off', 'cool', False, None, None), + DemoClimate('Ecobee', None, TEMP_CELSIUS, None, None, 23, 'Auto Low', + None, None, 'Auto', 'auto', None, 24, 21) ]) @@ -41,15 +41,15 @@ class DemoClimate(ClimateDevice): self._current_operation = current_operation self._aux = aux self._current_swing_mode = current_swing_mode - self._fan_list = ["On Low", "On High", "Auto Low", "Auto High", "Off"] - self._operation_list = ["heat", "cool", "auto", "off"] - self._swing_list = ["Auto", "1", "2", "3", "Off"] + self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'] + self._operation_list = ['heat', 'cool', 'auto', 'off'] + self._swing_list = ['Auto', '1', '2', '3', 'Off'] self._target_temperature_high = target_temp_high self._target_temperature_low = target_temp_low @property def should_poll(self): - """Polling not needed for a demo climate device.""" + """Return the polling state.""" return False @property @@ -99,7 +99,7 @@ class DemoClimate(ClimateDevice): @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" return self._operation_list @property @@ -124,7 +124,7 @@ class DemoClimate(ClimateDevice): @property def fan_list(self): - """List of available fan modes.""" + """Return the list of available fan modes.""" return self._fan_list def set_temperature(self, **kwargs): diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index c9403fbf2ed..9e04013d53f 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -45,7 +45,7 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Ecobee Thermostat Platform.""" + """Set up the Ecobee Thermostat Platform.""" if discovery_info is None: return data = ecobee.NETWORK @@ -197,19 +197,19 @@ class Thermostat(ClimateDevice): if event['holdClimateRef'] == 'away': if int(event['endDate'][0:4]) - \ int(event['startDate'][0:4]) <= 1: - # a temporary hold from away climate is a hold + # A temporary hold from away climate is a hold return 'away' else: - # a premanent hold from away climate is away_mode + # A permanent hold from away climate is away_mode return None elif event['holdClimateRef'] != "": - # any other hold based on climate + # Any other hold based on climate return event['holdClimateRef'] else: - # any hold not based on a climate is a temp hold + # Any hold not based on a climate is a temp hold return TEMPERATURE_HOLD elif event['type'].startswith('auto'): - # all auto modes are treated as holds + # All auto modes are treated as holds return event['type'][4:].lower() elif event['type'] == 'vacation': self.vacation = event['name'] @@ -278,6 +278,11 @@ class Thermostat(ClimateDevice): """Return true if away mode is on.""" return self.current_hold_mode == 'away' + @property + def is_aux_heat_on(self): + """Return true if aux heater.""" + return 'auxHeat' in self.thermostat['equipmentStatus'] + def turn_away_mode_on(self): """Turn away on.""" self.set_hold_mode('away') @@ -295,17 +300,16 @@ class Thermostat(ClimateDevice): return elif hold_mode == 'None' or hold_mode is None: if hold == VACATION_HOLD: - self.data.ecobee.delete_vacation(self.thermostat_index, - self.vacation) + self.data.ecobee.delete_vacation( + self.thermostat_index, self.vacation) else: self.data.ecobee.resume_program(self.thermostat_index) else: if hold_mode == TEMPERATURE_HOLD: self.set_temp_hold(int(self.current_temperature)) else: - self.data.ecobee.set_climate_hold(self.thermostat_index, - hold_mode, - self.hold_preference()) + self.data.ecobee.set_climate_hold( + self.thermostat_index, hold_mode, self.hold_preference()) self.update_without_throttle = True def set_auto_temp_hold(self, heat_temp, cool_temp): @@ -351,7 +355,7 @@ class Thermostat(ClimateDevice): self.set_temp_hold(int(temp)) else: _LOGGER.error( - 'Missing valid arguments for set_temperature in %s', kwargs) + "Missing valid arguments for set_temperature in %s", kwargs) def set_operation_mode(self, operation_mode): """Set HVAC mode (auto, auxHeatOnly, cool, heat, off).""" @@ -360,14 +364,14 @@ class Thermostat(ClimateDevice): def set_fan_min_on_time(self, fan_min_on_time): """Set the minimum fan on time.""" - self.data.ecobee.set_fan_min_on_time(self.thermostat_index, - fan_min_on_time) + self.data.ecobee.set_fan_min_on_time( + self.thermostat_index, fan_min_on_time) self.update_without_throttle = True def resume_program(self, resume_all): """Resume the thermostat schedule program.""" - self.data.ecobee.resume_program(self.thermostat_index, - str(resume_all).lower()) + self.data.ecobee.resume_program( + self.thermostat_index, str(resume_all).lower()) self.update_without_throttle = True def hold_preference(self): diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 7e0712647ea..ff13dd48cac 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -21,15 +21,15 @@ REQUIREMENTS = ['python-eq3bt==0.1.5'] _LOGGER = logging.getLogger(__name__) -STATE_BOOST = "boost" -STATE_AWAY = "away" -STATE_MANUAL = "manual" +STATE_BOOST = 'boost' +STATE_AWAY = 'away' +STATE_MANUAL = 'manual' -ATTR_STATE_WINDOW_OPEN = "window_open" -ATTR_STATE_VALVE = "valve" -ATTR_STATE_LOCKED = "is_locked" -ATTR_STATE_LOW_BAT = "low_battery" -ATTR_STATE_AWAY_END = "away_end" +ATTR_STATE_WINDOW_OPEN = 'window_open' +ATTR_STATE_VALVE = 'valve' +ATTR_STATE_LOCKED = 'is_locked' +ATTR_STATE_LOW_BAT = 'low_battery' +ATTR_STATE_AWAY_END = 'away_end' DEVICE_SCHEMA = vol.Schema({ vol.Required(CONF_MAC): cv.string, @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the eQ-3 BLE thermostats.""" + """Set up the eQ-3 BLE thermostats.""" devices = [] for name, device_cfg in config[CONF_DEVICES].items(): @@ -112,14 +112,14 @@ class EQ3BTSmartThermostat(ClimateDevice): @property def current_operation(self): - """Current mode.""" + """Return the current operation mode.""" if self._thermostat.mode < 0: return None return self.modes[self._thermostat.mode] @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" return [x for x in self.modes.values()] def set_operation_mode(self, operation_mode): diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 4fc667a5326..11400466c4f 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -14,7 +14,8 @@ from homeassistant.components import switch from homeassistant.components.climate import ( STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE) + ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, + CONF_NAME) from homeassistant.helpers import condition from homeassistant.helpers.event import ( async_track_state_change, async_track_time_interval) @@ -27,7 +28,6 @@ DEPENDENCIES = ['switch', 'sensor'] DEFAULT_TOLERANCE = 0.3 DEFAULT_NAME = 'Generic Thermostat' -CONF_NAME = 'name' CONF_HEATER = 'heater' CONF_SENSOR = 'target_sensor' CONF_MIN_TEMP = 'min_temp' @@ -56,7 +56,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the generic thermostat.""" + """Set up the generic thermostat platform.""" name = config.get(CONF_NAME) heater_entity_id = config.get(CONF_HEATER) sensor_entity_id = config.get(CONF_SENSOR) @@ -74,7 +74,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class GenericThermostat(ClimateDevice): - """Representation of a GenericThermostat device.""" + """Representation of a Generic Thermostat device.""" def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, @@ -110,7 +110,7 @@ class GenericThermostat(ClimateDevice): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property @@ -175,7 +175,7 @@ class GenericThermostat(ClimateDevice): @asyncio.coroutine def _async_sensor_changed(self, entity_id, old_state, new_state): - """Called when temperature changes.""" + """Handle temperature changes.""" if new_state is None: return @@ -185,14 +185,14 @@ class GenericThermostat(ClimateDevice): @callback def _async_switch_changed(self, entity_id, old_state, new_state): - """Called when heater switch changes state.""" + """Handle heater switch state changes.""" if new_state is None: return self.hass.async_add_job(self.async_update_ha_state()) @callback def _async_keep_alive(self, time): - """Called at constant intervals for keep-alive purposes.""" + """Call at constant intervals for keep-alive purposes.""" if self.current_operation in [STATE_COOL, STATE_HEAT]: switch.async_turn_on(self.hass, self.heater_entity_id) else: diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py index 28d419597d3..56015ebeb5a 100644 --- a/homeassistant/components/climate/heatmiser.py +++ b/homeassistant/components/climate/heatmiser.py @@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the heatmiser thermostat.""" + """Set up the heatmiser thermostat.""" from heatmiserV3 import heatmiser, connection ipaddress = config.get(CONF_IPADDRESS) diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index 7385eeac98a..1f9ca7c1abf 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -14,17 +14,17 @@ DEPENDENCIES = ['homematic'] _LOGGER = logging.getLogger(__name__) -STATE_MANUAL = "manual" -STATE_BOOST = "boost" -STATE_COMFORT = "comfort" -STATE_LOWERING = "lowering" +STATE_MANUAL = 'manual' +STATE_BOOST = 'boost' +STATE_COMFORT = 'comfort' +STATE_LOWERING = 'lowering' HM_STATE_MAP = { - "AUTO_MODE": STATE_AUTO, - "MANU_MODE": STATE_MANUAL, - "BOOST_MODE": STATE_BOOST, - "COMFORT_MODE": STATE_COMFORT, - "LOWERING_MODE": STATE_LOWERING + 'AUTO_MODE': STATE_AUTO, + 'MANU_MODE': STATE_MANUAL, + 'BOOST_MODE': STATE_BOOST, + 'COMFORT_MODE': STATE_COMFORT, + 'LOWERING_MODE': STATE_LOWERING } HM_TEMP_MAP = [ @@ -41,7 +41,7 @@ HM_CONTROL_MODE = 'CONTROL_MODE' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Homematic thermostat platform.""" + """Set up the Homematic thermostat platform.""" if discovery_info is None: return @@ -76,10 +76,9 @@ class HMThermostat(HMDevice, ClimateDevice): @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" op_list = [] - # generate list for mode in self._hmdevice.ACTIONNODE: if mode in HM_STATE_MAP: op_list.append(HM_STATE_MAP.get(mode)) @@ -132,11 +131,9 @@ class HMThermostat(HMDevice, ClimateDevice): def _init_data_struct(self): """Generate a data dict (self._data) from the Homematic metadata.""" - # Add state to data dict self._state = next(iter(self._hmdevice.WRITENODE.keys())) self._data[self._state] = STATE_UNKNOWN - # support state if HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE: self._data[HM_CONTROL_MODE] = STATE_UNKNOWN diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 5152519459b..94c48258bf5 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -8,13 +8,12 @@ import logging import socket import datetime -import voluptuous as vol import requests +import voluptuous as vol -from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA, - ATTR_FAN_MODE, ATTR_FAN_LIST, - ATTR_OPERATION_MODE, - ATTR_OPERATION_LIST) +from homeassistant.components.climate import ( + ClimateDevice, PLATFORM_SCHEMA, ATTR_FAN_MODE, ATTR_FAN_LIST, + ATTR_OPERATION_MODE, ATTR_OPERATION_LIST) from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) @@ -54,7 +53,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Honeywell thermostat.""" + """Set up the Honeywell thermostat.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) region = config.get(CONF_REGION) @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def _setup_round(username, password, config, add_devices): - """Setup rounding function.""" + """Set up the rounding function.""" from evohomeclient import EvohomeClient away_temp = config.get(CONF_AWAY_TEMPERATURE) @@ -87,16 +86,16 @@ def _setup_round(username, password, config, add_devices): # config will be used later def _setup_us(username, password, config, add_devices): - """Setup user.""" + """Set up the user.""" import somecomfort try: client = somecomfort.SomeComfort(username, password) except somecomfort.AuthError: - _LOGGER.error('Failed to login to honeywell account %s', username) + _LOGGER.error("Failed to login to honeywell account %s", username) return False except somecomfort.SomeComfortError as ex: - _LOGGER.error('Failed to initialize honeywell client: %s', str(ex)) + _LOGGER.error("Failed to initialize honeywell client: %s", str(ex)) return False dev_id = config.get('thermostat') @@ -199,7 +198,7 @@ class RoundThermostat(ClimateDevice): except StopIteration: _LOGGER.error("Did not receive any temperature data from the " - "evohomeclient API.") + "evohomeclient API") return self._current_temperature = data['temp'] @@ -289,7 +288,7 @@ class HoneywellUSThermostat(ClimateDevice): "setpoint_{}".format(mode), temperature) except somecomfort.SomeComfortError: - _LOGGER.error('Temperature %.1f out of range', temperature) + _LOGGER.error("Temperature %.1f out of range", temperature) @property def device_state_attributes(self): @@ -369,8 +368,8 @@ class HoneywellUSThermostat(ClimateDevice): raise exp if not self._retry(): raise exp - _LOGGER.error("SomeComfort update failed, Retrying " - "- Error: %s", exp) + _LOGGER.error( + "SomeComfort update failed, Retrying - Error: %s", exp) def _retry(self): """Recreate a new somecomfort client. @@ -380,14 +379,14 @@ class HoneywellUSThermostat(ClimateDevice): """ import somecomfort try: - self._client = somecomfort.SomeComfort(self._username, - self._password) + self._client = somecomfort.SomeComfort( + self._username, self._password) except somecomfort.AuthError: - _LOGGER.error('Failed to login to honeywell account %s', + _LOGGER.error("Failed to login to honeywell account %s", self._username) return False except somecomfort.SomeComfortError as ex: - _LOGGER.error('Failed to initialize honeywell client: %s', + _LOGGER.error("Failed to initialize honeywell client: %s", str(ex)) return False @@ -397,7 +396,7 @@ class HoneywellUSThermostat(ClimateDevice): if device.name == self._device.name] if len(devices) != 1: - _LOGGER.error('Failed to find device %s', self._device.name) + _LOGGER.error("Failed to find device %s", self._device.name) return False self._device = devices[0] diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index 888a217d90c..e399e2f3dca 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -61,7 +61,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): @property def should_poll(self): - """Polling is needed for the KNX thermostat.""" + """Return the polling state, is needed for the KNX thermostat.""" return True @property diff --git a/homeassistant/components/climate/maxcube.py b/homeassistant/components/climate/maxcube.py index a04a547f534..9d6c1bbab5b 100644 --- a/homeassistant/components/climate/maxcube.py +++ b/homeassistant/components/climate/maxcube.py @@ -4,7 +4,6 @@ Support for MAX! Thermostats via MAX! Cube. For more details about this platform, please refer to the documentation https://home-assistant.io/components/maxcube/ """ - import socket import logging @@ -15,29 +14,25 @@ from homeassistant.const import STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) -STATE_MANUAL = "manual" -STATE_BOOST = "boost" -STATE_VACATION = "vacation" +STATE_MANUAL = 'manual' +STATE_BOOST = 'boost' +STATE_VACATION = 'vacation' def setup_platform(hass, config, add_devices, discovery_info=None): - """Iterate through all MAX! Devices and add thermostats to HASS.""" + """Iterate through all MAX! Devices and add thermostats.""" cube = hass.data[MAXCUBE_HANDLE].cube - # List of devices devices = [] for device in cube.devices: - # Create device name by concatenating room name + device name - name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name) + name = '{} {}'.format( + cube.room_by_id(device.room_id).name, device.name) - # Only add thermostats and wallthermostats if cube.is_thermostat(device) or cube.is_wallthermostat(device): - # Add device to HASS devices.append(MaxCubeClimate(hass, name, device.rf_address)) - # Add all devices at once - if len(devices) > 0: + if devices: add_devices(devices) @@ -55,30 +50,24 @@ class MaxCubeClimate(ClimateDevice): @property def should_poll(self): - """Polling is required.""" + """Return the polling state.""" return True @property def name(self): - """Return the name of the ClimateDevice.""" + """Return the name of the climate device.""" return self._name @property def min_temp(self): """Return the minimum temperature.""" - # Get the device we want (does not do any IO, just reads from memory) device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Map and return minimum temperature return self.map_temperature_max_hass(device.min_temperature) @property def max_temp(self): """Return the maximum temperature.""" - # Get the device we want (does not do any IO, just reads from memory) device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Map and return maximum temperature return self.map_temperature_max_hass(device.max_temperature) @property @@ -89,7 +78,6 @@ class MaxCubeClimate(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - # Get the device we want (does not do any IO, just reads from memory) device = self._cubehandle.cube.device_by_rf(self._rf_address) # Map and return current temperature @@ -98,36 +86,26 @@ class MaxCubeClimate(ClimateDevice): @property def current_operation(self): """Return current operation (auto, manual, boost, vacation).""" - # Get the device we want (does not do any IO, just reads from memory) device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Mode Mapping return self.map_mode_max_hass(device.mode) @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" return self._operation_list @property def target_temperature(self): """Return the temperature we try to reach.""" - # Get the device we want (does not do any IO, just reads from memory) device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Map and return target temperature return self.map_temperature_max_hass(device.target_temperature) def set_temperature(self, **kwargs): """Set new target temperatures.""" - # Fail is target temperature has not been supplied as argument if kwargs.get(ATTR_TEMPERATURE) is None: return False - # Determine the new target temperature target_temperature = kwargs.get(ATTR_TEMPERATURE) - - # Write the target temperature to the MAX! Cube. device = self._cubehandle.cube.device_by_rf(self._rf_address) cube = self._cubehandle.cube @@ -141,13 +119,9 @@ class MaxCubeClimate(ClimateDevice): def set_operation_mode(self, operation_mode): """Set new operation mode.""" - # Get the device we want to update device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Mode Mapping mode = self.map_mode_hass_max(operation_mode) - # Write new mode to thermostat if mode is None: return False @@ -160,7 +134,6 @@ class MaxCubeClimate(ClimateDevice): def update(self): """Get latest data from MAX! Cube.""" - # Update the CubeHandle self._cubehandle.update() @staticmethod @@ -173,7 +146,7 @@ class MaxCubeClimate(ClimateDevice): @staticmethod def map_mode_hass_max(operation_mode): - """Map HASS Operation Modes to MAX! Operation Modes.""" + """Map Home Assistant Operation Modes to MAX! Operation Modes.""" from maxcube.device import \ MAX_DEVICE_MODE_AUTOMATIC, \ MAX_DEVICE_MODE_MANUAL, \ @@ -195,7 +168,7 @@ class MaxCubeClimate(ClimateDevice): @staticmethod def map_mode_max_hass(mode): - """Map MAX! Operation Modes to HASS Operation Modes.""" + """Map MAX! Operation Modes to Home Assistant Operation Modes.""" from maxcube.device import \ MAX_DEVICE_MODE_AUTOMATIC, \ MAX_DEVICE_MODE_MANUAL, \ diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/climate/mysensors.py index 02979e75f5f..adf44a81829 100755 --- a/homeassistant/components/climate/mysensors.py +++ b/homeassistant/components/climate/mysensors.py @@ -1,8 +1,8 @@ """ -mysensors platform that offers a Climate(MySensors-HVAC) component. +MySensors platform that offers a Climate (MySensors-HVAC) component. For more details about this platform, please refer to the documentation -https://home-assistant.io/components/climate.mysensors +https://home-assistant.io/components/climate.mysensors/ """ import logging @@ -14,14 +14,22 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE _LOGGER = logging.getLogger(__name__) -DICT_HA_TO_MYS = {STATE_COOL: "CoolOn", STATE_HEAT: "HeatOn", - STATE_AUTO: "AutoChangeOver", STATE_OFF: "Off"} -DICT_MYS_TO_HA = {"CoolOn": STATE_COOL, "HeatOn": STATE_HEAT, - "AutoChangeOver": STATE_AUTO, "Off": STATE_OFF} +DICT_HA_TO_MYS = { + STATE_AUTO: 'AutoChangeOver', + STATE_COOL: 'CoolOn', + STATE_HEAT: 'HeatOn', + STATE_OFF: 'Off', +} +DICT_MYS_TO_HA = { + 'AutoChangeOver': STATE_AUTO, + 'CoolOn': STATE_COOL, + 'HeatOn': STATE_HEAT, + 'Off': STATE_OFF, +} def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors climate.""" + """Set up the mysensors climate.""" if discovery_info is None: return @@ -43,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): - """Representation of a MySensorsHVAC hvac.""" + """Representation of a MySensors HVAC.""" @property def assumed_state(self): @@ -105,7 +113,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): @property def fan_list(self): """List of available fan modes.""" - return ["Auto", "Min", "Normal", "Max"] + return ['Auto', 'Min', 'Normal', 'Max'] def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -140,8 +148,8 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): def set_fan_mode(self, fan): """Set new target temperature.""" set_req = self.gateway.const.SetReq - self.gateway.set_child_value(self.node_id, self.child_id, - set_req.V_HVAC_SPEED, fan) + self.gateway.set_child_value( + self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan) if self.gateway.optimistic: # optimistically assume that switch has changed state self._values[set_req.V_HVAC_SPEED] = fan @@ -150,9 +158,9 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): def set_operation_mode(self, operation_mode): """Set new target temperature.""" set_req = self.gateway.const.SetReq - self.gateway.set_child_value(self.node_id, self.child_id, - set_req.V_HVAC_FLOW_STATE, - DICT_HA_TO_MYS[operation_mode]) + self.gateway.set_child_value( + self.node_id, self.child_id, set_req.V_HVAC_FLOW_STATE, + DICT_HA_TO_MYS[operation_mode]) if self.gateway.optimistic: # optimistically assume that switch has changed state self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode @@ -165,7 +173,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice): child = node.children[self.child_id] for value_type, value in child.values.items(): _LOGGER.debug( - '%s: value_type %s, value = %s', self._name, value_type, value) + "%s: value_type %s, value = %s", self._name, value_type, value) if value_type == set_req.V_HVAC_FLOW_STATE: self._values[value_type] = DICT_MYS_TO_HA[value] else: diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index 32cae5c4cec..522a1bebb16 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -30,12 +30,10 @@ STATE_HEAT_COOL = 'heat-cool' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Nest thermostat.""" + """Set up the Nest thermostat.""" if discovery_info is None: return - _LOGGER.debug("Setting up nest thermostat") - temp_unit = hass.config.units.temperature_unit add_devices( diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py index 0afc8c29bd9..cea1a41ec9f 100755 --- a/homeassistant/components/climate/netatmo.py +++ b/homeassistant/components/climate/netatmo.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_callback_devices, discovery_info=None): - """Setup the NetAtmo Thermostat.""" + """Set up the NetAtmo Thermostat.""" netatmo = get_component('netatmo') device = config.get(CONF_RELAY) @@ -150,7 +150,6 @@ class ThermostatData(object): self.current_temperature = None self.target_temperature = None self.setpoint_mode = None - # self.operation = def get_module_names(self): """Return all module available on the API as a list.""" diff --git a/homeassistant/components/climate/oem.py b/homeassistant/components/climate/oem.py index 0280f116f1e..fd43ff799db 100644 --- a/homeassistant/components/climate/oem.py +++ b/homeassistant/components/climate/oem.py @@ -19,15 +19,12 @@ from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, TEMP_CELSIUS, CONF_NAME) import homeassistant.helpers.config_validation as cv -# Home Assistant depends on 3rd party packages for API specific code. REQUIREMENTS = ['oemthermostat==1.1'] _LOGGER = logging.getLogger(__name__) -# Local configs CONF_AWAY_TEMP = 'away_temp' -# Validation of the user's configuration PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default="Thermostat"): cv.string, @@ -39,11 +36,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup oemthermostat.""" + """Set up the oemthermostat platform.""" from oemthermostat import Thermostat - # Assign configuration variables. The configuration check takes care they - # are present. name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -51,20 +46,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config.get(CONF_PASSWORD) away_temp = config.get(CONF_AWAY_TEMP) - # If creating the class raises an exception, it failed to connect or - # something else went wrong. try: - therm = Thermostat(host, port=port, - username=username, password=password) + therm = Thermostat( + host, port=port, username=username, password=password) except (ValueError, AssertionError, requests.RequestException): return False - # Add devices add_devices((ThermostatDevice(hass, therm, name, away_temp), ), True) class ThermostatDevice(ClimateDevice): - """Interface class for the oemthermostat module and HA.""" + """Interface class for the oemthermostat modul.""" def __init__(self, hass, thermostat, name, away_temp): """Initialize the device.""" @@ -87,12 +79,12 @@ class ThermostatDevice(ClimateDevice): @property def name(self): - """Name of this Thermostat.""" + """Return the name of this Thermostat.""" return self._name @property def temperature_unit(self): - """The unit of measurement used by the platform.""" + """Return the unit of measurement used by the platform.""" return TEMP_CELSIUS @property @@ -114,7 +106,7 @@ class ThermostatDevice(ClimateDevice): return self._setpoint def set_temperature(self, **kwargs): - """Change the setpoint of the thermostat.""" + """Set the temperature.""" # If we are setting the temp, then we don't want away mode anymore. self.turn_away_mode_off() diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index ef6553cc062..f168df04158 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Proliphix thermostats.""" + """Set up the Proliphix thermostats.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) host = config.get(CONF_HOST) @@ -43,13 +43,12 @@ class ProliphixThermostat(ClimateDevice): def __init__(self, pdp): """Initialize the thermostat.""" self._pdp = pdp - # initial data self._pdp.update() self._name = self._pdp.name @property def should_poll(self): - """Polling needed for thermostat.""" + """Set up polling needed for thermostat.""" return True def update(self): diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 9a0e5666036..5bbed953047 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -40,7 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Radio Thermostat.""" + """Set up the Radio Thermostat.""" import radiotherm hosts = [] diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py new file mode 100644 index 00000000000..afb04fa3c91 --- /dev/null +++ b/homeassistant/components/climate/sensibo.py @@ -0,0 +1,257 @@ +""" +Support for Sensibo wifi-enabled home thermostats. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.sensibo/ +""" + +import asyncio +import logging + +import aiohttp +import async_timeout +import voluptuous as vol + +from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.components.climate import ( + ATTR_CURRENT_HUMIDITY, ClimateDevice, PLATFORM_SCHEMA) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.util.temperature import convert as convert_temperature + +REQUIREMENTS = ['pysensibo==1.0.1'] + +_LOGGER = logging.getLogger(__name__) + +ALL = 'all' +TIMEOUT = 10 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_ID, default=ALL): vol.All(cv.ensure_list, [cv.string]), +}) + +_FETCH_FIELDS = ','.join([ + 'room{name}', 'measurements', 'remoteCapabilities', + 'acState', 'connectionStatus{isAlive}']) +_INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up Sensibo devices.""" + import pysensibo + + client = pysensibo.SensiboClient( + config[CONF_API_KEY], session=async_get_clientsession(hass), + timeout=TIMEOUT) + devices = [] + try: + for dev in ( + yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)): + if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]: + devices.append(SensiboClimate(client, dev)) + except aiohttp.client_exceptions.ClientConnectorError: + _LOGGER.exception('Failed to connct to Sensibo servers.') + return False + + if devices: + async_add_devices(devices) + + +class SensiboClimate(ClimateDevice): + """Representation os a Sensibo device.""" + + def __init__(self, client, data): + """Build SensiboClimate. + + client: aiohttp session. + data: initially-fetched data. + """ + self._client = client + self._id = data['id'] + self._do_update(data) + + def _do_update(self, data): + self._name = data['room']['name'] + self._measurements = data['measurements'] + self._ac_states = data['acState'] + self._status = data['connectionStatus']['isAlive'] + capabilities = data['remoteCapabilities'] + self._operations = sorted(capabilities['modes'].keys()) + self._current_capabilities = capabilities[ + 'modes'][self.current_operation] + temperature_unit_key = self._ac_states['temperatureUnit'] + self._temperature_unit = \ + TEMP_CELSIUS if temperature_unit_key == 'C' else TEMP_FAHRENHEIT + self._temperatures_list = self._current_capabilities[ + 'temperatures'][temperature_unit_key]['values'] + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return {ATTR_CURRENT_HUMIDITY: self.current_humidity} + + @property + def temperature_unit(self): + """Return the unit of measurement which this thermostat uses.""" + return self._temperature_unit + + @property + def available(self): + """Return True if entity is available.""" + return self._status + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._ac_states['targetTemperature'] + + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + if self.temperature_unit == self.unit_of_measurement: + # We are working in same units as the a/c unit. Use whole degrees + # like the API supports. + return 1 + else: + # Unit conversion is going on. No point to stick to specific steps. + return None + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return self._ac_states['mode'] + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._measurements['humidity'] + + @property + def current_temperature(self): + """Return the current temperature.""" + # This field is not affected by temperature_unit. + # It is always in C / nativeTemperatureUnit + if 'nativeTemperatureUnit' not in self._ac_states: + return self._measurements['temperature'] + return convert_temperature( + self._measurements['temperature'], + TEMP_CELSIUS, + self.temperature_unit) + + @property + def operation_list(self): + """List of available operation modes.""" + return self._operations + + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self._ac_states['fanLevel'] + + @property + def fan_list(self): + """List of available fan modes.""" + return self._current_capabilities['fanLevels'] + + @property + def current_swing_mode(self): + """Return the fan setting.""" + return self._ac_states['swing'] + + @property + def swing_list(self): + """List of available swing modes.""" + return self._current_capabilities['swing'] + + @property + def name(self): + """Return the name of the entity.""" + return self._name + + @property + def is_aux_heat_on(self): + """Return true if AC is on.""" + return self._ac_states['on'] + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self._temperatures_list[0] + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self._temperatures_list[-1] + + @asyncio.coroutine + def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is None: + return + temperature = int(temperature) + if temperature not in self._temperatures_list: + # Requested temperature is not supported. + if temperature == self.target_temperature: + return + index = self._temperatures_list.index(self.target_temperature) + if temperature > self.target_temperature and index < len( + self._temperatures_list) - 1: + temperature = self._temperatures_list[index + 1] + elif temperature < self.target_temperature and index > 0: + temperature = self._temperatures_list[index - 1] + else: + return + + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'targetTemperature', temperature) + + @asyncio.coroutine + def async_set_fan_mode(self, fan): + """Set new target fan mode.""" + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'fanLevel', fan) + + @asyncio.coroutine + def async_set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'mode', operation_mode) + + @asyncio.coroutine + def async_set_swing_mode(self, swing_mode): + """Set new target swing operation.""" + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'swing', swing_mode) + + @asyncio.coroutine + def async_turn_aux_heat_on(self): + """Turn Sensibo unit on.""" + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'on', True) + + @asyncio.coroutine + def async_turn_aux_heat_off(self): + """Turn Sensibo unit on.""" + with async_timeout.timeout(TIMEOUT): + yield from self._client.async_set_ac_state_property( + self._id, 'on', False) + + @asyncio.coroutine + def async_update(self): + """Retrieve latest state.""" + try: + with async_timeout.timeout(TIMEOUT): + data = yield from self._client.async_get_device( + self._id, _FETCH_FIELDS) + self._do_update(data) + except aiohttp.client_exceptions.ClientError: + _LOGGER.warning('Failed to connect to Sensibo servers.') diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/climate/tado.py index 734b13dc7e7..7bc99de26f9 100644 --- a/homeassistant/components/climate/tado.py +++ b/homeassistant/components/climate/tado.py @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): climate_devices.append(create_climate_device( tado, hass, zone, zone['name'], zone['id'])) - if len(climate_devices) > 0: + if climate_devices: add_devices(climate_devices, True) return True else: @@ -87,7 +87,7 @@ class TadoClimate(ClimateDevice): def __init__(self, store, zone_name, zone_id, data_id, min_temp, max_temp, ac_mode, tolerance=0.3): - """Initialization of Tado climate device.""" + """Initialize of Tado climate device.""" self._store = store self._data_id = data_id @@ -113,7 +113,7 @@ class TadoClimate(ClimateDevice): @property def name(self): - """Return the name of the sensor.""" + """Return the name of the device.""" return self.zone_name @property @@ -133,12 +133,12 @@ class TadoClimate(ClimateDevice): @property def operation_list(self): - """List of available operation modes (readable).""" + """Return the list of available operation modes (readable).""" return list(OPERATION_LIST.values()) @property def temperature_unit(self): - """The unit of measurement used by the platform.""" + """Return the unit of measurement used by the platform.""" return self._unit @property diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index 79f10bc0421..06325ae0561 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -20,12 +20,12 @@ DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) -OPERATION_LIST = ["Heat", "Cool", "Auto Changeover", "Off"] -FAN_OPERATION_LIST = ["On", "Auto", "Cycle"] +OPERATION_LIST = ['Heat', 'Cool', 'Auto Changeover', 'Off'] +FAN_OPERATION_LIST = ['On', 'Auto', 'Cycle'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Find and return Vera thermostats.""" + """Set up of Vera thermostats.""" add_devices_callback( VeraThermostat(device, VERA_CONTROLLER) for device in VERA_DEVICES['climate']) @@ -43,19 +43,19 @@ class VeraThermostat(VeraDevice, ClimateDevice): def current_operation(self): """Return current operation ie. heat, cool, idle.""" mode = self.vera_device.get_hvac_mode() - if mode == "HeatOn": + if mode == 'HeatOn': return OPERATION_LIST[0] # heat - elif mode == "CoolOn": + elif mode == 'CoolOn': return OPERATION_LIST[1] # cool - elif mode == "AutoChangeOver": + elif mode == 'AutoChangeOver': return OPERATION_LIST[2] # auto - elif mode == "Off": + elif mode == 'Off': return OPERATION_LIST[3] # off - return "Off" + return 'Off' @property def operation_list(self): - """List of available operation modes.""" + """Return the list of available operation modes.""" return OPERATION_LIST @property @@ -72,7 +72,7 @@ class VeraThermostat(VeraDevice, ClimateDevice): @property def fan_list(self): - """List of available fan modes.""" + """Return a list of available fan modes.""" return FAN_OPERATION_LIST def set_fan_mode(self, mode): @@ -86,13 +86,13 @@ class VeraThermostat(VeraDevice, ClimateDevice): @property def current_power_w(self): - """Current power usage in W.""" + """Return the current power usage in W.""" power = self.vera_device.power if power: return convert(power, float, 0.0) def update(self): - """Called by the vera device callback to update state.""" + """Handle state updates.""" self._state = self.vera_device.get_hvac_mode() @property diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py index df29768b3ff..256af2d013c 100644 --- a/homeassistant/components/climate/wink.py +++ b/homeassistant/components/climate/wink.py @@ -30,7 +30,7 @@ ATTR_OCCUPIED = "occupied" def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink thermostat.""" + """Set up the Wink thermostat.""" import pywink temp_unit = hass.config.units.temperature_unit for climate in pywink.get_thermostats(): @@ -449,7 +449,7 @@ class WinkAC(WinkDevice, ClimateDevice): @property def fan_list(self): - """List of available fan modes.""" + """Return a list of available fan modes.""" return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] def set_fan_mode(self, mode): diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 74fadb8c5fd..949bd10e0c2 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -33,7 +33,7 @@ DEVICE_MAPPINGS = { def get_device(hass, values, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" temp_unit = hass.config.units.temperature_unit return ZWaveClimate(values, temp_unit) @@ -65,13 +65,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): int(self.node.product_id, 16)) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: - _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat" - " workaround") + _LOGGER.debug( + "Remotec ZXT-120 Zwave Thermostat workaround") self._zxt_120 = 1 self.update_properties() def update_properties(self): - """Callback on data changes for node values.""" + """Handle the data changes for node values.""" # Operation Mode if self.values.mode: self._current_operation = self.values.mode.data @@ -134,7 +134,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): @property def fan_list(self): - """List of available fan modes.""" + """Return a list of available fan modes.""" return self._fan_list @property @@ -144,7 +144,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): @property def swing_list(self): - """List of available swing modes.""" + """Return a list of available swing modes.""" return self._swing_list @property @@ -169,7 +169,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): @property def operation_list(self): - """List of available operation modes.""" + """Return a list of available operation modes.""" return self._operation_list @property @@ -189,19 +189,18 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def set_fan_mode(self, fan): """Set new target fan mode.""" if self.values.fan_mode: - self.values.fan_mode.data = bytes(fan, 'utf-8') + self.values.fan_mode.data = fan def set_operation_mode(self, operation_mode): """Set new target operation mode.""" if self.values.mode: - self.values.mode.data = bytes(operation_mode, 'utf-8') + self.values.mode.data = operation_mode def set_swing_mode(self, swing_mode): """Set new target swing mode.""" if self._zxt_120 == 1: if self.values.zxt_120_swing_mode: - self.values.zxt_120_swing_mode.data = bytes( - swing_mode, 'utf-8') + self.values.zxt_120_swing_mode.data = swing_mode @property def device_state_attributes(self): diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index ab175d1d56f..1255043b6b5 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -20,14 +20,14 @@ ON_DEMAND = ('zwave', ) @asyncio.coroutine def async_setup(hass, config): - """Setup the config component.""" + """Set up the config component.""" register_built_in_panel(hass, 'config', 'Configuration', 'mdi:settings') @asyncio.coroutine def setup_panel(panel_name): - """Setup a panel.""" - panel = yield from async_prepare_setup_platform(hass, config, DOMAIN, - panel_name) + """Set up a panel.""" + panel = yield from async_prepare_setup_platform( + hass, config, DOMAIN, panel_name) if not panel: return diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index 15456ee74ad..4ff530ad2bc 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -7,7 +7,7 @@ from homeassistant.config import async_check_ha_config_file @asyncio.coroutine def async_setup(hass): - """Setup the hassbian config.""" + """Set up the Hassbian config.""" hass.http.register_view(CheckConfigView) return True @@ -20,7 +20,7 @@ class CheckConfigView(HomeAssistantView): @asyncio.coroutine def post(self, request): - """Validate config and return results.""" + """Validate configuration and return results.""" errors = yield from async_check_ha_config_file(request.app['hass']) state = 'invalid' if errors else 'valid' diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index 5c0fd23300e..16e1900c645 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -11,9 +11,8 @@ CONFIG_PATH = 'groups.yaml' @asyncio.coroutine def async_setup(hass): - """Setup the Group config API.""" + """Set up the Group config API.""" hass.http.register_view(EditKeyBasedConfigView( - 'group', 'config', CONFIG_PATH, cv.slug, - GROUP_SCHEMA + 'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA )) return True diff --git a/homeassistant/components/config/hassbian.py b/homeassistant/components/config/hassbian.py index e94d6d25b90..8de5f62d915 100644 --- a/homeassistant/components/config/hassbian.py +++ b/homeassistant/components/config/hassbian.py @@ -32,8 +32,8 @@ _TEST_OUTPUT = """ @asyncio.coroutine def async_setup(hass): - """Setup the hassbian config.""" - # Test if is hassbian + """Set up the Hassbian config.""" + # Test if is Hassbian test_mode = 'FORCE_HASSBIAN' in os.environ is_hassbian = test_mode @@ -49,7 +49,7 @@ def async_setup(hass): @asyncio.coroutine def hassbian_status(hass, test_mode=False): """Query for the Hassbian status.""" - # fetch real output when not in test mode + # Fetch real output when not in test mode if test_mode: return json.loads(_TEST_OUTPUT) diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index 5eca596c8f4..576ff09bf2b 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -11,7 +11,7 @@ CONFIG_PATH = 'zwave_device_config.yaml' @asyncio.coroutine def async_setup(hass): - """Setup the Z-Wave config API.""" + """Set up the Z-Wave config API.""" hass.http.register_view(EditKeyBasedConfigView( 'zwave', 'device_config', CONFIG_PATH, cv.entity_id, DEVICE_CONFIG_SCHEMA_ENTRY diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 3376815b9d5..c37f07956e4 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -75,7 +75,7 @@ def request_done(request_id): @asyncio.coroutine def async_setup(hass, config): - """Setup the configurator component.""" + """Set up the configurator component.""" return True @@ -104,7 +104,7 @@ class Configurator(object): self, name, callback, description, description_image, submit_caption, fields, link_name, link_url, entity_picture): - """Setup a request for configuration.""" + """Set up a request for configuration.""" entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) if fields is None: diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index c857793f579..bbee0e836a3 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -24,6 +24,7 @@ from homeassistant.const import ( SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID) +_LOGGER = logging.getLogger(__name__) DOMAIN = 'cover' SCAN_INTERVAL = timedelta(seconds=15) @@ -47,8 +48,6 @@ SUPPORT_CLOSE_TILT = 32 SUPPORT_STOP_TILT = 64 SUPPORT_SET_TILT_POSITION = 128 -_LOGGER = logging.getLogger(__name__) - ATTR_CURRENT_POSITION = 'current_position' ATTR_CURRENT_TILT_POSITION = 'current_tilt_position' ATTR_POSITION = 'position' diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index 778496ec6fc..cd48238cae9 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup cover controlled by shell commands.""" + """Set up cover controlled by shell commands.""" devices = config.get(CONF_COVERS, {}) covers = [] @@ -78,25 +78,25 @@ class CommandCover(CoverDevice): @staticmethod def _move_cover(command): """Execute the actual commands.""" - _LOGGER.info('Running command: %s', command) + _LOGGER.info("Running command: %s", command) success = (subprocess.call(command, shell=True) == 0) if not success: - _LOGGER.error('Command failed: %s', command) + _LOGGER.error("Command failed: %s", command) return success @staticmethod def _query_state_value(command): """Execute state command for return value.""" - _LOGGER.info('Running state command: %s', command) + _LOGGER.info("Running state command: %s", command) try: return_value = subprocess.check_output(command, shell=True) return return_value.strip().decode('utf-8') except subprocess.CalledProcessError: - _LOGGER.error('Command failed: %s', command) + _LOGGER.error("Command failed: %s", command) @property def should_poll(self): @@ -128,7 +128,7 @@ class CommandCover(CoverDevice): def _query_state(self): """Query for the state.""" if not self._command_state: - _LOGGER.error('No state command specified') + _LOGGER.error("No state command specified") return return self._query_state_value(self._command_state) diff --git a/homeassistant/components/cover/demo.py b/homeassistant/components/cover/demo.py index 8a8a675703a..35574150596 100644 --- a/homeassistant/components/cover/demo.py +++ b/homeassistant/components/cover/demo.py @@ -10,7 +10,7 @@ from homeassistant.helpers.event import track_utc_time_change def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo covers.""" + """Set up the Demo covers.""" add_devices([ DemoCover(hass, 'Kitchen Window'), DemoCover(hass, 'Hall Window', 10), diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/cover/garadget.py index 04df954aac6..457312c8a4a 100644 --- a/homeassistant/components/cover/garadget.py +++ b/homeassistant/components/cover/garadget.py @@ -1,73 +1,68 @@ """ -Platform for the garadget cover component. +Platform for the Garadget cover component. For more details about this platform, please refer to the documentation https://home-assistant.io/components/garadget/ """ import logging +import requests import voluptuous as vol -import requests - +import homeassistant.helpers.config_validation as cv from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.helpers.event import track_utc_time_change -from homeassistant.const import CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD,\ - CONF_ACCESS_TOKEN, CONF_NAME, STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN,\ - CONF_COVERS -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME, + STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, CONF_COVERS) + +_LOGGER = logging.getLogger(__name__) + +ATTR_AVAILABLE = "available" +ATTR_SENSOR_STRENGTH = "sensor reflection rate" +ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)" +ATTR_TIME_IN_STATE = "time in state" DEFAULT_NAME = 'Garadget' -ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)" -ATTR_TIME_IN_STATE = "time in state" -ATTR_SENSOR_STRENGTH = "sensor reflection rate" -ATTR_AVAILABLE = "available" - -STATE_OPENING = "opening" -STATE_CLOSING = "closing" -STATE_STOPPED = "stopped" -STATE_OFFLINE = "offline" +STATE_CLOSING = 'closing' +STATE_OFFLINE = 'offline' +STATE_OPENING = 'opening' +STATE_STOPPED = 'stopped' STATES_MAP = { - "open": STATE_OPEN, - "opening": STATE_OPENING, - "closed": STATE_CLOSED, - "closing": STATE_CLOSING, - "stopped": STATE_STOPPED + 'open': STATE_OPEN, + 'opening': STATE_OPENING, + 'closed': STATE_CLOSED, + 'closing': STATE_CLOSING, + 'stopped': STATE_STOPPED } - -# Validation of the user's configuration COVER_SCHEMA = vol.Schema({ - vol.Optional(CONF_DEVICE): cv.string, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string + vol.Optional(CONF_DEVICE): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), }) -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo covers.""" + """Set up the Garadget covers.""" covers = [] - devices = config.get(CONF_COVERS, {}) - - _LOGGER.debug(devices) + devices = config.get(CONF_COVERS) for device_id, device_config in devices.items(): args = { - "name": device_config.get(CONF_NAME), - "device_id": device_config.get(CONF_DEVICE, device_id), - "username": device_config.get(CONF_USERNAME), - "password": device_config.get(CONF_PASSWORD), - "access_token": device_config.get(CONF_ACCESS_TOKEN) + 'name': device_config.get(CONF_NAME), + 'device_id': device_config.get(CONF_DEVICE, device_id), + 'username': device_config.get(CONF_USERNAME), + 'password': device_config.get(CONF_PASSWORD), + 'access_token': device_config.get(CONF_ACCESS_TOKEN) } covers.append(GaradgetCover(hass, args)) @@ -76,9 +71,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class GaradgetCover(CoverDevice): - """Representation of a demo cover.""" + """Representation of a Garadget cover.""" - # pylint: disable=no-self-use, too-many-instance-attributes + # pylint: disable=no-self-use def __init__(self, hass, args): """Initialize the cover.""" self.particle_url = 'https://api.particle.io' @@ -100,21 +95,20 @@ class GaradgetCover(CoverDevice): self.access_token = self.get_token() self._obtained_token = True - # Lets try to get the configured name if not provided. try: if self._name is None: - doorconfig = self._get_variable("doorConfig") - if doorconfig["nme"] is not None: - self._name = doorconfig["nme"] + doorconfig = self._get_variable('doorConfig') + if doorconfig['nme'] is not None: + self._name = doorconfig['nme'] self.update() except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to server: %(reason)s', - dict(reason=ex)) + _LOGGER.error( + "Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE self._available = False self._name = DEFAULT_NAME except KeyError as ex: - _LOGGER.warning('Garadget device %(device)s seems to be offline', + _LOGGER.warning("Garadget device %(device)s seems to be offline", dict(device=self.device_id)) self._name = DEFAULT_NAME self._state = STATE_OFFLINE @@ -181,18 +175,20 @@ class GaradgetCover(CoverDevice): 'password': self._password } url = '{}/oauth/token'.format(self.particle_url) - ret = requests.post(url, - auth=('particle', 'particle'), - data=args) + ret = requests.post( + url, auth=('particle', 'particle'), data=args, timeout=10) - return ret.json()['access_token'] + try: + return ret.json()['access_token'] + except KeyError: + _LOGGER.error("Unable to retrieve access token") def remove_token(self): """Remove authorization token from API.""" - ret = requests.delete('{}/v1/access_tokens/{}'.format( - self.particle_url, - self.access_token), - auth=(self._username, self._password)) + url = '{}/v1/access_tokens/{}'.format( + self.particle_url, self.access_token) + ret = requests.delete( + url, auth=(self._username, self._password), timeout=10) return ret.text def _start_watcher(self, command): @@ -208,41 +204,41 @@ class GaradgetCover(CoverDevice): def close_cover(self): """Close the cover.""" - if self._state not in ["close", "closing"]: - ret = self._put_command("setState", "close") + if self._state not in ['close', 'closing']: + ret = self._put_command('setState', 'close') self._start_watcher('close') return ret.get('return_value') == 1 def open_cover(self): """Open the cover.""" - if self._state not in ["open", "opening"]: - ret = self._put_command("setState", "open") + if self._state not in ['open', 'opening']: + ret = self._put_command('setState', 'open') self._start_watcher('open') return ret.get('return_value') == 1 def stop_cover(self): """Stop the door where it is.""" - if self._state not in ["stopped"]: - ret = self._put_command("setState", "stop") + if self._state not in ['stopped']: + ret = self._put_command('setState', 'stop') self._start_watcher('stop') return ret['return_value'] == 1 def update(self): """Get updated status from API.""" try: - status = self._get_variable("doorStatus") + status = self._get_variable('doorStatus') _LOGGER.debug("Current Status: %s", status['status']) self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN) self.time_in_state = status['time'] self.signal = status['signal'] self.sensor = status['sensor'] - self._availble = True + self._available = True except requests.exceptions.ConnectionError as ex: - _LOGGER.error('Unable to connect to server: %(reason)s', - dict(reason=ex)) + _LOGGER.error( + "Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE except KeyError as ex: - _LOGGER.warning('Garadget device %(device)s seems to be offline', + _LOGGER.warning("Garadget device %(device)s seems to be offline", dict(device=self.device_id)) self._state = STATE_OFFLINE @@ -254,12 +250,8 @@ class GaradgetCover(CoverDevice): def _get_variable(self, var): """Get latest status.""" url = '{}/v1/devices/{}/{}?access_token={}'.format( - self.particle_url, - self.device_id, - var, - self.access_token, - ) - ret = requests.get(url) + self.particle_url, self.device_id, var, self.access_token) + ret = requests.get(url, timeout=10) result = {} for pairs in ret.json()['result'].split('|'): key = pairs.split('=') @@ -272,8 +264,6 @@ class GaradgetCover(CoverDevice): if arg: params['command'] = arg url = '{}/v1/devices/{}/{}'.format( - self.particle_url, - self.device_id, - func) - ret = requests.post(url, data=params) + self.particle_url, self.device_id, func) + ret = requests.post(url, data=params, timeout=10) return ret.json() diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/cover/homematic.py index 8420b8b47e3..ace08f53e3c 100644 --- a/homeassistant/components/cover/homematic.py +++ b/homeassistant/components/cover/homematic.py @@ -3,11 +3,7 @@ The homematic cover platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.homematic/ - -Important: For this platform to work the homematic component has to be -properly configured. """ - import logging from homeassistant.const import STATE_UNKNOWN from homeassistant.components.cover import CoverDevice, ATTR_POSITION @@ -19,7 +15,7 @@ DEPENDENCIES = ['homematic'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the platform.""" + """Set up the platform.""" if discovery_info is None: return @@ -33,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class HMCover(HMDevice, CoverDevice): - """Represents a Homematic Cover in Home Assistant.""" + """Representation a Homematic Cover.""" @property def current_cover_position(self): diff --git a/homeassistant/components/cover/isy994.py b/homeassistant/components/cover/isy994.py index 27619de738d..1e83038278c 100644 --- a/homeassistant/components/cover/isy994.py +++ b/homeassistant/components/cover/isy994.py @@ -12,7 +12,6 @@ import homeassistant.components.isy994 as isy from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN from homeassistant.helpers.typing import ConfigType - _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { @@ -27,15 +26,14 @@ STATES = [STATE_OPEN, STATE_CLOSED, 'closing', 'opening', 'stopped'] # pylint: disable=unused-argument def setup_platform(hass, config: ConfigType, add_devices: Callable[[list], None], discovery_info=None): - """Setup the ISY994 cover platform.""" + """Set up the ISY994 cover platform.""" if isy.ISY is None or not isy.ISY.connected: - _LOGGER.error('A connection has not been made to the ISY controller.') + _LOGGER.error("A connection has not been made to the ISY controller") return False devices = [] - for node in isy.filter_nodes(isy.NODES, units=UOM, - states=STATES): + for node in isy.filter_nodes(isy.NODES, units=UOM, states=STATES): devices.append(ISYCoverDevice(node)) for program in isy.PROGRAMS.get(DOMAIN, []): @@ -60,7 +58,7 @@ class ISYCoverDevice(isy.ISYDevice, CoverDevice): @property def current_cover_position(self) -> int: - """Get the current cover position.""" + """Return the current cover position.""" return sorted((0, self.value, 100))[1] @property @@ -76,12 +74,12 @@ class ISYCoverDevice(isy.ISYDevice, CoverDevice): def open_cover(self, **kwargs) -> None: """Send the open cover command to the ISY994 cover device.""" if not self._node.on(val=100): - _LOGGER.error('Unable to open the cover') + _LOGGER.error("Unable to open the cover") def close_cover(self, **kwargs) -> None: """Send the close cover command to the ISY994 cover device.""" if not self._node.off(): - _LOGGER.error('Unable to close the cover') + _LOGGER.error("Unable to close the cover") class ISYCoverProgram(ISYCoverDevice): @@ -101,9 +99,9 @@ class ISYCoverProgram(ISYCoverDevice): def open_cover(self, **kwargs) -> None: """Send the open cover command to the ISY994 cover program.""" if not self._actions.runThen(): - _LOGGER.error('Unable to open the cover') + _LOGGER.error("Unable to open the cover") def close_cover(self, **kwargs) -> None: """Send the close cover command to the ISY994 cover program.""" if not self._actions.runElse(): - _LOGGER.error('Unable to close the cover') + _LOGGER.error("Unable to close the cover") diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 6403e0bbc85..829c3748d2f 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -11,23 +11,35 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.components.mqtt as mqtt -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import ( + CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT, + SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, + SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION) from homeassistant.const import ( CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN, - STATE_CLOSED) + STATE_CLOSED, STATE_UNKNOWN) from homeassistant.components.mqtt import ( - CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) + CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + valid_publish_topic, valid_subscribe_topic) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] +CONF_TILT_COMMAND_TOPIC = 'tilt_command_topic' +CONF_TILT_STATUS_TOPIC = 'tilt_status_topic' + CONF_PAYLOAD_OPEN = 'payload_open' CONF_PAYLOAD_CLOSE = 'payload_close' CONF_PAYLOAD_STOP = 'payload_stop' CONF_STATE_OPEN = 'state_open' CONF_STATE_CLOSED = 'state_closed' +CONF_TILT_CLOSED_POSITION = 'tilt_closed_value' +CONF_TILT_OPEN_POSITION = 'tilt_opened_value' +CONF_TILT_MIN = 'tilt_min' +CONF_TILT_MAX = 'tilt_max' +CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic' DEFAULT_NAME = 'MQTT Cover' DEFAULT_PAYLOAD_OPEN = 'OPEN' @@ -35,6 +47,14 @@ DEFAULT_PAYLOAD_CLOSE = 'CLOSE' DEFAULT_PAYLOAD_STOP = 'STOP' DEFAULT_OPTIMISTIC = False DEFAULT_RETAIN = False +DEFAULT_TILT_CLOSED_POSITION = 0 +DEFAULT_TILT_OPEN_POSITION = 100 +DEFAULT_TILT_MIN = 0 +DEFAULT_TILT_MAX = 100 +DEFAULT_TILT_OPTIMISTIC = False + +TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | + SUPPORT_SET_TILT_POSITION) PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -44,12 +64,22 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_TILT_COMMAND_TOPIC, default=None): valid_publish_topic, + vol.Optional(CONF_TILT_STATUS_TOPIC, default=None): valid_subscribe_topic, + vol.Optional(CONF_TILT_CLOSED_POSITION, + default=DEFAULT_TILT_CLOSED_POSITION): int, + vol.Optional(CONF_TILT_OPEN_POSITION, + default=DEFAULT_TILT_OPEN_POSITION): int, + vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int, + vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int, + vol.Optional(CONF_TILT_STATE_OPTIMISTIC, + default=DEFAULT_TILT_OPTIMISTIC): cv.boolean, }) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the MQTT Cover.""" + """Set up the MQTT Cover.""" value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass @@ -58,6 +88,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), + config.get(CONF_TILT_COMMAND_TOPIC), + config.get(CONF_TILT_STATUS_TOPIC), config.get(CONF_QOS), config.get(CONF_RETAIN), config.get(CONF_STATE_OPEN), @@ -67,21 +99,30 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.get(CONF_PAYLOAD_STOP), config.get(CONF_OPTIMISTIC), value_template, + config.get(CONF_TILT_OPEN_POSITION), + config.get(CONF_TILT_CLOSED_POSITION), + config.get(CONF_TILT_MIN), + config.get(CONF_TILT_MAX), + config.get(CONF_TILT_STATE_OPTIMISTIC), )]) class MqttCover(CoverDevice): """Representation of a cover that can be controlled using MQTT.""" - def __init__(self, name, state_topic, command_topic, qos, retain, - state_open, state_closed, payload_open, payload_close, - payload_stop, optimistic, value_template): + def __init__(self, name, state_topic, command_topic, tilt_command_topic, + tilt_status_topic, qos, retain, state_open, state_closed, + payload_open, payload_close, payload_stop, + optimistic, value_template, tilt_open_position, + tilt_closed_position, tilt_min, tilt_max, tilt_optimistic): """Initialize the cover.""" self._position = None self._state = None self._name = name self._state_topic = state_topic self._command_topic = command_topic + self._tilt_command_topic = tilt_command_topic + self._tilt_status_topic = tilt_status_topic self._qos = qos self._payload_open = payload_open self._payload_close = payload_close @@ -89,18 +130,34 @@ class MqttCover(CoverDevice): self._state_open = state_open self._state_closed = state_closed self._retain = retain + self._tilt_open_position = tilt_open_position + self._tilt_closed_position = tilt_closed_position self._optimistic = optimistic or state_topic is None self._template = value_template + self._tilt_value = None + self._tilt_min = tilt_min + self._tilt_max = tilt_max + self._tilt_optimistic = tilt_optimistic @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe MQTT events. This method is a coroutine. """ + @callback + def tilt_updated(topic, payload, qos): + """Handle tilt updates.""" + if (payload.isnumeric() and + self._tilt_min <= int(payload) <= self._tilt_max): + tilt_range = self._tilt_max - self._tilt_min + level = round(float(payload) / tilt_range * 100.0) + self._tilt_value = level + self.hass.async_add_job(self.async_update_ha_state()) + @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT message.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) @@ -130,6 +187,14 @@ class MqttCover(CoverDevice): yield from mqtt.async_subscribe( self.hass, self._state_topic, message_received, self._qos) + if self._tilt_status_topic is None: + self._tilt_optimistic = True + else: + self._tilt_optimistic = False + self._tilt_value = STATE_UNKNOWN + yield from mqtt.async_subscribe( + self.hass, self._tilt_status_topic, tilt_updated, self._qos) + @property def should_poll(self): """No polling needed.""" @@ -153,6 +218,24 @@ class MqttCover(CoverDevice): """ return self._position + @property + def current_cover_tilt_position(self): + """Return current position of cover tilt.""" + return self._tilt_value + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + + if self.current_cover_position is not None: + supported_features |= SUPPORT_SET_POSITION + + if self._tilt_command_topic is not None: + supported_features |= TILT_FEATURES + + return supported_features + @asyncio.coroutine def async_open_cover(self, **kwargs): """Move the cover up. @@ -190,3 +273,37 @@ class MqttCover(CoverDevice): mqtt.async_publish( self.hass, self._command_topic, self._payload_stop, self._qos, self._retain) + + @asyncio.coroutine + def async_open_cover_tilt(self, **kwargs): + """Tilt the cover open.""" + mqtt.async_publish(self.hass, self._tilt_command_topic, + self._tilt_open_position, self._qos, self._retain) + if self._tilt_optimistic: + self._tilt_value = self._tilt_open_position + self.hass.async_add_job(self.async_update_ha_state()) + + @asyncio.coroutine + def async_close_cover_tilt(self, **kwargs): + """Tilt the cover closed.""" + mqtt.async_publish(self.hass, self._tilt_command_topic, + self._tilt_closed_position, self._qos, self._retain) + if self._tilt_optimistic: + self._tilt_value = self._tilt_closed_position + self.hass.async_add_job(self.async_update_ha_state()) + + @asyncio.coroutine + def async_set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + if ATTR_TILT_POSITION not in kwargs: + return + + position = float(kwargs[ATTR_TILT_POSITION]) + + # The position needs to be between min and max + tilt_range = self._tilt_max - self._tilt_min + percentage = position / 100.0 + level = round(tilt_range * percentage) + + mqtt.async_publish(self.hass, self._tilt_command_topic, + level, self._qos, self._retain) diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py index b749fde465f..06ec7ca6211 100644 --- a/homeassistant/components/cover/myq.py +++ b/homeassistant/components/cover/myq.py @@ -27,7 +27,7 @@ DEFAULT_NAME = 'myq' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the MyQ component.""" + """Set up the MyQ component.""" from pymyq import MyQAPI as pymyq username = config.get(CONF_USERNAME) @@ -39,17 +39,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): myq = pymyq(username, password, brand) if not myq.is_supported_brand(): - logger.error('MyQ Cover - Unsupported Type. See documentation') + logger.error("Unsupported type. See documentation") return if not myq.is_login_valid(): - logger.error('MyQ Cover - Username or Password is incorrect') + logger.error("Username or Password is incorrect") return try: add_devices(MyQDevice(myq, door) for door in myq.get_garage_doors()) except (TypeError, KeyError, NameError) as ex: - logger.error("MyQ Cover - %s", ex) + logger.error("%s", ex) class MyQDevice(CoverDevice): @@ -74,7 +74,7 @@ class MyQDevice(CoverDevice): @property def is_closed(self): - """Return True if cover is closed, else False.""" + """Return true if cover is closed, else False.""" return self._status == STATE_CLOSED def close_cover(self): diff --git a/homeassistant/components/cover/mysensors.py b/homeassistant/components/cover/mysensors.py index 7daadebadad..43942dacd05 100644 --- a/homeassistant/components/cover/mysensors.py +++ b/homeassistant/components/cover/mysensors.py @@ -11,11 +11,12 @@ from homeassistant.components.cover import CoverDevice, ATTR_POSITION from homeassistant.const import STATE_ON, STATE_OFF _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = [] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for covers.""" + """Set up the MySensors platform for covers.""" if discovery_info is None: return diff --git a/homeassistant/components/cover/opengarage.py b/homeassistant/components/cover/opengarage.py new file mode 100644 index 00000000000..d22e8f9104c --- /dev/null +++ b/homeassistant/components/cover/opengarage.py @@ -0,0 +1,192 @@ +""" +Platform for the opengarage.io cover component. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/cover.opengarage/ +""" +import logging + +import requests +import voluptuous as vol + +from homeassistant.components.cover import ( + CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE) +from homeassistant.const import ( + CONF_DEVICE, CONF_NAME, STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, + CONF_COVERS, CONF_HOST, CONF_PORT) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +ATTR_DISTANCE_SENSOR = "distance_sensor" +ATTR_DOOR_STATE = "door_state" +ATTR_SIGNAL_STRENGTH = "wifi_signal" + +CONF_DEVICEKEY = "device_key" + +DEFAULT_NAME = 'OpenGarage' +DEFAULT_PORT = 80 + +STATE_CLOSING = "closing" +STATE_OFFLINE = "offline" +STATE_OPENING = "opening" +STATE_STOPPED = "stopped" + +STATES_MAP = { + 0: STATE_CLOSED, + 1: STATE_OPEN +} + +COVER_SCHEMA = vol.Schema({ + vol.Required(CONF_DEVICEKEY): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME): cv.string +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up OpenGarage covers.""" + covers = [] + devices = config.get(CONF_COVERS) + + for device_id, device_config in devices.items(): + args = { + CONF_NAME: device_config.get(CONF_NAME), + CONF_HOST: device_config.get(CONF_HOST), + CONF_PORT: device_config.get(CONF_PORT), + "device_id": device_config.get(CONF_DEVICE, device_id), + CONF_DEVICEKEY: device_config.get(CONF_DEVICEKEY) + } + + covers.append(OpenGarageCover(hass, args)) + + add_devices(covers, True) + + +class OpenGarageCover(CoverDevice): + """Representation of a OpenGarage cover.""" + + # pylint: disable=no-self-use + def __init__(self, hass, args): + """Initialize the cover.""" + self.opengarage_url = 'http://{}:{}'.format( + args[CONF_HOST], args[CONF_PORT]) + self.hass = hass + self._name = args[CONF_NAME] + self.device_id = args['device_id'] + self._devicekey = args[CONF_DEVICEKEY] + self._state = STATE_UNKNOWN + self._state_before_move = None + self.dist = None + self.signal = None + self._available = True + + @property + def name(self): + """Return the name of the cover.""" + return self._name + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + data = {} + + if self.signal is not None: + data[ATTR_SIGNAL_STRENGTH] = self.signal + + if self.dist is not None: + data[ATTR_DISTANCE_SENSOR] = self.dist + + if self._state is not None: + data[ATTR_DOOR_STATE] = self._state + + return data + + @property + def is_closed(self): + """Return if the cover is closed.""" + if self._state == STATE_UNKNOWN: + return None + else: + return self._state in [STATE_CLOSED, STATE_OPENING] + + def close_cover(self): + """Close the cover.""" + if self._state not in [STATE_CLOSED, STATE_CLOSING]: + self._state_before_move = self._state + self._state = STATE_CLOSING + self._push_button() + + def open_cover(self): + """Open the cover.""" + if self._state not in [STATE_OPEN, STATE_OPENING]: + self._state_before_move = self._state + self._state = STATE_OPENING + self._push_button() + + def update(self): + """Get updated status from API.""" + try: + status = self._get_status() + if self._name is None: + if status["name"] is not None: + self._name = status["name"] + state = STATES_MAP.get(status.get('door'), STATE_UNKNOWN) + if self._state_before_move is not None: + if self._state_before_move != state: + self._state = state + self._state_before_move = None + else: + self._state = state + + _LOGGER.debug("%s status: %s", self._name, self._state) + self.signal = status.get('rssi') + self.dist = status.get('dist') + self._available = True + except (requests.exceptions.RequestException) as ex: + _LOGGER.error("Unable to connect to OpenGarage device: %(reason)s", + dict(reason=ex)) + self._state = STATE_OFFLINE + + def _get_status(self): + """Get latest status.""" + url = '{}/jc'.format(self.opengarage_url) + ret = requests.get(url, timeout=10) + return ret.json() + + def _push_button(self): + """Send commands to API.""" + url = '{}/cc?dkey={}&click=1'.format( + self.opengarage_url, self._devicekey) + try: + response = requests.get(url, timeout=10).json() + if response["result"] == 2: + _LOGGER.error("Unable to control %s: device_key is incorrect.", + self._name) + self._state = self._state_before_move + self._state_before_move = None + except (requests.exceptions.RequestException) as ex: + _LOGGER.error("Unable to connect to OpenGarage device: %(reason)s", + dict(reason=ex)) + self._state = self._state_before_move + self._state_before_move = None + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return 'garage' + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py index a016103a8fd..f599ea3ede1 100644 --- a/homeassistant/components/cover/rfxtrx.py +++ b/homeassistant/components/cover/rfxtrx.py @@ -4,7 +4,6 @@ Support for RFXtrx cover components. For more details about this platform, please refer to the documentation https://home-assistant.io/components/cover.rfxtrx/ """ - import homeassistant.components.rfxtrx as rfxtrx from homeassistant.components.cover import CoverDevice @@ -14,38 +13,36 @@ PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the RFXtrx cover.""" + """Set up the RFXtrx cover.""" import RFXtrx as rfxtrxmod - # Add cover from config file - covers = rfxtrx.get_devices_from_config(config, - RfxtrxCover) + covers = rfxtrx.get_devices_from_config(config, RfxtrxCover, hass) add_devices_callback(covers) def cover_update(event): - """Callback for cover updates from the RFXtrx gateway.""" + """Handle cover updates from the RFXtrx gateway.""" if not isinstance(event.device, rfxtrxmod.LightingDevice) or \ event.device.known_to_be_dimmable or \ not event.device.known_to_be_rollershutter: return - new_device = rfxtrx.get_new_device(event, config, RfxtrxCover) + new_device = rfxtrx.get_new_device(event, config, RfxtrxCover, hass) if new_device: add_devices_callback([new_device]) rfxtrx.apply_received_command(event) - # Subscribe to main rfxtrx events + # Subscribe to main RFXtrx events if cover_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(cover_update) class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice): - """Representation of an rfxtrx cover.""" + """Representation of a RFXtrx cover.""" @property def should_poll(self): - """No polling available in rfxtrx cover.""" + """No polling available in RFXtrx cover.""" return False @property diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py index 4cd4e74be06..07be998035c 100644 --- a/homeassistant/components/cover/rpi_gpio.py +++ b/homeassistant/components/cover/rpi_gpio.py @@ -1,5 +1,5 @@ """ -Support for building a Raspberry Pi cover in HA. +Support for controlling a Raspberry Pi cover. Instructions for building the controller can be found here https://github.com/andrewshilliday/garage-door-controller @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the RPi cover platform.""" + """Set up the RPi cover platform.""" relay_time = config.get(CONF_RELAY_TIME) state_pull_mode = config.get(CONF_STATE_PULL_MODE) covers = [] diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py index f2047b03230..ac4fddf98bb 100644 --- a/homeassistant/components/cover/scsgate.py +++ b/homeassistant/components/cover/scsgate.py @@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SCSGate cover.""" + """Set up the SCSGate cover.""" devices = config.get(CONF_DEVICES) covers = [] logger = logging.getLogger(__name__) diff --git a/homeassistant/components/cover/tellduslive.py b/homeassistant/components/cover/tellduslive.py index c48a14e9133..4a78cb96d06 100644 --- a/homeassistant/components/cover/tellduslive.py +++ b/homeassistant/components/cover/tellduslive.py @@ -5,7 +5,6 @@ This platform uses the Telldus Live online service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.tellduslive/ - """ import logging @@ -16,9 +15,10 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup covers.""" + """Set up the Telldus Live covers.""" if discovery_info is None: return + add_devices(TelldusLiveCover(hass, cover) for cover in discovery_info) diff --git a/homeassistant/components/cover/vera.py b/homeassistant/components/cover/vera.py index 033cea4a828..bcccf17da8a 100644 --- a/homeassistant/components/cover/vera.py +++ b/homeassistant/components/cover/vera.py @@ -16,14 +16,14 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Find and return Vera covers.""" + """Set up the Vera covers.""" add_devices( VeraCover(device, VERA_CONTROLLER) for device in VERA_DEVICES['cover']) class VeraCover(VeraDevice, CoverDevice): - """Represents a Vera Cover in Home Assistant.""" + """Representation a Vera Cover.""" def __init__(self, vera_device, controller): """Initialize the Vera device.""" diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/cover/wink.py index 79e2ee334dc..5472180db62 100644 --- a/homeassistant/components/cover/wink.py +++ b/homeassistant/components/cover/wink.py @@ -4,7 +4,6 @@ Support for Wink Covers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.wink/ """ - from homeassistant.components.cover import CoverDevice from homeassistant.components.wink import WinkDevice, DOMAIN @@ -12,7 +11,7 @@ DEPENDENCIES = ['wink'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink cover platform.""" + """Set up the Wink cover platform.""" import pywink for shade in pywink.get_shades(): diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index f860e52de95..781d7a03280 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -1,5 +1,5 @@ """ -Support for Zwave cover components. +Support for Z-Wave cover components. For more details about this platform, please refer to the documentation https://home-assistant.io/components/cover.zwave/ @@ -21,7 +21,7 @@ SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE def get_device(hass, values, node_config, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS) if (values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL @@ -35,10 +35,10 @@ def get_device(hass, values, node_config, **kwargs): class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): - """Representation of an Zwave roller shutter.""" + """Representation of an Z-Wave cover.""" def __init__(self, hass, values, invert_buttons): - """Initialize the zwave rollershutter.""" + """Initialize the Z-Wave rollershutter.""" ZWaveDeviceEntity.__init__(self, values, DOMAIN) # pylint: disable=no-member self._network = hass.data[zwave.ZWAVE_NETWORK] @@ -53,7 +53,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): self.update_properties() def update_properties(self): - """Callback on data changes for node values.""" + """Handle data changes for node values.""" # Position value self._current_position = self.values.primary.data @@ -115,7 +115,7 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): self.update_properties() def update_properties(self): - """Callback on data changes for node values.""" + """Handle data changes for node values.""" self._state = self.values.primary.data @property diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 6212567f3d7..f926d059db2 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -1,5 +1,5 @@ """ -Sets up a demo environment that mimics interaction with devices. +Set up the demo environment that mimics interaction with devices. For more details about this component, please refer to the documentation https://home-assistant.io/components/demo/ @@ -37,7 +37,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [ @asyncio.coroutine def async_setup(hass, config): - """Setup a demo environment.""" + """Set up the demo environment.""" group = loader.get_component('group') configurator = loader.get_component('configurator') persistent_notification = loader.get_component('persistent_notification') @@ -59,7 +59,7 @@ def async_setup(hass, config): bootstrap.async_setup_component(hass, 'sun') ] - # Setup demo platforms + # Set up demo platforms demo_config = config.copy() for component in COMPONENTS_WITH_DEMO_PLATFORM: demo_config[component] = {CONF_PLATFORM: 'demo'} @@ -106,19 +106,19 @@ def async_setup(hass, config): if any(not result for result in results): return False - # Setup example persistent notification + # Set up example persistent notification persistent_notification.async_create( hass, 'This is an example of a persistent notification.', title='Example Notification') - # Setup room groups + # Set up room groups lights = sorted(hass.states.async_entity_ids('light')) switches = sorted(hass.states.async_entity_ids('switch')) media_players = sorted(hass.states.async_entity_ids('media_player')) tasks2 = [] - # Setup scripts + # Set up scripts tasks2.append(bootstrap.async_setup_component( hass, 'script', {'script': { @@ -140,7 +140,7 @@ def async_setup(hass, config): }] }}})) - # Setup scenes + # Set up scenes tasks2.append(bootstrap.async_setup_component( hass, 'scene', {'scene': [ @@ -186,7 +186,7 @@ def async_setup(hass, config): if any(not result for result in results): return False - # Setup configurator + # Set up configurator configurator_ids = [] def hue_configuration_callback(data): @@ -204,7 +204,7 @@ def async_setup(hass, config): configurator.request_done(configurator_ids[0]) def setup_configurator(): - """Setup configurator.""" + """Set up a configurator.""" request_id = configurator.request_config( hass, "Philips Hue", hue_configuration_callback, description=("Press the button on the bridge to register Philips " diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 0a4d7173c20..9119394e357 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -45,7 +45,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """The triggers to turn lights on or off based on device presence.""" + """Set up the triggers to control lights based on device presence.""" logger = logging.getLogger(__name__) device_tracker = get_component('device_tracker') group = get_component('group') @@ -55,10 +55,10 @@ def async_setup(hass, config): disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) - device_group = conf.get(CONF_DEVICE_GROUP, - device_tracker.ENTITY_ID_ALL_DEVICES) - device_entity_ids = group.get_entity_ids(hass, device_group, - device_tracker.DOMAIN) + device_group = conf.get( + CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) + 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") @@ -84,7 +84,7 @@ def async_setup(hass, config): return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) def async_turn_on_before_sunset(light_id): - """Helper function to turn on lights.""" + """Turn on lights.""" if not device_tracker.is_on(hass) or light.is_on(hass, light_id): return light.async_turn_on(hass, light_id, @@ -92,7 +92,7 @@ def async_setup(hass, config): profile=light_profile) def async_turn_on_factory(light_id): - """Factory to generate turn on callbacks.""" + """Generate turn on callbacks as factory.""" @callback def async_turn_on_light(now): """Turn on specific light.""" @@ -104,7 +104,7 @@ def async_setup(hass, config): # pre-sun set event @callback def schedule_light_turn_on(entity, old_state, new_state): - """The moment sun sets we want to have all the lights on. + """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another and slowly transition in. diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index f2a538507a0..6582ba3f57e 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -34,9 +34,11 @@ from homeassistant.util.yaml import dump from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.const import ( - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, + ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME, CONF_MAC, DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_ID) +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'device_tracker' DEPENDENCIES = ['zone'] @@ -63,13 +65,14 @@ EVENT_NEW_DEVICE = 'device_tracker_new_device' SERVICE_SEE = 'see' -ATTR_MAC = 'mac' +ATTR_ATTRIBUTES = 'attributes' +ATTR_BATTERY = 'battery' ATTR_DEV_ID = 'dev_id' +ATTR_GPS = 'gps' ATTR_HOST_NAME = 'host_name' ATTR_LOCATION_NAME = 'location_name' -ATTR_GPS = 'gps' -ATTR_BATTERY = 'battery' -ATTR_ATTRIBUTES = 'attributes' +ATTR_MAC = 'mac' +ATTR_NAME = 'name' ATTR_SOURCE_TYPE = 'source_type' SOURCE_TYPE_GPS = 'gps' @@ -86,7 +89,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ DISCOVERY_PLATFORMS = { SERVICE_NETGEAR: 'netgear', } -_LOGGER = logging.getLogger(__name__) def is_on(hass: HomeAssistantType, entity_id: str=None): @@ -116,7 +118,7 @@ def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None, @asyncio.coroutine def async_setup(hass: HomeAssistantType, config: ConfigType): - """Setup device tracker.""" + """Set up the device tracker.""" yaml_path = hass.config.path(YAML_DEVICES) try: @@ -125,7 +127,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): async_log_exception(ex, DOMAIN, config, hass) return False else: - conf = conf[0] if len(conf) > 0 else {} + conf = conf[0] if conf else {} consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME) track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) @@ -134,7 +136,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): @asyncio.coroutine def async_setup_platform(p_type, p_config, disc_info=None): - """Setup a device tracker platform.""" + """Set up a device tracker platform.""" platform = yield from async_prepare_setup_platform( hass, config, DOMAIN, p_type) if platform is None: @@ -166,11 +168,11 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): return if not setup: - _LOGGER.error('Error setting up platform %s', p_type) + _LOGGER.error("Error setting up platform %s", p_type) return except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error setting up platform %s', p_type) + _LOGGER.exception("Error setting up platform %s", p_type) setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config in config_per_platform(config, DOMAIN)] @@ -181,7 +183,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): @callback def async_device_tracker_discovered(service, info): - """Called when a device tracker platform is discovered.""" + """Handle the discovery of device tracker platforms.""" hass.async_add_job( async_setup_platform(DISCOVERY_PLATFORMS[service], {}, info)) @@ -190,7 +192,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): @asyncio.coroutine def async_platform_discovered(platform, info): - """Callback to load a platform.""" + """Load a platform.""" yield from async_setup_platform(platform, {}, disc_info=info) discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) @@ -271,9 +273,9 @@ class DeviceTracker(object): device = self.devices.get(dev_id) if device: - yield from device.async_seen(host_name, location_name, gps, - gps_accuracy, battery, attributes, - source_type) + yield from device.async_seen( + host_name, location_name, gps, gps_accuracy, battery, + attributes, source_type) if device.track: yield from device.async_update_ha_state() return @@ -287,9 +289,9 @@ class DeviceTracker(object): if mac is not None: self.mac_to_dev[mac] = device - yield from device.async_seen(host_name, location_name, gps, - gps_accuracy, battery, attributes, - source_type) + yield from device.async_seen( + host_name, location_name, gps, gps_accuracy, battery, attributes, + source_type) if device.track: yield from device.async_update_ha_state() @@ -309,8 +311,8 @@ class DeviceTracker(object): # update known_devices.yaml self.hass.async_add_job( - self.async_update_config(self.hass.config.path(YAML_DEVICES), - dev_id, device) + self.async_update_config( + self.hass.config.path(YAML_DEVICES), dev_id, device) ) @asyncio.coroutine @@ -348,7 +350,7 @@ class DeviceTracker(object): @asyncio.coroutine def async_setup_tracked_device(self): - """Setup all not exists tracked devices. + """Set up all not exists tracked devices. This method is a coroutine. """ @@ -485,8 +487,8 @@ class Device(Entity): except (ValueError, TypeError, IndexError): self.gps = None self.gps_accuracy = 0 - _LOGGER.warning('Could not parse gps value for %s: %s', - self.dev_id, gps) + _LOGGER.warning( + "Could not parse gps value for %s: %s", self.dev_id, gps) # pylint: disable=not-an-iterable yield from self.async_update() @@ -534,7 +536,6 @@ class Device(Entity): @asyncio.coroutine def get_vendor_for_mac(self): """Try to find the vendor string for a given MAC address.""" - # can't continue without a mac if not self.mac: return None @@ -543,11 +544,10 @@ class Device(Entity): else: mac = self.mac - # prevent lookup of invalid macs if not len(mac.split(':')) == 6: return 'unknown' - # we only need the first 3 bytes of the mac for a lookup + # We only need the first 3 bytes of the MAC for a lookup # this improves somewhat on privacy oui_bytes = mac.split(':')[0:3] # bytes like 00 get truncates to 0, API needs full bytes @@ -562,7 +562,7 @@ class Device(Entity): if resp.status == 200: vendor_string = yield from resp.text() return vendor_string - # if vendor is not known to the API (404) or there + # If vendor is not known to the API (404) or there # was a failure during the lookup (500); set vendor # to something other then None to prevent retry # as the value is only relevant when it is to be stored @@ -570,12 +570,12 @@ class Device(Entity): # the first time the device is seen. return 'unknown' except (asyncio.TimeoutError, aiohttp.ClientError): - # same as above + # Same as above return 'unknown' @asyncio.coroutine def async_added_to_hass(self): - """Called when entity about to be added to hass.""" + """Add an entity.""" state = yield from async_get_last_state(self.hass, self.entity_id) if not state: return @@ -636,10 +636,10 @@ def async_load_config(path: str, hass: HomeAssistantType, This method is a coroutine. """ dev_schema = vol.Schema({ - vol.Required('name'): cv.string, + vol.Required(CONF_NAME): cv.string, vol.Optional('track', default=False): cv.boolean, - vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string, - vol.Upper)), + vol.Optional(CONF_MAC, default=None): + vol.Any(None, vol.All(cv.string, vol.Upper)), vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, vol.Optional('gravatar', default=None): vol.Any(None, cv.string), vol.Optional('picture', default=None): vol.Any(None, cv.string), @@ -653,7 +653,7 @@ def async_load_config(path: str, hass: HomeAssistantType, devices = yield from hass.loop.run_in_executor( None, load_yaml_config_file, path) except HomeAssistantError as err: - _LOGGER.error('Unable to load %s: %s', path, str(err)) + _LOGGER.error("Unable to load %s: %s", path, str(err)) return [] for dev_id, device in devices.items(): @@ -674,7 +674,7 @@ def async_load_config(path: str, hass: HomeAssistantType, def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, scanner: Any, async_see_device: Callable, platform: str): - """Helper method to connect scanner-based platform to device tracker. + """Set up the connect scanner-based platform to device tracker. This method must be run in the event loop. """ @@ -687,7 +687,7 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, @asyncio.coroutine def async_device_tracker_scan(now: dt_util.dt.datetime): - """Called when interval matches.""" + """Handle interval matches.""" if update_lock.locked(): _LOGGER.warning( "Updating device list from %s took longer than the scheduled " @@ -726,8 +726,8 @@ def update_config(path: str, dev_id: str, device: Device): """Add device to YAML configuration file.""" with open(path, 'a') as out: device = {device.dev_id: { - 'name': device.name, - 'mac': device.mac, + ATTR_NAME: device.name, + ATTR_MAC: device.mac, 'picture': device.config_picture, 'track': device.track, CONF_AWAY_HIDE: device.away_hide, diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 95286800b3c..882df575385 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -19,7 +19,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple("Device", ["mac", "ip", "last_update"]) +Device = namedtuple('Device', ['mac', 'ip', 'last_update']) class ActiontecDeviceScanner(DeviceScanner): @@ -59,7 +58,7 @@ class ActiontecDeviceScanner(DeviceScanner): self.last_results = [] data = self.get_actiontec_data() self.success_init = data is not None - _LOGGER.info("actiontec scanner initialized") + _LOGGER.info("canner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -93,7 +92,7 @@ class ActiontecDeviceScanner(DeviceScanner): self.last_results = [Device(data['mac'], name, now) for name, data in actiontec_data.items() if data['timevalid'] > -60] - _LOGGER.info("actiontec scan successful") + _LOGGER.info("Scan successful") return True def get_actiontec_data(self): @@ -115,8 +114,7 @@ class ActiontecDeviceScanner(DeviceScanner): _LOGGER.exception("Unexpected response from router") return except ConnectionRefusedError: - _LOGGER.exception("Connection refused by router," + - " is telnet enabled?") + _LOGGER.exception("Connection refused by router. Telnet enabled?") return None devices = {} diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 42e8a5b2d5c..bfb1588b323 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -17,11 +17,11 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +_LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pexpect==4.0.1'] -_LOGGER = logging.getLogger(__name__) + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) _DEVICES_REGEX = re.compile( r'(?P([^\s]+))\s+' + @@ -102,22 +102,22 @@ class ArubaDeviceScanner(DeviceScanner): 'Connection refused', 'Connection timed out'], timeout=120) if query == 1: - _LOGGER.error('Timeout') + _LOGGER.error("Timeout") return elif query == 2: - _LOGGER.error('Unexpected response from router') + _LOGGER.error("Unexpected response from router") return elif query == 3: ssh.sendline('yes') ssh.expect('password:') elif query == 4: - _LOGGER.error('Host key Changed') + _LOGGER.error("Host key changed") return elif query == 5: - _LOGGER.error('Connection refused by server') + _LOGGER.error("Connection refused by server") return elif query == 6: - _LOGGER.error('Connection timed out') + _LOGGER.error("Connection timed out") return ssh.sendline(self.password) ssh.expect('#') diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 8ebc790ad3a..a0405b0b690 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -21,14 +21,19 @@ from homeassistant.const import ( from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -# Return cached results if last scan was less then this time ago. +REQUIREMENTS = ['pexpect==4.0.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_MODE = 'mode' +CONF_PROTOCOL = 'protocol' +CONF_PUB_KEY = 'pub_key' +CONF_SSH_KEY = 'ssh_key' + +DEFAULT_SSH_PORT = 22 + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) -CONF_PROTOCOL = 'protocol' -CONF_MODE = 'mode' -DEFAULT_SSH_PORT = 22 -CONF_SSH_KEY = 'ssh_key' -CONF_PUB_KEY = 'pub_key' SECRET_GROUP = 'Password or SSH Key' PLATFORM_SCHEMA = vol.All( @@ -47,9 +52,6 @@ PLATFORM_SCHEMA = vol.All( })) -_LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pexpect==4.0.1'] - _LEASES_CMD = 'cat /var/lib/misc/dnsmasq.leases' _LEASES_REGEX = re.compile( r'\w+\s' + @@ -57,7 +59,7 @@ _LEASES_REGEX = re.compile( r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' + r'(?P([^\s]+))') -# command to get both 5GHz and 2.4GHz clients +# Command to get both 5GHz and 2.4GHz clients _WL_CMD = '{ wl -i eth2 assoclist & wl -i eth1 assoclist ; }' _WL_REGEX = re.compile( r'\w+\s' + @@ -126,12 +128,12 @@ class AsusWrtDeviceScanner(DeviceScanner): elif self.password: self.ssh_args['password'] = self.password else: - _LOGGER.error('No password or private key specified') + _LOGGER.error("No password or private key specified") self.success_init = False return else: if not self.password: - _LOGGER.error('No password specified') + _LOGGER.error("No password specified") self.success_init = False return @@ -188,10 +190,10 @@ class AsusWrtDeviceScanner(DeviceScanner): try: ssh.login(self.host, self.username, **self.ssh_args) except exceptions.EOF as err: - _LOGGER.error('Connection refused. Is SSH enabled?') + _LOGGER.error("Connection refused. SSH enabled?") return None except pxssh.ExceptionPxssh as err: - _LOGGER.error('Unable to connect via SSH: %s', str(err)) + _LOGGER.error("Unable to connect via SSH: %s", str(err)) return None try: @@ -218,7 +220,7 @@ class AsusWrtDeviceScanner(DeviceScanner): return AsusWrtResult(neighbors, leases_result, arp_result, nvram_result) except pxssh.ExceptionPxssh as exc: - _LOGGER.error('Unexpected response from router: %s', exc) + _LOGGER.error("Unexpected response from router: %s", exc) return None def telnet_connection(self): @@ -252,16 +254,16 @@ class AsusWrtDeviceScanner(DeviceScanner): return AsusWrtResult(neighbors, leases_result, arp_result, nvram_result) except EOFError: - _LOGGER.error('Unexpected response from router') + _LOGGER.error("Unexpected response from router") return None except ConnectionRefusedError: - _LOGGER.error('Connection refused by router, is telnet enabled?') + _LOGGER.error("Connection refused by router. Telnet enabled?") return None except socket.gaierror as exc: - _LOGGER.error('Socket exception: %s', exc) + _LOGGER.error("Socket exception: %s", exc) return None except OSError as exc: - _LOGGER.error('OSError: %s', exc) + _LOGGER.error("OSError: %s", exc) return None def get_asuswrt_data(self): @@ -289,7 +291,7 @@ class AsusWrtDeviceScanner(DeviceScanner): match = _WL_REGEX.search(lease.decode('utf-8')) if not match: - _LOGGER.warning('Could not parse wl row: %s', lease) + _LOGGER.warning("Could not parse wl row: %s", lease) continue host = '' @@ -301,7 +303,7 @@ class AsusWrtDeviceScanner(DeviceScanner): arp_match = _ARP_REGEX.search( arp.decode('utf-8').lower()) if not arp_match: - _LOGGER.warning('Could not parse arp row: %s', arp) + _LOGGER.warning("Could not parse arp row: %s", arp) continue devices[arp_match.group('ip')] = { @@ -316,7 +318,7 @@ class AsusWrtDeviceScanner(DeviceScanner): if match.group('mac').upper() in nvr.decode('utf-8'): nvram_match = _NVRAM_REGEX.search(nvr.decode('utf-8')) if not nvram_match: - _LOGGER.warning('Could not parse nvr row: %s', nvr) + _LOGGER.warning("Could not parse nvr row: %s", nvr) continue # skip current check if already in ARP table @@ -337,7 +339,7 @@ class AsusWrtDeviceScanner(DeviceScanner): match = _LEASES_REGEX.search(lease.decode('utf-8')) if not match: - _LOGGER.warning('Could not parse lease row: %s', lease) + _LOGGER.warning("Could not parse lease row: %s", lease) continue # For leases where the client doesn't set a hostname, ensure it @@ -356,7 +358,7 @@ class AsusWrtDeviceScanner(DeviceScanner): for neighbor in result.neighbors: match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8')) if not match: - _LOGGER.warning('Could not parse neighbor row: %s', neighbor) + _LOGGER.warning("Could not parse neighbor row: %s", neighbor) continue if match.group('ip') in devices: devices[match.group('ip')]['status'] = match.group('status') diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 55ec3151227..56dccd75d6d 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -11,13 +11,17 @@ import logging import voluptuous as vol from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, ATTR_ATTRIBUTES) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD + PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC, + ATTR_GPS, ATTR_GPS_ACCURACY) +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START) +from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval -REQUIREMENTS = ['aioautomatic==0.2.1'] +REQUIREMENTS = ['aioautomatic==0.3.1'] _LOGGER = logging.getLogger(__name__) @@ -29,6 +33,10 @@ DEFAULT_TIMEOUT = 5 SCOPE = ['location', 'vehicle:profile', 'trip'] +ATTR_FUEL_LEVEL = 'fuel_level' + +EVENT_AUTOMATIC_UPDATE = 'automatic_update' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_SECRET): cv.string, @@ -52,64 +60,172 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): try: session = yield from client.create_session_from_password( SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD]) - data = AutomaticData(hass, session, config[CONF_DEVICES], async_see) + data = AutomaticData( + hass, client, session, config[CONF_DEVICES], async_see) + + # Load the initial vehicle data + vehicles = yield from session.get_vehicles() + for vehicle in vehicles: + hass.async_add_job(data.load_vehicle(vehicle)) except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) return False - yield from data.update() + @callback + def ws_connect(event): + """Open the websocket connection.""" + hass.async_add_job(data.ws_connect()) + + @callback + def ws_close(event): + """Close the websocket connection.""" + hass.async_add_job(data.ws_close()) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close) + return True class AutomaticData(object): """A class representing an Automatic cloud service connection.""" - def __init__(self, hass, session, devices, async_see): + def __init__(self, hass, client, session, devices, async_see): """Initialize the automatic device scanner.""" self.hass = hass self.devices = devices + self.vehicle_info = {} + self.client = client self.session = session self.async_see = async_see + self.ws_reconnect_handle = None + self.ws_close_requested = False - async_track_time_interval(hass, self.update, timedelta(seconds=30)) + self.client.on_app_event( + lambda name, event: self.hass.async_add_job( + self.handle_event(name, event))) @asyncio.coroutine - def update(self, now=None): - """Update the device info.""" + def handle_event(self, name, event): + """Coroutine to update state for a realtime event.""" import aioautomatic - _LOGGER.debug('Updating devices %s', now) + # Fire a hass event + self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data) + + if event.vehicle.id not in self.vehicle_info: + # If vehicle hasn't been seen yet, request the detailed + # info for this vehicle. + _LOGGER.info("New vehicle found.") + try: + vehicle = yield from event.get_vehicle() + except aioautomatic.exceptions.AutomaticError as err: + _LOGGER.error(str(err)) + return + yield from self.get_vehicle_info(vehicle) + + kwargs = self.vehicle_info[event.vehicle.id] + if kwargs is None: + # Ignored device + return + + # If this is a vehicle status report, update the fuel level + if name == "vehicle:status_report": + fuel_level = event.vehicle.fuel_level_percent + if fuel_level is not None: + kwargs[ATTR_ATTRIBUTES][ATTR_FUEL_LEVEL] = fuel_level + + # Send the device seen notification + if event.location is not None: + kwargs[ATTR_GPS] = (event.location.lat, event.location.lon) + kwargs[ATTR_GPS_ACCURACY] = event.location.accuracy_m + + yield from self.async_see(**kwargs) + + @asyncio.coroutine + def ws_connect(self, now=None): + """Open the websocket connection.""" + import aioautomatic + self.ws_close_requested = False + + if self.ws_reconnect_handle is not None: + _LOGGER.debug("Retrying websocket connection.") + try: + ws_loop_future = yield from self.client.ws_connect() + except aioautomatic.exceptions.UnauthorizedClientError: + _LOGGER.error("Client unauthorized for websocket connection. " + "Ensure Websocket is selected in the Automatic " + "developer application event delivery preferences.") + return + except aioautomatic.exceptions.AutomaticError as err: + if self.ws_reconnect_handle is None: + # Show log error and retry connection every 5 minutes + _LOGGER.error("Error opening websocket connection: %s", err) + self.ws_reconnect_handle = async_track_time_interval( + self.hass, self.ws_connect, timedelta(minutes=5)) + return + + if self.ws_reconnect_handle is not None: + self.ws_reconnect_handle() + self.ws_reconnect_handle = None + + _LOGGER.info("Websocket connected.") try: - vehicles = yield from self.session.get_vehicles() + yield from ws_loop_future except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) - return False - for vehicle in vehicles: - name = vehicle.display_name - if name is None: - name = ' '.join(filter(None, ( - str(vehicle.year), vehicle.make, vehicle.model))) + _LOGGER.info("Websocket closed.") - if self.devices is not None and name not in self.devices: - continue - - self.hass.async_add_job(self.update_vehicle(vehicle, name)) + # If websocket was close was not requested, attempt to reconnect + if not self.ws_close_requested: + self.hass.loop.create_task(self.ws_connect()) @asyncio.coroutine - def update_vehicle(self, vehicle, name): - """Updated the specified vehicle's data.""" + def ws_close(self): + """Close the websocket connection.""" + self.ws_close_requested = True + if self.ws_reconnect_handle is not None: + self.ws_reconnect_handle() + self.ws_reconnect_handle = None + + yield from self.client.ws_close() + + @asyncio.coroutine + def load_vehicle(self, vehicle): + """Load the vehicle's initial state and update hass.""" + kwargs = yield from self.get_vehicle_info(vehicle) + yield from self.async_see(**kwargs) + + @asyncio.coroutine + def get_vehicle_info(self, vehicle): + """Fetch the latest vehicle info from automatic.""" import aioautomatic - kwargs = { - 'dev_id': vehicle.id, - 'host_name': name, - 'mac': vehicle.id, - ATTR_ATTRIBUTES: { - 'fuel_level': vehicle.fuel_level_percent, + name = vehicle.display_name + if name is None: + name = ' '.join(filter(None, ( + str(vehicle.year), vehicle.make, vehicle.model))) + + if self.devices is not None and name not in self.devices: + self.vehicle_info[vehicle.id] = None + return + else: + self.vehicle_info[vehicle.id] = kwargs = { + ATTR_DEV_ID: vehicle.id, + ATTR_HOST_NAME: name, + ATTR_MAC: vehicle.id, + ATTR_ATTRIBUTES: { + ATTR_FUEL_LEVEL: vehicle.fuel_level_percent, } - } + } + + if vehicle.latest_location is not None: + location = vehicle.latest_location + kwargs[ATTR_GPS] = (location.lat, location.lon) + kwargs[ATTR_GPS_ACCURACY] = location.accuracy_m + return kwargs trips = [] try: @@ -120,8 +236,8 @@ class AutomaticData(object): _LOGGER.error(str(err)) if trips: - end_location = trips[0].end_location - kwargs['gps'] = (end_location.lat, end_location.lon) - kwargs['gps_accuracy'] = end_location.accuracy_m + location = trips[0].end_location + kwargs[ATTR_GPS] = (location.lat, location.lon) + kwargs[ATTR_GPS_ACCURACY] = location.accuracy_m - yield from self.async_see(**kwargs) + return kwargs diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/device_tracker/bbox.py index 60b9738cc71..0d0cdd7b1d5 100644 --- a/homeassistant/components/device_tracker/bbox.py +++ b/homeassistant/components/device_tracker/bbox.py @@ -37,7 +37,7 @@ class BboxDeviceScanner(DeviceScanner): self.last_results = [] # type: List[Device] self.success_init = self._update_info() - _LOGGER.info("Bbox scanner initialized") + _LOGGER.info("Scanner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -57,7 +57,7 @@ class BboxDeviceScanner(DeviceScanner): @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): - """Check the bbox for devices. + """Check the Bbox for devices. Returns boolean if scanning successful. """ @@ -79,5 +79,5 @@ class BboxDeviceScanner(DeviceScanner): self.last_results = last_results - _LOGGER.info("Bbox scan successful") + _LOGGER.info("Scan successful") return True diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 7b7454d0a28..22713cdc18e 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -1,4 +1,9 @@ -"""Tracking for bluetooth low energy devices.""" +""" +Tracking for bluetooth low energy devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.bluetooth_le_tracker/ +""" import logging import voluptuous as vol @@ -16,17 +21,17 @@ REQUIREMENTS = ['gattlib==0.20150805'] BLE_PREFIX = 'BLE_' MIN_SEEN_NEW = 5 -CONF_SCAN_DURATION = "scan_duration" -CONF_BLUETOOTH_DEVICE = "device_id" +CONF_SCAN_DURATION = 'scan_duration' +CONF_BLUETOOTH_DEVICE = 'device_id' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int, - vol.Optional(CONF_BLUETOOTH_DEVICE, default="hci0"): cv.string + vol.Optional(CONF_BLUETOOTH_DEVICE, default='hci0'): cv.string }) def setup_scanner(hass, config, see, discovery_info=None): - """Setup the Bluetooth LE Scanner.""" + """Set up the Bluetooth LE Scanner.""" # pylint: disable=import-error from gattlib import DiscoveryService @@ -36,8 +41,8 @@ def setup_scanner(hass, config, see, discovery_info=None): """Mark a device as seen.""" if new_device: if address in new_devices: - _LOGGER.debug("Seen %s %s times", address, - new_devices[address]) + _LOGGER.debug( + "Seen %s %s times", address, new_devices[address]) new_devices[address] += 1 if new_devices[address] >= MIN_SEEN_NEW: _LOGGER.debug("Adding %s to tracked devices", address) diff --git a/homeassistant/components/device_tracker/bluetooth_tracker.py b/homeassistant/components/device_tracker/bluetooth_tracker.py index f71f8c4271a..9e0957e363f 100644 --- a/homeassistant/components/device_tracker/bluetooth_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_tracker.py @@ -1,4 +1,9 @@ -"""Tracking for bluetooth devices.""" +""" +Tracking for bluetooth devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.bluetooth_tracker/ +""" import logging import voluptuous as vol @@ -22,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_scanner(hass, config, see, discovery_info=None): - """Setup the Bluetooth Scanner.""" + """Set up the Bluetooth Scanner.""" # pylint: disable=import-error import bluetooth @@ -31,11 +36,10 @@ def setup_scanner(hass, config, see, discovery_info=None): see(mac=BT_PREFIX + device[0], host_name=device[1]) def discover_devices(): - """Discover bluetooth devices.""" - result = bluetooth.discover_devices(duration=8, - lookup_names=True, - flush_cache=True, - lookup_class=False) + """Discover Bluetooth devices.""" + result = bluetooth.discover_devices( + duration=8, lookup_names=True, flush_cache=True, + lookup_class=False) _LOGGER.debug("Bluetooth devices discovered = " + str(len(result))) return result @@ -47,14 +51,14 @@ def setup_scanner(hass, config, see, discovery_info=None): # We just need the devices so set consider_home and home range # to 0 for device in load_config(yaml_path, hass, 0): - # check if device is a valid bluetooth device + # Check if device is a valid bluetooth device if device.mac and device.mac[:3].upper() == BT_PREFIX: if device.track: devs_to_track.append(device.mac[3:]) else: devs_donot_track.append(device.mac[3:]) - # if track new devices is true discover new devices on startup. + # If track new devices is true discover new devices on startup. track_new = config.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) if track_new: for dev in discover_devices(): @@ -66,7 +70,7 @@ def setup_scanner(hass, config, see, discovery_info=None): interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) def update_bluetooth(now): - """Lookup bluetooth device and update status.""" + """Lookup Bluetooth device and update status.""" try: if track_new: for dev in discover_devices(): @@ -74,14 +78,14 @@ def setup_scanner(hass, config, see, discovery_info=None): dev[0] not in devs_donot_track: devs_to_track.append(dev[0]) for mac in devs_to_track: - _LOGGER.debug("Scanning " + mac) + _LOGGER.debug("Scanning %s", mac) result = bluetooth.lookup_name(mac, timeout=5) if not result: # Could not lookup device name continue see_device((mac, result)) except bluetooth.BluetoothError: - _LOGGER.exception('Error looking up bluetooth device!') + _LOGGER.exception("Error looking up Bluetooth device") track_point_in_utc_time( hass, update_bluetooth, dt_util.utcnow() + interval) diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/device_tracker/bt_home_hub_5.py index 301ec61abc2..5c1a14b446b 100644 --- a/homeassistant/components/device_tracker/bt_home_hub_5.py +++ b/homeassistant/components/device_tracker/bt_home_hub_5.py @@ -21,13 +21,11 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) - _LOGGER = logging.getLogger(__name__) - _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string }) @@ -46,7 +44,7 @@ class BTHomeHub5DeviceScanner(DeviceScanner): def __init__(self, config): """Initialise the scanner.""" - _LOGGER.info('Initialising BT Home Hub 5') + _LOGGER.info("Initialising BT Home Hub 5") self.host = config.get(CONF_HOST, '192.168.1.254') self.lock = threading.Lock() @@ -87,12 +85,12 @@ class BTHomeHub5DeviceScanner(DeviceScanner): return False with self.lock: - _LOGGER.info('Scanning') + _LOGGER.info("Scanning") data = _get_homehub_data(self.url) if not data: - _LOGGER.warning('Error scanning devices') + _LOGGER.warning("Error scanning devices") return False self.last_results = data diff --git a/homeassistant/components/device_tracker/cisco_ios.py b/homeassistant/components/device_tracker/cisco_ios.py index 95319a872ae..35ffa754348 100644 --- a/homeassistant/components/device_tracker/cisco_ios.py +++ b/homeassistant/components/device_tracker/cisco_ios.py @@ -16,7 +16,6 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \ CONF_PORT from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -57,7 +56,7 @@ class CiscoDeviceScanner(DeviceScanner): # pylint: disable=no-self-use def get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Get the firmware doesn't save the name of the wireless device.""" return None def scan_devices(self): @@ -135,9 +134,9 @@ class CiscoDeviceScanner(DeviceScanner): devices_result = cisco_ssh.before - return devices_result.decode("utf-8") + return devices_result.decode('utf-8') except pxssh.ExceptionPxssh as px_e: - _LOGGER.error("pxssh failed on login.") + _LOGGER.error("pxssh failed on login") _LOGGER.error(px_e) return None diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 2c8a2ec4907..4f1efcdb27c 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -18,7 +18,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -108,7 +107,7 @@ class DdWrtDeviceScanner(DeviceScanner): Return boolean if scanning successful. """ with self.lock: - _LOGGER.info('Checking ARP') + _LOGGER.info("Checking ARP") url = 'http://{}/Status_Wireless.live.asp'.format(self.host) data = self.get_ddwrt_data(url) @@ -137,22 +136,19 @@ class DdWrtDeviceScanner(DeviceScanner): """Retrieve data from DD-WRT and return parsed result.""" try: response = requests.get( - url, - auth=(self.username, self.password), - timeout=4) + url, auth=(self.username, self.password), timeout=4) except requests.exceptions.Timeout: - _LOGGER.exception('Connection to the router timed out') + _LOGGER.exception("Connection to the router timed out") return if response.status_code == 200: return _parse_ddwrt_response(response.text) elif response.status_code == 401: # Authentication error _LOGGER.exception( - 'Failed to authenticate, ' - 'please check your username and password') + "Failed to authenticate, check your username and password") return else: - _LOGGER.error('Invalid response from ddwrt: %s', response) + _LOGGER.error("Invalid response from DD-WRT: %s", response) def _parse_ddwrt_response(data_str): diff --git a/homeassistant/components/device_tracker/demo.py b/homeassistant/components/device_tracker/demo.py index dfd50a2b991..608fc560cf9 100644 --- a/homeassistant/components/device_tracker/demo.py +++ b/homeassistant/components/device_tracker/demo.py @@ -1,11 +1,16 @@ -"""Demo platform for the device tracker.""" +""" +Demo platform for the Device tracker component. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ +""" import random from homeassistant.components.device_tracker import DOMAIN def setup_scanner(hass, config, see, discovery_info=None): - """Setup the demo tracker.""" + """Set up the demo tracker.""" def offset(): """Return random offset.""" return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1)) diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index a11139afa0f..25de0a35c82 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -17,7 +17,6 @@ from homeassistant.util import Throttle REQUIREMENTS = ['fritzconnection==0.6.3'] -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -53,9 +52,8 @@ class FritzBoxScanner(DeviceScanner): # Establish a connection to the FRITZ!Box. try: - self.fritz_box = fc.FritzHosts(address=self.host, - user=self.username, - password=self.password) + self.fritz_box = fc.FritzHosts( + address=self.host, user=self.username, password=self.password) except (ValueError, TypeError): self.fritz_box = None @@ -65,12 +63,12 @@ class FritzBoxScanner(DeviceScanner): self.success_init = False if self.success_init: - _LOGGER.info('Successfully connected to %s', + _LOGGER.info("Successfully connected to %s", self.fritz_box.modelname) self._update_info() else: - _LOGGER.error('Failed to establish connection to FRITZ!Box ' - 'with IP: %s', self.host) + _LOGGER.error("Failed to establish connection to FRITZ!Box " + "with IP: %s", self.host) def scan_devices(self): """Scan for new devices and return a list of found device ids.""" @@ -96,6 +94,6 @@ class FritzBoxScanner(DeviceScanner): if not self.success_init: return False - _LOGGER.info('Scanning') + _LOGGER.info("Scanning") self.last_results = self.fritz_box.get_hosts_info() return True diff --git a/homeassistant/components/device_tracker/gpslogger.py b/homeassistant/components/device_tracker/gpslogger.py index c76c8fdd51b..733127cb0f2 100644 --- a/homeassistant/components/device_tracker/gpslogger.py +++ b/homeassistant/components/device_tracker/gpslogger.py @@ -20,14 +20,14 @@ DEPENDENCIES = ['http'] def setup_scanner(hass, config, see, discovery_info=None): - """Setup an endpoint for the GPSLogger application.""" + """Set up an endpoint for the GPSLogger application.""" hass.http.register_view(GPSLoggerView(see)) return True class GPSLoggerView(HomeAssistantView): - """View to handle gpslogger requests.""" + """View to handle GPSLogger requests.""" url = '/api/gpslogger' name = 'api:gpslogger' @@ -38,21 +38,20 @@ class GPSLoggerView(HomeAssistantView): @asyncio.coroutine def get(self, request): - """A GPSLogger message received as GET.""" + """Handle for GPSLogger message received as GET.""" res = yield from self._handle(request.app['hass'], request.GET) return res @asyncio.coroutine def _handle(self, hass, data): - """Handle gpslogger request.""" + """Handle GPSLogger requests.""" if 'latitude' not in data or 'longitude' not in data: return ('Latitude and longitude not specified.', HTTP_UNPROCESSABLE_ENTITY) if 'device' not in data: - _LOGGER.error('Device id not specified.') - return ('Device id not specified.', - HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Device id not specified") + return ('Device id not specified.', HTTP_UNPROCESSABLE_ENTITY) device = data['device'].replace('-', '') gps_location = (data['latitude'], data['longitude']) diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index b6fd9121295..194a2f4bfac 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -55,8 +55,12 @@ DEVICESTATUSSET = ['features', 'maxMsgChar', 'darkWake', 'fmlyShare', 'wipedTimestamp', 'modelDisplayName', 'locationEnabled', 'isMac', 'locFoundEnabled'] -DEVICESTATUSCODES = {'200': 'online', '201': 'offline', '203': 'pending', - '204': 'unregistered'} +DEVICESTATUSCODES = { + '200': 'online', + '201': 'offline', + '203': 'pending', + '204': 'unregistered', +} SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ACCOUNTNAME): vol.All(cv.ensure_list, [cv.slugify]), @@ -87,7 +91,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): return False def lost_iphone(call): - """Call the lost iphone function if the device is found.""" + """Call the lost iPhone function if the device is found.""" accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) devicename = call.data.get(ATTR_DEVICENAME) for account in accounts: @@ -97,7 +101,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): schema=SERVICE_SCHEMA) def update_icloud(call): - """Call the update function of an icloud account.""" + """Call the update function of an iCloud account.""" accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) devicename = call.data.get(ATTR_DEVICENAME) for account in accounts: @@ -107,7 +111,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): schema=SERVICE_SCHEMA) def reset_account_icloud(call): - """Reset an icloud account.""" + """Reset an iCloud account.""" accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) for account in accounts: if account in ICLOUDTRACKERS: @@ -116,7 +120,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): reset_account_icloud, schema=SERVICE_SCHEMA) def setinterval(call): - """Call the update function of an icloud account.""" + """Call the update function of an iCloud account.""" accounts = call.data.get(ATTR_ACCOUNTNAME, ICLOUDTRACKERS) interval = call.data.get(ATTR_INTERVAL) devicename = call.data.get(ATTR_DEVICENAME) @@ -132,7 +136,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): class Icloud(DeviceScanner): - """Represent an icloud account in Home Assistant.""" + """Representation of an iCloud account.""" def __init__(self, hass, username, password, name, see): """Initialize an iCloud account.""" @@ -157,12 +161,10 @@ class Icloud(DeviceScanner): randomseconds = random.randint(10, 59) track_utc_time_change( - self.hass, self.keep_alive, - second=randomseconds - ) + self.hass, self.keep_alive, second=randomseconds) def reset_account_icloud(self): - """Reset an icloud account.""" + """Reset an iCloud account.""" from pyicloud import PyiCloudService from pyicloud.exceptions import ( PyiCloudFailedLoginException, PyiCloudNoDevicesException) @@ -178,7 +180,7 @@ class Icloud(DeviceScanner): verify=True) except PyiCloudFailedLoginException as error: self.api = None - _LOGGER.error('Error logging into iCloud Service: %s', error) + _LOGGER.error("Error logging into iCloud Service: %s", error) return try: @@ -196,12 +198,12 @@ class Icloud(DeviceScanner): _LOGGER.error('No iCloud Devices found!') def icloud_trusted_device_callback(self, callback_data): - """The trusted device is chosen.""" + """Handle chosen trusted devices.""" self._trusted_device = int(callback_data.get('trusted_device')) self._trusted_device = self.api.trusted_devices[self._trusted_device] if not self.api.send_verification_code(self._trusted_device): - _LOGGER.error('Failed to send verification code') + _LOGGER.error("Failed to send verification code") self._trusted_device = None return @@ -223,8 +225,7 @@ class Icloud(DeviceScanner): devices = self.api.trusted_devices for i, device in enumerate(devices): devicename = device.get( - 'deviceName', - 'SMS to %s' % device.get('phoneNumber')) + 'deviceName', 'SMS to %s' % device.get('phoneNumber')) devicesstring += "{}: {};".format(i, devicename) _CONFIGURING[self.accountname] = configurator.request_config( @@ -239,7 +240,7 @@ class Icloud(DeviceScanner): ) def icloud_verification_callback(self, callback_data): - """The trusted device is chosen.""" + """Handle the chosen trusted device.""" from pyicloud.exceptions import PyiCloudException self._verification_code = callback_data.get('code') @@ -249,7 +250,7 @@ class Icloud(DeviceScanner): raise PyiCloudException('Unknown failure') except PyiCloudException as error: # Reset to the inital 2FA state to allow the user to retry - _LOGGER.error('Failed to verify verification code: %s', error) + _LOGGER.error("Failed to verify verification code: %s", error) self._trusted_device = None self._verification_code = None @@ -262,7 +263,7 @@ class Icloud(DeviceScanner): configurator.request_done(request_id) def icloud_need_verification_code(self): - """We need a verification code.""" + """Return the verification code.""" configurator = get_component('configurator') if self.accountname in _CONFIGURING: return @@ -277,7 +278,7 @@ class Icloud(DeviceScanner): ) def keep_alive(self, now): - """Keep the api alive.""" + """Keep the API alive.""" if self.api is None: self.reset_account_icloud() @@ -302,7 +303,7 @@ class Icloud(DeviceScanner): self._trusted_device = None self._verification_code = None except PyiCloudException as error: - _LOGGER.error("Error setting up 2fa: %s", error) + _LOGGER.error("Error setting up 2FA: %s", error) else: self.api.authenticate() @@ -320,8 +321,8 @@ class Icloud(DeviceScanner): zone_state = self.hass.states.get('zone.home') zone_state_lat = zone_state.attributes['latitude'] zone_state_long = zone_state.attributes['longitude'] - distancefromhome = distance(latitude, longitude, zone_state_lat, - zone_state_long) + distancefromhome = distance( + latitude, longitude, zone_state_lat, zone_state_long) distancefromhome = round(distancefromhome / 1000, 1) currentzone = active_zone(self.hass, latitude, longitude) @@ -400,7 +401,7 @@ class Icloud(DeviceScanner): _LOGGER.error('No iCloud Devices found!') def lost_iphone(self, devicename): - """Call the lost iphone function if the device is found.""" + """Call the lost iPhone function if the device is found.""" if self.api is None: return @@ -428,13 +429,13 @@ class Icloud(DeviceScanner): for device in self.devices: self.devices[device].location() except PyiCloudNoDevicesException: - _LOGGER.error('No iCloud Devices found!') + _LOGGER.error("No iCloud Devices found") def setinterval(self, interval=None, devicename=None): """Set the interval of the given devices.""" devs = [devicename] if devicename else self.devices for device in devs: - devid = DOMAIN + '.' + device + devid = '{}.{}'.format(DOMAIN, device) devicestate = self.hass.states.get(devid) if interval is not None: if devicestate is not None: diff --git a/homeassistant/components/device_tracker/linksys_ap.py b/homeassistant/components/device_tracker/linksys_ap.py index fc8f9f96a37..a337f71cec4 100644 --- a/homeassistant/components/device_tracker/linksys_ap.py +++ b/homeassistant/components/device_tracker/linksys_ap.py @@ -99,9 +99,8 @@ class LinksysAPDeviceScanner(object): # No, the '&&' is not a typo - this is expected by the web interface. login = base64.b64encode(bytes(self.username, 'utf8')).decode('ascii') pwd = base64.b64encode(bytes(self.password, 'utf8')).decode('ascii') + url = 'https://{}/StatusClients.htm&&unit={}&vap=0'.format( + self.host, unit) return requests.get( - 'https://%s/StatusClients.htm&&unit=%s&vap=0' % (self.host, unit), - timeout=DEFAULT_TIMEOUT, - verify=self.verify_ssl, - cookies={'LoginName': login, - 'LoginPWD': pwd}) + url, timeout=DEFAULT_TIMEOUT, verify=self.verify_ssl, + cookies={'LoginName': login, 'LoginPWD': pwd}) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index 8cc6af48767..24af81b281e 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -20,7 +20,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -46,10 +45,7 @@ def get_scanner(hass, config): class LuciDeviceScanner(DeviceScanner): - """This class queries a wireless router running OpenWrt firmware. - - Adapted from Tomato scanner. - """ + """This class queries a wireless router running OpenWrt firmware.""" def __init__(self, config): """Initialize the scanner.""" @@ -106,7 +102,7 @@ class LuciDeviceScanner(DeviceScanner): return False with self.lock: - _LOGGER.info('Checking ARP') + _LOGGER.info("Checking ARP") url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host) @@ -114,7 +110,7 @@ class LuciDeviceScanner(DeviceScanner): result = _req_json_rpc(url, 'net.arptable', params={'auth': self.token}) except InvalidLuciTokenError: - _LOGGER.info('Refreshing token') + _LOGGER.info("Refreshing token") self.refresh_token() return False @@ -138,28 +134,27 @@ def _req_json_rpc(url, method, *args, **kwargs): try: res = requests.post(url, data=data, timeout=5, **kwargs) except requests.exceptions.Timeout: - _LOGGER.exception('Connection to the router timed out') + _LOGGER.exception("Connection to the router timed out") return if res.status_code == 200: try: result = res.json() except ValueError: # If json decoder could not parse the response - _LOGGER.exception('Failed to parse response from luci') + _LOGGER.exception("Failed to parse response from luci") return try: return result['result'] except KeyError: - _LOGGER.exception('No result in response from luci') + _LOGGER.exception("No result in response from luci") return elif res.status_code == 401: # Authentication error _LOGGER.exception( - "Failed to authenticate, " - "please check your username and password") + "Failed to authenticate, check your username and password") return elif res.status_code == 403: - _LOGGER.error('Luci responded with a 403 Invalid token') + _LOGGER.error("Luci responded with a 403 Invalid token") raise InvalidLuciTokenError else: diff --git a/homeassistant/components/device_tracker/mikrotik.py b/homeassistant/components/device_tracker/mikrotik.py new file mode 100644 index 00000000000..f22b297b78d --- /dev/null +++ b/homeassistant/components/device_tracker/mikrotik.py @@ -0,0 +1,131 @@ +""" +Support for Mikrotik routers as device tracker. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.mikrotik/ +""" +import logging +import threading +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import (CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_PORT) +from homeassistant.util import Throttle + +REQUIREMENTS = ['librouteros==1.0.2'] + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +MTK_DEFAULT_API_PORT = '8728' + +_LOGGER = logging.getLogger(__name__) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=MTK_DEFAULT_API_PORT): cv.port +}) + + +def get_scanner(hass, config): + """Validate the configuration and return MTikScanner.""" + scanner = MikrotikScanner(config[DOMAIN]) + return scanner if scanner.success_init else None + + +class MikrotikScanner(DeviceScanner): + """This class queries a Mikrotik router.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.last_results = {} + + self.host = config[CONF_HOST] + self.port = config[CONF_PORT] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.connected = False + self.success_init = False + self.client = None + + self.success_init = self.connect_to_device() + + if self.success_init: + _LOGGER.info("Start polling Mikrotik router...") + self._update_info() + else: + _LOGGER.error("Connection to Mikrotik failed") + + def connect_to_device(self): + """Connect to Mikrotik method.""" + # pylint: disable=import-error + import librouteros + try: + self.client = librouteros.connect( + self.host, + self.username, + self.password, + port=int(self.port) + ) + + routerboard_info = self.client(cmd='/system/routerboard/getall') + + if routerboard_info: + _LOGGER.info("Connected to Mikrotik %s with IP %s", + routerboard_info[0].get('model', 'Router'), + self.host) + self.connected = True + + except (librouteros.exceptions.TrapError, + librouteros.exceptions.ConnectionError) as api_error: + _LOGGER.error("Connection error: %s", api_error) + + return self.connected + + def scan_devices(self): + """Scan for new devices and return a list with found device MACs.""" + self._update_info() + return [device for device in self.last_results] + + def get_device_name(self, mac): + """Return the name of the given device or None if we don't know.""" + with self.lock: + return self.last_results.get(mac) + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """Retrieve latest information from the Mikrotik box.""" + with self.lock: + _LOGGER.info("Loading wireless device from Mikrotik...") + + wireless_clients = self.client( + cmd='/interface/wireless/registration-table/getall' + ) + device_names = self.client(cmd='/ip/dhcp-server/lease/getall') + + if device_names is None or wireless_clients is None: + return False + + mac_names = {device.get('mac-address'): device.get('host-name') + for device in device_names + if device.get('mac-address')} + + self.last_results = { + device.get('mac-address'): + mac_names.get(device.get('mac-address')) + for device in wireless_clients + } + + return True diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py index 1f7fa9c1b84..aab5b43acea 100644 --- a/homeassistant/components/device_tracker/mqtt.py +++ b/homeassistant/components/device_tracker/mqtt.py @@ -27,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({ @asyncio.coroutine def async_setup_scanner(hass, config, async_see, discovery_info=None): - """Setup the MQTT tracker.""" + """Set up the MQTT tracker.""" devices = config[CONF_DEVICES] qos = config[CONF_QOS] @@ -35,7 +35,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): @callback def async_tracker_message_received(topic, payload, qos): - """MQTT message received.""" + """Handle received MQTT message.""" hass.async_add_job( async_see(dev_id=dev_id_lookup[topic], location_name=payload)) diff --git a/homeassistant/components/device_tracker/mqtt_json.py b/homeassistant/components/device_tracker/mqtt_json.py index da85055ba96..0ef4f1835b6 100644 --- a/homeassistant/components/device_tracker/mqtt_json.py +++ b/homeassistant/components/device_tracker/mqtt_json.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({ @asyncio.coroutine def async_setup_scanner(hass, config, async_see, discovery_info=None): - """Setup the MQTT tracker.""" + """Set up the MQTT JSON tracker.""" devices = config[CONF_DEVICES] qos = config[CONF_QOS] @@ -45,18 +45,18 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): @callback def async_tracker_message_received(topic, payload, qos): - """MQTT message received.""" + """Handle received MQTT message.""" dev_id = dev_id_lookup[topic] try: data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload)) except vol.MultipleInvalid: - _LOGGER.error('Skipping update for following data ' - 'because of missing or malformatted data: %s', + _LOGGER.error("Skipping update for following data " + "because of missing or malformatted data: %s", payload) return except ValueError: - _LOGGER.error('Error parsing JSON payload: %s', payload) + _LOGGER.error("Error parsing JSON payload: %s", payload) return kwargs = _parse_see_args(dev_id, data) diff --git a/homeassistant/components/device_tracker/mysensors.py b/homeassistant/components/device_tracker/mysensors.py index 04801f834df..4503c4d1b26 100644 --- a/homeassistant/components/device_tracker/mysensors.py +++ b/homeassistant/components/device_tracker/mysensors.py @@ -15,12 +15,12 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see, discovery_info=None): - """Setup the MySensors tracker.""" + """Set up the MySensors tracker.""" def mysensors_callback(gateway, msg): - """Callback for mysensors platform.""" + """Set up callback for mysensors platform.""" node = gateway.sensors[msg.node_id] if node.sketch_name is None: - _LOGGER.debug('No sketch_name: node %s', msg.node_id) + _LOGGER.debug("No sketch_name: node %s", msg.node_id) return pres = gateway.const.Presentation @@ -35,8 +35,8 @@ def setup_scanner(hass, config, see, discovery_info=None): try: latitude, longitude, _ = position.split(',') except ValueError: - _LOGGER.error('Payload for V_POSITION %s is not of format ' - 'latitude,longitude,altitude', position) + _LOGGER.error("Payload for V_POSITION %s is not of format " + "latitude, longitude, altitude", position) return name = '{} {} {}'.format( node.sketch_name, msg.node_id, child.id) diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index d6716dfb9b1..b3ec442198e 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -17,11 +17,11 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT) from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) +REQUIREMENTS = ['pynetgear==0.3.3'] _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pynetgear==0.3.3'] + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) DEFAULT_HOST = 'routerlogin.net' DEFAULT_USER = 'admin' @@ -60,7 +60,7 @@ class NetgearDeviceScanner(DeviceScanner): self._api = pynetgear.Netgear(password, host, username, port) - _LOGGER.info('Logging in') + _LOGGER.info("Logging in") results = self._api.get_attached_devices() @@ -69,7 +69,7 @@ class NetgearDeviceScanner(DeviceScanner): if self.success_init: self.last_results = results else: - _LOGGER.error('Failed to Login') + _LOGGER.error("Failed to Login") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -95,11 +95,11 @@ class NetgearDeviceScanner(DeviceScanner): return with self.lock: - _LOGGER.info('Scanning') + _LOGGER.info("Scanning") results = self._api.get_attached_devices() if results is None: - _LOGGER.warning('Error scanning devices') + _LOGGER.warning("Error scanning devices") self.last_results = results or [] diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index 182826a1f62..99dfc3829d7 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -80,7 +80,7 @@ class NmapDeviceScanner(DeviceScanner): self.home_interval = timedelta(minutes=minutes) self.success_init = self._update_info() - _LOGGER.info("nmap scanner initialized") + _LOGGER.info("Scanner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 156e9d6a08a..40ab48b384a 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -85,8 +85,8 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): try: keylen, decrypt = get_cipher() except OSError: - _LOGGER.warning('Ignoring encrypted payload ' - 'because libsodium not installed.') + _LOGGER.warning( + "Ignoring encrypted payload because libsodium not installed") return None if isinstance(secret, dict): @@ -95,9 +95,9 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): key = secret if key is None: - _LOGGER.warning('Ignoring encrypted payload ' - 'because no decryption key known ' - 'for topic %s.', topic) + _LOGGER.warning( + "Ignoring encrypted payload because no decryption key known " + "for topic %s", topic) return None key = key.encode("utf-8") @@ -111,9 +111,9 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): _LOGGER.debug("Decrypted payload: %s", message) return message except ValueError: - _LOGGER.warning('Ignoring encrypted payload ' - 'because unable to decrypt using key ' - 'for topic %s.', topic) + _LOGGER.warning( + "Ignoring encrypted payload because unable to decrypt using " + "key for topic %s", topic) return None # pylint: disable=too-many-return-statements @@ -123,7 +123,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): data = json.loads(payload) except ValueError: # If invalid JSON - _LOGGER.error('Unable to parse payload as JSON: %s', payload) + _LOGGER.error("Unable to parse payload as JSON: %s", payload) return None if isinstance(data, dict) and \ @@ -136,22 +136,22 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): return validate_payload(topic, plaintext_payload, data_type) if not isinstance(data, dict) or data.get('_type') != data_type: - _LOGGER.debug('Skipping %s update for following data ' - 'because of missing or malformatted data: %s', + _LOGGER.debug("Skipping %s update for following data " + "because of missing or malformatted data: %s", data_type, data) return None if data_type == VALIDATE_TRANSITION or data_type == VALIDATE_WAYPOINTS: return data if max_gps_accuracy is not None and \ convert(data.get('acc'), float, 0.0) > max_gps_accuracy: - _LOGGER.info('Ignoring %s update because expected GPS ' - 'accuracy %s is not met: %s', + _LOGGER.info("Ignoring %s update because expected GPS " + "accuracy %s is not met: %s", data_type, max_gps_accuracy, payload) return None if convert(data.get('acc'), float, 1.0) == 0.0: - _LOGGER.warning('Ignoring %s update because GPS accuracy ' - 'is zero: %s', - data_type, payload) + _LOGGER.warning( + "Ignoring %s update because GPS accuracy is zero: %s", + data_type, payload) return None return data @@ -169,7 +169,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): if regions_entered[dev_id]: _LOGGER.debug( - "location update ignored - inside region %s", + "Location update ignored, inside region %s", regions_entered[-1]) return @@ -178,7 +178,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): @callback def async_owntracks_event_update(topic, payload, qos): - """MQTT event (geofences) received.""" + """Handle MQTT event (geofences).""" # Docs on available data: # http://owntracks.org/booklet/tech/json/#_typetransition data = validate_payload(topic, payload, VALIDATE_TRANSITION) @@ -242,15 +242,14 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): if data['acc'] == 0.0: valid_gps = False _LOGGER.warning( - 'Ignoring GPS in region exit because accuracy' - 'is zero: %s', - payload) + "Ignoring GPS in region exit because accuracy" + "is zero: %s", payload) if (max_gps_accuracy is not None and data['acc'] > max_gps_accuracy): valid_gps = False _LOGGER.info( - 'Ignoring GPS in region exit because expected ' - 'GPS accuracy %s is not met: %s', + "Ignoring GPS in region exit because expected " + "GPS accuracy %s is not met: %s", max_gps_accuracy, payload) if valid_gps: hass.async_add_job(async_see(**kwargs)) @@ -267,7 +266,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): leave_event() else: _LOGGER.error( - 'Misformatted mqtt msgs, _type=transition, event=%s', + "Misformatted mqtt msgs, _type=transition, event=%s", data['event']) return diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/device_tracker/ping.py index ee9a4d19d37..1f21a25359c 100644 --- a/homeassistant/components/device_tracker/ping.py +++ b/homeassistant/components/device_tracker/ping.py @@ -37,8 +37,8 @@ class Host(object): self.ip_address = ip_address self.dev_id = dev_id self._count = config[CONF_PING_COUNT] - if sys.platform == "win32": - self._ping_cmd = ['ping', '-n 1', '-w 1000', self.ip_address] + if sys.platform == 'win32': + self._ping_cmd = ['ping', '-n 1', '-w', '1000', self.ip_address] else: self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1', self.ip_address] @@ -67,7 +67,7 @@ class Host(object): def setup_scanner(hass, config, see, discovery_info=None): - """Setup the Host objects and return the update function.""" + """Set up the Host objects and return the update function.""" hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in config[const.CONF_HOSTS].items()] interval = timedelta(seconds=len(hosts) * config[CONF_PING_COUNT]) + \ diff --git a/homeassistant/components/device_tracker/swisscom.py b/homeassistant/components/device_tracker/swisscom.py index 530e39d3e57..d2a5a57e491 100644 --- a/homeassistant/components/device_tracker/swisscom.py +++ b/homeassistant/components/device_tracker/swisscom.py @@ -17,7 +17,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/device_tracker/tado.py b/homeassistant/components/device_tracker/tado.py index 3c21037d028..46096e62fbb 100644 --- a/homeassistant/components/device_tracker/tado.py +++ b/homeassistant/components/device_tracker/tado.py @@ -11,21 +11,19 @@ from collections import namedtuple import asyncio import aiohttp import async_timeout - import voluptuous as vol -import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.util import Throttle from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.helpers.aiohttp_client import async_create_clientsession -# Configuration constant specific for tado -CONF_HOME_ID = 'home_id' - _LOGGER = logging.getLogger(__name__) +CONF_HOME_ID = 'home_id' + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -71,7 +69,7 @@ class TadoDeviceScanner(DeviceScanner): hass, cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop)) self.success_init = self._update_info() - _LOGGER.info("Tado scanner initialized") + _LOGGER.info("Scanner initialized") @asyncio.coroutine def async_scan_devices(self): @@ -110,14 +108,12 @@ class TadoDeviceScanner(DeviceScanner): with async_timeout.timeout(10, loop=self.hass.loop): # Format the URL here, so we can log the template URL if # anything goes wrong without exposing username and password. - url = self.tadoapiurl.format(home_id=self.home_id, - username=self.username, - password=self.password) + url = self.tadoapiurl.format( + home_id=self.home_id, username=self.username, + password=self.password) - # Go get 'em! response = yield from self.websession.get(url) - # error on Tado webservice if response.status != 200: _LOGGER.warning( "Error %d on %s.", response.status, self.tadoapiurl) diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index e9ab4e347eb..6efe8d59beb 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -18,7 +18,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) @@ -88,7 +87,7 @@ class ThomsonDeviceScanner(DeviceScanner): return False with self.lock: - _LOGGER.info('Checking ARP') + _LOGGER.info("Checking ARP") data = self.get_thomson_data() if not data: return False @@ -112,11 +111,11 @@ class ThomsonDeviceScanner(DeviceScanner): devices_result = telnet.read_until(b'=>').split(b'\r\n') telnet.write('exit\r\n'.encode('ascii')) except EOFError: - _LOGGER.exception('Unexpected response from router') + _LOGGER.exception("Unexpected response from router") return except ConnectionRefusedError: - _LOGGER.exception('Connection refused by router,' - ' is telnet enabled?') + _LOGGER.exception( + "Connection refused by router. Telnet enabled?") return devices = {} diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index 3f01600259b..51394ae64fe 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -19,10 +19,9 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) -CONF_HTTP_ID = "http_id" +CONF_HTTP_ID = 'http_id' _LOGGER = logging.getLogger(__name__) @@ -47,12 +46,10 @@ class TomatoDeviceScanner(DeviceScanner): host, http_id = config[CONF_HOST], config[CONF_HTTP_ID] username, password = config[CONF_USERNAME], config[CONF_PASSWORD] - self.req = requests.Request('POST', - 'http://{}/update.cgi'.format(host), - data={'_http_id': http_id, - 'exec': 'devlist'}, - auth=requests.auth.HTTPBasicAuth( - username, password)).prepare() + self.req = requests.Request( + 'POST', 'http://{}/update.cgi'.format(host), + data={'_http_id': http_id, 'exec': 'devlist'}, + auth=requests.auth.HTTPBasicAuth(username, password)).prepare() self.parse_api_pattern = re.compile(r"(?P\w*) = (?P.*);") @@ -112,20 +109,17 @@ class TomatoDeviceScanner(DeviceScanner): except requests.exceptions.ConnectionError: # We get this if we could not connect to the router or # an invalid http_id was supplied. - self.logger.exception(( - "Failed to connect to the router" - " or invalid http_id 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. - self.logger.exception( - "Connection to the router timed out") + self.logger.exception("Connection to the router timed out") return False except ValueError: # If JSON decoder could not parse the response. - self.logger.exception( - "Failed to parse response from router") + 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 8d476136d23..65fa89d737f 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -20,7 +20,6 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ class TplinkDeviceScanner(DeviceScanner): # pylint: disable=no-self-use def get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Get firmware doesn't save the name of the wireless device.""" return None @Throttle(MIN_TIME_BETWEEN_SCANS) @@ -83,8 +82,9 @@ class TplinkDeviceScanner(DeviceScanner): url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host) referer = 'http://{}'.format(self.host) - page = requests.get(url, auth=(self.username, self.password), - headers={'referer': referer}) + page = requests.get( + url, auth=(self.username, self.password), + headers={'referer': referer}, timeout=4) result = self.parse_macs.findall(page.text) @@ -105,7 +105,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner): # pylint: disable=no-self-use def get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Get firmware doesn't save the name of the wireless device.""" return self.last_results.get(device) @Throttle(MIN_TIME_BETWEEN_SCANS) @@ -130,8 +130,9 @@ class Tplink2DeviceScanner(TplinkDeviceScanner): cookie = 'Authorization=Basic {}' \ .format(b64_encoded_username_password) - response = requests.post(url, headers={'referer': referer, - 'cookie': cookie}) + response = requests.post( + url, headers={'referer': referer, 'cookie': cookie}, + timeout=4) try: result = response.json().get('data') @@ -166,7 +167,7 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): # pylint: disable=no-self-use def get_device_name(self, device): - """The firmware doesn't save the name of the wireless device. + """Get the firmware doesn't save the name of the wireless device. We are forced to use the MAC address as name here. """ @@ -181,17 +182,16 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): referer = 'http://{}/webpages/login.html'.format(self.host) # If possible implement rsa encryption of password here. - response = requests.post(url, - params={'operation': 'login', - 'username': self.username, - 'password': self.password}, - headers={'referer': referer}) + response = requests.post( + url, params={'operation': 'login', 'username': self.username, + 'password': self.password}, + headers={'referer': referer}, timeout=4) try: self.stok = response.json().get('data').get('stok') _LOGGER.info(self.stok) - regex_result = re.search('sysauth=(.*);', - response.headers['set-cookie']) + regex_result = re.search( + 'sysauth=(.*);', response.headers['set-cookie']) self.sysauth = regex_result.group(1) _LOGGER.info(self.sysauth) return True @@ -218,7 +218,8 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): response = requests.post(url, params={'operation': 'load'}, headers={'referer': referer}, - cookies={'sysauth': self.sysauth}) + cookies={'sysauth': self.sysauth}, + timeout=5) try: json_response = response.json() @@ -227,18 +228,17 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): result = response.json().get('data') else: if json_response.get('errorcode') == 'timeout': - _LOGGER.info("Token timed out. " - "Relogging on next scan.") + _LOGGER.info("Token timed out. Relogging on next scan") self.stok = '' self.sysauth = '' return False else: - _LOGGER.error("An unknown error happened " - "while fetching data.") + _LOGGER.error( + "An unknown error happened while fetching data") return False except ValueError: _LOGGER.error("Router didn't respond with JSON. " - "Check if credentials are correct.") + "Check if credentials are correct") return False if result: @@ -267,7 +267,7 @@ class Tplink4DeviceScanner(TplinkDeviceScanner): # pylint: disable=no-self-use def get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Get the name of the wireless device.""" return None def _get_auth_tokens(self): @@ -298,7 +298,7 @@ class Tplink4DeviceScanner(TplinkDeviceScanner): self.token = result.group(1) return True except ValueError: - _LOGGER.error("Couldn't fetch auth tokens!") + _LOGGER.error("Couldn't fetch auth tokens") return False @Throttle(MIN_TIME_BETWEEN_SCANS) diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/device_tracker/trackr.py index 557d1f68b2a..84fb449c070 100644 --- a/homeassistant/components/device_tracker/trackr.py +++ b/homeassistant/components/device_tracker/trackr.py @@ -37,18 +37,18 @@ class TrackRDeviceScanner(object): """Initialize the TrackR device scanner.""" from pytrackr.api import trackrApiInterface self.hass = hass - self.api = trackrApiInterface(config.get(CONF_USERNAME), - config.get(CONF_PASSWORD)) + self.api = trackrApiInterface( + config.get(CONF_USERNAME), config.get(CONF_PASSWORD)) self.see = see self.devices = self.api.get_trackrs() self._update_info() - track_utc_time_change(self.hass, self._update_info, - second=range(0, 60, 30)) + track_utc_time_change( + self.hass, self._update_info, second=range(0, 60, 30)) def _update_info(self, now=None) -> None: """Update the device info.""" - _LOGGER.debug('Updating devices %s', now) + _LOGGER.debug("Updating devices %s", now) # Update self.devices to collect new devices added # to the users account. diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index 083e1599d11..31c7d32c4c1 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -70,9 +70,9 @@ class UbusDeviceScanner(DeviceScanner): """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, - 'call', 'uci', 'get', - config="dhcp", type="dnsmasq") + result = _req_json_rpc( + self.url, self.session_id, 'call', 'uci', 'get', + config="dhcp", type="dnsmasq") if result: values = result["values"].values() self.leasefile = next(iter(values))["leasefile"] @@ -80,9 +80,9 @@ class UbusDeviceScanner(DeviceScanner): return if self.mac2name is None: - result = _req_json_rpc(self.url, self.session_id, - 'call', 'file', 'read', - path=self.leasefile) + result = _req_json_rpc( + self.url, self.session_id, 'call', 'file', 'read', + path=self.leasefile) if result: self.mac2name = dict() for line in result["data"].splitlines(): @@ -107,15 +107,15 @@ class UbusDeviceScanner(DeviceScanner): _LOGGER.info("Checking ARP") if not self.hostapd: - hostapd = _req_json_rpc(self.url, self.session_id, - 'list', 'hostapd.*', '') + hostapd = _req_json_rpc( + self.url, self.session_id, 'list', 'hostapd.*', '') self.hostapd.extend(hostapd.keys()) self.last_results = [] results = 0 for hostapd in self.hostapd: - result = _req_json_rpc(self.url, self.session_id, - 'call', hostapd, 'get_clients') + result = _req_json_rpc( + self.url, self.session_id, 'call', hostapd, 'get_clients') if result: results = results + 1 diff --git a/homeassistant/components/device_tracker/unifi.py b/homeassistant/components/device_tracker/unifi.py index e46abda08d0..42b5070b046 100644 --- a/homeassistant/components/device_tracker/unifi.py +++ b/homeassistant/components/device_tracker/unifi.py @@ -15,29 +15,31 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD from homeassistant.const import CONF_VERIFY_SSL -# Unifi package doesn't list urllib3 as a requirement REQUIREMENTS = ['pyunifi==2.0'] _LOGGER = logging.getLogger(__name__) CONF_PORT = 'port' CONF_SITE_ID = 'site_id' + +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = 8443 DEFAULT_VERIFY_SSL = True NOTIFICATION_ID = 'unifi_notification' NOTIFICATION_TITLE = 'Unifi Device Tracker Setup' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default='localhost'): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_SITE_ID, default='default'): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PORT, default=8443): cv.port, + vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }) def get_scanner(hass, config): - """Setup Unifi device_tracker.""" + """Set up the Unifi device_tracker.""" from pyunifi.controller import Controller host = config[DOMAIN].get(CONF_HOST) @@ -52,7 +54,7 @@ def get_scanner(hass, config): ctrl = Controller(host, username, password, port, version='v4', site_id=site_id, ssl_verify=verify_ssl) except urllib.error.HTTPError as ex: - _LOGGER.error('Failed to connect to Unifi: %s', ex) + _LOGGER.error("Failed to connect to Unifi: %s", ex) persistent_notification.create( hass, 'Failed to connect to Unifi. ' 'Error: {}
' @@ -78,7 +80,7 @@ class UnifiScanner(DeviceScanner): try: clients = self._controller.get_clients() except urllib.error.HTTPError as ex: - _LOGGER.error('Failed to scan clients: %s', ex) + _LOGGER.error("Failed to scan clients: %s", ex) clients = [] self._clients = {client['mac']: client for client in clients} @@ -96,5 +98,5 @@ class UnifiScanner(DeviceScanner): """ client = self._clients.get(mac, {}) name = client.get('name') or client.get('hostname') - _LOGGER.debug('Device %s name %s', mac, name) + _LOGGER.debug("Device %s name %s", mac, name) return name diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/device_tracker/upc_connect.py index cec00221aaf..ace7c4455a9 100644 --- a/homeassistant/components/device_tracker/upc_connect.py +++ b/homeassistant/components/device_tracker/upc_connect.py @@ -95,7 +95,7 @@ class UPCDeviceScanner(DeviceScanner): @asyncio.coroutine def async_get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Ge the firmware doesn't save the name of the wireless device.""" return None @asyncio.coroutine @@ -118,7 +118,7 @@ class UPCDeviceScanner(DeviceScanner): 'Password': self.password, }) - # successfull? + # Successful? return data is not None except (asyncio.TimeoutError, aiohttp.ClientError): diff --git a/homeassistant/components/device_tracker/volvooncall.py b/homeassistant/components/device_tracker/volvooncall.py index 9d5220b877c..df1c778dcb6 100644 --- a/homeassistant/components/device_tracker/volvooncall.py +++ b/homeassistant/components/device_tracker/volvooncall.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see, discovery_info=None): - """Setup Volvo tracker.""" + """Set up the Volvo tracker.""" if discovery_info is None: return @@ -24,7 +24,7 @@ def setup_scanner(hass, config, see, discovery_info=None): dev_id = 'volvo_' + slugify(host_name) def see_vehicle(vehicle): - """Callback for reporting vehicle position.""" + """Handle the reporting of the vehicle position.""" see(dev_id=dev_id, host_name=host_name, gps=(vehicle.position['latitude'], diff --git a/homeassistant/components/device_tracker/xiaomi.py b/homeassistant/components/device_tracker/xiaomi.py index 7c5c415f054..e87cae3d50b 100644 --- a/homeassistant/components/device_tracker/xiaomi.py +++ b/homeassistant/components/device_tracker/xiaomi.py @@ -17,11 +17,10 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -# Return cached results if last scan was less then this time ago. -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) - _LOGGER = logging.getLogger(__name__) +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME, default='admin'): cv.string, @@ -98,12 +97,12 @@ class XiaomiDeviceScanner(DeviceScanner): Return the list if successful. """ - _LOGGER.info('Refreshing device list') + _LOGGER.info("Refreshing device list") result = _retrieve_list(self.host, self.token) if result: return result else: - _LOGGER.info('Refreshing token and retrying device list refresh') + _LOGGER.info("Refreshing token and retrying device list refresh") self.token = _get_token(self.host, self.username, self.password) return _retrieve_list(self.host, self.token) @@ -117,40 +116,40 @@ class XiaomiDeviceScanner(DeviceScanner): def _retrieve_list(host, token, **kwargs): - """"Get device list for the given host.""" + """Get device list for the given host.""" url = "http://{}/cgi-bin/luci/;stok={}/api/misystem/devicelist" url = url.format(host, token) try: res = requests.get(url, timeout=5, **kwargs) except requests.exceptions.Timeout: - _LOGGER.exception('Connection to the router timed out at URL [%s]', - url) + _LOGGER.exception( + "Connection to the router timed out at URL %s", url) return if res.status_code != 200: - _LOGGER.exception('Connection failed with http code [%s]', - res.status_code) + _LOGGER.exception( + "Connection failed with http code %s", res.status_code) return try: result = res.json() except ValueError: # If json decoder could not parse the response - _LOGGER.exception('Failed to parse response from mi router') + _LOGGER.exception("Failed to parse response from mi router") return try: xiaomi_code = result['code'] except KeyError: - _LOGGER.exception('No field code in response from mi router. %s', - result) + _LOGGER.exception( + "No field code in response from mi router. %s", result) return if xiaomi_code == 0: try: return result['list'] except KeyError: - _LOGGER.exception('No list in response from mi router. %s', result) + _LOGGER.exception("No list in response from mi router. %s", result) return else: _LOGGER.info( - 'Receive wrong Xiaomi code [%s], expected [0] in response [%s]', + "Receive wrong Xiaomi code %s, expected 0 in response %s", xiaomi_code, result) return @@ -162,14 +161,14 @@ def _get_token(host, username, password): try: res = requests.post(url, data=data, timeout=5) except requests.exceptions.Timeout: - _LOGGER.exception('Connection to the router timed out') + _LOGGER.exception("Connection to the router timed out") return if res.status_code == 200: try: result = res.json() except ValueError: - # If json decoder could not parse the response - _LOGGER.exception('Failed to parse response from mi router') + # If JSON decoder could not parse the response + _LOGGER.exception("Failed to parse response from mi router") return try: return result['token'] diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 01b25cf0a87..58fc56d2cba 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -83,7 +83,7 @@ def async_setup(hass, config): @asyncio.coroutine def new_service_found(service, info): - """Called when a new service is found.""" + """Handle a new service if one is found.""" if service in ignored_platforms: logger.info("Ignoring service: %s %s", service, info) return diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py index 4330ad5be2f..2e26b306673 100644 --- a/homeassistant/components/downloader.py +++ b/homeassistant/components/downloader.py @@ -48,7 +48,7 @@ def setup(hass, config): if not os.path.isdir(download_path): _LOGGER.error( - "Download path %s does not exist. File Downloader not active.", + "Download path %s does not exist. File Downloader not active", download_path) return False @@ -76,7 +76,7 @@ def setup(hass, config): match = re.findall(r"filename=(\S+)", req.headers['content-disposition']) - if len(match) > 0: + if match: filename = match[0].strip("'\" ") if not filename: @@ -84,7 +84,7 @@ def setup(hass, config): url).strip() if not filename: - filename = "ha_download" + filename = 'ha_download' # Remove stuff to ruin paths filename = sanitize_filename(filename) diff --git a/homeassistant/components/dweet.py b/homeassistant/components/dweet.py index d812daf50a6..b4e8d68e960 100644 --- a/homeassistant/components/dweet.py +++ b/homeassistant/components/dweet.py @@ -33,7 +33,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Dweet.io component.""" + """Set up the Dweet.io component.""" conf = config[DOMAIN] name = conf.get(CONF_NAME) whitelist = conf.get(CONF_WHITELIST) diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index f6aa0c33e0f..f0c95f7de3d 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -16,9 +16,7 @@ from homeassistant.const import CONF_API_KEY from homeassistant.loader import get_component from homeassistant.util import Throttle -REQUIREMENTS = [ - 'https://github.com/nkgilley/python-ecobee-api/archive/' - 'a4496b293956b2eac285305136a62ac78bef510d.zip#python-ecobee==0.0.7'] +REQUIREMENTS = ['python-ecobee-api==0.0.7'] _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) @@ -52,7 +50,7 @@ def request_configuration(network, hass, config): # pylint: disable=unused-argument def ecobee_configuration_callback(callback_data): - """The actions to do when our configuration callback is called.""" + """Handle configuration callbacks.""" network.request_tokens() network.update() setup_ecobee(hass, network, config) @@ -68,7 +66,7 @@ def request_configuration(network, hass, config): def setup_ecobee(hass, network, config): - """Setup Ecobee thermostat.""" + """Set up the Ecobee thermostat.""" # If ecobee has a PIN then it needs to be configured. if network.pin is not None: request_configuration(network, hass, config) @@ -80,8 +78,8 @@ def setup_ecobee(hass, network, config): hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP) - discovery.load_platform(hass, 'climate', DOMAIN, - {'hold_temp': hold_temp}, config) + discovery.load_platform( + hass, 'climate', DOMAIN, {'hold_temp': hold_temp}, config) discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) @@ -90,7 +88,7 @@ class EcobeeData(object): """Get the latest data and update the states.""" def __init__(self, config_file): - """Initialize the Ecobee data object.""" + """Init the Ecobee data object.""" from pyecobee import Ecobee self.ecobee = Ecobee(config_file) @@ -102,7 +100,7 @@ class EcobeeData(object): def setup(hass, config): - """Setup Ecobee. + """Set up the Ecobee. Will automatically load thermostat and sensor components to support devices discovered on the network. diff --git a/homeassistant/components/eight_sleep.py b/homeassistant/components/eight_sleep.py new file mode 100644 index 00000000000..db718aec05e --- /dev/null +++ b/homeassistant/components/eight_sleep.py @@ -0,0 +1,241 @@ +""" +Support for Eight smart mattress covers and mattresses. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/eight_sleep/ +""" +import asyncio +import logging +import os +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.config import load_yaml_config_file +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, CONF_SENSORS, CONF_BINARY_SENSORS, + ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_send, async_dispatcher_connect) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.util.dt import utcnow + +REQUIREMENTS = ['pyeight==0.0.4'] + +_LOGGER = logging.getLogger(__name__) + +CONF_PARTNER = 'partner' + +DATA_EIGHT = 'eight_sleep' +DEFAULT_PARTNER = False +DOMAIN = 'eight_sleep' + +HEAT_ENTITY = 'heat' +USER_ENTITY = 'user' + +HEAT_SCAN_INTERVAL = timedelta(seconds=60) +USER_SCAN_INTERVAL = timedelta(seconds=300) + +SIGNAL_UPDATE_HEAT = 'eight_heat_update' +SIGNAL_UPDATE_USER = 'eight_user_update' + +NAME_MAP = { + 'left_current_sleep': 'Left Sleep Session', + 'left_last_sleep': 'Left Previous Sleep Session', + 'left_bed_state': 'Left Bed State', + 'left_presence': 'Left Bed Presence', + 'left_bed_temp': 'Left Bed Temperature', + 'left_sleep_stage': 'Left Sleep Stage', + 'right_current_sleep': 'Right Sleep Session', + 'right_last_sleep': 'Right Previous Sleep Session', + 'right_bed_state': 'Right Bed State', + 'right_presence': 'Right Bed Presence', + 'right_bed_temp': 'Right Bed Temperature', + 'right_sleep_stage': 'Right Sleep Stage', + 'room_temp': 'Room Temperature', +} + +SENSORS = ['current_sleep', + 'last_sleep', + 'bed_state', + 'bed_temp', + 'sleep_stage'] + +SERVICE_HEAT_SET = 'heat_set' + +ATTR_TARGET_HEAT = 'target' +ATTR_HEAT_DURATION = 'duration' + +VALID_TARGET_HEAT = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=100)) +VALID_DURATION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=28800)) + +SERVICE_EIGHT_SCHEMA = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_TARGET_HEAT: VALID_TARGET_HEAT, + ATTR_HEAT_DURATION: VALID_DURATION, + }) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PARTNER, default=DEFAULT_PARTNER): cv.boolean, + }), +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Set up the Eight Sleep component.""" + from pyeight.eight import EightSleep + + conf = config.get(DOMAIN) + user = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + partner = conf.get(CONF_PARTNER) + + if hass.config.time_zone is None: + _LOGGER.error('Timezone is not set in Home Assistant.') + return False + + timezone = hass.config.time_zone + + eight = EightSleep(user, password, timezone, partner, None, hass.loop) + + hass.data[DATA_EIGHT] = eight + + # Authenticate, build sensors + success = yield from eight.start() + if not success: + # Authentication failed, cannot continue + return False + + @asyncio.coroutine + def async_update_heat_data(now): + """Update heat data from eight in HEAT_SCAN_INTERVAL.""" + yield from eight.update_device_data() + async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT) + + async_track_point_in_utc_time( + hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL) + + @asyncio.coroutine + def async_update_user_data(now): + """Update user data from eight in USER_SCAN_INTERVAL.""" + yield from eight.update_user_data() + async_dispatcher_send(hass, SIGNAL_UPDATE_USER) + + async_track_point_in_utc_time( + hass, async_update_user_data, utcnow() + USER_SCAN_INTERVAL) + + yield from async_update_heat_data(None) + yield from async_update_user_data(None) + + # Load sub components + sensors = [] + binary_sensors = [] + if eight.users: + for user in eight.users: + obj = eight.users[user] + for sensor in SENSORS: + sensors.append('{}_{}'.format(obj.side, sensor)) + binary_sensors.append('{}_presence'.format(obj.side)) + sensors.append('room_temp') + + hass.async_add_job(discovery.async_load_platform( + hass, 'sensor', DOMAIN, { + CONF_SENSORS: sensors, + }, config)) + + hass.async_add_job(discovery.async_load_platform( + hass, 'binary_sensor', DOMAIN, { + CONF_BINARY_SENSORS: binary_sensors, + }, config)) + + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, + os.path.join(os.path.dirname(__file__), 'services.yaml')) + + @asyncio.coroutine + def async_service_handler(service): + """Handle eight sleep service calls.""" + params = service.data.copy() + + sensor = params.pop(ATTR_ENTITY_ID, None) + target = params.pop(ATTR_TARGET_HEAT, None) + duration = params.pop(ATTR_HEAT_DURATION, 0) + + for sens in sensor: + side = sens.split('_')[1] + userid = eight.fetch_userid(side) + usrobj = eight.users[userid] + yield from usrobj.set_heating_level(target, duration) + + async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT) + + # Register services + hass.services.async_register( + DOMAIN, SERVICE_HEAT_SET, async_service_handler, + descriptions[DOMAIN].get(SERVICE_HEAT_SET), + schema=SERVICE_EIGHT_SCHEMA) + + @asyncio.coroutine + def stop_eight(event): + """Handle stopping eight api session.""" + yield from eight.stop() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_eight) + + return True + + +class EightSleepUserEntity(Entity): + """The Eight Sleep device entity.""" + + def __init__(self, eight): + """Initialize the data oject.""" + self._eight = eight + + @asyncio.coroutine + def async_added_to_hass(self): + """Register update dispatcher.""" + @callback + def async_eight_user_update(): + """Update callback.""" + self.hass.async_add_job(self.async_update_ha_state(True)) + + async_dispatcher_connect( + self.hass, SIGNAL_UPDATE_USER, async_eight_user_update) + + @property + def should_poll(self): + """Return True if entity has to be polled for state.""" + return False + + +class EightSleepHeatEntity(Entity): + """The Eight Sleep device entity.""" + + def __init__(self, eight): + """Initialize the data oject.""" + self._eight = eight + + @asyncio.coroutine + def async_added_to_hass(self): + """Register update dispatcher.""" + @callback + def async_eight_heat_update(): + """Update callback.""" + self.hass.async_add_job(self.async_update_ha_state(True)) + + async_dispatcher_connect( + self.hass, SIGNAL_UPDATE_HEAT, async_eight_heat_update) + + @property + def should_poll(self): + """Return True if entity has to be polled for state.""" + return False diff --git a/homeassistant/components/emoncms_history.py b/homeassistant/components/emoncms_history.py index b2bc3967bc8..34d9fd0f458 100644 --- a/homeassistant/components/emoncms_history.py +++ b/homeassistant/components/emoncms_history.py @@ -7,13 +7,13 @@ https://home-assistant.io/components/emoncms_history/ import logging from datetime import timedelta -import voluptuous as vol import requests +import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_API_KEY, CONF_WHITELIST, CONF_URL, STATE_UNKNOWN, STATE_UNAVAILABLE, CONF_SCAN_INTERVAL) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers import state as state_helper from homeassistant.helpers.event import track_point_in_time from homeassistant.util import dt as dt_util @@ -50,14 +50,13 @@ def setup(hass, config): timeout=5) except requests.exceptions.RequestException: - _LOGGER.error("Error saving data '%s' to '%s'", - payload, fullurl) + _LOGGER.error("Error saving data '%s' to '%s'", payload, fullurl) else: if req.status_code != 200: - _LOGGER.error("Error saving data '%s' to '%s'" + - "(http status code = %d)", payload, - fullurl, req.status_code) + _LOGGER.error( + "Error saving data %s to %s (http status code = %d)", + payload, fullurl, req.status_code) def update_emoncms(time): """Send whitelisted entities states reguarly to Emoncms.""" @@ -71,12 +70,11 @@ def setup(hass, config): continue try: - payload_dict[entity_id] = state_helper.state_as_number( - state) + payload_dict[entity_id] = state_helper.state_as_number(state) except ValueError: continue - if len(payload_dict) > 0: + if payload_dict: payload = "{%s}" % ",".join("{}:{}".format(key, val) for key, val in payload_dict.items()) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 2412b283abe..b0b6ec0324f 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -53,11 +53,9 @@ DEFAULT_TYPE = TYPE_GOOGLE CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_HOST_IP): cv.string, - vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): cv.port, vol.Optional(CONF_ADVERTISE_IP): cv.string, - vol.Optional(CONF_ADVERTISE_PORT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + vol.Optional(CONF_ADVERTISE_PORT): cv.port, vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean, vol.Optional(CONF_OFF_MAPS_TO_ON_DOMAINS): cv.ensure_list, vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean, @@ -111,8 +109,8 @@ def setup(hass, yaml_config): """Start the emulated hue bridge.""" upnp_listener.start() yield from server.start() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - stop_emulated_hue_bridge) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_emulated_hue_bridge) @@ -120,7 +118,7 @@ def setup(hass, yaml_config): class Config(object): - """Holds configuration variables for the emulated hue bridge.""" + """Hold configuration variables for the emulated hue bridge.""" def __init__(self, hass, conf): """Initialize the instance.""" @@ -130,8 +128,8 @@ class Config(object): self.cached_states = {} if self.type == TYPE_ALEXA: - _LOGGER.warning('Alexa type is deprecated and will be removed in a' - ' future version') + _LOGGER.warning("Alexa type is deprecated and will be removed in a" + "future version") # Get the IP address that will be passed to the Echo during discovery self.host_ip_addr = conf.get(CONF_HOST_IP) @@ -150,8 +148,8 @@ class Config(object): self.listen_port) if self.type == TYPE_GOOGLE and self.listen_port != 80: - _LOGGER.warning('When targetting Google Home, listening port has ' - 'to be port 80') + _LOGGER.warning("When targetting Google Home, listening port has " + "to be port 80") # Get whether or not UPNP binds to multicast address (239.255.255.250) # or to the unicast address (host_ip_addr) @@ -236,7 +234,7 @@ class Config(object): return is_default_exposed or explicit_expose def _load_numbers_json(self): - """Helper method to load numbers json.""" + """Set up helper method to load numbers json.""" try: with open(self.hass.config.path(NUMBERS_FILE), encoding='utf-8') as fil: @@ -245,15 +243,15 @@ class Config(object): # OSError if file not found or unaccessible/no permissions # ValueError if could not parse JSON if not isinstance(err, FileNotFoundError): - _LOGGER.warning('Failed to open %s: %s', NUMBERS_FILE, err) + _LOGGER.warning("Failed to open %s: %s", NUMBERS_FILE, err) return {} def _save_numbers_json(self): - """Helper method to save numbers json.""" + """Set up helper method to save numbers json.""" try: with open(self.hass.config.path(NUMBERS_FILE), 'w', encoding='utf-8') as fil: fil.write(json.dumps(self.numbers)) except OSError as err: # OSError if file write permissions - _LOGGER.warning('Failed to write %s: %s', NUMBERS_FILE, err) + _LOGGER.warning("Failed to write %s: %s", NUMBERS_FILE, err) diff --git a/homeassistant/components/enocean.py b/homeassistant/components/enocean.py index 33c6359d43f..79c2e3dce8d 100644 --- a/homeassistant/components/enocean.py +++ b/homeassistant/components/enocean.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the EnOcean component.""" + """Set up the EnOcean component.""" global ENOCEAN_DONGLE serial_dev = config[DOMAIN].get(CONF_DEVICE) @@ -43,8 +43,8 @@ class EnOceanDongle: def __init__(self, hass, ser): """Initialize the EnOcean dongle.""" from enocean.communicators.serialcommunicator import SerialCommunicator - self.__communicator = SerialCommunicator(port=ser, - callback=self.callback) + self.__communicator = SerialCommunicator( + port=ser, callback=self.callback) self.__communicator.start() self.__devices = [] @@ -65,11 +65,10 @@ class EnOceanDongle: return output def callback(self, temp): - """Callback function for EnOcean Device. + """Handle EnOcean device's callback. - This is the callback function called by - python-enocan whenever there is an incoming - packet. + This is the callback function called by python-enocan whenever there + is an incoming packet. """ from enocean.protocol.packet import RadioPacket if isinstance(temp, RadioPacket): diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py index 05439213284..8aa41f6274a 100644 --- a/homeassistant/components/envisalink.py +++ b/homeassistant/components/envisalink.py @@ -19,26 +19,26 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send REQUIREMENTS = ['pyenvisalink==2.0'] _LOGGER = logging.getLogger(__name__) + DOMAIN = 'envisalink' DATA_EVL = 'envisalink' -CONF_EVL_HOST = 'host' -CONF_EVL_PORT = 'port' -CONF_PANEL_TYPE = 'panel_type' -CONF_EVL_VERSION = 'evl_version' CONF_CODE = 'code' -CONF_USERNAME = 'user_name' -CONF_PASS = 'password' +CONF_EVL_HOST = 'host' CONF_EVL_KEEPALIVE = 'keepalive_interval' -CONF_ZONEDUMP_INTERVAL = 'zonedump_interval' -CONF_ZONES = 'zones' -CONF_PARTITIONS = 'partitions' - -CONF_ZONENAME = 'name' -CONF_ZONETYPE = 'type' -CONF_PARTITIONNAME = 'name' +CONF_EVL_PORT = 'port' +CONF_EVL_VERSION = 'evl_version' +CONF_PANEL_TYPE = 'panel_type' CONF_PANIC = 'panic_type' +CONF_PARTITIONNAME = 'name' +CONF_PARTITIONS = 'partitions' +CONF_PASS = 'password' +CONF_USERNAME = 'user_name' +CONF_ZONEDUMP_INTERVAL = 'zonedump_interval' +CONF_ZONENAME = 'name' +CONF_ZONES = 'zones' +CONF_ZONETYPE = 'type' DEFAULT_PORT = 4025 DEFAULT_EVL_VERSION = 3 @@ -83,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Common setup for Envisalink devices.""" + """Set up for Envisalink devices.""" from pyenvisalink import EnvisalinkAlarmPanel conf = config.get(DOMAIN) @@ -109,27 +109,27 @@ def async_setup(hass, config): @callback def login_fail_callback(data): - """Callback for when the evl rejects our login.""" - _LOGGER.error("The envisalink rejected your credentials.") + """Handle when the evl rejects our login.""" + _LOGGER.error("The Envisalink rejected your credentials") sync_connect.set_result(False) @callback def connection_fail_callback(data): """Network failure callback.""" - _LOGGER.error("Could not establish a connection with the envisalink.") + _LOGGER.error("Could not establish a connection with the Envisalink") sync_connect.set_result(False) @callback def connection_success_callback(data): - """Callback for a successful connection.""" - _LOGGER.info("Established a connection with the envisalink.") + """Handle a successful connection.""" + _LOGGER.info("Established a connection with the Envisalink") hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) sync_connect.set_result(True) @callback def zones_updated_callback(data): """Handle zone timer updates.""" - _LOGGER.info("Envisalink sent a zone update event. Updating zones...") + _LOGGER.info("Envisalink sent a zone update event. Updating zones...") async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data) @callback @@ -141,13 +141,13 @@ def async_setup(hass, config): @callback def partition_updated_callback(data): """Handle partition changes thrown by evl (including alarms).""" - _LOGGER.info("The envisalink sent a partition update event.") + _LOGGER.info("The envisalink sent a partition update event") async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data) @callback def stop_envisalink(event): """Shutdown envisalink connection and thread on exit.""" - _LOGGER.info("Shutting down envisalink.") + _LOGGER.info("Shutting down Envisalink") controller.stop() controller.callback_zone_timer_dump = zones_updated_callback diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index fc2ddb32500..500cebfe73b 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -22,11 +22,11 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa import homeassistant.helpers.config_validation as cv +_LOGGER = logging.getLogger(__name__) DOMAIN = 'fan' -SCAN_INTERVAL = timedelta(seconds=30) -_LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=30) GROUP_NAME_ALL_FANS = 'all fans' ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS) diff --git a/homeassistant/components/fan/demo.py b/homeassistant/components/fan/demo.py index 931f4914552..3a3f255b806 100644 --- a/homeassistant/components/fan/demo.py +++ b/homeassistant/components/fan/demo.py @@ -4,13 +4,11 @@ Demo fan platform that has a fake fan. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ - from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, FanEntity, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE, SUPPORT_DIRECTION) from homeassistant.const import STATE_OFF - FAN_NAME = 'Living Room Fan' FAN_ENTITY_ID = 'fan.living_room_fan' @@ -19,7 +17,7 @@ DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup demo fan platform.""" + """Set up the demo fan platform.""" add_devices_callback([ DemoFan(hass, FAN_NAME, STATE_OFF), ]) diff --git a/homeassistant/components/fan/isy994.py b/homeassistant/components/fan/isy994.py index f8c7b2e867d..8b9236fdb32 100644 --- a/homeassistant/components/fan/isy994.py +++ b/homeassistant/components/fan/isy994.py @@ -35,9 +35,9 @@ STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH] # pylint: disable=unused-argument def setup_platform(hass, config: ConfigType, add_devices: Callable[[list], None], discovery_info=None): - """Setup the ISY994 fan platform.""" + """Set up the ISY994 fan platform.""" if isy.ISY is None or not isy.ISY.connected: - _LOGGER.error('A connection has not been made to the ISY controller.') + _LOGGER.error("A connection has not been made to the ISY controller") return False devices = [] @@ -78,7 +78,7 @@ class ISYFanDevice(isy.ISYDevice, FanEntity): def set_speed(self, speed: str) -> None: """Send the set speed command to the ISY994 fan device.""" if not self._node.on(val=STATE_TO_VALUE.get(speed, 0)): - _LOGGER.debug('Unable to set fan speed') + _LOGGER.debug("Unable to set fan speed") else: self.speed = self.state @@ -89,7 +89,7 @@ class ISYFanDevice(isy.ISYDevice, FanEntity): def turn_off(self, **kwargs) -> None: """Send the turn off command to the ISY994 fan device.""" if not self._node.off(): - _LOGGER.debug('Unable to set fan speed') + _LOGGER.debug("Unable to set fan speed") else: self.speed = self.state @@ -112,13 +112,13 @@ class ISYFanProgram(ISYFanDevice): def turn_off(self, **kwargs) -> None: """Send the turn on command to ISY994 fan program.""" if not self._actions.runThen(): - _LOGGER.error('Unable to open the cover') + _LOGGER.error("Unable to turn off the fan") else: self.speed = STATE_ON if self.is_on else STATE_OFF def turn_on(self, **kwargs) -> None: """Send the turn off command to ISY994 fan program.""" if not self._actions.runElse(): - _LOGGER.error('Unable to close the cover') + _LOGGER.error("Unable to turn on the fan") else: self.speed = STATE_ON if self.is_on else STATE_OFF diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py index 968f666fa72..bc732aa0aff 100644 --- a/homeassistant/components/fan/mqtt.py +++ b/homeassistant/components/fan/mqtt.py @@ -77,7 +77,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup MQTT fan platform.""" + """Set up the MQTT fan platform.""" async_add_devices([MqttFan( config.get(CONF_NAME), { @@ -140,7 +140,7 @@ class MqttFan(FanEntity): @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @@ -154,7 +154,7 @@ class MqttFan(FanEntity): @callback def state_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new received MQTT message.""" payload = templates[CONF_STATE](payload) if payload == self._payload[STATE_ON]: self._state = True @@ -169,7 +169,7 @@ class MqttFan(FanEntity): @callback def speed_received(topic, payload, qos): - """A new MQTT message for the speed has been received.""" + """Handle new received MQTT message for the speed.""" payload = templates[ATTR_SPEED](payload) if payload == self._payload[SPEED_LOW]: self._speed = SPEED_LOW @@ -187,7 +187,7 @@ class MqttFan(FanEntity): @callback def oscillation_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new received MQTT message for the oscillation.""" payload = templates[OSCILLATION](payload) if payload == self._payload[OSCILLATE_ON_PAYLOAD]: self._oscillation = True diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py index 00eb082212d..e8f5d6fd17a 100644 --- a/homeassistant/components/fan/wink.py +++ b/homeassistant/components/fan/wink.py @@ -14,12 +14,12 @@ from homeassistant.components.wink import WinkDevice, DOMAIN _LOGGER = logging.getLogger(__name__) -SPEED_LOWEST = "lowest" -SPEED_AUTO = "auto" +SPEED_LOWEST = 'lowest' +SPEED_AUTO = 'auto' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink platform.""" + """Set up the Wink platform.""" import pywink for fan in pywink.get_fans(): diff --git a/homeassistant/components/feedreader.py b/homeassistant/components/feedreader.py index a563b51402e..3d73901b4d8 100644 --- a/homeassistant/components/feedreader.py +++ b/homeassistant/components/feedreader.py @@ -36,7 +36,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the feedreader component.""" + """Set up the Feedreader component.""" urls = config.get(DOMAIN)[CONF_URLS] data_file = hass.config.path("{}.pickle".format(DOMAIN)) storage = StoredData(data_file) @@ -45,7 +45,7 @@ def setup(hass, config): class FeedManager(object): - """Abstraction over feedparser module.""" + """Abstraction over Feedparser module.""" def __init__(self, url, hass, storage): """Initialize the FeedManager object, poll every hour.""" @@ -56,46 +56,45 @@ class FeedManager(object): self._storage = storage self._last_entry_timestamp = None self._has_published_parsed = False - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, - lambda _: self._update()) - track_utc_time_change(hass, lambda now: self._update(), - minute=0, second=0) + hass.bus.listen_once( + EVENT_HOMEASSISTANT_START, lambda _: self._update()) + track_utc_time_change( + hass, lambda now: self._update(), minute=0, second=0) def _log_no_entries(self): """Send no entries log at debug level.""" - _LOGGER.debug('No new entries to be published in feed "%s"', self._url) + _LOGGER.debug("No new entries to be published in feed %s", self._url) def _update(self): """Update the feed and publish new entries to the event bus.""" import feedparser - _LOGGER.info('Fetching new data from feed "%s"', self._url) + _LOGGER.info("Fetching new data from feed %s", self._url) self._feed = feedparser.parse(self._url, etag=None if not self._feed else self._feed.get('etag'), modified=None if not self._feed else self._feed.get('modified')) if not self._feed: - _LOGGER.error('Error fetching feed data from "%s"', self._url) + _LOGGER.error("Error fetching feed data from %s", self._url) else: if self._feed.bozo != 0: - _LOGGER.error('Error parsing feed "%s"', self._url) + _LOGGER.error("Error parsing feed %s", self._url) # Using etag and modified, if there's no new data available, # the entries list will be empty - elif len(self._feed.entries) > 0: - _LOGGER.debug('%s entri(es) available in feed "%s"', - len(self._feed.entries), - self._url) + elif self._feed.entries: + _LOGGER.debug("%s entri(es) available in feed %s", + len(self._feed.entries), self._url) if len(self._feed.entries) > MAX_ENTRIES: - _LOGGER.debug('Processing only the first %s entries ' - 'in feed "%s"', MAX_ENTRIES, self._url) + _LOGGER.debug("Processing only the first %s entries " + "in feed %s", MAX_ENTRIES, self._url) self._feed.entries = self._feed.entries[0:MAX_ENTRIES] self._publish_new_entries() if self._has_published_parsed: - self._storage.put_timestamp(self._url, - self._last_entry_timestamp) + self._storage.put_timestamp( + self._url, self._last_entry_timestamp) else: self._log_no_entries() - _LOGGER.info('Fetch from feed "%s" completed', self._url) + _LOGGER.info("Fetch from feed %s completed", self._url) def _update_and_fire_entry(self, entry): """Update last_entry_timestamp and fire entry.""" @@ -103,12 +102,12 @@ class FeedManager(object): # it to publish only new available entries since the last run if 'published_parsed' in entry.keys(): self._has_published_parsed = True - self._last_entry_timestamp = max(entry.published_parsed, - self._last_entry_timestamp) + self._last_entry_timestamp = max( + entry.published_parsed, self._last_entry_timestamp) else: self._has_published_parsed = False - _LOGGER.debug('No `published_parsed` info available ' - 'for entry "%s"', entry.title) + _LOGGER.debug("No published_parsed info available for entry %s", + entry.title) entry.update({'feed_url': self._url}) self._hass.bus.fire(EVENT_FEEDREADER, entry) @@ -129,7 +128,7 @@ class FeedManager(object): self._update_and_fire_entry(entry) new_entries = True else: - _LOGGER.debug('Entry "%s" already processed', entry.title) + _LOGGER.debug("Entry %s already processed", entry.title) if not new_entries: self._log_no_entries() self._firstrun = False @@ -150,13 +149,13 @@ class StoredData(object): """Fetch data stored into pickle file.""" if self._cache_outdated and exists(self._data_file): try: - _LOGGER.debug('Fetching data from file %s', self._data_file) + _LOGGER.debug("Fetching data from file %s", self._data_file) with self._lock, open(self._data_file, 'rb') as myfile: self._data = pickle.load(myfile) or {} self._cache_outdated = False # pylint: disable=bare-except except: - _LOGGER.error('Error loading data from pickled file %s', + _LOGGER.error("Error loading data from pickled file %s", self._data_file) def get_timestamp(self, url): @@ -165,16 +164,16 @@ class StoredData(object): return self._data.get(url) def put_timestamp(self, url, timestamp): - """Update timestamp for given url.""" + """Update timestamp for given URL.""" self._fetch_data() with self._lock, open(self._data_file, 'wb') as myfile: self._data.update({url: timestamp}) - _LOGGER.debug('Overwriting feed "%s" timestamp in storage file %s', + _LOGGER.debug("Overwriting feed %s timestamp in storage file %s", url, self._data_file) try: pickle.dump(self._data, myfile) # pylint: disable=bare-except except: - _LOGGER.error('Error saving pickled data to %s', - self._data_file) + _LOGGER.error( + "Error saving pickled data to %s", self._data_file) self._cache_outdated = True diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index 5b012ffad4a..959962f02ac 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -1,5 +1,5 @@ """ -Component that will help set the ffmpeg component. +Component that will help set the FFmpeg component. For more details about this component, please refer to the documentation at https://home-assistant.io/components/ffmpeg/ @@ -19,8 +19,9 @@ from homeassistant.helpers.dispatcher import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +REQUIREMENTS = ['ha-ffmpeg==1.5'] + DOMAIN = 'ffmpeg' -REQUIREMENTS = ["ha-ffmpeg==1.5"] _LOGGER = logging.getLogger(__name__) @@ -58,28 +59,28 @@ SERVICE_FFMPEG_SCHEMA = vol.Schema({ @callback def async_start(hass, entity_id=None): - """Start a ffmpeg process on entity.""" + """Start a FFmpeg process on entity.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_START, data)) @callback def async_stop(hass, entity_id=None): - """Stop a ffmpeg process on entity.""" + """Stop a FFmpeg process on entity.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_STOP, data)) @callback def async_restart(hass, entity_id=None): - """Restart a ffmpeg process on entity.""" + """Restart a FFmpeg process on entity.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RESTART, data)) @asyncio.coroutine def async_setup(hass, config): - """Setup the FFmpeg component.""" + """Set up the FFmpeg component.""" conf = config.get(DOMAIN, {}) manager = FFmpegManager( @@ -92,7 +93,7 @@ def async_setup(hass, config): None, load_yaml_config_file, os.path.join(os.path.dirname(__file__), 'services.yaml')) - # register service + # Register service @asyncio.coroutine def async_service_handle(service): """Handle service ffmpeg process.""" @@ -162,7 +163,7 @@ class FFmpegManager(object): class FFmpegBase(Entity): - """Interface object for ffmpeg.""" + """Interface object for FFmpeg.""" def __init__(self, initial_state=True): """Initialize ffmpeg base object.""" @@ -197,7 +198,7 @@ class FFmpegBase(Entity): @asyncio.coroutine def _async_start_ffmpeg(self, entity_ids): - """Start a ffmpeg process. + """Start a FFmpeg process. This method is a coroutine. """ @@ -205,7 +206,7 @@ class FFmpegBase(Entity): @asyncio.coroutine def _async_stop_ffmpeg(self, entity_ids): - """Stop a ffmpeg process. + """Stop a FFmpeg process. This method is a coroutine. """ @@ -214,7 +215,7 @@ class FFmpegBase(Entity): @asyncio.coroutine def _async_restart_ffmpeg(self, entity_ids): - """Stop a ffmpeg process. + """Stop a FFmpeg process. This method is a coroutine. """ @@ -224,10 +225,10 @@ class FFmpegBase(Entity): @callback def _async_register_events(self): - """Register a ffmpeg process/device.""" + """Register a FFmpeg process/device.""" @asyncio.coroutine def async_shutdown_handle(event): - """Stop ffmpeg process.""" + """Stop FFmpeg process.""" yield from self._async_stop_ffmpeg(None) self.hass.bus.async_listen_once( @@ -239,7 +240,7 @@ class FFmpegBase(Entity): @asyncio.coroutine def async_start_handle(event): - """Start ffmpeg process.""" + """Start FFmpeg process.""" yield from self._async_start_ffmpeg(None) self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/foursquare.py b/homeassistant/components/foursquare.py index 2afa808b502..61c5e9b1da6 100644 --- a/homeassistant/components/foursquare.py +++ b/homeassistant/components/foursquare.py @@ -1,5 +1,5 @@ """ -Allows utilizing the Foursquare (Swarm) API. +Support for the Foursquare (Swarm) API. For more details about this component, please refer to the documentation at https://home-assistant.io/components/foursquare/ @@ -11,9 +11,9 @@ import os import requests import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_BAD_REQUEST from homeassistant.config import load_yaml_config_file -import homeassistant.helpers.config_validation as cv from homeassistant.components.http import HomeAssistantView _LOGGER = logging.getLogger(__name__) @@ -49,7 +49,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Foursquare component.""" + """Set up the Foursquare component.""" descriptions = load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index b6f65d9953a..617db06be2c 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -17,27 +17,30 @@ from .version import FINGERPRINTS DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api'] + URL_PANEL_COMPONENT = '/frontend/panels/{}.html' URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' + STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static/') + MANIFEST_JSON = { - "background_color": "#FFFFFF", - "description": "Open-source home automation platform running on Python 3.", - "dir": "ltr", - "display": "standalone", - "icons": [], - "lang": "en-US", - "name": "Home Assistant", - "short_name": "Assistant", - "start_url": "/", - "theme_color": "#03A9F4" + 'background_color': '#FFFFFF', + 'description': 'Open-source home automation platform running on Python 3.', + 'dir': 'ltr', + 'display': 'standalone', + 'icons': [], + 'lang': 'en-US', + 'name': 'Home Assistant', + 'short_name': 'Assistant', + 'start_url': '/', + 'theme_color': '#03A9F4' } for size in (192, 384, 512, 1024): MANIFEST_JSON['icons'].append({ - "src": "/static/icons/favicon-{}x{}.png".format(size, size), - "sizes": "{}x{}".format(size, size), - "type": "image/png" + 'src': '/static/icons/favicon-{}x{}.png'.format(size, size), + 'sizes': '{}x{}'.format(size, size), + 'type': 'image/png' }) DATA_PANELS = 'frontend_panels' @@ -92,10 +95,10 @@ def register_panel(hass, component_name, path, md5=None, sidebar_title=None, url_path = component_name if url_path in panels: - _LOGGER.warning('Overwriting component %s', url_path) + _LOGGER.warning("Overwriting component %s", url_path) if not os.path.isfile(path): - _LOGGER.error('Panel %s component does not exist: %s', - component_name, path) + _LOGGER.error( + "Panel %s component does not exist: %s", component_name, path) return if md5 is None: @@ -133,8 +136,8 @@ def register_panel(hass, component_name, path, md5=None, sidebar_title=None, index_view = hass.data.get(DATA_INDEX_VIEW) if index_view: - hass.http.app.router.add_route('get', '/{}'.format(url_path), - index_view.get) + hass.http.app.router.add_route( + 'get', '/{}'.format(url_path), index_view.get) def add_manifest_json_key(key, val): @@ -143,7 +146,7 @@ def add_manifest_json_key(key, val): def setup(hass, config): - """Setup serving the frontend.""" + """Set up the serving of the frontend.""" hass.http.register_view(BootstrapView) hass.http.register_view(ManifestJSONView) @@ -186,8 +189,8 @@ def setup(hass, config): class BootstrapView(HomeAssistantView): """View to bootstrap frontend with all needed data.""" - url = "/api/bootstrap" - name = "api:bootstrap" + url = '/api/bootstrap' + name = 'api:bootstrap' @callback def get(self, request): @@ -207,7 +210,7 @@ class IndexView(HomeAssistantView): """Serve the frontend.""" url = '/' - name = "frontend:index" + name = 'frontend:index' requires_auth = False extra_urls = ['/states', '/states/{entity_id}'] @@ -284,8 +287,8 @@ class ManifestJSONView(HomeAssistantView): """View to return a manifest.json.""" requires_auth = False - url = "/manifest.json" - name = "manifestjson" + url = '/manifest.json' + name = 'manifestjson' @asyncio.coroutine def get(self, request): # pylint: disable=no-self-use diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 263c3f7c425..943074beb40 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -3,20 +3,20 @@ FINGERPRINTS = { "compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0", "core.js": "5d08475f03adb5969bd31855d5ca0cfd", - "frontend.html": "1533f44c55927e814294de757cd7eada", - "mdi.html": "1cc8593d3684f7f6f3b3854403216f77", + "frontend.html": "5999c8fac69c503b846672cae75a12b0", + "mdi.html": "f407a5a57addbe93817ee1b244d33fbe", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", - "panels/ha-panel-config.html": "39f00f769faa63ee61f1fe6fc85d67f7", + "panels/ha-panel-config.html": "59d9eb28758b497a4d9b2428f978b9b1", "panels/ha-panel-dev-event.html": "2db9c218065ef0f61d8d08db8093cad2", "panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d", "panels/ha-panel-dev-service.html": "415552027cb083badeff5f16080410ed", "panels/ha-panel-dev-state.html": "d70314913b8923d750932367b1099750", "panels/ha-panel-dev-template.html": "567fbf86735e1b891e40c2f4060fec9b", - "panels/ha-panel-hassio.html": "1d954cfe5f47c4be3cf4f6f5db9a83b2", + "panels/ha-panel-hassio.html": "333f86e5f516b31e52365e412deb7fdc", "panels/ha-panel-history.html": "89062c48c76206cad1cec14ddbb1cbb1", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", "panels/ha-panel-logbook.html": "6dd6a16f52117318b202e60f98400163", "panels/ha-panel-map.html": "31c592c239636f91e07c7ac232a5ebc4", - "panels/ha-panel-zwave.html": "a81f82b48439da80286798558f414a2e", + "panels/ha-panel-zwave.html": "84fb45638d2a69bac343246a687f647c", "websocket_test.html": "575de64b431fe11c3785bf96d7813450" } diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 827325cb058..1fd4bda0275 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -8,6 +8,7 @@ window.hassUtil.DEFAULT_ICON = 'mdi:bookmark'; window.hassUtil.OFF_STATES = ['off', 'closed', 'unlocked']; window.hassUtil.DOMAINS_WITH_CARD = [ + 'binary_sensor', 'climate', 'cover', 'configurator', @@ -20,7 +21,7 @@ window.hassUtil.DOMAINS_WITH_CARD = [ ]; window.hassUtil.DOMAINS_WITH_MORE_INFO = [ - 'alarm_control_panel', 'automation', 'camera', 'climate', 'configurator', + 'alarm_control_panel', 'automation', 'binary_sensor', 'camera', 'climate', 'configurator', 'cover', 'fan', 'group', 'light', 'lock', 'media_player', 'script', 'sun', 'updater', ]; @@ -440,7 +441,7 @@ window.hassUtil.isComponentLoaded = function (hass, component) { window.hassUtil.computeLocationName = function (hass) { return hass.config.core.location_name; -}; \ No newline at end of file +}()); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 46d65a09b02..a9879e06152 100644 Binary files a/homeassistant/components/frontend/www_static/frontend.html.gz and b/homeassistant/components/frontend/www_static/frontend.html.gz differ diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 863ccb54861..f020e60b67e 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 863ccb548616236faafa3b3393a1f51429bb8afd +Subproject commit f020e60b67ec38e3ede72b1ebc86d4e055565cd7 diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index 0a4e6cffa38..bf70b13f7e1 100644 --- a/homeassistant/components/frontend/www_static/mdi.html +++ b/homeassistant/components/frontend/www_static/mdi.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/mdi.html.gz b/homeassistant/components/frontend/www_static/mdi.html.gz index 1c442302c66..83641f9059d 100644 Binary files a/homeassistant/components/frontend/www_static/mdi.html.gz and b/homeassistant/components/frontend/www_static/mdi.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html index 84813853c21..fc00afe012c 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html @@ -1,4 +1,4 @@ - \ No newline at end of file +}); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-hassio.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-hassio.html.gz index 6254eb394a6..c7689872442 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-hassio.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-hassio.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html b/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html index 627c7c15035..6af056b0db4 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html.gz index 89c637ad527..fec3a4f832b 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-zwave.html.gz differ diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index a1ee58da087..79fc54a2fc6 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -1 +1 @@ -"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","39154f75cd31edbe1d4a249039273520"],["/frontend/panels/dev-event-2db9c218065ef0f61d8d08db8093cad2.html","b5b751e49b1bba55f633ae0d7a92677d"],["/frontend/panels/dev-info-61610e015a411cfc84edd2c4d489e71d.html","6568377ee31cbd78fedc003b317f7faf"],["/frontend/panels/dev-service-415552027cb083badeff5f16080410ed.html","a4b1ec9bfa5bc3529af7783ae56cb55c"],["/frontend/panels/dev-state-d70314913b8923d750932367b1099750.html","c61b5b1461959aac106400e122993e9e"],["/frontend/panels/dev-template-567fbf86735e1b891e40c2f4060fec9b.html","d2853ecf45de1dbadf49fe99a7424ef3"],["/frontend/panels/map-31c592c239636f91e07c7ac232a5ebc4.html","182580419ce2c935ae6ec65502b6db96"],["/static/compatibility-83d9c77748dafa9db49ae77d7f3d8fb0.js","5f05c83be2b028d577962f9625904806"],["/static/core-5d08475f03adb5969bd31855d5ca0cfd.js","1cd99ba798bfcff9768c9d2bb2f58a7c"],["/static/frontend-1533f44c55927e814294de757cd7eada.html","4a5d18075e9c90493b51531c3d13d343"],["/static/mdi-d86ee142ae2476f49384bfe866a2885e.html","1a47930d13e98b823c3b00a824449f04"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","32b5a9b7ada86304bec6b43d3f2194f0"]],cacheName="sw-precache-v3--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,a){var c=new URL(e);return a&&c.pathname.match(a)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],a=new URL(t,self.location),c=createCacheKey(a,hashParamName,n,!1);return[a.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var a=new Request(n,{credentials:"same-origin"});return fetch(a).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(n);t||(n=addDirectoryIndex(n,"index.html"),t=urlsToCacheKeys.has(n));!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(n=new URL("/",self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url)&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var n,a;for(n=0;n 0: + # Have events? + if hmdevice.EVENTNODE: _LOGGER.debug("Register Events from %s", dev) - hmdevice.setEventCallback(callback=bound_event_callback, - bequeath=True) + hmdevice.setEventCallback( + callback=bound_event_callback, bequeath=True) # If configuration allows autodetection of devices, # all devices not configured are added. @@ -447,6 +450,14 @@ def _system_callback_handler(hass, config, src, *args): ATTR_DISCOVER_DEVICES: found_devices }, config) + elif src == 'error': + _LOGGER.debug("Error: %s", args) + (interface_id, errorcode, message) = args + hass.bus.fire(EVENT_ERROR, { + ATTR_ERRORCODE: errorcode, + ATTR_MESSAGE: message + }) + def _get_devices(hass, discovery_type, keys, proxy): """Get the Homematic devices for given discovery_type.""" @@ -544,28 +555,30 @@ def _hm_event_handler(hass, proxy, device, caller, attribute, value): # keypress event if attribute in HM_PRESS_EVENTS: - hass.add_job(hass.bus.async_fire(EVENT_KEYPRESS, { + hass.bus.fire(EVENT_KEYPRESS, { ATTR_NAME: hmdevice.NAME, ATTR_PARAM: attribute, ATTR_CHANNEL: channel - })) + }) return # impulse event if attribute in HM_IMPULSE_EVENTS: - hass.add_job(hass.bus.async_fire(EVENT_KEYPRESS, { + hass.bus.fire(EVENT_IMPULSE, { ATTR_NAME: hmdevice.NAME, ATTR_CHANNEL: channel - })) + }) return - _LOGGER.warning("Event is unknown and not forwarded to HA") + _LOGGER.warning("Event is unknown and not forwarded") def _device_from_servicecall(hass, service): """Extract homematic device from service call.""" address = service.data.get(ATTR_ADDRESS) proxy = service.data.get(ATTR_PROXY) + if address == 'BIDCOS-RF': + address = 'BidCoS-RF' if proxy: return hass.data[DATA_HOMEMATIC].devices[proxy].get(address) @@ -727,7 +740,7 @@ class HMDevice(Entity): def link_homematic(self): """Connect to Homematic.""" - # device is already linked + # Device is already linked if self._connected: return True diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index ee107ec5cfa..dc5e59f4155 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -34,7 +34,7 @@ from .static import ( staticresource_middleware, CachingFileResponse, CachingStaticResource) from .util import get_real_ip -REQUIREMENTS = ['aiohttp_cors==0.5.2'] +REQUIREMENTS = ['aiohttp_cors==0.5.3'] DOMAIN = 'http' @@ -81,14 +81,13 @@ DEFAULT_LOGIN_ATTEMPT_THRESHOLD = -1 HTTP_SCHEMA = vol.Schema({ vol.Optional(CONF_API_PASSWORD, default=None): cv.string, vol.Optional(CONF_SERVER_HOST, default=DEFAULT_SERVER_HOST): cv.string, - vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): cv.port, vol.Optional(CONF_BASE_URL): cv.string, vol.Optional(CONF_DEVELOPMENT, default=DEFAULT_DEVELOPMENT): cv.string, vol.Optional(CONF_SSL_CERTIFICATE, default=None): cv.isfile, vol.Optional(CONF_SSL_KEY, default=None): cv.isfile, - vol.Optional(CONF_CORS_ORIGINS, default=[]): vol.All(cv.ensure_list, - [cv.string]), + vol.Optional(CONF_CORS_ORIGINS, default=[]): + vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_USE_X_FORWARDED_FOR, default=False): cv.boolean, vol.Optional(CONF_TRUSTED_NETWORKS, default=[]): vol.All(cv.ensure_list, [ip_network]), @@ -143,12 +142,12 @@ def async_setup(hass, config): @asyncio.coroutine def stop_server(event): - """Callback to stop the server.""" + """Stop the server.""" yield from server.stop() @asyncio.coroutine def start_server(event): - """Callback to start the server.""" + """Start the server.""" hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) yield from server.start() @@ -294,7 +293,7 @@ class HomeAssistantWSGI(object): @asyncio.coroutine def start(self): - """Start the wsgi server.""" + """Start the WSGI server.""" cors_added = set() if self.cors is not None: for route in list(self.app.router.routes()): @@ -400,7 +399,7 @@ class HomeAssistantView(object): def request_handler_factory(view, handler): - """Factory to wrap our handler classes.""" + """Wrap the handler classes.""" assert asyncio.iscoroutinefunction(handler) or is_callback(handler), \ "Handler should be a coroutine or a callback." diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 6ff653eef35..2a0413e9dbc 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -14,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def auth_middleware(app, handler): - """Authentication middleware.""" + """Authenticate as middleware.""" # If no password set, just always set authenticated=True if app['hass'].http.api_password is None: @asyncio.coroutine @@ -32,8 +32,8 @@ def auth_middleware(app, handler): authenticated = False if (HTTP_HEADER_HA_AUTH in request.headers and - validate_password(request, - request.headers[HTTP_HEADER_HA_AUTH])): + validate_password( + request, request.headers[HTTP_HEADER_HA_AUTH])): # A valid auth header has been set authenticated = True @@ -62,5 +62,5 @@ def is_trusted_ip(request): def validate_password(request, api_password): """Test if password is valid.""" - return hmac.compare_digest(api_password, - request.app['hass'].http.api_password) + return hmac.compare_digest( + api_password, request.app['hass'].http.api_password) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 8ae18ef6e80..53635af9fc8 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -94,8 +94,8 @@ def process_wrong_login(request): None, update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) - _LOGGER.warning('Banned IP %s for too many login attempts', - remote_addr) + _LOGGER.warning( + "Banned IP %s for too many login attempts", remote_addr) persistent_notification.async_create( hass, @@ -107,13 +107,13 @@ class IpBan(object): """Represents banned IP address.""" def __init__(self, ip_ban: str, banned_at: datetime=None) -> None: - """Initializing Ip Ban object.""" + """Initialize IP Ban object.""" self.ip_address = ip_address(ip_ban) self.banned_at = banned_at or datetime.utcnow() def load_ip_bans_config(path: str): - """Loading list of banned IPs from config file.""" + """Load list of banned IPs from config file.""" ip_list = [] if not os.path.isfile(path): @@ -130,7 +130,7 @@ def load_ip_bans_config(path: str): ip_info = SCHEMA_IP_BAN_ENTRY(ip_info) ip_list.append(IpBan(ip_ban, ip_info['banned_at'])) except vol.Invalid as err: - _LOGGER.error('Failed to load IP ban %s: %s', ip_info, err) + _LOGGER.error("Failed to load IP ban %s: %s", ip_info, err) continue return ip_list diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py index 123d1a9d382..0a4ad66ce56 100644 --- a/homeassistant/components/ifttt.py +++ b/homeassistant/components/ifttt.py @@ -52,7 +52,7 @@ def trigger(hass, event, value1=None, value2=None, value3=None): def setup(hass, config): - """Setup the IFTTT service component.""" + """Set up the IFTTT service component.""" key = config[DOMAIN][CONF_KEY] def trigger_service(call): diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index c63d3d01ce9..a37f1163b3d 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -20,7 +20,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.loader import get_component - DOMAIN = 'image_processing' DEPENDENCIES = ['camera'] @@ -67,7 +66,7 @@ def scan(hass, entity_id=None): @asyncio.coroutine def async_setup(hass, config): - """Setup image processing.""" + """Set up image processing.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) yield from component.async_setup(config) diff --git a/homeassistant/components/image_processing/demo.py b/homeassistant/components/image_processing/demo.py index 3cc2c17654c..a7f4ad5e3d6 100644 --- a/homeassistant/components/image_processing/demo.py +++ b/homeassistant/components/image_processing/demo.py @@ -12,7 +12,7 @@ from homeassistant.components.image_processing.microsoft_face_identify import ( def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the demo image_processing platform.""" + """Set up the demo image_processing platform.""" add_devices([ DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"), DemoImageProcessingFace( diff --git a/homeassistant/components/image_processing/dlib_face_detect.py b/homeassistant/components/image_processing/dlib_face_detect.py new file mode 100644 index 00000000000..5877535d3e0 --- /dev/null +++ b/homeassistant/components/image_processing/dlib_face_detect.py @@ -0,0 +1,71 @@ +""" +Component that will help set the dlib face detect processing. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/image_processing.dlib_face_detect/ +""" +import logging +import io + +from homeassistant.core import split_entity_id +# pylint: disable=unused-import +from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa +from homeassistant.components.image_processing import ( + CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) +from homeassistant.components.image_processing.microsoft_face_identify import ( + ImageProcessingFaceEntity) + +REQUIREMENTS = ['face_recognition==0.1.14'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Microsoft Face detection platform.""" + entities = [] + for camera in config[CONF_SOURCE]: + entities.append(DlibFaceDetectEntity( + camera[CONF_ENTITY_ID], camera.get(CONF_NAME) + )) + + add_devices(entities) + + +class DlibFaceDetectEntity(ImageProcessingFaceEntity): + """Dlib Face API entity for identify.""" + + def __init__(self, camera_entity, name=None): + """Initialize Dlib.""" + super().__init__() + + self._camera = camera_entity + + if name: + self._name = name + else: + self._name = "Dlib Face {0}".format( + split_entity_id(camera_entity)[1]) + + @property + def camera_entity(self): + """Return camera entity id from process pictures.""" + return self._camera + + @property + def name(self): + """Return the name of the entity.""" + return self._name + + def process_image(self, image): + """Process image.""" + # pylint: disable=import-error + import face_recognition + + fak_file = io.BytesIO(image) + fak_file.name = "snapshot.jpg" + fak_file.seek(0) + + image = face_recognition.load_image_file(fak_file) + face_locations = face_recognition.face_locations(image) + + self.process_faces(face_locations, len(face_locations)) diff --git a/homeassistant/components/image_processing/dlib_face_identify.py b/homeassistant/components/image_processing/dlib_face_identify.py new file mode 100644 index 00000000000..a0f50796a9f --- /dev/null +++ b/homeassistant/components/image_processing/dlib_face_identify.py @@ -0,0 +1,95 @@ +""" +Component that will help set the dlib face detect processing. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/image_processing.dlib_face_identify/ +""" +import logging +import io + +import voluptuous as vol + +from homeassistant.core import split_entity_id +from homeassistant.components.image_processing import ( + PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) +from homeassistant.components.image_processing.microsoft_face_identify import ( + ImageProcessingFaceEntity) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['face_recognition==0.1.14'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_NAME = 'name' +CONF_FACES = 'faces' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_FACES): {cv.string: cv.isfile}, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Microsoft Face detection platform.""" + entities = [] + for camera in config[CONF_SOURCE]: + entities.append(DlibFaceIdentifyEntity( + camera[CONF_ENTITY_ID], config[CONF_FACES], camera.get(CONF_NAME) + )) + + add_devices(entities) + + +class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): + """Dlib Face API entity for identify.""" + + def __init__(self, camera_entity, faces, name=None): + """Initialize Dlib.""" + # pylint: disable=import-error + import face_recognition + super().__init__() + + self._camera = camera_entity + + if name: + self._name = name + else: + self._name = "Dlib Face {0}".format( + split_entity_id(camera_entity)[1]) + + self._faces = {} + for name, face_file in faces.items(): + image = face_recognition.load_image_file(face_file) + self._faces[name] = face_recognition.face_encodings(image)[0] + + @property + def camera_entity(self): + """Return camera entity id from process pictures.""" + return self._camera + + @property + def name(self): + """Return the name of the entity.""" + return self._name + + def process_image(self, image): + """Process image.""" + # pylint: disable=import-error + import face_recognition + + fak_file = io.BytesIO(image) + fak_file.name = "snapshot.jpg" + fak_file.seek(0) + + image = face_recognition.load_image_file(fak_file) + unknowns = face_recognition.face_encodings(image) + + found = [] + for unknown_face in unknowns: + for name, face in self._faces.items(): + result = face_recognition.compare_faces([face], unknown_face) + if result[0]: + found.append({ + ATTR_NAME: name + }) + + self.process_faces(found, len(unknowns)) diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py index bb1a7accd15..5c4daec1067 100644 --- a/homeassistant/components/image_processing/microsoft_face_detect.py +++ b/homeassistant/components/image_processing/microsoft_face_detect.py @@ -22,8 +22,6 @@ DEPENDENCIES = ['microsoft_face'] _LOGGER = logging.getLogger(__name__) -EVENT_IDENTIFY_FACE = 'detect_face' - SUPPORTED_ATTRIBUTES = [ ATTR_AGE, ATTR_GENDER, @@ -50,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the microsoft face detection platform.""" + """Set up the Microsoft Face detection platform.""" api = hass.data[DATA_MICROSOFT_FACE] attributes = config[CONF_ATTRIBUTES] @@ -64,10 +62,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity): - """Microsoft face api entity for identify.""" + """Microsoft Face API entity for identify.""" def __init__(self, camera_entity, api, attributes, name=None): - """Initialize openalpr local api.""" + """Initialize Microsoft Face.""" super().__init__() self._api = api diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py index ec4549dfe0c..a645aef2fcb 100644 --- a/homeassistant/components/image_processing/microsoft_face_identify.py +++ b/homeassistant/components/image_processing/microsoft_face_identify.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the microsoft face identify platform.""" + """Set up the Microsoft Face identify platform.""" api = hass.data[DATA_MICROSOFT_FACE] face_group = config[CONF_GROUP] confidence = config[CONF_CONFIDENCE] @@ -145,10 +145,10 @@ class ImageProcessingFaceEntity(ImageProcessingEntity): class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): - """Microsoft face api entity for identify.""" + """Representation of the Microsoft Face API entity for identify.""" def __init__(self, camera_entity, api, face_group, confidence, name=None): - """Initialize openalpr local api.""" + """Initialize the Microsoft Face API.""" super().__init__() self._api = api @@ -197,7 +197,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): {'faceIds': face_ids, 'personGroupId': self._face_group}) except HomeAssistantError as err: - _LOGGER.error("Can't process image on microsoft face: %s", err) + _LOGGER.error("Can't process image on Microsoft face: %s", err) return # parse data @@ -205,7 +205,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): total = 0 for face in detect: total += 1 - if len(face['candidates']) == 0: + if not face['candidates']: continue data = face['candidates'][0] diff --git a/homeassistant/components/image_processing/openalpr_cloud.py b/homeassistant/components/image_processing/openalpr_cloud.py index 1ba8dfcfbc4..1143c1e04ae 100644 --- a/homeassistant/components/image_processing/openalpr_cloud.py +++ b/homeassistant/components/image_processing/openalpr_cloud.py @@ -1,5 +1,5 @@ """ -Component that will help set the openalpr cloud for alpr processing. +Component that will help set the OpenALPR cloud for ALPR processing. For more details about this component, please refer to the documentation at https://home-assistant.io/components/image_processing.openalpr_cloud/ @@ -51,7 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the openalpr cloud api platform.""" + """Set up the OpenALPR cloud API platform.""" confidence = config[CONF_CONFIDENCE] params = { 'secret_key': config[CONF_API_KEY], @@ -70,10 +70,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class OpenAlprCloudEntity(ImageProcessingAlprEntity): - """OpenAlpr cloud entity.""" + """OpenALPR cloud entity.""" def __init__(self, camera_entity, params, confidence, name=None): - """Initialize openalpr local api.""" + """Initialize OpenALPR cloud API.""" super().__init__() self._params = params @@ -126,7 +126,7 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity): return except (asyncio.TimeoutError, aiohttp.ClientError): - _LOGGER.error("Timeout for openalpr api.") + _LOGGER.error("Timeout for OpenALPR API") return # processing api data diff --git a/homeassistant/components/image_processing/openalpr_local.py b/homeassistant/components/image_processing/openalpr_local.py index 4040efe3bf4..05ca2cffcd0 100644 --- a/homeassistant/components/image_processing/openalpr_local.py +++ b/homeassistant/components/image_processing/openalpr_local.py @@ -1,5 +1,5 @@ """ -Component that will help set the openalpr local for alpr processing. +Component that will help set the OpenALPR local for ALPR processing. For more details about this component, please refer to the documentation at https://home-assistant.io/components/image_processing.openalpr_local/ @@ -60,7 +60,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the openalpr local platform.""" + """Set up the OpenALPR local platform.""" command = [config[CONF_ALPR_BIN], '-c', config[CONF_REGION], '-'] confidence = config[CONF_CONFIDENCE] @@ -74,7 +74,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class ImageProcessingAlprEntity(ImageProcessingEntity): - """Base entity class for alpr image processing.""" + """Base entity class for ALPR image processing.""" def __init__(self): """Initialize base alpr entity.""" @@ -144,10 +144,10 @@ class ImageProcessingAlprEntity(ImageProcessingEntity): class OpenAlprLocalEntity(ImageProcessingAlprEntity): - """OpenAlpr local api entity.""" + """OpenALPR local api entity.""" def __init__(self, camera_entity, command, confidence, name=None): - """Initialize openalpr local api.""" + """Initialize OpenALPR local API.""" super().__init__() self._cmd = command diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/image_processing/opencv.py new file mode 100644 index 00000000000..e48c14aeea5 --- /dev/null +++ b/homeassistant/components/image_processing/opencv.py @@ -0,0 +1,120 @@ +""" +Component that performs OpenCV classification on images. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/image_processing.opencv/ +""" +from datetime import timedelta +import logging + +from homeassistant.core import split_entity_id +from homeassistant.components.image_processing import ( + ImageProcessingEntity, + PLATFORM_SCHEMA, +) +from homeassistant.components.opencv import ( + ATTR_MATCHES, + CLASSIFIER_GROUP_CONFIG, + CONF_CLASSIFIER, + CONF_ENTITY_ID, + CONF_NAME, + process_image, +) + +DEPENDENCIES = ['opencv'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_TIMEOUT = 10 + +SCAN_INTERVAL = timedelta(seconds=2) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(CLASSIFIER_GROUP_CONFIG) + + +def _create_processor_from_config(hass, camera_entity, config): + """Create an OpenCV processor from configurtaion.""" + classifier_config = config[CONF_CLASSIFIER] + name = '{} {}'.format( + config[CONF_NAME], + split_entity_id(camera_entity)[1].replace('_', ' ')) + + processor = OpenCVImageProcessor( + hass, + camera_entity, + name, + classifier_config, + ) + + return processor + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the OpenCV image processing platform.""" + if discovery_info is None: + return + + devices = [] + for camera_entity in discovery_info[CONF_ENTITY_ID]: + devices.append( + _create_processor_from_config( + hass, + camera_entity, + discovery_info)) + + add_devices(devices) + + +class OpenCVImageProcessor(ImageProcessingEntity): + """Representation of an OpenCV image processor.""" + + def __init__(self, hass, camera_entity, name, classifier_configs): + """Initialize the OpenCV entity.""" + self.hass = hass + self._camera_entity = camera_entity + self._name = name + self._classifier_configs = classifier_configs + self._matches = {} + self._last_image = None + + @property + def last_image(self): + """Return the last image.""" + return self._last_image + + @property + def matches(self): + """Return the matches it found.""" + return self._matches + + @property + def camera_entity(self): + """Return camera entity id from process pictures.""" + return self._camera_entity + + @property + def name(self): + """Return the name of the image processor.""" + return self._name + + @property + def state(self): + """Return the state of the entity.""" + total_matches = 0 + for group in self._matches.values(): + total_matches += len(group) + return total_matches + + @property + def state_attributes(self): + """Return device specific state attributes.""" + return { + ATTR_MATCHES: self._matches + } + + def process_image(self, image): + """Process the image.""" + self._last_image = image + self._matches = process_image(image, + self._classifier_configs, + False) diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index 4e44296bc0c..abd02554ac0 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -10,8 +10,8 @@ import voluptuous as vol from homeassistant.const import ( EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN, CONF_HOST, - CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_USERNAME, CONF_BLACKLIST, - CONF_PASSWORD, CONF_WHITELIST) + CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, + CONF_EXCLUDE, CONF_INCLUDE, CONF_DOMAINS, CONF_ENTITIES) from homeassistant.helpers import state as state_helper import homeassistant.helpers.config_validation as cv @@ -23,6 +23,7 @@ CONF_DB_NAME = 'database' CONF_TAGS = 'tags' CONF_DEFAULT_MEASUREMENT = 'default_measurement' CONF_OVERRIDE_MEASUREMENT = 'override_measurement' +CONF_BLACKLIST_DOMAINS = "blacklist_domains" DEFAULT_DATABASE = 'home_assistant' DEFAULT_VERIFY_SSL = True @@ -34,8 +35,16 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_HOST): cv.string, vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_BLACKLIST, default=[]): - vol.All(cv.ensure_list, [cv.entity_id]), + vol.Optional(CONF_EXCLUDE, default={}): vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) + }), + vol.Optional(CONF_INCLUDE, default={}): vol.Schema({ + vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_DOMAINS, default=[]): + vol.All(cv.ensure_list, [cv.string]) + }), vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string, vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL): cv.boolean, @@ -43,15 +52,13 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string, vol.Optional(CONF_TAGS, default={}): vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_WHITELIST, default=[]): - vol.All(cv.ensure_list, [cv.entity_id]), vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) def setup(hass, config): - """Setup the InfluxDB component.""" + """Set up the InfluxDB component.""" from influxdb import InfluxDBClient, exceptions conf = config[DOMAIN] @@ -77,8 +84,12 @@ def setup(hass, config): if CONF_SSL in conf: kwargs['ssl'] = conf[CONF_SSL] - blacklist = conf.get(CONF_BLACKLIST) - whitelist = conf.get(CONF_WHITELIST) + include = conf.get(CONF_INCLUDE, {}) + exclude = conf.get(CONF_EXCLUDE, {}) + whitelist_e = set(include.get(CONF_ENTITIES, [])) + whitelist_d = set(include.get(CONF_DOMAINS, [])) + blacklist_e = set(exclude.get(CONF_ENTITIES, [])) + blacklist_d = set(exclude.get(CONF_DOMAINS, [])) tags = conf.get(CONF_TAGS) default_measurement = conf.get(CONF_DEFAULT_MEASUREMENT) override_measurement = conf.get(CONF_OVERRIDE_MEASUREMENT) @@ -97,11 +108,13 @@ def setup(hass, config): state = event.data.get('new_state') if state is None or state.state in ( STATE_UNKNOWN, '', STATE_UNAVAILABLE) or \ - state.entity_id in blacklist: + state.entity_id in blacklist_e or \ + state.domain in blacklist_d: return try: - if len(whitelist) > 0 and state.entity_id not in whitelist: + if (whitelist_e and state.entity_id not in whitelist_e) or \ + (whitelist_d and state.domain not in whitelist_d): return _state = float(state_helper.state_as_number(state)) @@ -154,7 +167,7 @@ def setup(hass, config): try: influx.write_points(json_body) except exceptions.InfluxDBClientError: - _LOGGER.exception('Error saving event "%s" to InfluxDB', json_body) + _LOGGER.exception("Error saving event %s to InfluxDB", json_body) hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener) diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py index dd27ab3ebda..e975e42bcdc 100644 --- a/homeassistant/components/input_boolean.py +++ b/homeassistant/components/input_boolean.py @@ -63,7 +63,7 @@ def toggle(hass, entity_id): @asyncio.coroutine def async_setup(hass, config): - """Set up input boolean.""" + """Set up an input boolean.""" component = EntityComponent(_LOGGER, DOMAIN, hass) entities = [] @@ -140,7 +140,7 @@ class InputBoolean(ToggleEntity): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity about to be added to hass.""" + """Call when entity about to be added to hass.""" # If not None, we got an initial value. if self._state is not None: return diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index 7247a8b1093..c7f3a7f2236 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -15,10 +15,10 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import async_get_last_state +_LOGGER = logging.getLogger(__name__) DOMAIN = 'input_select' ENTITY_ID_FORMAT = DOMAIN + '.{}' -_LOGGER = logging.getLogger(__name__) CONF_INITIAL = 'initial' CONF_OPTIONS = 'options' @@ -56,7 +56,7 @@ SERVICE_SET_OPTIONS_SCHEMA = vol.Schema({ def _cv_input_select(cfg): - """Config validation helper for input select (Voluptuous).""" + """Configure validation helper for input select (voluptuous).""" options = cfg[CONF_OPTIONS] initial = cfg.get(CONF_INITIAL) if initial is not None and initial not in options: @@ -109,7 +109,7 @@ def set_options(hass, entity_id, options): @asyncio.coroutine def async_setup(hass, config): - """Setup input select.""" + """Set up an input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) entities = [] @@ -197,7 +197,7 @@ class InputSelect(Entity): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity about to be added to hass.""" + """Run when entity about to be added.""" if self._current_option is not None: return diff --git a/homeassistant/components/input_slider.py b/homeassistant/components/input_slider.py index c4976bb43e8..bd17376b2ef 100644 --- a/homeassistant/components/input_slider.py +++ b/homeassistant/components/input_slider.py @@ -9,16 +9,17 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_NAME) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import async_get_last_state +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'input_slider' ENTITY_ID_FORMAT = DOMAIN + '.{}' -_LOGGER = logging.getLogger(__name__) CONF_INITIAL = 'initial' CONF_MIN = 'min' @@ -39,7 +40,7 @@ SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({ def _cv_input_slider(cfg): - """Config validation helper for input slider (Voluptuous).""" + """Configure validation helper for input slider (voluptuous).""" minimum = cfg.get(CONF_MIN) maximum = cfg.get(CONF_MAX) if minimum >= maximum: @@ -59,8 +60,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_MIN): vol.Coerce(float), vol.Required(CONF_MAX): vol.Coerce(float), vol.Optional(CONF_INITIAL): vol.Coerce(float), - vol.Optional(CONF_STEP, default=1): vol.All(vol.Coerce(float), - vol.Range(min=1e-3)), + vol.Optional(CONF_STEP, default=1): + vol.All(vol.Coerce(float), vol.Range(min=1e-3)), vol.Optional(CONF_ICON): cv.icon, vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string }, _cv_input_slider) @@ -78,7 +79,7 @@ def select_value(hass, entity_id, value): @asyncio.coroutine def async_setup(hass, config): - """Set up input slider.""" + """Set up an input slider.""" component = EntityComponent(_LOGGER, DOMAIN, hass) entities = [] @@ -92,8 +93,8 @@ def async_setup(hass, config): icon = cfg.get(CONF_ICON) unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT) - entities.append(InputSlider(object_id, name, initial, minimum, maximum, - step, icon, unit)) + entities.append(InputSlider( + object_id, name, initial, minimum, maximum, step, icon, unit)) if not entities: return False @@ -138,27 +139,27 @@ class InputSlider(Entity): @property def name(self): - """Name of the select input.""" + """Return the name of the select input slider.""" 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_value @property def unit_of_measurement(self): - """Unit of measurement of slider.""" + """Return the unit the value is expressed in.""" return self._unit @property def state_attributes(self): - """State attributes.""" + """Return the state attributes.""" return { ATTR_MIN: self._minimum, ATTR_MAX: self._maximum, @@ -167,7 +168,7 @@ class InputSlider(Entity): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity about to be added to hass.""" + """Run when entity about to be added to hass.""" if self._current_value is not None: return @@ -185,7 +186,7 @@ class InputSlider(Entity): """Select new value.""" num_value = float(value) if num_value < self._minimum or num_value > self._maximum: - _LOGGER.warning('Invalid value: %s (range %s - %s)', + _LOGGER.warning("Invalid value: %s (range %s - %s)", num_value, self._minimum, self._maximum) return self._current_value = num_value diff --git a/homeassistant/components/insteon_hub.py b/homeassistant/components/insteon_hub.py index 9d77a0fc5c3..49e6c566c46 100644 --- a/homeassistant/components/insteon_hub.py +++ b/homeassistant/components/insteon_hub.py @@ -29,12 +29,12 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup Insteon Hub component. + """Set up the Insteon Hub component. This will automatically import associated lights. """ - _LOGGER.warning('Component disabled at request from Insteon. ' - 'For more information: https://goo.gl/zLJaic') + _LOGGER.warning("Component disabled at request from Insteon. " + "For more information: https://goo.gl/zLJaic") return False # pylint: disable=unreachable import insteon diff --git a/homeassistant/components/insteon_local.py b/homeassistant/components/insteon_local.py index cf93bc97e57..1f700b5ff3e 100644 --- a/homeassistant/components/insteon_local.py +++ b/homeassistant/components/insteon_local.py @@ -33,7 +33,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Set up Insteon Hub component. + """Set up the Insteon Hub component. This will automatically import associated lights. """ @@ -48,7 +48,7 @@ def setup(hass, config): try: insteonhub = Hub(host, username, password, port, timeout, _LOGGER) - # check for successful connection + # Check for successful connection insteonhub.get_buffer_status() except requests.exceptions.ConnectTimeout: _LOGGER.error("Error on insteon_local." diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm.py index d786de7ac8a..b62a2e229ae 100644 --- a/homeassistant/components/insteon_plm.py +++ b/homeassistant/components/insteon_plm.py @@ -40,7 +40,7 @@ PLM_PLATFORMS = { @asyncio.coroutine def async_setup(hass, config): - """Set up our connection to the PLM.""" + """Set up the connection to the PLM.""" import insteonplm conf = config[DOMAIN] @@ -49,12 +49,12 @@ def async_setup(hass, config): @callback def async_plm_new_device(device): - """New device detected from transport to be delegated to platform.""" + """Detect device from transport to be delegated to platform.""" name = device.get('address') address = device.get('address_hex') capabilities = device.get('capabilities', []) - _LOGGER.info('New INSTEON PLM device: %s (%s) %r', + _LOGGER.info("New INSTEON PLM device: %s (%s) %r", name, address, capabilities) loadlist = [] @@ -72,7 +72,7 @@ def async_setup(hass, config): hass, loadplatform, DOMAIN, discovered=[device], hass_config=config)) - _LOGGER.info('Looking for PLM on %s', port) + _LOGGER.info("Looking for PLM on %s", port) plm = yield from insteonplm.Connection.create(device=port, loop=hass.loop) for device in overrides: diff --git a/homeassistant/components/introduction.py b/homeassistant/components/introduction.py index afbcca14253..367eeb1a6c3 100644 --- a/homeassistant/components/introduction.py +++ b/homeassistant/components/introduction.py @@ -16,7 +16,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config=None): - """Setup the introduction component.""" + """Set up the introduction component.""" log = logging.getLogger(__name__) log.info(""" diff --git a/homeassistant/components/ios.py b/homeassistant/components/ios.py index bac5bb59d96..26a462bb2a8 100644 --- a/homeassistant/components/ios.py +++ b/homeassistant/components/ios.py @@ -1,7 +1,7 @@ """ Native Home Assistant iOS app component. -For more details about this platform, please refer to the documentation at +For more details about this component, please refer to the documentation at https://home-assistant.io/ecosystem/ios/ """ import asyncio @@ -11,7 +11,7 @@ import logging import datetime import voluptuous as vol -from voluptuous.humanize import humanize_error +# from voluptuous.humanize import humanize_error from homeassistant.helpers import config_validation as cv @@ -28,76 +28,76 @@ from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR, _LOGGER = logging.getLogger(__name__) -DOMAIN = "ios" +DOMAIN = 'ios' -DEPENDENCIES = ["device_tracker", "http", "zeroconf"] +DEPENDENCIES = ['device_tracker', 'http', 'zeroconf'] -CONF_PUSH = "push" -CONF_PUSH_CATEGORIES = "categories" -CONF_PUSH_CATEGORIES_NAME = "name" -CONF_PUSH_CATEGORIES_IDENTIFIER = "identifier" -CONF_PUSH_CATEGORIES_ACTIONS = "actions" +CONF_PUSH = 'push' +CONF_PUSH_CATEGORIES = 'categories' +CONF_PUSH_CATEGORIES_NAME = 'name' +CONF_PUSH_CATEGORIES_IDENTIFIER = 'identifier' +CONF_PUSH_CATEGORIES_ACTIONS = 'actions' -CONF_PUSH_ACTIONS_IDENTIFIER = "identifier" -CONF_PUSH_ACTIONS_TITLE = "title" -CONF_PUSH_ACTIONS_ACTIVATION_MODE = "activationMode" -CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED = "authenticationRequired" -CONF_PUSH_ACTIONS_DESTRUCTIVE = "destructive" -CONF_PUSH_ACTIONS_BEHAVIOR = "behavior" -CONF_PUSH_ACTIONS_CONTEXT = "context" -CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE = "textInputButtonTitle" -CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER = "textInputPlaceholder" +CONF_PUSH_ACTIONS_IDENTIFIER = 'identifier' +CONF_PUSH_ACTIONS_TITLE = 'title' +CONF_PUSH_ACTIONS_ACTIVATION_MODE = 'activationMode' +CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED = 'authenticationRequired' +CONF_PUSH_ACTIONS_DESTRUCTIVE = 'destructive' +CONF_PUSH_ACTIONS_BEHAVIOR = 'behavior' +CONF_PUSH_ACTIONS_CONTEXT = 'context' +CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE = 'textInputButtonTitle' +CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER = 'textInputPlaceholder' -ATTR_FOREGROUND = "foreground" -ATTR_BACKGROUND = "background" +ATTR_FOREGROUND = 'foreground' +ATTR_BACKGROUND = 'background' ACTIVATION_MODES = [ATTR_FOREGROUND, ATTR_BACKGROUND] -ATTR_DEFAULT_BEHAVIOR = "default" -ATTR_TEXT_INPUT_BEHAVIOR = "textInput" +ATTR_DEFAULT_BEHAVIOR = 'default' +ATTR_TEXT_INPUT_BEHAVIOR = 'textInput' BEHAVIORS = [ATTR_DEFAULT_BEHAVIOR, ATTR_TEXT_INPUT_BEHAVIOR] -ATTR_LAST_SEEN_AT = "lastSeenAt" +ATTR_LAST_SEEN_AT = 'lastSeenAt' -ATTR_DEVICE = "device" -ATTR_PUSH_TOKEN = "pushToken" -ATTR_APP = "app" -ATTR_PERMISSIONS = "permissions" -ATTR_PUSH_ID = "pushId" -ATTR_DEVICE_ID = "deviceId" -ATTR_PUSH_SOUNDS = "pushSounds" -ATTR_BATTERY = "battery" +ATTR_DEVICE = 'device' +ATTR_PUSH_TOKEN = 'pushToken' +ATTR_APP = 'app' +ATTR_PERMISSIONS = 'permissions' +ATTR_PUSH_ID = 'pushId' +ATTR_DEVICE_ID = 'deviceId' +ATTR_PUSH_SOUNDS = 'pushSounds' +ATTR_BATTERY = 'battery' -ATTR_DEVICE_NAME = "name" -ATTR_DEVICE_LOCALIZED_MODEL = "localizedModel" -ATTR_DEVICE_MODEL = "model" -ATTR_DEVICE_PERMANENT_ID = "permanentID" -ATTR_DEVICE_SYSTEM_VERSION = "systemVersion" -ATTR_DEVICE_TYPE = "type" -ATTR_DEVICE_SYSTEM_NAME = "systemName" +ATTR_DEVICE_NAME = 'name' +ATTR_DEVICE_LOCALIZED_MODEL = 'localizedModel' +ATTR_DEVICE_MODEL = 'model' +ATTR_DEVICE_PERMANENT_ID = 'permanentID' +ATTR_DEVICE_SYSTEM_VERSION = 'systemVersion' +ATTR_DEVICE_TYPE = 'type' +ATTR_DEVICE_SYSTEM_NAME = 'systemName' -ATTR_APP_BUNDLE_IDENTIFER = "bundleIdentifer" -ATTR_APP_BUILD_NUMBER = "buildNumber" -ATTR_APP_VERSION_NUMBER = "versionNumber" +ATTR_APP_BUNDLE_IDENTIFER = 'bundleIdentifer' +ATTR_APP_BUILD_NUMBER = 'buildNumber' +ATTR_APP_VERSION_NUMBER = 'versionNumber' -ATTR_LOCATION_PERMISSION = "location" -ATTR_NOTIFICATIONS_PERMISSION = "notifications" +ATTR_LOCATION_PERMISSION = 'location' +ATTR_NOTIFICATIONS_PERMISSION = 'notifications' PERMISSIONS = [ATTR_LOCATION_PERMISSION, ATTR_NOTIFICATIONS_PERMISSION] -ATTR_BATTERY_STATE = "state" -ATTR_BATTERY_LEVEL = "level" +ATTR_BATTERY_STATE = 'state' +ATTR_BATTERY_LEVEL = 'level' -ATTR_BATTERY_STATE_UNPLUGGED = "Unplugged" -ATTR_BATTERY_STATE_CHARGING = "Charging" -ATTR_BATTERY_STATE_FULL = "Full" -ATTR_BATTERY_STATE_UNKNOWN = "Unknown" +ATTR_BATTERY_STATE_UNPLUGGED = 'Unplugged' +ATTR_BATTERY_STATE_CHARGING = 'Charging' +ATTR_BATTERY_STATE_FULL = 'Full' +ATTR_BATTERY_STATE_UNKNOWN = 'Unknown' BATTERY_STATES = [ATTR_BATTERY_STATE_UNPLUGGED, ATTR_BATTERY_STATE_CHARGING, ATTR_BATTERY_STATE_FULL, ATTR_BATTERY_STATE_UNKNOWN] -ATTR_DEVICES = "devices" +ATTR_DEVICES = 'devices' ACTION_SCHEMA = vol.Schema({ vol.Required(CONF_PUSH_ACTIONS_IDENTIFIER): vol.Upper, @@ -167,7 +167,7 @@ IDENTIFY_SCHEMA = vol.Schema({ vol.Optional(ATTR_PUSH_SOUNDS): list }, extra=vol.ALLOW_EXTRA) -CONFIGURATION_FILE = "ios.conf" +CONFIGURATION_FILE = 'ios.conf' CONFIG_FILE = {ATTR_DEVICES: {}} @@ -196,7 +196,7 @@ def _load_config(filename): def _save_config(filename, config): """Save configuration.""" try: - with open(filename, "w") as fdesc: + with open(filename, 'w') as fdesc: fdesc.write(json.dumps(config, cls=JSONEncoder)) except (IOError, TypeError) as error: _LOGGER.error("Saving config file failed: %s", error) @@ -237,7 +237,7 @@ def device_name_for_push_id(push_id): def setup(hass, config): - """Setup the iOS component.""" + """Set up the iOS component.""" # pylint: disable=global-statement, import-error global CONFIG_FILE global CONFIG_FILE_PATH @@ -249,9 +249,9 @@ def setup(hass, config): if CONFIG_FILE == {}: CONFIG_FILE[ATTR_DEVICES] = {} - discovery.load_platform(hass, "notify", DOMAIN, {}, config) + discovery.load_platform(hass, 'notify', DOMAIN, {}, config) - discovery.load_platform(hass, "sensor", DOMAIN, {}, config) + discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) hass.http.register_view(iOSIdentifyDeviceView) @@ -265,8 +265,8 @@ def setup(hass, config): class iOSPushConfigView(HomeAssistantView): """A view that provides the push categories configuration.""" - url = "/api/ios/push" - name = "api:ios:push" + url = '/api/ios/push' + name = 'api:ios:push' def __init__(self, push_config): """Init the view.""" @@ -281,22 +281,23 @@ class iOSPushConfigView(HomeAssistantView): class iOSIdentifyDeviceView(HomeAssistantView): """A view that accepts device identification requests.""" - url = "/api/ios/identify" - name = "api:ios:identify" + url = '/api/ios/identify' + name = 'api:ios:identify' @asyncio.coroutine def post(self, request): """Handle the POST request for device identification.""" try: - req_data = yield from request.json() + data = yield from request.json() except ValueError: return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) - try: - data = IDENTIFY_SCHEMA(req_data) - except vol.Invalid as ex: - return self.json_message(humanize_error(request.json, ex), - HTTP_BAD_REQUEST) + # Commented for now while iOS app is getting frequent updates + # try: + # data = IDENTIFY_SCHEMA(req_data) + # except vol.Invalid as ex: + # return self.json_message(humanize_error(request.json, ex), + # HTTP_BAD_REQUEST) data[ATTR_LAST_SEEN_AT] = datetime.datetime.now() diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 171c78a2fc8..a834cc0a3e4 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -7,32 +7,35 @@ https://home-assistant.io/components/isy994/ from collections import namedtuple import logging from urllib.parse import urlparse + import voluptuous as vol from homeassistant.core import HomeAssistant # noqa from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import discovery, config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, Dict # noqa - -DOMAIN = "isy994" REQUIREMENTS = ['PyISY==1.0.7'] -ISY = None -DEFAULT_SENSOR_STRING = 'sensor' -DEFAULT_HIDDEN_STRING = '{HIDE ME}' -CONF_TLS_VER = 'tls' +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'isy994' + CONF_HIDDEN_STRING = 'hidden_string' CONF_SENSOR_STRING = 'sensor_string' -KEY_MY_PROGRAMS = 'My Programs' -KEY_FOLDER = 'folder' -KEY_ACTIONS = 'actions' -KEY_STATUS = 'status' +CONF_TLS_VER = 'tls' -_LOGGER = logging.getLogger(__name__) +DEFAULT_HIDDEN_STRING = '{HIDE ME}' +DEFAULT_SENSOR_STRING = 'sensor' + +ISY = None + +KEY_ACTIONS = 'actions' +KEY_FOLDER = 'folder' +KEY_MY_PROGRAMS = 'My Programs' +KEY_STATUS = 'status' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -158,10 +161,10 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: host = urlparse(isy_config.get(CONF_HOST)) port = host.port addr = host.geturl() - hidden_identifier = isy_config.get(CONF_HIDDEN_STRING, - DEFAULT_HIDDEN_STRING) - sensor_identifier = isy_config.get(CONF_SENSOR_STRING, - DEFAULT_SENSOR_STRING) + hidden_identifier = isy_config.get( + CONF_HIDDEN_STRING, DEFAULT_HIDDEN_STRING) + sensor_identifier = isy_config.get( + CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING) global HIDDEN_STRING HIDDEN_STRING = hidden_identifier @@ -173,7 +176,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: addr = addr.replace('https://', '') https = True else: - _LOGGER.error('isy994 host value in configuration is invalid.') + _LOGGER.error("isy994 host value in configuration is invalid") return False addr = addr.replace(':{}'.format(port), '') @@ -225,8 +228,8 @@ class ISYDevice(Entity): """Initialize the insteon device.""" self._node = node - self._change_handler = self._node.status.subscribe('changed', - self.on_update) + self._change_handler = self._node.status.subscribe( + 'changed', self.on_update) # pylint: disable=unused-argument def on_update(self, event: object) -> None: diff --git a/homeassistant/components/joaoapps_join.py b/homeassistant/components/joaoapps_join.py index 4f01a3cf411..b5bcb1e1a8a 100644 --- a/homeassistant/components/joaoapps_join.py +++ b/homeassistant/components/joaoapps_join.py @@ -1,62 +1,75 @@ """ Component for Joaoapps Join services. -For more details about this platform, please refer to the documentation at +For more details about this component, please refer to the documentation at https://home-assistant.io/components/join/ """ import logging -import voluptuous as vol -from homeassistant.const import CONF_NAME, CONF_API_KEY -import homeassistant.helpers.config_validation as cv -REQUIREMENTS = [ - 'https://github.com/nkgilley/python-join-api/archive/' - '3e1e849f1af0b4080f551b62270c6d244d5fbcbd.zip#python-join-api==0.0.1'] +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_NAME, CONF_API_KEY + +REQUIREMENTS = ['python-join-api==0.0.2'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'joaoapps_join' CONF_DEVICE_ID = 'device_id' +CONF_DEVICE_IDS = 'device_ids' +CONF_DEVICE_NAMES = 'device_names' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(cv.ensure_list, [{ - vol.Required(CONF_DEVICE_ID): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_API_KEY): cv.string + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_DEVICE_ID): cv.string, + vol.Optional(CONF_DEVICE_IDS): cv.string, + vol.Optional(CONF_DEVICE_NAMES): cv.string, + vol.Optional(CONF_NAME): cv.string }]) }, extra=vol.ALLOW_EXTRA) -def register_device(hass, device_id, api_key, name): - """Method to register services for each join device listed.""" +def register_device(hass, api_key, name, device_id, device_ids, device_names): + """Register services for each join device listed.""" from pyjoin import (ring_device, set_wallpaper, send_sms, send_file, send_url, send_notification) def ring_service(service): """Service to ring devices.""" - ring_device(device_id, api_key=api_key) + ring_device(api_key=api_key, device_id=device_id, + device_ids=device_ids, device_names=device_names) def set_wallpaper_service(service): """Service to set wallpaper on devices.""" - set_wallpaper(device_id, url=service.data.get('url'), api_key=api_key) + set_wallpaper(api_key=api_key, device_id=device_id, + device_ids=device_ids, device_names=device_names, + url=service.data.get('url')) def send_file_service(service): """Service to send files to devices.""" - send_file(device_id, url=service.data.get('url'), api_key=api_key) + send_file(api_key=api_key, device_id=device_id, + device_ids=device_ids, device_names=device_names, + url=service.data.get('url')) def send_url_service(service): """Service to open url on devices.""" - send_url(device_id, url=service.data.get('url'), api_key=api_key) + send_url(api_key=api_key, device_id=device_id, + device_ids=device_ids, device_names=device_names, + url=service.data.get('url')) def send_tasker_service(service): """Service to open url on devices.""" - send_notification(device_id=device_id, - text=service.data.get('command'), - api_key=api_key) + send_notification(api_key=api_key, device_id=device_id, + device_ids=device_ids, device_names=device_names, + text=service.data.get('command')) def send_sms_service(service): """Service to send sms from devices.""" send_sms(device_id=device_id, + device_ids=device_ids, + device_names=device_names, sms_number=service.data.get('number'), sms_text=service.data.get('message'), api_key=api_key) @@ -71,16 +84,24 @@ def register_device(hass, device_id, api_key, name): def setup(hass, config): - """Setup Join services.""" + """Set up the Join services.""" from pyjoin import get_devices for device in config[DOMAIN]: - device_id = device.get(CONF_DEVICE_ID) api_key = device.get(CONF_API_KEY) + device_id = device.get(CONF_DEVICE_ID) + device_ids = device.get(CONF_DEVICE_IDS) + device_names = device.get(CONF_DEVICE_NAMES) name = device.get(CONF_NAME) name = name.lower().replace(" ", "_") + "_" if name else "" if api_key: if not get_devices(api_key): _LOGGER.error("Error connecting to Join, check API key") return False - register_device(hass, device_id, api_key, name) + if device_id is None and device_ids is None and device_names is None: + _LOGGER.error("No device was provided. Please specify device_id" + ", device_ids, or device_names") + return False + + register_device(hass, api_key, name, + device_id, device_ids, device_names) return True diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote.py index 69151043276..88d406ace0b 100644 --- a/homeassistant/components/keyboard_remote.py +++ b/homeassistant/components/keyboard_remote.py @@ -4,7 +4,6 @@ Receive signals from a keyboard and use it as a remote control. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/keyboard_remote/ """ - # pylint: disable=import-error import threading import logging @@ -15,24 +14,26 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP -) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -DOMAIN = "keyboard_remote" REQUIREMENTS = ['evdev==0.6.1'] + _LOGGER = logging.getLogger(__name__) + +DEVICE_DESCRIPTOR = 'device_descriptor' +DEVICE_ID_GROUP = 'Device descriptor or name' +DEVICE_NAME = 'device_name' +DOMAIN = 'keyboard_remote' + ICON = 'mdi:remote' + +KEY_CODE = 'key_code' +KEY_VALUE = {'key_up': 0, 'key_down': 1, 'key_hold': 2} KEYBOARD_REMOTE_COMMAND_RECEIVED = 'keyboard_remote_command_received' KEYBOARD_REMOTE_CONNECTED = 'keyboard_remote_connected' KEYBOARD_REMOTE_DISCONNECTED = 'keyboard_remote_disconnected' -KEY_CODE = 'key_code' -KEY_VALUE = {'key_up': 0, 'key_down': 1, 'key_hold': 2} -TYPE = 'type' -DEVICE_DESCRIPTOR = 'device_descriptor' -DEVICE_NAME = 'device_name' -DEVICE_ID_GROUP = 'Device descriptor or name' +TYPE = 'type' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -45,12 +46,12 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup keyboard_remote.""" + """Set up the keyboard_remote.""" config = config.get(DOMAIN) if not config.get(DEVICE_DESCRIPTOR) and\ not config.get(DEVICE_NAME): - _LOGGER.error('No device_descriptor or device_name found.') + _LOGGER.error("No device_descriptor or device_name found") return keyboard_remote = KeyboardRemote( @@ -91,10 +92,7 @@ class KeyboardRemote(threading.Thread): self.device_id = self.device_name self.dev = self._get_keyboard_device() if self.dev is not None: - _LOGGER.debug( - 'Keyboard connected, %s', - self.device_id - ) + _LOGGER.debug("Keyboard connected, %s", self.device_id) else: id_folder = '/dev/input/by-id/' device_names = [InputDevice(file_name).name @@ -116,6 +114,7 @@ class KeyboardRemote(threading.Thread): self.key_value = KEY_VALUE.get(config.get(TYPE, 'key_up')) def _get_keyboard_device(self): + """Get the keyboard device.""" from evdev import InputDevice, list_devices if self.device_name: devices = [InputDevice(file_name) for file_name in list_devices()] @@ -132,14 +131,12 @@ class KeyboardRemote(threading.Thread): return None def run(self): - """Main loop of the KeyboardRemote.""" + """Run the loop of the KeyboardRemote.""" from evdev import categorize, ecodes if self.dev is not None: self.dev.grab() - _LOGGER.debug( - 'Interface started for %s', - self.dev) + _LOGGER.debug("Interface started for %s", self.dev) while not self.stopped.isSet(): # Sleeps to ease load on processor @@ -152,8 +149,7 @@ class KeyboardRemote(threading.Thread): self.hass.bus.fire( KEYBOARD_REMOTE_CONNECTED ) - _LOGGER.debug('Keyboard re-connected, %s', - self.device_id) + _LOGGER.debug("Keyboard re-connected, %s", self.device_id) else: continue @@ -164,8 +160,7 @@ class KeyboardRemote(threading.Thread): self.hass.bus.fire( KEYBOARD_REMOTE_DISCONNECTED ) - _LOGGER.debug('Keyboard disconnected, %s', - self.device_id) + _LOGGER.debug("Keyboard disconnected, %s", self.device_id) continue if not event: diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index 53c414922e4..ff951e55810 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -8,17 +8,17 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT) from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['knxip==0.3.3'] _LOGGER = logging.getLogger(__name__) DEFAULT_HOST = '0.0.0.0' -DEFAULT_PORT = '3671' +DEFAULT_PORT = 3671 DOMAIN = 'knx' EVENT_KNX_FRAME_RECEIVED = 'knx_frame_received' @@ -34,7 +34,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the connection to the KNX IP interface.""" + """Set up the connection to the KNX IP interface.""" global KNXTUNNEL from knxip.ip import KNXIPTunnel @@ -94,12 +94,12 @@ class KNXConfig(object): @property def name(self): - """The name given to the entity.""" + """Return the name given to the entity.""" return self.config['name'] @property def address(self): - """The address of the device as an integer value. + """Return the address of the device as an integer value. 3 types of addresses are supported: integer - 0-65535 @@ -110,7 +110,7 @@ class KNXConfig(object): @property def state_address(self): - """The group address the device sends its current state to. + """Return the group address the device sends its current state to. Some KNX devices can send the current state to a seperate group address. This makes send e.g. when an actuator can @@ -145,12 +145,12 @@ class KNXGroupAddress(Entity): @property def name(self): - """The entity's display name.""" + """Return the entity's display name.""" return self._config.name @property def config(self): - """The entity's configuration.""" + """Return the entity's configuration.""" return self._config @property @@ -175,7 +175,7 @@ class KNXGroupAddress(Entity): @property def cache(self): - """The name given to the entity.""" + """Return the name given to the entity.""" return self._config.config.get('cache', True) def group_write(self, value): @@ -257,12 +257,12 @@ class KNXMultiAddressDevice(Entity): @property def name(self): - """The entity's display name.""" + """Return the entity's display name.""" return self._config.name @property def config(self): - """The entity's configuration.""" + """Return the entity's configuration.""" return self._config @property @@ -272,7 +272,7 @@ class KNXMultiAddressDevice(Entity): @property def cache(self): - """The name given to the entity.""" + """Return the name given to the entity.""" return self._config.config.get('cache', True) def has_attribute(self, name): diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 25dcf9e78da..853fe4d9fb1 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -50,6 +50,8 @@ ATTR_TRANSITION = "transition" ATTR_RGB_COLOR = "rgb_color" ATTR_XY_COLOR = "xy_color" ATTR_COLOR_TEMP = "color_temp" +ATTR_MIN_MIREDS = "min_mireds" +ATTR_MAX_MIREDS = "max_mireds" ATTR_COLOR_NAME = "color_name" ATTR_WHITE_VALUE = "white_value" @@ -78,6 +80,8 @@ LIGHT_PROFILES_FILE = "light_profiles.csv" PROP_TO_ATTR = { 'brightness': ATTR_BRIGHTNESS, 'color_temp': ATTR_COLOR_TEMP, + 'min_mireds': ATTR_MIN_MIREDS, + 'max_mireds': ATTR_MAX_MIREDS, 'rgb_color': ATTR_RGB_COLOR, 'xy_color': ATTR_XY_COLOR, 'white_value': ATTR_WHITE_VALUE, @@ -99,9 +103,7 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({ vol.Coerce(tuple)), ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), - ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), - vol.Range(min=color_util.HASS_COLOR_MIN, - max=color_util.HASS_COLOR_MAX)), + ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=1)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_EFFECT: cv.string, @@ -337,6 +339,18 @@ class Light(ToggleEntity): """Return the CT color value in mireds.""" return None + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + # Default to the Philips Hue value that HA has always assumed + return 154 + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + # Default to the Philips Hue value that HA has always assumed + return 500 + @property def white_value(self): """Return the white value of this light between 0..255.""" diff --git a/homeassistant/components/light/avion.py b/homeassistant/components/light/avion.py index 929b2bc33ac..9b717c64c86 100644 --- a/homeassistant/components/light/avion.py +++ b/homeassistant/components/light/avion.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['avion==0.5'] +REQUIREMENTS = ['avion==0.6'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/light/blinksticklight.py index 700840fb4bd..bbbde10ecc8 100644 --- a/homeassistant/components/light/blinksticklight.py +++ b/homeassistant/components/light/blinksticklight.py @@ -54,7 +54,7 @@ class BlinkStickLight(Light): @property def should_poll(self): - """Polling needed.""" + """Set up polling.""" return True @property diff --git a/homeassistant/components/light/blinkt.py b/homeassistant/components/light/blinkt.py new file mode 100644 index 00000000000..ffd3c102c7f --- /dev/null +++ b/homeassistant/components/light/blinkt.py @@ -0,0 +1,120 @@ +""" +Support for Blinkt! lights on Raspberry Pi. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.blinkt/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, + Light, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME + +REQUIREMENTS = ['blinkt==0.1.0'] + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_BLINKT = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR) + +DEFAULT_NAME = 'blinkt' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Blinkt Light platform.""" + import blinkt + + # ensure that the lights are off when exiting + blinkt.set_clear_on_exit() + + name = config.get(CONF_NAME) + + add_devices([BlinktLight(blinkt, name)]) + + +class BlinktLight(Light): + """Representation of a Blinkt! Light.""" + + def __init__(self, blinkt, name): + """Initialize a Blinkt Light. + + Default brightness and white color. + """ + self._blinkt = blinkt + self._name = name + self._is_on = False + self._brightness = 255 + self._rgb_color = [255, 255, 255] + + @property + def name(self): + """Return the display name of this light.""" + return self._name + + @property + def brightness(self): + """Read back the brightness of the light. + + Returns integer in the range of 1-255. + """ + return self._brightness + + @property + def rgb_color(self): + """Read back the color of the light. + + Returns [r, g, b] list with values in range of 0-255. + """ + return self._rgb_color + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BLINKT + + @property + def is_on(self): + """Return true if light is on.""" + return self._is_on + + @property + def should_poll(self): + """Return if we should poll this device.""" + return False + + @property + def assumed_state(self) -> bool: + """Return True if unable to access real state of the entity.""" + return True + + def turn_on(self, **kwargs): + """Instruct the light to turn on and set correct brightness & color.""" + if ATTR_RGB_COLOR in kwargs: + self._rgb_color = kwargs[ATTR_RGB_COLOR] + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + + percent_bright = (self._brightness / 255) + self._blinkt.set_all(self._rgb_color[0], + self._rgb_color[1], + self._rgb_color[2], + percent_bright) + + self._blinkt.show() + + self._is_on = True + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Instruct the light to turn off.""" + self._blinkt.set_brightness(0) + self._blinkt.show() + self._is_on = False + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/decora.py b/homeassistant/components/light/decora.py index eaae90f486e..856fcacd967 100644 --- a/homeassistant/components/light/decora.py +++ b/homeassistant/components/light/decora.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['decora==0.3'] +REQUIREMENTS = ['decora==0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index 6482e31fbaa..22ab404a3b2 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -27,7 +27,7 @@ SUPPORT_DEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the demo light platform.""" + """Set up the demo light platform.""" add_devices_callback([ DemoLight("Bed Light", False, True, effect_list=LIGHT_EFFECT_LIST, effect=LIGHT_EFFECT_LIST[0]), diff --git a/homeassistant/components/light/enocean.py b/homeassistant/components/light/enocean.py index 844cba1e631..ad4bc381b80 100644 --- a/homeassistant/components/light/enocean.py +++ b/homeassistant/components/light/enocean.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the EnOcean light platform.""" + """Set up the EnOcean light platform.""" sender_id = config.get(CONF_SENDER_ID) devname = config.get(CONF_NAME) dev_id = config.get(CONF_ID) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index f39ef3881a6..4b9bed10201 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -12,13 +12,13 @@ import voluptuous as vol from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_PROTOCOL from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, EFFECT_COLORLOOP, - EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, - SUPPORT_RGB_COLOR, Light, + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, ATTR_WHITE_VALUE, + EFFECT_COLORLOOP, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, + SUPPORT_RGB_COLOR, SUPPORT_WHITE_VALUE, Light, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['flux_led==0.18'] +REQUIREMENTS = ['flux_led==0.19'] _LOGGER = logging.getLogger(__name__) @@ -27,8 +27,10 @@ ATTR_MODE = 'mode' DOMAIN = 'flux_led' -SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | - SUPPORT_RGB_COLOR) +SUPPORT_FLUX_LED_RGB = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | + SUPPORT_RGB_COLOR) +SUPPORT_FLUX_LED_RGBW = (SUPPORT_WHITE_VALUE | SUPPORT_EFFECT | + SUPPORT_RGB_COLOR) MODE_RGB = 'rgb' MODE_RGBW = 'rgbw' @@ -92,7 +94,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Flux lights.""" + """Set up the Flux lights.""" import flux_led lights = [] light_ips = [] @@ -180,7 +182,16 @@ class FluxLight(Light): @property def brightness(self): """Return the brightness of this light between 0..255.""" - return self._bulb.brightness + if self._mode == MODE_RGB: + return self._bulb.brightness + return None # not used for RGBW + + @property + def white_value(self): + """Return the white value of this light between 0..255.""" + if self._mode == MODE_RGBW: + return self._bulb.getRgbw()[3] + return None # not used for RGB @property def rgb_color(self): @@ -190,7 +201,11 @@ class FluxLight(Light): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_FLUX_LED + if self._mode == MODE_RGBW: + return SUPPORT_FLUX_LED_RGBW + elif self._mode == MODE_RGB: + return SUPPORT_FLUX_LED_RGB + return 0 @property def effect_list(self): @@ -204,17 +219,23 @@ class FluxLight(Light): rgb = kwargs.get(ATTR_RGB_COLOR) brightness = kwargs.get(ATTR_BRIGHTNESS) + white_value = kwargs.get(ATTR_WHITE_VALUE) effect = kwargs.get(ATTR_EFFECT) + if rgb is not None and brightness is not None: self._bulb.setRgb(*tuple(rgb), brightness=brightness) + elif rgb is not None and white_value is not None: + self._bulb.setRgbw(*tuple(rgb), w=white_value) elif rgb is not None: - self._bulb.setRgb(*tuple(rgb)) + # self.white_value and self.brightness are appropriately + # returning None for MODE_RGB and MODE_RGBW respectively + self._bulb.setRgbw(*tuple(rgb), + w=self.white_value, + brightness=self.brightness) elif brightness is not None: - if self._mode == 'rgbw': - self._bulb.setWarmWhite255(brightness) - elif self._mode == 'rgb': - (red, green, blue) = self._bulb.getRgb() - self._bulb.setRgb(red, green, blue, brightness=brightness) + self._bulb.setRgb(*self.rgb_color, brightness=brightness) + elif white_value is not None: + self._bulb.setRgbw(*self.rgb_color, w=white_value) elif effect == EFFECT_RANDOM: self._bulb.setRgb(random.randint(0, 255), random.randint(0, 255), diff --git a/homeassistant/components/light/homematic.py b/homeassistant/components/light/homematic.py index 08d79b3245a..11bba4260d5 100644 --- a/homeassistant/components/light/homematic.py +++ b/homeassistant/components/light/homematic.py @@ -18,7 +18,7 @@ SUPPORT_HOMEMATIC = SUPPORT_BRIGHTNESS def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Homematic light platform.""" + """Set up the Homematic light platform.""" if discovery_info is None: return diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 22e2541b271..0f00519ab91 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -104,7 +104,7 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Hue lights.""" + """Set up the Hue lights.""" # Default needed in case of discovery filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE) allow_unreachable = config.get(CONF_ALLOW_UNREACHABLE, @@ -140,7 +140,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def setup_bridge(host, hass, add_devices, filename, allow_unreachable, allow_in_emulated_hue, allow_hue_groups): - """Setup a phue bridge based on host parameter.""" + """Set up a phue bridge based on host parameter.""" import phue try: @@ -273,7 +273,7 @@ def request_configuration(host, hass, add_devices, filename, # pylint: disable=unused-argument def hue_configuration_callback(data): - """The actions to do when our configuration callback is called.""" + """Set up actions to do when our configuration callback is called.""" setup_bridge(host, hass, add_devices, filename, allow_unreachable, allow_in_emulated_hue, allow_hue_groups) @@ -380,12 +380,10 @@ class HueLight(Light): if ATTR_XY_COLOR in kwargs: if self.info.get('manufacturername') == "OSRAM": - hsv = color_util.color_xy_brightness_to_hsv( - *kwargs[ATTR_XY_COLOR], - ibrightness=self.info['bri']) - command['hue'] = hsv[0] - command['sat'] = hsv[1] - command['bri'] = hsv[2] + hue, sat = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR]) + command['hue'] = hue + command['sat'] = sat + command['bri'] = self.info['bri'] else: command['xy'] = kwargs[ATTR_XY_COLOR] elif ATTR_RGB_COLOR in kwargs: @@ -405,7 +403,8 @@ class HueLight(Light): command['bri'] = kwargs[ATTR_BRIGHTNESS] if ATTR_COLOR_TEMP in kwargs: - command['ct'] = kwargs[ATTR_COLOR_TEMP] + temp = kwargs[ATTR_COLOR_TEMP] + command['ct'] = max(self.min_mireds, min(temp, self.max_mireds)) flash = kwargs.get(ATTR_FLASH) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index cfa9a64580e..99fc557af20 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup a Hyperion server remote.""" + """Set up a Hyperion server remote.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) default_color = config.get(CONF_DEFAULT_COLOR) diff --git a/homeassistant/components/light/insteon_hub.py b/homeassistant/components/light/insteon_hub.py index 6f547b5f92a..3bdf69671e0 100644 --- a/homeassistant/components/light/insteon_hub.py +++ b/homeassistant/components/light/insteon_hub.py @@ -14,7 +14,7 @@ SUPPORT_INSTEON_HUB = SUPPORT_BRIGHTNESS def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Insteon Hub light platform.""" + """Set up the Insteon Hub light platform.""" devs = [] for device in INSTEON.devices: if device.DeviceCategory == "Switched Lighting Control": diff --git a/homeassistant/components/light/insteon_local.py b/homeassistant/components/light/insteon_local.py index 27f297a82f0..f7beb0c31ac 100644 --- a/homeassistant/components/light/insteon_local.py +++ b/homeassistant/components/light/insteon_local.py @@ -33,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): insteonhub = hass.data['insteon_local'] conf_lights = config_from_file(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF)) - if len(conf_lights): + if conf_lights: for device_id in conf_lights: setup_light(device_id, conf_lights[device_id], insteonhub, hass, add_devices) @@ -64,7 +64,7 @@ def request_configuration(device_id, insteonhub, model, hass, return def insteon_light_config_callback(data): - """The actions to do when our configuration callback is called.""" + """Set up actions to do when our configuration callback is called.""" setup_light(device_id, data.get('name'), insteonhub, hass, add_devices_callback) diff --git a/homeassistant/components/light/knx.py b/homeassistant/components/light/knx.py new file mode 100644 index 00000000000..6d5cd12c23e --- /dev/null +++ b/homeassistant/components/light/knx.py @@ -0,0 +1,53 @@ +""" +Support KNX Lighting actuators. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/Light.knx/ +""" +import voluptuous as vol + +from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) +from homeassistant.components.light import (Light, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv + +CONF_ADDRESS = 'address' +CONF_STATE_ADDRESS = 'state_address' + +DEFAULT_NAME = 'KNX Light' +DEPENDENCIES = ['knx'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_STATE_ADDRESS): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the KNX light platform.""" + add_devices([KNXLight(hass, KNXConfig(config))]) + + +class KNXLight(KNXGroupAddress, Light): + """Representation of a KNX Light device.""" + + def turn_on(self, **kwargs): + """Turn the switch on. + + This sends a value 1 to the group address of the device + """ + self.group_write(1) + self._state = [1] + if not self.should_poll: + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Turn the switch off. + + This sends a value 1 to the group address of the device + """ + self.group_write(0) + self._state = [0] + if not self.should_poll: + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/lifx/__init__.py b/homeassistant/components/light/lifx/__init__.py index 4d3cd0d4931..f13934011e9 100644 --- a/homeassistant/components/light/lifx/__init__.py +++ b/homeassistant/components/light/lifx/__init__.py @@ -8,6 +8,7 @@ import colorsys import logging import asyncio import sys +import math from functools import partial from datetime import timedelta import async_timeout @@ -31,7 +32,7 @@ from . import effects as lifx_effects _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['aiolifx==0.4.4'] +REQUIREMENTS = ['aiolifx==0.4.6'] UDP_BROADCAST_PORT = 56700 @@ -55,12 +56,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the LIFX platform.""" + """Set up the LIFX platform.""" import aiolifx if sys.platform == 'win32': - _LOGGER.warning('The lifx platform is known to not work on Windows. ' - 'Consider using the lifx_legacy platform instead.') + _LOGGER.warning("The lifx platform is known to not work on Windows. " + "Consider using the lifx_legacy platform instead") server_addr = config.get(CONF_SERVER) @@ -88,19 +89,25 @@ class LIFXManager(object): @callback def register(self, device): - """Callback for newly detected bulb.""" + """Handle for newly detected bulb.""" if device.mac_addr in self.entities: entity = self.entities[device.mac_addr] entity.device = device + entity.registered = True _LOGGER.debug("%s register AGAIN", entity.who) self.hass.async_add_job(entity.async_update_ha_state()) else: _LOGGER.debug("%s register NEW", device.ip_addr) - device.get_color(self.ready) + device.get_version(self.got_version) + + @callback + def got_version(self, device, msg): + """Request current color setting once we have the product version.""" + device.get_color(self.ready) @callback def ready(self, device, msg): - """Callback that adds the device once all data is retrieved.""" + """Handle the device once all data is retrieved.""" entity = LIFXLight(device) _LOGGER.debug("%s register READY", entity.who) self.entities[device.mac_addr] = entity @@ -108,11 +115,11 @@ class LIFXManager(object): @callback def unregister(self, device): - """Callback for disappearing bulb.""" + """Handle disappearing bulbs.""" if device.mac_addr in self.entities: entity = self.entities[device.mac_addr] _LOGGER.debug("%s unregister", entity.who) - entity.device = None + entity.registered = False self.hass.async_add_job(entity.async_update_ha_state()) @@ -128,7 +135,7 @@ class AwaitAioLIFX: @callback def callback(self, device, message): - """Callback that aiolifx invokes when the response is received.""" + """Handle responses.""" self.device = device self.message = message self.event.set() @@ -166,6 +173,8 @@ class LIFXLight(Light): def __init__(self, device): """Initialize the light.""" self.device = device + self.registered = True + self.product = device.product self.blocker = None self.effect_data = None self.postponed_update = None @@ -176,7 +185,7 @@ class LIFXLight(Light): @property def available(self): """Return the availability of the device.""" - return self.device is not None + return self.registered @property def name(self): @@ -213,6 +222,28 @@ class LIFXLight(Light): _LOGGER.debug("color_temp: %d", temperature) return temperature + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + # The 3 LIFX "White" products supported a limited temperature range + # https://lan.developer.lifx.com/docs/lifx-products + if self.product in [10, 11, 18]: + kelvin = 6500 + else: + kelvin = 9000 + return math.floor(color_temperature_kelvin_to_mired(kelvin)) + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + # The 3 LIFX "White" products supported a limited temperature range + # https://lan.developer.lifx.com/docs/lifx-products + if self.product in [10, 11, 18]: + kelvin = 2700 + else: + kelvin = 2500 + return math.ceil(color_temperature_kelvin_to_mired(kelvin)) + @property def is_on(self): """Return true if device is on.""" @@ -234,17 +265,19 @@ class LIFXLight(Light): """Return the list of supported effects.""" return lifx_effects.effect_list() - @callback + @asyncio.coroutine def update_after_transition(self, now): """Request new status after completion of the last transition.""" self.postponed_update = None - self.hass.async_add_job(self.async_update_ha_state(force_refresh=True)) + yield from self.refresh_state() + yield from self.async_update_ha_state() - @callback + @asyncio.coroutine def unblock_updates(self, now): """Allow async_update after the new state has settled on the bulb.""" self.blocker = None - self.hass.async_add_job(self.async_update_ha_state(force_refresh=True)) + yield from self.refresh_state() + yield from self.async_update_ha_state() def update_later(self, when): """Block immediate update requests and schedule one for later.""" @@ -256,6 +289,7 @@ class LIFXLight(Light): if self.postponed_update: self.postponed_update() + self.postponed_update = None if when > BULB_LATENCY: self.postponed_update = async_track_point_in_utc_time( self.hass, self.update_after_transition, @@ -313,7 +347,7 @@ class LIFXLight(Light): def async_update(self): """Update bulb status (if it is available).""" _LOGGER.debug("%s async_update", self.who) - if self.available and self.blocker is None: + if self.blocker is None: yield from self.refresh_state() @asyncio.coroutine @@ -325,11 +359,12 @@ class LIFXLight(Light): @asyncio.coroutine def refresh_state(self): """Ask the device about its current state and update our copy.""" - msg = yield from AwaitAioLIFX(self).wait(self.device.get_color) - if msg is not None: - self.set_power(self.device.power_level) - self.set_color(*self.device.color) - self._name = self.device.label + if self.available: + msg = yield from AwaitAioLIFX(self).wait(self.device.get_color) + if msg is not None: + self.set_power(self.device.power_level) + self.set_color(*self.device.color) + self._name = self.device.label def find_hsbk(self, **kwargs): """Find the desired color from a number of possible inputs.""" @@ -352,27 +387,30 @@ class LIFXLight(Light): saturation = self._sat brightness = self._bri + if ATTR_XY_COLOR in kwargs: + hue, saturation = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR]) + saturation = saturation * (BYTE_MAX + 1) + changed_color = True + + # When color or temperature is set, use a default value for the other + if ATTR_COLOR_TEMP in kwargs: + kelvin = int(color_temperature_mired_to_kelvin( + kwargs[ATTR_COLOR_TEMP])) + if not changed_color: + saturation = 0 + changed_color = True + else: + if changed_color: + kelvin = 3500 + else: + kelvin = self._kel + if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1) changed_color = True else: brightness = self._bri - if ATTR_XY_COLOR in kwargs: - hue, saturation, _ = \ - color_util.color_xy_brightness_to_hsv( - *kwargs[ATTR_XY_COLOR], - ibrightness=(brightness // (BYTE_MAX + 1))) - saturation = saturation * (BYTE_MAX + 1) - changed_color = True - - if ATTR_COLOR_TEMP in kwargs: - kelvin = int(color_temperature_mired_to_kelvin( - kwargs[ATTR_COLOR_TEMP])) - changed_color = True - else: - kelvin = self._kel - return [[hue, saturation, brightness, kelvin], changed_color] def set_power(self, power): @@ -387,9 +425,8 @@ class LIFXLight(Light): self._bri = bri self._kel = kel - red, green, blue = colorsys.hsv_to_rgb(hue / SHORT_MAX, - sat / SHORT_MAX, - bri / SHORT_MAX) + red, green, blue = colorsys.hsv_to_rgb( + hue / SHORT_MAX, sat / SHORT_MAX, bri / SHORT_MAX) red = int(red * BYTE_MAX) green = int(green * BYTE_MAX) diff --git a/homeassistant/components/light/lifx/effects.py b/homeassistant/components/light/lifx/effects.py index e2ba0a73534..a15360df33e 100644 --- a/homeassistant/components/light/lifx/effects.py +++ b/homeassistant/components/light/lifx/effects.py @@ -7,7 +7,8 @@ from os import path import voluptuous as vol from homeassistant.components.light import ( - DOMAIN, ATTR_BRIGHTNESS, ATTR_COLOR_NAME, ATTR_RGB_COLOR, ATTR_EFFECT) + DOMAIN, ATTR_BRIGHTNESS, ATTR_COLOR_NAME, ATTR_RGB_COLOR, ATTR_EFFECT, + ATTR_TRANSITION) from homeassistant.config import load_yaml_config_file from homeassistant.const import (ATTR_ENTITY_ID) from homeassistant.helpers.service import extract_entity_ids @@ -30,6 +31,8 @@ ATTR_CHANGE = 'change' WAVEFORM_SINE = 1 WAVEFORM_PULSE = 4 +NEUTRAL_WHITE = 3500 + LIFX_EFFECT_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_POWER_ON, default=True): cv.boolean, @@ -40,22 +43,23 @@ LIFX_EFFECT_BREATHE_SCHEMA = LIFX_EFFECT_SCHEMA.extend({ ATTR_COLOR_NAME: cv.string, ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), - vol.Optional(ATTR_PERIOD, default=1.0): vol.All(vol.Coerce(float), - vol.Range(min=0.05)), - vol.Optional(ATTR_CYCLES, default=1.0): vol.All(vol.Coerce(float), - vol.Range(min=1)), + vol.Optional(ATTR_PERIOD, default=1.0): + vol.All(vol.Coerce(float), vol.Range(min=0.05)), + vol.Optional(ATTR_CYCLES, default=1.0): + vol.All(vol.Coerce(float), vol.Range(min=1)), }) LIFX_EFFECT_PULSE_SCHEMA = LIFX_EFFECT_BREATHE_SCHEMA LIFX_EFFECT_COLORLOOP_SCHEMA = LIFX_EFFECT_SCHEMA.extend({ ATTR_BRIGHTNESS: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)), - vol.Optional(ATTR_PERIOD, default=60): vol.All(vol.Coerce(float), - vol.Clamp(min=1)), - vol.Optional(ATTR_CHANGE, default=20): vol.All(vol.Coerce(float), - vol.Clamp(min=0, max=360)), - vol.Optional(ATTR_SPREAD, default=30): vol.All(vol.Coerce(float), - vol.Clamp(min=0, max=360)), + vol.Optional(ATTR_PERIOD, default=60): + vol.All(vol.Coerce(float), vol.Clamp(min=0.05)), + vol.Optional(ATTR_CHANGE, default=20): + vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)), + vol.Optional(ATTR_SPREAD, default=30): + vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)), + ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Range(min=0)), }) LIFX_EFFECT_STOP_SCHEMA = vol.Schema({ @@ -68,7 +72,7 @@ def setup(hass, lifx_manager): """Register the LIFX effects as hass service calls.""" @asyncio.coroutine def async_service_handle(service): - """Internal func for applying a service.""" + """Apply a service.""" entity_ids = extract_entity_ids(hass, service) if entity_ids: devices = [entity for entity in lifx_manager.entities.values() @@ -131,13 +135,6 @@ def default_effect(light, **kwargs): data = { ATTR_ENTITY_ID: light.entity_id, } - if service in (SERVICE_EFFECT_BREATHE, SERVICE_EFFECT_PULSE): - data[ATTR_RGB_COLOR] = [ - random.randint(1, 127), - random.randint(1, 127), - random.randint(1, 127), - ] - data[ATTR_BRIGHTNESS] = 255 yield from light.hass.services.async_call(DOMAIN, service, data) @@ -179,18 +176,16 @@ class LIFXEffect(object): def async_setup(self, **kwargs): """Prepare all lights for the effect.""" for light in self.lights: + # Remember the current state (as far as we know it) yield from light.refresh_state() - if not light.device: - self.lights.remove(light) - else: - light.effect_data = LIFXEffectData( - self, light.is_on, light.device.color) + light.effect_data = LIFXEffectData( + self, light.is_on, light.device.color) - # Temporarily turn on power for the effect to be visible - if kwargs[ATTR_POWER_ON] and not light.is_on: - hsbk = self.from_poweroff_hsbk(light, **kwargs) - light.device.set_color(hsbk) - light.device.set_power(True) + # Temporarily turn on power for the effect to be visible + if kwargs[ATTR_POWER_ON] and not light.is_on: + hsbk = self.from_poweroff_hsbk(light, **kwargs) + light.device.set_color(hsbk) + light.device.set_power(True) # pylint: disable=no-self-use @asyncio.coroutine @@ -201,20 +196,23 @@ class LIFXEffect(object): @asyncio.coroutine def async_restore(self, light): """Restore to the original state (if we are still running).""" - if light.effect_data: - if light.effect_data.effect == self: - if light.device and not light.effect_data.power: - light.device.set_power(False) - yield from asyncio.sleep(0.5) - if light.device: - light.device.set_color(light.effect_data.color) - yield from asyncio.sleep(0.5) - light.effect_data = None + if light in self.lights: self.lights.remove(light) + if light.effect_data and light.effect_data.effect == self: + if not light.effect_data.power: + light.device.set_power(False) + yield from asyncio.sleep(0.5) + + light.device.set_color(light.effect_data.color) + yield from asyncio.sleep(0.5) + + light.effect_data = None + yield from light.refresh_state() + def from_poweroff_hsbk(self, light, **kwargs): - """The initial color when starting from a powered off state.""" - return None + """Return the color when starting from a powered off state.""" + return [random.randint(0, 65535), 65535, 0, NEUTRAL_WHITE] class LIFXEffectBreathe(LIFXEffect): @@ -237,7 +235,14 @@ class LIFXEffectBreathe(LIFXEffect): """Play a light effect on the bulb.""" period = kwargs[ATTR_PERIOD] cycles = kwargs[ATTR_CYCLES] - hsbk, _ = light.find_hsbk(**kwargs) + hsbk, color_changed = light.find_hsbk(**kwargs) + + # Default color is to fully (de)saturate with full brightness + if not color_changed: + if hsbk[1] > 65536/2: + hsbk = [hsbk[0], 0, 65535, 4000] + else: + hsbk = [hsbk[0], 65535, 65535, hsbk[3]] # Start the effect args = { @@ -255,7 +260,7 @@ class LIFXEffectBreathe(LIFXEffect): yield from self.async_restore(light) def from_poweroff_hsbk(self, light, **kwargs): - """Initial color is the target color, but no brightness.""" + """Return the color is the target color, but no brightness.""" hsbk, _ = light.find_hsbk(**kwargs) return [hsbk[0], hsbk[1], 0, hsbk[2]] @@ -295,9 +300,10 @@ class LIFXEffectColorloop(LIFXEffect): random.shuffle(self.lights) lhue = hue - transition = int(1000 * random.uniform(period/2, period)) for light in self.lights: - if spread > 0: + if ATTR_TRANSITION in kwargs: + transition = int(1000*kwargs[ATTR_TRANSITION]) + elif light == self.lights[0] or spread > 0: transition = int(1000 * random.uniform(period/2, period)) if ATTR_BRIGHTNESS in kwargs: @@ -309,7 +315,7 @@ class LIFXEffectColorloop(LIFXEffect): int(65535/359*lhue), int(random.uniform(0.8, 1.0)*65535), brightness, - 4000, + NEUTRAL_WHITE, ] light.device.set_color(hsbk, None, transition) @@ -319,10 +325,6 @@ class LIFXEffectColorloop(LIFXEffect): yield from asyncio.sleep(period) - def from_poweroff_hsbk(self, light, **kwargs): - """Start from a random hue.""" - return [random.randint(0, 65535), 65535, 0, 4000] - class LIFXEffectStop(LIFXEffect): """A no-op effect, but starting it will stop an existing effect.""" diff --git a/homeassistant/components/light/lifx/services.yaml b/homeassistant/components/light/lifx/services.yaml index 1b34c54f253..d939e1432bc 100644 --- a/homeassistant/components/light/lifx/services.yaml +++ b/homeassistant/components/light/lifx/services.yaml @@ -71,19 +71,19 @@ lifx_effect_colorloop: example: 'light.disco1, light.disco2, light.disco3' brightness: - description: Number between 0..255 indicating brightness of the effect. Leave this out to maintain the current brightness of each participating light + description: Number between 0 and 255 indicating brightness of the effect. Leave this out to maintain the current brightness of each participating light example: 120 period: - description: Duration between color changes (deafult 60) + description: Duration (in seconds) between color changes (default 60) example: 180 change: - description: Hue movement per period, in degrees on a color wheel (default 20) + description: Hue movement per period, in degrees on a color wheel (ranges from 0 to 360, default 20) example: 45 spread: - description: Maximum hue difference between participating lights, in degrees on a color wheel (default 30) + description: Maximum hue difference between participating lights, in degrees on a color wheel (ranges from 0 to 360, default 30) example: 0 power_on: diff --git a/homeassistant/components/light/lifx_legacy.py b/homeassistant/components/light/lifx_legacy.py index 8a81c992a13..cc48f4cf4c1 100644 --- a/homeassistant/components/light/lifx_legacy.py +++ b/homeassistant/components/light/lifx_legacy.py @@ -48,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the LIFX platform.""" + """Set up the LIFX platform.""" server_addr = config.get(CONF_SERVER) broadcast_addr = config.get(CONF_BROADCAST) @@ -121,7 +121,7 @@ class LIFX(object): # pylint: disable=unused-argument def poll(self, now): - """Polling for the light.""" + """Set up polling for the light.""" self.probe() def probe(self, address=None): diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index c2769ab6638..1a1fe1cffd7 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -95,7 +95,7 @@ def rewrite_legacy(config): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the LimitlessLED lights.""" + """Set up the LimitlessLED lights.""" from limitlessled.bridge import Bridge # Two legacy configuration formats are supported to maintain backwards @@ -123,7 +123,7 @@ def state(new_state): Specify True (turn on) or False (turn off). """ def decorator(function): - """Decorator function.""" + """Set up the decorator function.""" # pylint: disable=no-member,protected-access def wrapper(self, **kwargs): """Wrap a group state change.""" @@ -376,12 +376,12 @@ class LimitlessLEDRGBWWGroup(LimitlessLEDGroup): def _from_hass_temperature(temperature): """Convert Home Assistant color temperature units to percentage.""" - return (temperature - 154) / 346 + return 1 - (temperature - 154) / 346 def _to_hass_temperature(temperature): """Convert percentage to Home Assistant color temperature units.""" - return int(temperature * 346) + 154 + return 500 - int(temperature * 346) def _from_hass_brightness(brightness): diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/light/litejet.py index 907d7f27fb9..15f16d56a68 100644 --- a/homeassistant/components/light/litejet.py +++ b/homeassistant/components/light/litejet.py @@ -46,7 +46,7 @@ class LiteJetLight(Light): self.update() def _on_load_changed(self): - """Called on a LiteJet thread when a load's state changes.""" + """Handle state changes.""" _LOGGER.debug("Updating due to notification for %s", self._name) self.schedule_update_ha_state(True) @@ -57,7 +57,7 @@ class LiteJetLight(Light): @property def name(self): - """The light's name.""" + """Return the light's name.""" return self._name @property diff --git a/homeassistant/components/light/lutron.py b/homeassistant/components/light/lutron.py index 5322fd79489..47cadec0f7d 100644 --- a/homeassistant/components/light/lutron.py +++ b/homeassistant/components/light/lutron.py @@ -1,4 +1,9 @@ -"""Support for Lutron lights.""" +""" +Support for Lutron lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.lutron/ +""" import logging from homeassistant.components.light import ( @@ -13,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Lutron lights.""" + """Set up Lutron lights.""" devs = [] for (area_name, device) in hass.data[LUTRON_DEVICES]['light']: dev = LutronLight(area_name, device, hass.data[LUTRON_CONTROLLER]) @@ -82,6 +87,6 @@ class LutronLight(LutronDevice, Light): return self._lutron_device.last_level() > 0 def update(self): - """Called when forcing a refresh of the device.""" + """Call when forcing a refresh of the device.""" if self._prev_brightness is None: self._prev_brightness = to_hass_level(self._lutron_device.level) diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/light/lutron_caseta.py index 705886855b0..8e4e9d7450e 100644 --- a/homeassistant/components/light/lutron_caseta.py +++ b/homeassistant/components/light/lutron_caseta.py @@ -1,4 +1,9 @@ -"""Support for Lutron Caseta lights.""" +""" +Support for Lutron Caseta lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.lutron_caseta/ +""" import logging from homeassistant.components.light import ( @@ -8,7 +13,6 @@ from homeassistant.components.light.lutron import ( from homeassistant.components.lutron_caseta import ( LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) - _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] @@ -16,7 +20,7 @@ DEPENDENCIES = ['lutron_caseta'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Lutron Caseta lights.""" + """Set up the Lutron Caseta lights.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] light_devices = bridge.get_devices_by_types(["WallDimmer", "PlugInDimmer"]) @@ -59,6 +63,6 @@ class LutronCasetaLight(LutronCasetaDevice, Light): return self._state["current_state"] > 0 def update(self): - """Called when forcing a refresh of the device.""" + """Call when forcing a refresh of the device.""" self._state = self._smartbridge.get_device_by_id(self._device_id) _LOGGER.debug(self._state) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 018d7a40639..038cacd300e 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -92,7 +92,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Add MQTT Light.""" + """Set up a MQTT Light.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) @@ -142,7 +142,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class MqttLight(Light): - """MQTT light.""" + """Representation of a MQTT light.""" def __init__(self, name, effect_list, topic, templates, qos, retain, payload, optimistic, brightness_scale, @@ -197,7 +197,7 @@ class MqttLight(Light): @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @@ -211,7 +211,7 @@ class MqttLight(Light): @callback def state_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" payload = templates[CONF_STATE](payload) if payload == self._payload['on']: self._state = True @@ -226,7 +226,7 @@ class MqttLight(Light): @callback def brightness_received(topic, payload, qos): - """A new MQTT message for the brightness has been received.""" + """Handle new MQTT messages for the brightness.""" device_value = float(templates[CONF_BRIGHTNESS](payload)) percent_bright = device_value / self._brightness_scale self._brightness = int(percent_bright * 255) @@ -244,7 +244,7 @@ class MqttLight(Light): @callback def rgb_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages for RGB.""" self._rgb = [int(val) for val in templates[CONF_RGB](payload).split(',')] self.hass.async_add_job(self.async_update_ha_state()) @@ -261,7 +261,7 @@ class MqttLight(Light): @callback def color_temp_received(topic, payload, qos): - """A new MQTT message for color temp has been received.""" + """Handle new MQTT messages for color temperature.""" self._color_temp = int(templates[CONF_COLOR_TEMP](payload)) self.hass.async_add_job(self.async_update_ha_state()) @@ -277,7 +277,7 @@ class MqttLight(Light): @callback def effect_received(topic, payload, qos): - """A new MQTT message for effect has been received.""" + """Handle new MQTT messages for effect.""" self._effect = templates[CONF_EFFECT](payload) self.hass.async_add_job(self.async_update_ha_state()) @@ -293,7 +293,7 @@ class MqttLight(Light): @callback def white_value_received(topic, payload, qos): - """A new MQTT message for the white value has been received.""" + """Handle new MQTT messages for white value.""" device_value = float(templates[CONF_WHITE_VALUE](payload)) percent_white = device_value / self._white_value_scale self._white_value = int(percent_white * 255) @@ -311,7 +311,7 @@ class MqttLight(Light): @callback def xy_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages for color.""" self._xy = [float(val) for val in templates[CONF_XY](payload).split(',')] self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index b109460700e..4fee1138909 100755 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -71,7 +71,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a MQTT JSON Light.""" + """Set up a MQTT JSON Light.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) async_add_devices([MqttJson( @@ -157,13 +157,13 @@ class MqttJson(Light): @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @callback def state_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" values = json.loads(payload) if values['state'] == 'ON': @@ -189,7 +189,7 @@ class MqttJson(Light): except KeyError: pass except ValueError: - _LOGGER.warning('Invalid brightness value received') + _LOGGER.warning("Invalid brightness value received") if self._color_temp is not None: try: @@ -197,7 +197,7 @@ class MqttJson(Light): except KeyError: pass except ValueError: - _LOGGER.warning('Invalid color temp value received') + _LOGGER.warning("Invalid color temp value received") if self._effect is not None: try: @@ -205,7 +205,7 @@ class MqttJson(Light): except KeyError: pass except ValueError: - _LOGGER.warning('Invalid effect value received') + _LOGGER.warning("Invalid effect value received") if self._white_value is not None: try: @@ -213,7 +213,7 @@ class MqttJson(Light): except KeyError: pass except ValueError: - _LOGGER.warning('Invalid white value value received') + _LOGGER.warning("Invalid white value value received") if self._xy is not None: try: diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt_template.py index fa0d71f46ef..07fd6d45d8c 100755 --- a/homeassistant/components/light/mqtt_template.py +++ b/homeassistant/components/light/mqtt_template.py @@ -65,7 +65,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a MQTT Template light.""" + """Set up a MQTT Template light.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) @@ -104,7 +104,7 @@ class MqttTemplate(Light): def __init__(self, hass, name, effect_list, topics, templates, optimistic, qos, retain): - """Initialize MQTT Template light.""" + """Initialize a MQTT Template light.""" self._name = name self._effect_list = effect_list self._topics = topics @@ -139,21 +139,19 @@ class MqttTemplate(Light): self._rgb = None self._effect = None - # init hass to template for tpl in self._templates.values(): if tpl is not None: tpl.hass = hass @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @callback def state_received(topic, payload, qos): - """A new MQTT message has been received.""" - # read state + """Handle new MQTT messages.""" state = self._templates[CONF_STATE_TEMPLATE].\ async_render_with_possible_json_value(payload) if state == STATE_ON: @@ -161,9 +159,8 @@ class MqttTemplate(Light): elif state == STATE_OFF: self._state = False else: - _LOGGER.warning('Invalid state value received') + _LOGGER.warning("Invalid state value received") - # read brightness if self._brightness is not None: try: self._brightness = int( @@ -171,9 +168,8 @@ class MqttTemplate(Light): async_render_with_possible_json_value(payload) ) except ValueError: - _LOGGER.warning('Invalid brightness value received') + _LOGGER.warning("Invalid brightness value received") - # read color temperature if self._color_temp is not None: try: self._color_temp = int( @@ -181,9 +177,8 @@ class MqttTemplate(Light): async_render_with_possible_json_value(payload) ) except ValueError: - _LOGGER.warning('Invalid color temperature value received') + _LOGGER.warning("Invalid color temperature value received") - # read color if self._rgb is not None: try: self._rgb[0] = int( @@ -196,9 +191,8 @@ class MqttTemplate(Light): self._templates[CONF_BLUE_TEMPLATE]. async_render_with_possible_json_value(payload)) except ValueError: - _LOGGER.warning('Invalid color value received') + _LOGGER.warning("Invalid color value received") - # read white value if self._white_value is not None: try: self._white_value = int( @@ -208,16 +202,14 @@ class MqttTemplate(Light): except ValueError: _LOGGER.warning('Invalid white value received') - # read effect if self._templates[CONF_EFFECT_TEMPLATE] is not None: effect = self._templates[CONF_EFFECT_TEMPLATE].\ async_render_with_possible_json_value(payload) - # validate effect value if effect in self._effect_list: self._effect = effect else: - _LOGGER.warning('Unsupported effect value received') + _LOGGER.warning("Unsupported effect value received") self.hass.async_add_job(self.async_update_ha_state()) @@ -285,26 +277,22 @@ class MqttTemplate(Light): This method is a coroutine. """ - # state values = {'state': True} if self._optimistic: self._state = True - # brightness if ATTR_BRIGHTNESS in kwargs: values['brightness'] = int(kwargs[ATTR_BRIGHTNESS]) if self._optimistic: self._brightness = kwargs[ATTR_BRIGHTNESS] - # color_temp if ATTR_COLOR_TEMP in kwargs: values['color_temp'] = int(kwargs[ATTR_COLOR_TEMP]) if self._optimistic: self._color_temp = kwargs[ATTR_COLOR_TEMP] - # color if ATTR_RGB_COLOR in kwargs: values['red'] = kwargs[ATTR_RGB_COLOR][0] values['green'] = kwargs[ATTR_RGB_COLOR][1] @@ -313,22 +301,18 @@ class MqttTemplate(Light): if self._optimistic: self._rgb = kwargs[ATTR_RGB_COLOR] - # white value if ATTR_WHITE_VALUE in kwargs: values['white_value'] = int(kwargs[ATTR_WHITE_VALUE]) if self._optimistic: self._white_value = kwargs[ATTR_WHITE_VALUE] - # effect if ATTR_EFFECT in kwargs: values['effect'] = kwargs.get(ATTR_EFFECT) - # flash if ATTR_FLASH in kwargs: values['flash'] = kwargs.get(ATTR_FLASH) - # transition if ATTR_TRANSITION in kwargs: values['transition'] = int(kwargs[ATTR_TRANSITION]) @@ -347,12 +331,10 @@ class MqttTemplate(Light): This method is a coroutine. """ - # state values = {'state': False} if self._optimistic: self._state = False - # transition if ATTR_TRANSITION in kwargs: values['transition'] = int(kwargs[ATTR_TRANSITION]) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 7cb978bc10c..203119e5e51 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -4,15 +4,12 @@ Support for MySensors lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.mysensors/ """ - import logging from homeassistant.components import mysensors -from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR, - ATTR_WHITE_VALUE, - SUPPORT_BRIGHTNESS, - SUPPORT_RGB_COLOR, - SUPPORT_WHITE_VALUE, Light) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_WHITE_VALUE, Light) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.util.color import rgb_hex_to_rgb_list @@ -25,9 +22,7 @@ SUPPORT_MYSENSORS = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR | def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for sensors.""" - # Only act if loaded via mysensors by discovery event. - # Otherwise gateway is not setup. + """Set up the MySensors platform for lights.""" if discovery_info is None: return @@ -62,10 +57,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): - """Represent the value of a MySensors Light child node.""" + """Representation of a MySensors Light child node.""" def __init__(self, *args): - """Setup instance attributes.""" + """Initialize a MySensors Light.""" mysensors.MySensorsDeviceEntity.__init__(self, *args) self._state = None self._brightness = None @@ -157,7 +152,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): elif white is not None: rgb.append(white) else: - _LOGGER.error('White value is not updated for RGBW light') + _LOGGER.error("White value is not updated for RGBW light") return hex_color = hex_template % tuple(rgb) if len(rgb) > 3: @@ -206,9 +201,8 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): set_req = self.gateway.const.SetReq if value_type is None or value is None: _LOGGER.warning( - '%s: value_type %s, value = %s, ' - 'None is not valid argument when setting child value' - '', self._name, value_type, value) + "%s: value_type %s, value = %s, None is not valid argument " + "when setting child value", self._name, value_type, value) return self.gateway.set_child_value( self.node_id, self.child_id, value_type, value) @@ -244,7 +238,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): value = self._values[self.value_type] if len(value) != 6 and len(value) != 8: _LOGGER.error( - 'Wrong value %s for %s', value, set_req(self.value_type).name) + "Wrong value %s for %s", value, set_req(self.value_type).name) return color_list = rgb_hex_to_rgb_list(value) if set_req.V_LIGHT not in self._values and \ @@ -253,7 +247,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): if len(color_list) > 3: if set_req.V_RGBW != self.value_type: _LOGGER.error( - 'Wrong value %s for %s', + "Wrong value %s for %s", value, set_req(self.value_type).name) return self._white = color_list.pop() @@ -265,7 +259,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): child = node.children[self.child_id] for value_type, value in child.values.items(): _LOGGER.debug( - '%s: value_type %s, value = %s', self._name, value_type, value) + "%s: value_type %s, value = %s", self._name, value_type, value) self._values[value_type] = value diff --git a/homeassistant/components/light/mystrom.py b/homeassistant/components/light/mystrom.py index e29535d60e6..2eb7c106bf2 100644 --- a/homeassistant/components/light/mystrom.py +++ b/homeassistant/components/light/mystrom.py @@ -72,7 +72,7 @@ class MyStromLight(Light): @property def brightness(self): - """Brightness of the light.""" + """Return the brightness of the light.""" return self._brightness @property diff --git a/homeassistant/components/light/osramlightify.py b/homeassistant/components/light/osramlightify.py index 1ddfe1baa53..143dc52cbee 100644 --- a/homeassistant/components/light/osramlightify.py +++ b/homeassistant/components/light/osramlightify.py @@ -15,55 +15,68 @@ from homeassistant import util from homeassistant.const import CONF_HOST from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_RGB_COLOR, - ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, - SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA) + ATTR_XY_COLOR, ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, + SUPPORT_EFFECT, SUPPORT_XY_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, + SUPPORT_TRANSITION, PLATFORM_SCHEMA) from homeassistant.util.color import ( - color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired) + color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired, + color_xy_brightness_to_RGB) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['https://github.com/tfriedel/python-lightify/archive/' - 'd6eadcf311e6e21746182d1480e97b350dda2b3e.zip#lightify==1.0.4'] + '1bb1db0e7bd5b14304d7bb267e2398cd5160df46.zip#lightify==1.0.5'] _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) +CONF_ALLOW_LIGHTIFY_GROUPS = "allow_lightify_groups" +DEFAULT_ALLOW_LIGHTIFY_GROUPS = True SUPPORT_OSRAMLIGHTIFY = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | SUPPORT_RGB_COLOR | - SUPPORT_TRANSITION) + SUPPORT_TRANSITION | SUPPORT_XY_COLOR) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_ALLOW_LIGHTIFY_GROUPS, + default=DEFAULT_ALLOW_LIGHTIFY_GROUPS): cv.boolean, }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Osram Lightify lights.""" + """Set up the Osram Lightify lights.""" import lightify host = config.get(CONF_HOST) + add_groups = config.get(CONF_ALLOW_LIGHTIFY_GROUPS) if host: try: bridge = lightify.Lightify(host) except socket.error as err: - msg = 'Error connecting to bridge: {} due to: {}'.format(host, - str(err)) + msg = "Error connecting to bridge: {} due to: {}".format( + host, str(err)) _LOGGER.exception(msg) return False - setup_bridge(bridge, add_devices) + setup_bridge(bridge, add_devices, add_groups) else: - _LOGGER.error('No host found in configuration') + _LOGGER.error("No host found in configuration") return False -def setup_bridge(bridge, add_devices_callback): - """Setup the Lightify bridge.""" +def setup_bridge(bridge, add_devices_callback, add_groups): + """Set up the Lightify bridge.""" lights = {} @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_lights(): """Update the lights objects with latest info from bridge.""" - bridge.update_all_light_status() + try: + bridge.update_all_light_status() + bridge.update_group_list() + except TimeoutError: + _LOGGER.error('Timeout during updating of lights.') + except OSError: + _LOGGER.error('OSError during updating of lights.') new_lights = [] @@ -77,22 +90,31 @@ def setup_bridge(bridge, add_devices_callback): else: lights[light_id].light = light + if add_groups: + for (group_name, group) in bridge.groups().items(): + if group_name not in lights: + osram_group = OsramLightifyGroup(group, bridge, + update_lights) + lights[group_name] = osram_group + new_lights.append(osram_group) + else: + lights[group_name].group = group + if new_lights: add_devices_callback(new_lights) update_lights() -class OsramLightifyLight(Light): - """Representation of an Osram Lightify Light.""" +class Luminary(Light): + """ABS for Lightify Lights and Groups.""" - def __init__(self, light_id, light, update_lights): - """Initialize the light.""" - self._light = light - self._light_id = light_id + def __init__(self, luminary, update_lights): + """Init Luminary object.""" self.update_lights = update_lights + self._luminary = luminary self._brightness = None - self._rgb = None + self._rgb = [None] self._name = None self._temperature = None self._state = False @@ -106,8 +128,6 @@ class OsramLightifyLight(Light): @property def rgb_color(self): """Last RGB color value set.""" - _LOGGER.debug("rgb_color light state for light: %s is: %s %s %s ", - self._name, self._rgb[0], self._rgb[1], self._rgb[2]) return self._rgb @property @@ -118,15 +138,11 @@ class OsramLightifyLight(Light): @property def brightness(self): """Brightness of this light between 0..255.""" - _LOGGER.debug("brightness for light %s is: %s", - self._name, self._brightness) return self._brightness @property def is_on(self): """Update Status to True if device is on.""" - _LOGGER.debug("is_on light state for light: %s is: %s", - self._name, self._state) return self._state @property @@ -134,57 +150,64 @@ class OsramLightifyLight(Light): """Flag supported features.""" return SUPPORT_OSRAMLIGHTIFY + @property + def effect_list(self): + """List of supported effects.""" + return [EFFECT_RANDOM] + def turn_on(self, **kwargs): """Turn the device on.""" - _LOGGER.debug("turn_on Attempting to turn on light: %s ", - self._name) - - self._light.set_onoff(1) - self._state = self._light.on() + self._luminary.set_onoff(1) if ATTR_TRANSITION in kwargs: transition = int(kwargs[ATTR_TRANSITION] * 10) - _LOGGER.debug("turn_on requested transition time for light:" - " %s is: %s ", - self._name, transition) + _LOGGER.debug("turn_on requested transition time for light: " + "%s is: %s", self._name, transition) else: transition = 0 - _LOGGER.debug("turn_on requested transition time for light:" - " %s is: %s ", - self._name, transition) + _LOGGER.debug("turn_on requested transition time for light: " + "%s is: %s", self._name, transition) if ATTR_RGB_COLOR in kwargs: red, green, blue = kwargs[ATTR_RGB_COLOR] _LOGGER.debug("turn_on requested ATTR_RGB_COLOR for light:" " %s is: %s %s %s ", self._name, red, green, blue) - self._light.set_rgb(red, green, blue, transition) + self._luminary.set_rgb(red, green, blue, transition) + + if ATTR_XY_COLOR in kwargs: + x_mired, y_mired = kwargs[ATTR_XY_COLOR] + _LOGGER.debug("turn_on requested ATTR_XY_COLOR for light:" + " %s is: %s,%s", self._name, x_mired, y_mired) + red, green, blue = color_xy_brightness_to_RGB( + x_mired, y_mired, self._brightness + ) + self._luminary.set_rgb(red, green, blue, transition) if ATTR_COLOR_TEMP in kwargs: color_t = kwargs[ATTR_COLOR_TEMP] kelvin = int(color_temperature_mired_to_kelvin(color_t)) - _LOGGER.debug("turn_on requested set_temperature for light:" - " %s: %s ", self._name, kelvin) - self._light.set_temperature(kelvin, transition) + _LOGGER.debug("turn_on requested set_temperature for light: " + "%s: %s", self._name, kelvin) + self._luminary.set_temperature(kelvin, transition) if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] _LOGGER.debug("turn_on requested brightness for light: %s is: %s ", self._name, self._brightness) - self._brightness = self._light.set_luminance( + self._brightness = self._luminary.set_luminance( int(self._brightness / 2.55), transition) if ATTR_EFFECT in kwargs: effect = kwargs.get(ATTR_EFFECT) if effect == EFFECT_RANDOM: - self._light.set_rgb(random.randrange(0, 255), - random.randrange(0, 255), - random.randrange(0, 255), - transition) - _LOGGER.debug("turn_on requested random effect for light:" - " %s with transition %s ", - self._name, transition) + self._luminary.set_rgb(random.randrange(0, 255), + random.randrange(0, 255), + random.randrange(0, 255), + transition) + _LOGGER.debug("turn_on requested random effect for light: " + "%s with transition %s", self._name, transition) self.schedule_update_ha_state() @@ -197,26 +220,71 @@ class OsramLightifyLight(Light): _LOGGER.debug("turn_off requested transition time for light:" " %s is: %s ", self._name, transition) - self._light.set_luminance(0, transition) + self._luminary.set_luminance(0, transition) else: transition = 0 _LOGGER.debug("turn_off requested transition time for light:" " %s is: %s ", self._name, transition) - self._light.set_onoff(0) - self._state = self._light.on() - + self._luminary.set_onoff(0) self.schedule_update_ha_state() def update(self): """Synchronize state with bridge.""" self.update_lights(no_throttle=True) - self._brightness = int(self._light.lum() * 2.55) - self._name = self._light.name() - self._rgb = self._light.rgb() - o_temp = self._light.temp() + self._name = self._luminary.name() + + +class OsramLightifyLight(Luminary): + """Representation of an Osram Lightify Light.""" + + def __init__(self, light_id, light, update_lights): + """Initialize the light.""" + self._light_id = light_id + super().__init__(light, update_lights) + + def update(self): + """Update status of a Light.""" + super().update() + self._state = self._luminary.on() + self._rgb = self._luminary.rgb() + o_temp = self._luminary.temp() + if o_temp == 0: + self._temperature = None + else: + self._temperature = color_temperature_kelvin_to_mired( + self._luminary.temp() + ) + self._brightness = int(self._luminary.lum() * 2.55) + + +class OsramLightifyGroup(Luminary): + """Representation of an Osram Lightify Group.""" + + def __init__(self, group, bridge, update_lights): + """Init light group.""" + self._bridge = bridge + self._light_ids = [] + super().__init__(group, update_lights) + + def _get_state(self): + """Get state of group. + + The group is on, if any of the lights in on. + """ + lights = self._bridge.lights() + return any(lights[light_id].on() for light_id in self._light_ids) + + def update(self): + """Update group status.""" + super().update() + self._light_ids = self._luminary.lights() + light = self._bridge.lights()[self._light_ids[0]] + self._brightness = int(light.lum() * 2.55) + self._rgb = light.rgb() + o_temp = light.temp() if o_temp == 0: self._temperature = None else: self._temperature = color_temperature_kelvin_to_mired(o_temp) - self._state = self._light.on() + self._state = light.on() diff --git a/homeassistant/components/light/piglow.py b/homeassistant/components/light/piglow.py index afdda745721..40798810c0e 100644 --- a/homeassistant/components/light/piglow.py +++ b/homeassistant/components/light/piglow.py @@ -59,7 +59,7 @@ class PiglowLight(Light): @property def brightness(self): - """Brightness of the light (an integer in the range 1-255).""" + """Return the brightness of the light.""" return self._brightness @property @@ -72,6 +72,16 @@ class PiglowLight(Light): """Flag supported features.""" return SUPPORT_PIGLOW + @property + def should_poll(self): + """Return if we should poll this device.""" + return False + + @property + def assumed_state(self) -> bool: + """Return True if unable to access real state of the entity.""" + return True + @property def is_on(self): """Return true if light is on.""" @@ -80,7 +90,9 @@ class PiglowLight(Light): def turn_on(self, **kwargs): """Instruct the light to turn on.""" self._piglow.clear() - self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255) + + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] percent_bright = (self._brightness / 255) if ATTR_RGB_COLOR in kwargs: @@ -92,9 +104,11 @@ class PiglowLight(Light): self._piglow.all(self._brightness) self._piglow.show() self._is_on = True + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Instruct the light to turn off.""" self._piglow.clear() self._piglow.show() self._is_on = False + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/qwikswitch.py b/homeassistant/components/light/qwikswitch.py index b5b4378d526..63051d2ea8c 100644 --- a/homeassistant/components/light/qwikswitch.py +++ b/homeassistant/components/light/qwikswitch.py @@ -15,7 +15,7 @@ DEPENDENCIES = ['qwikswitch'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Add lights from the main Qwikswitch component.""" + """Set up the lights from the main Qwikswitch component.""" if discovery_info is None: _LOGGER.error("Configure Qwikswitch component failed") return False diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/light/rflink.py index e0b627f1ffa..fb054407dff 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/light/rflink.py @@ -119,7 +119,7 @@ def devices_from_config(domain_config, hass=None): # Register entity (and aliasses) to listen to incoming rflink events - # device id and normal aliasses respond to normal and group command + # Device id and normal aliasses respond to normal and group command hass.data[DATA_ENTITY_LOOKUP][ EVENT_KEY_COMMAND][device_id].append(device) if config[CONF_GROUP]: @@ -188,7 +188,7 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light): # rflink only support 16 brightness levels self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17 - # turn on light at the requested dim level + # Turn on light at the requested dim level yield from self._async_handle_command('dim', self._brightness) @property diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 623b42d77ad..f831d6c04ce 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -20,31 +20,31 @@ SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the RFXtrx platform.""" + """Set up the RFXtrx platform.""" import RFXtrx as rfxtrxmod - lights = rfxtrx.get_devices_from_config(config, RfxtrxLight) + lights = rfxtrx.get_devices_from_config(config, RfxtrxLight, hass) add_devices(lights) def light_update(event): - """Callback for light updates from the RFXtrx gateway.""" + """Handle light updates from the RFXtrx gateway.""" if not isinstance(event.device, rfxtrxmod.LightingDevice) or \ not event.device.known_to_be_dimmable: return - new_device = rfxtrx.get_new_device(event, config, RfxtrxLight) + new_device = rfxtrx.get_new_device(event, config, RfxtrxLight, hass) if new_device: add_devices([new_device]) rfxtrx.apply_received_command(event) - # Subscribe to main rfxtrx events + # Subscribe to main RFXtrx events if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(light_update) class RfxtrxLight(rfxtrx.RfxtrxDevice, Light): - """Represenation of a RFXtrx light.""" + """Representation of a RFXtrx light.""" @property def brightness(self): @@ -61,8 +61,8 @@ class RfxtrxLight(rfxtrx.RfxtrxDevice, Light): brightness = kwargs.get(ATTR_BRIGHTNESS) if brightness is None: self._brightness = 255 - self._send_command("turn_on") + self._send_command('turn_on') else: self._brightness = brightness _brightness = (brightness * 100 // 255) - self._send_command("dim", _brightness) + self._send_command('dim', _brightness) diff --git a/homeassistant/components/light/rpi_gpio_pwm.py b/homeassistant/components/light/rpi_gpio_pwm.py index c8ba110c58a..f2d39dea633 100644 --- a/homeassistant/components/light/rpi_gpio_pwm.py +++ b/homeassistant/components/light/rpi_gpio_pwm.py @@ -4,7 +4,6 @@ Support for LED lights that can be controlled using PWM. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.pwm/ """ - import logging import voluptuous as vol @@ -55,7 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the pwm lights.""" + """Set up the PWM LED lights.""" from pwmled.led import SimpleLed from pwmled.led.rgb import RgbLed from pwmled.led.rgbw import RgbwLed @@ -76,7 +75,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): opt_args['address'] = led_conf[CONF_ADDRESS] driver = Pca9685Driver(pins, **opt_args) else: - _LOGGER.error("Invalid driver type.") + _LOGGER.error("Invalid driver type") return name = led_conf[CONF_NAME] @@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): elif led_type == CONF_LED_TYPE_RGBW: led = PwmRgbLed(RgbwLed(driver), name) else: - _LOGGER.error("Invalid led type.") + _LOGGER.error("Invalid led type") return leds.append(led) @@ -96,10 +95,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class PwmSimpleLed(Light): - """Representation of a simple on-color pwm led.""" + """Representation of a simple one-color PWM LED.""" def __init__(self, led, name): - """Initialize led.""" + """Initialize one-color PWM LED.""" self._led = led self._name = name self._is_on = False @@ -149,7 +148,7 @@ class PwmSimpleLed(Light): self.schedule_update_ha_state() def turn_off(self, **kwargs): - """Turn off a led.""" + """Turn off a LED.""" if self.is_on: if ATTR_TRANSITION in kwargs: transition_time = kwargs[ATTR_TRANSITION] @@ -162,10 +161,10 @@ class PwmSimpleLed(Light): class PwmRgbLed(PwmSimpleLed): - """Representation of a rgb(w) pwm led.""" + """Representation of a RGB(W) PWM LED.""" def __init__(self, led, name): - """Initialize led.""" + """Initialize a RGB(W) PWM LED.""" super().__init__(led, name) self._color = DEFAULT_COLOR @@ -180,7 +179,7 @@ class PwmRgbLed(PwmSimpleLed): return SUPPORT_RGB_LED def turn_on(self, **kwargs): - """Turn on a led.""" + """Turn on a LED.""" if ATTR_RGB_COLOR in kwargs: self._color = kwargs[ATTR_RGB_COLOR] if ATTR_BRIGHTNESS in kwargs: diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index 532dc67562f..214a2d99449 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -24,7 +24,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SCSGate switches.""" + """Set up the SCSGate switches.""" devices = config.get(CONF_DEVICES) lights = [] logger = logging.getLogger(__name__) @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SCSGateLight(Light): - """representation of a SCSGate light.""" + """Representation of a SCSGate light.""" def __init__(self, scs_id, name, logger): """Initialize the light.""" diff --git a/homeassistant/components/light/sensehat.py b/homeassistant/components/light/sensehat.py new file mode 100644 index 00000000000..6c5467f8c6d --- /dev/null +++ b/homeassistant/components/light/sensehat.py @@ -0,0 +1,112 @@ +""" +Support for Sense Hat LEDs. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.sensehat/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, + Light, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME + +REQUIREMENTS = ['sense-hat==2.2.0'] + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_SENSEHAT = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR) + +DEFAULT_NAME = 'sensehat' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Sense Hat Light platform.""" + from sense_hat import SenseHat + sensehat = SenseHat() + + name = config.get(CONF_NAME) + + add_devices([SenseHatLight(sensehat, name)]) + + +class SenseHatLight(Light): + """Representation of an Sense Hat Light.""" + + def __init__(self, sensehat, name): + """Initialize an Sense Hat Light. + + Full brightness and white color. + """ + self._sensehat = sensehat + self._name = name + self._is_on = False + self._brightness = 255 + self._rgb_color = [255, 255, 255] + + @property + def name(self): + """Return the display name of this light.""" + return self._name + + @property + def brightness(self): + """Read back the brightness of the light.""" + return self._brightness + + @property + def rgb_color(self): + """Read back the color of the light. + + Returns [r, g, b] list with values in range of 0-255. + """ + return self._rgb_color + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_SENSEHAT + + @property + def is_on(self): + """Return true if light is on.""" + return self._is_on + + @property + def should_poll(self): + """Return if we should poll this device.""" + return False + + @property + def assumed_state(self) -> bool: + """Return True if unable to access real state of the entity.""" + return True + + def turn_on(self, **kwargs): + """Instruct the light to turn on and set correct brightness & color.""" + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + percent_bright = (self._brightness / 255) + + if ATTR_RGB_COLOR in kwargs: + self._rgb_color = kwargs[ATTR_RGB_COLOR] + + self._sensehat.clear(int(self._rgb_color[0] * percent_bright), + int(self._rgb_color[1] * percent_bright), + int(self._rgb_color[2] * percent_bright)) + + self._is_on = True + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Instruct the light to turn off.""" + self._sensehat.clear() + self._is_on = False + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 8931a46bb73..495ef9c8b39 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -25,7 +25,7 @@ turn_on: example: '[0.52, 0.43]' color_temp: - description: Color temperature for the light in mireds (154-500) + description: Color temperature for the light in mireds example: '250' white_value: diff --git a/homeassistant/components/light/tellduslive.py b/homeassistant/components/light/tellduslive.py index 31f9eb1d253..321cfd677b5 100644 --- a/homeassistant/components/light/tellduslive.py +++ b/homeassistant/components/light/tellduslive.py @@ -5,7 +5,6 @@ This platform uses the Telldus Live online service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.tellduslive/ - """ import logging @@ -17,22 +16,22 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup lights.""" + """Set up the Tellstick Net lights.""" if discovery_info is None: return add_devices(TelldusLiveLight(hass, light) for light in discovery_info) class TelldusLiveLight(TelldusLiveEntity, Light): - """Representation of a light.""" + """Representation of a Tellstick Net light.""" def __init__(self, hass, device_id): - """Initialize the light.""" + """Initialize the Tellstick Net light.""" super().__init__(hass, device_id) self._last_brightness = self.brightness def changed(self): - """A property of the device might have changed.""" + """Define a property of the device that might have changed.""" self._last_brightness = self.brightness super().changed() diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 9002731e44e..d2a6dadd9da 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -6,12 +6,11 @@ https://home-assistant.io/components/light.tellstick/ """ import voluptuous as vol -from homeassistant.components.light import (ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS, - ATTR_DISCOVER_DEVICES, - ATTR_DISCOVER_CONFIG, - DOMAIN, TellstickDevice) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from homeassistant.components.tellstick import ( + DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, + DOMAIN, TellstickDevice) PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) @@ -20,14 +19,13 @@ SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick lights.""" + """Set up the Tellstick lights.""" if (discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None): return - # Allow platform level override, fallback to module config - signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG, - DEFAULT_SIGNAL_REPETITIONS) + signal_repetitions = discovery_info.get( + ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS) add_devices(TellstickLight(tellcore_id, hass.data['tellcore_registry'], signal_repetitions) @@ -38,7 +36,7 @@ class TellstickLight(TellstickDevice, Light): """Representation of a Tellstick light.""" def __init__(self, tellcore_id, tellcore_registry, signal_repetitions): - """Initialize the light.""" + """Initialize the Tellstick light.""" super().__init__(tellcore_id, tellcore_registry, signal_repetitions) self._brightness = 255 diff --git a/homeassistant/components/light/tikteck.py b/homeassistant/components/light/tikteck.py index 7b0222107a2..07d4b63e99a 100644 --- a/homeassistant/components/light/tikteck.py +++ b/homeassistant/components/light/tikteck.py @@ -60,8 +60,8 @@ class TikteckLight(Light): self._rgb = [255, 255, 255] self._state = False self.is_valid = True - self._bulb = tikteck.tikteck(self._address, "Smart Light", - self._password) + self._bulb = tikteck.tikteck( + self._address, "Smart Light", self._password) if self._bulb.connect() is False: self.is_valid = False _LOGGER.error( @@ -99,12 +99,12 @@ class TikteckLight(Light): @property def should_poll(self): - """Don't poll.""" + """Return the polling state.""" return False @property def assumed_state(self): - """We can't read the actual state, so assume it matches.""" + """Return the assumed state.""" return True def set_state(self, red, green, blue, brightness): diff --git a/homeassistant/components/light/tradfri.py b/homeassistant/components/light/tradfri.py index ca1d5a38330..016771a15ca 100644 --- a/homeassistant/components/light/tradfri.py +++ b/homeassistant/components/light/tradfri.py @@ -20,7 +20,7 @@ ALLOWED_TEMPERATURES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the IKEA Tradfri Light platform.""" + """Set up the IKEA Tradfri Light platform.""" if discovery_info is None: return @@ -30,9 +30,56 @@ def setup_platform(hass, config, add_devices, discovery_info=None): lights = [dev for dev in devices if dev.has_light_control] add_devices(Tradfri(light) for light in lights) + groups = gateway.get_groups() + add_devices(TradfriGroup(group) for group in groups) + + +class TradfriGroup(Light): + """The platform class required by hass.""" + + def __init__(self, light): + """Initialize a Group.""" + self._group = light + self._name = light.name + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS + + @property + def name(self): + """Return the display name of this group.""" + return self._name + + @property + def is_on(self): + """Return true if group lights are on.""" + return self._group.state + + @property + def brightness(self): + """Return the brightness of the group lights.""" + return self._group.dimmer + + def turn_off(self, **kwargs): + """Instruct the group lights to turn off.""" + return self._group.set_state(0) + + def turn_on(self, **kwargs): + """Instruct the group lights to turn on, or dim.""" + if ATTR_BRIGHTNESS in kwargs: + self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS]) + else: + self._group.set_state(1) + + def update(self): + """Fetch new state data for this group.""" + self._group.update() + class Tradfri(Light): - """The platform class required by hass.""" + """The platform class required by Home Asisstant.""" def __init__(self, light): """Initialize a Light.""" @@ -71,7 +118,7 @@ class Tradfri(Light): @property def brightness(self): - """Brightness of the light (an integer in the range 1-255).""" + """Return the brightness of the light.""" return self._light_data.dimmer @property @@ -87,7 +134,7 @@ class Tradfri(Light): if hex_color == self._light_data.hex_color), None) if kelvin is None: _LOGGER.error( - 'unexpected color temperature found for %s: %s', + "Unexpected color temperature found for %s: %s", self.name, self._light_data.hex_color) return return color_util.color_temperature_kelvin_to_mired(kelvin) diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 3dc5f774785..70432de6644 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -20,7 +20,7 @@ SUPPORT_VERA = SUPPORT_BRIGHTNESS # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Vera lights.""" + """Set up the Vera lights.""" add_devices( VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light']) @@ -67,5 +67,5 @@ class VeraLight(VeraDevice, Light): return self._state def update(self): - """Called by the vera device callback to update state.""" + """Call to update state.""" self._state = self.vera_device.is_switched_on() diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index 02106511fe2..620271a1071 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -26,7 +26,7 @@ SUPPORT_WEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR | def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup WeMo bridges and register connected lights.""" + """Set up the WeMo bridges and register connected lights.""" import pywemo.discovery as discovery if discovery_info is not None: @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def setup_bridge(bridge, add_devices): - """Setup a WeMo link.""" + """Set up a WeMo link.""" lights = {} @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @@ -64,7 +64,7 @@ class WemoLight(Light): """Representation of a WeMo light.""" def __init__(self, device, update_lights): - """Initialize the light.""" + """Initialize the WeMo light.""" self.light_id = device.name self.device = device self.update_lights = update_lights @@ -97,7 +97,7 @@ class WemoLight(Light): @property def is_on(self): - """True if device is on.""" + """Return true if device is on.""" return self.device.state['onoff'] != 0 @property diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 26c85f19fa4..82b7c9f4f8c 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -22,7 +22,7 @@ RGB_MODES = ['hsb', 'rgb'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink lights.""" + """Set up the Wink lights.""" import pywink for light in pywink.get_light_bulbs(): @@ -53,7 +53,7 @@ class WinkLight(WinkDevice, Light): @property def rgb_color(self): - """Current bulb color in RGB.""" + """Define current bulb color in RGB.""" if not self.wink.supports_hue_saturation(): return None elif self.wink.color_model() not in RGB_MODES: @@ -72,14 +72,14 @@ class WinkLight(WinkDevice, Light): @property def xy_color(self): - """Current bulb color in CIE 1931 (XY) color space.""" + """Define current bulb color in CIE 1931 (XY) color space.""" if not self.wink.supports_xy_color(): return None return self.wink.color_xy() @property def color_temp(self): - """Current bulb color in degrees Kelvin.""" + """Define current bulb color in degrees Kelvin.""" if not self.wink.supports_temperature(): return None return color_util.color_temperature_kelvin_to_mired( @@ -105,8 +105,8 @@ class WinkLight(WinkDevice, Light): state_kwargs['color_xy'] = xyb[0], xyb[1] state_kwargs['brightness'] = xyb[2] elif self.wink.supports_hue_saturation(): - hsv = colorsys.rgb_to_hsv(rgb_color[0], - rgb_color[1], rgb_color[2]) + hsv = colorsys.rgb_to_hsv( + rgb_color[0], rgb_color[1], rgb_color[2]) state_kwargs['color_hue_saturation'] = hsv[0], hsv[1] if color_temp_mired: diff --git a/homeassistant/components/light/x10.py b/homeassistant/components/light/x10.py index 48df8368294..e782e664801 100644 --- a/homeassistant/components/light/x10.py +++ b/homeassistant/components/light/x10.py @@ -40,7 +40,7 @@ def get_unit_status(code): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the x10 Light platform.""" + """Set up the x10 Light platform.""" try: x10_command('info') except CalledProcessError as err: @@ -67,7 +67,7 @@ class X10Light(Light): @property def brightness(self): - """Brightness of the light (an integer in the range 1-255).""" + """Return the brightness of the light.""" return self._brightness @property diff --git a/homeassistant/components/light/yeelight.py b/homeassistant/components/light/yeelight.py index 9253f471431..dbb1e4e26ca 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/light/yeelight.py @@ -26,11 +26,11 @@ REQUIREMENTS = ['yeelight==0.2.2'] _LOGGER = logging.getLogger(__name__) -CONF_TRANSITION = "transition" +CONF_TRANSITION = 'transition' DEFAULT_TRANSITION = 350 -CONF_SAVE_ON_CHANGE = "save_on_change" -CONF_MODE_MUSIC = "use_music_mode" +CONF_SAVE_ON_CHANGE = 'save_on_change' +CONF_MODE_MUSIC = 'use_music_mode' DOMAIN = 'yeelight' @@ -54,7 +54,7 @@ SUPPORT_YEELIGHT_RGB = (SUPPORT_YEELIGHT | def _cmd(func): - """A wrapper to catch exceptions from the bulb.""" + """Define a wrapper to catch exceptions from the bulb.""" def _wrap(self, *args, **kwargs): import yeelight try: @@ -67,14 +67,14 @@ def _cmd(func): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Yeelight bulbs.""" + """Set up the Yeelight bulbs.""" lights = [] if discovery_info is not None: _LOGGER.debug("Adding autodetected %s", discovery_info['hostname']) - # not using hostname, as it seems to vary. - name = "yeelight_%s_%s" % (discovery_info["device_type"], - discovery_info["properties"]["mac"]) + # Not using hostname, as it seems to vary. + name = "yeelight_%s_%s" % (discovery_info['device_type'], + discovery_info['properties']['mac']) device = {'name': name, 'ipaddr': discovery_info['host']} lights.append(YeelightLight(device, DEVICE_SCHEMA({}))) @@ -85,14 +85,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device = {'name': device_config[CONF_NAME], 'ipaddr': ipaddr} lights.append(YeelightLight(device, device_config)) - add_devices(lights, True) # true to request an update before adding. + add_devices(lights, True) class YeelightLight(Light): """Representation of a Yeelight light.""" def __init__(self, device, config): - """Initialize the light.""" + """Initialize the Yeelight light.""" self.config = config self._name = device['name'] self._ipaddr = device['ipaddr'] @@ -142,8 +142,8 @@ class YeelightLight(Light): return self._brightness def _get_rgb_from_properties(self): - rgb = self._properties.get("rgb", None) - color_mode = self._properties.get("color_mode", None) + rgb = self._properties.get('rgb', None) + color_mode = self._properties.get('color_mode', None) if not rgb or not color_mode: return rgb @@ -151,9 +151,9 @@ class YeelightLight(Light): if color_mode == 2: # color temperature return color_temperature_to_rgb(self.color_temp) if color_mode == 3: # hsv - hue = self._properties.get("hue") - sat = self._properties.get("sat") - val = self._properties.get("bright") + hue = self._properties.get('hue') + sat = self._properties.get('sat') + val = self._properties.get('bright') return colorsys.hsv_to_rgb(hue, sat, val) rgb = int(rgb) @@ -204,13 +204,13 @@ class YeelightLight(Light): if self._bulb_device.bulb_type == yeelight.BulbType.Color: self._supported_features = SUPPORT_YEELIGHT_RGB - self._is_on = self._properties.get("power") == "on" + self._is_on = self._properties.get('power') == 'on' - bright = self._properties.get("bright", None) + bright = self._properties.get('bright', None) if bright: self._brightness = 255 * (int(bright) / 100) - temp_in_k = self._properties.get("ct", None) + temp_in_k = self._properties.get('ct', None) if temp_in_k: self._color_temp = kelvin_to_mired(int(temp_in_k)) diff --git a/homeassistant/components/light/yeelightsunflower.py b/homeassistant/components/light/yeelightsunflower.py index 70df2136716..5f48e3a0a71 100644 --- a/homeassistant/components/light/yeelightsunflower.py +++ b/homeassistant/components/light/yeelightsunflower.py @@ -67,7 +67,7 @@ class SunflowerBulb(Light): @property def brightness(self): - """HA brightness is 0-255; Yeelight Sunflower's brightness is 0-100.""" + """Return the brightness is 0-255; Yeelight's brightness is 0-100.""" return int(self._brightness / 100 * 255) @property diff --git a/homeassistant/components/light/zha.py b/homeassistant/components/light/zha.py new file mode 100644 index 00000000000..0c230877625 --- /dev/null +++ b/homeassistant/components/light/zha.py @@ -0,0 +1,131 @@ +""" +Lights on Zigbee Home Automation networks. + +For more details on this platform, please refer to the documentation +at https://home-assistant.io/components/light.zha/ +""" +import asyncio +import logging + +from homeassistant.components import light, zha +from homeassistant.util.color import color_RGB_to_xy + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zha'] + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the Zigbee Home Automation lights.""" + discovery_info = zha.get_discovery_info(hass, discovery_info) + if discovery_info is None: + return + + endpoint = discovery_info['endpoint'] + try: + primaries = yield from endpoint.light_color['num_primaries'] + discovery_info['num_primaries'] = primaries + except (AttributeError, KeyError): + pass + + async_add_devices([Light(**discovery_info)]) + + +class Light(zha.Entity, light.Light): + """Representation of a ZHA or ZLL light.""" + + _domain = light.DOMAIN + + def __init__(self, **kwargs): + """Initialize the ZHA light.""" + super().__init__(**kwargs) + self._supported_features = 0 + self._color_temp = None + self._xy_color = None + self._brightness = None + + import bellows.zigbee.zcl.clusters as zcl_clusters + if zcl_clusters.general.LevelControl.cluster_id in self._clusters: + self._supported_features |= light.SUPPORT_BRIGHTNESS + self._brightness = 0 + if zcl_clusters.lighting.Color.cluster_id in self._clusters: + # Not sure all color lights necessarily support this directly + # Should we emulate it? + self._supported_features |= light.SUPPORT_COLOR_TEMP + # Silly heuristic, not sure if it works widely + if kwargs.get('num_primaries', 1) >= 3: + self._supported_features |= light.SUPPORT_XY_COLOR + self._supported_features |= light.SUPPORT_RGB_COLOR + self._xy_color = (1.0, 1.0) + + @property + def is_on(self) -> bool: + """Return true if entity is on.""" + if self._state == 'unknown': + return False + return bool(self._state) + + @asyncio.coroutine + def async_turn_on(self, **kwargs): + """Turn the entity on.""" + duration = 5 # tenths of s + if light.ATTR_COLOR_TEMP in kwargs: + temperature = kwargs[light.ATTR_COLOR_TEMP] + yield from self._endpoint.light_color.move_to_color_temp( + temperature, duration) + self._color_temp = temperature + + if light.ATTR_XY_COLOR in kwargs: + self._xy_color = kwargs[light.ATTR_XY_COLOR] + elif light.ATTR_RGB_COLOR in kwargs: + xyb = color_RGB_to_xy( + *(int(val) for val in kwargs[light.ATTR_RGB_COLOR])) + self._xy_color = (xyb[0], xyb[1]) + self._brightness = xyb[2] + if light.ATTR_XY_COLOR in kwargs or light.ATTR_RGB_COLOR in kwargs: + yield from self._endpoint.light_color.move_to_color( + int(self._xy_color[0] * 65535), + int(self._xy_color[1] * 65535), + duration, + ) + + if self._brightness is not None: + brightness = kwargs.get('brightness', self._brightness or 255) + self._brightness = brightness + # Move to level with on/off: + yield from self._endpoint.level.move_to_level_with_on_off( + brightness, + duration + ) + self._state = 1 + return + + yield from self._endpoint.on_off.on() + self._state = 1 + + @asyncio.coroutine + def async_turn_off(self, **kwargs): + """Turn the entity off.""" + yield from self._endpoint.on_off.off() + self._state = 0 + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return self._brightness + + @property + def xy_color(self): + """Return the XY color value [float, float].""" + return self._xy_color + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + return self._color_temp + + @property + def supported_features(self): + """Flag supported features.""" + return self._supported_features diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index ce85276cae9..9488ad38a59 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -15,9 +15,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ from homeassistant.components import zwave from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \ - color_temperature_mired_to_kelvin, color_temperature_to_rgb, \ - color_rgb_to_rgbw, color_rgbw_to_rgb +from homeassistant.util.color import color_temperature_mired_to_kelvin, \ + color_temperature_to_rgb, color_rgb_to_rgbw, color_rgbw_to_rgb _LOGGER = logging.getLogger(__name__) @@ -39,19 +38,20 @@ DEVICE_MAPPINGS = { # Generate midpoint color temperatures for bulbs that have limited # support for white light colors -TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN -TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN -TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN +TEMP_COLOR_MAX = 500 # mireds (inverted) +TEMP_COLOR_MIN = 154 +TEMP_MID_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 2 + TEMP_COLOR_MIN +TEMP_WARM_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 * 2 + TEMP_COLOR_MIN +TEMP_COLD_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 + TEMP_COLOR_MIN def get_device(node, values, node_config, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" name = '{}.{}'.format(DOMAIN, zwave.object_id(values.primary)) refresh = node_config.get(zwave.CONF_REFRESH_VALUE) delay = node_config.get(zwave.CONF_REFRESH_DELAY) - _LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s' - ' CONF_REFRESH_DELAY=%s', name, node_config, - refresh, delay) + _LOGGER.debug("name=%s node_config=%s CONF_REFRESH_VALUE=%s" + " CONF_REFRESH_DELAY=%s", name, node_config, refresh, delay) if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR): return ZwaveColorLight(values, refresh, delay) @@ -112,19 +112,19 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): self._brightness, self._state = brightness_state(self.values.primary) def value_added(self): - """Called when a new value is added to this entity.""" + """Call when a new value is added to this entity.""" self._supported_features = SUPPORT_BRIGHTNESS if self.values.dimming_duration is not None: self._supported_features |= SUPPORT_TRANSITION def value_changed(self): - """Called when a value for this entity's node has changed.""" + """Call when a value for this entity's node has changed.""" if self._refresh_value: if self._refreshing: self._refreshing = False else: def _refresh_value(): - """Used timer callback for delayed value refresh.""" + """Use timer callback for delayed value refresh.""" self._refreshing = True self.values.primary.refresh() @@ -217,7 +217,7 @@ class ZwaveColorLight(ZwaveDimmer): super().__init__(values, refresh, delay) def value_added(self): - """Called when a new value is added to this entity.""" + """Call when a new value is added to this entity.""" super().value_added() self._supported_features |= SUPPORT_RGB_COLOR diff --git a/homeassistant/components/lirc.py b/homeassistant/components/lirc.py index ac4807b26af..8b9ad0209da 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc.py @@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup LIRC capability.""" + """Set up the LIRC capability.""" import lirc # blocking=True gives unexpected behavior (multiple responses for 1 press) @@ -70,7 +70,7 @@ class LircInterface(threading.Thread): self.hass = hass def run(self): - """Main loop of LIRC interface thread.""" + """Run the loop of the LIRC interface thread.""" import lirc _LOGGER.debug("LIRC interface thread started") while not self.stopped.isSet(): diff --git a/homeassistant/components/litejet.py b/homeassistant/components/litejet.py index aa4488c1277..e7c8452b27b 100644 --- a/homeassistant/components/litejet.py +++ b/homeassistant/components/litejet.py @@ -7,9 +7,9 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.const import CONF_PORT -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pylitejet==0.1'] @@ -30,7 +30,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Initialize the LiteJet component.""" + """Set up the LiteJet component.""" from pylitejet import LiteJet url = config[DOMAIN].get(CONF_PORT) diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 3d83ae5895d..7d3e75595e2 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -22,17 +22,19 @@ from homeassistant.const import ( STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) from homeassistant.components import group -DOMAIN = 'lock' -SCAN_INTERVAL = timedelta(seconds=30) ATTR_CHANGED_BY = 'changed_by' -GROUP_NAME_ALL_LOCKS = 'all locks' -ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks') +DOMAIN = 'lock' +ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks') ENTITY_ID_FORMAT = DOMAIN + '.{}' +GROUP_NAME_ALL_LOCKS = 'all locks' + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +SCAN_INTERVAL = timedelta(seconds=30) + LOCK_SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_CODE): cv.string, diff --git a/homeassistant/components/lock/demo.py b/homeassistant/components/lock/demo.py index fca922b11e2..aca25e7e16d 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, discovery_info=None): - """Setup the Demo lock platform.""" + """Set up the Demo lock platform.""" add_devices([ DemoLock('Front Door', STATE_LOCKED), DemoLock('Kitchen Door', STATE_UNLOCKED) diff --git a/homeassistant/components/lock/isy994.py b/homeassistant/components/lock/isy994.py index d7e921a16e5..edbb8a34f24 100644 --- a/homeassistant/components/lock/isy994.py +++ b/homeassistant/components/lock/isy994.py @@ -28,7 +28,7 @@ def setup_platform(hass, config: ConfigType, add_devices: Callable[[list], None], discovery_info=None): """Set up the ISY994 lock platform.""" if isy.ISY is None or not isy.ISY.connected: - _LOGGER.error('A connection has not been made to the ISY controller.') + _LOGGER.error("A connection has not been made to the ISY controller") return False devices = [] @@ -115,9 +115,9 @@ class ISYLockProgram(ISYLockDevice): def lock(self, **kwargs) -> None: """Lock the device.""" if not self._actions.runThen(): - _LOGGER.error('Unable to lock device') + _LOGGER.error("Unable to lock device") def unlock(self, **kwargs) -> None: """Unlock the device.""" if not self._actions.runElse(): - _LOGGER.error('Unable to unlock device') + _LOGGER.error("Unable to unlock device") diff --git a/homeassistant/components/lock/lockitron.py b/homeassistant/components/lock/lockitron.py index 86821711fd2..eb301eeb013 100644 --- a/homeassistant/components/lock/lockitron.py +++ b/homeassistant/components/lock/lockitron.py @@ -28,16 +28,17 @@ API_ACTION_URL = BASE_URL + '/v2/locks/{}?access_token={}&state={}' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Lockitron platform.""" + """Set up the Lockitron platform.""" access_token = config.get(CONF_ACCESS_TOKEN) device_id = config.get(CONF_ID) - response = requests.get(API_STATE_URL.format(device_id, access_token)) + response = requests.get( + API_STATE_URL.format(device_id, access_token), timeout=5) if response.status_code == 200: add_devices([Lockitron(response.json()['state'], access_token, device_id)]) else: - _LOGGER.error('Error retrieving lock status during init: %s', - response.text) + _LOGGER.error( + "Error retrieving lock status during init: %s", response.text) class Lockitron(LockDevice): @@ -72,21 +73,20 @@ class Lockitron(LockDevice): def update(self): """Update the internal state of the device.""" - response = requests \ - .get(API_STATE_URL.format(self.device_id, self.access_token)) + response = requests.get(API_STATE_URL.format( + self.device_id, self.access_token), timeout=5) if response.status_code == 200: self._state = response.json()['state'] else: - _LOGGER.error('Error retrieving lock status: %s', response.text) + _LOGGER.error("Error retrieving lock status: %s", response.text) def do_change_request(self, requested_state): """Execute the change request and pull out the new state.""" - response = requests.put( - API_ACTION_URL.format(self.device_id, self.access_token, - requested_state)) + response = requests.put(API_ACTION_URL.format( + self.device_id, self.access_token, requested_state), timeout=5) if response.status_code == 200: return response.json()['state'] else: - _LOGGER.error('Error setting lock state: %s\n%s', + _LOGGER.error("Error setting lock state: %s\n%s", requested_state, response.text) return self._state diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py index 43d5788af9b..de14d21a09b 100644 --- a/homeassistant/components/lock/mqtt.py +++ b/homeassistant/components/lock/mqtt.py @@ -20,7 +20,6 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) - CONF_PAYLOAD_LOCK = 'payload_lock' CONF_PAYLOAD_UNLOCK = 'payload_unlock' @@ -42,7 +41,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the MQTT lock.""" + """Set up the MQTT lock.""" value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass @@ -61,7 +60,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class MqttLock(LockDevice): - """Represents a lock that can be toggled using MQTT.""" + """Representation of a lock that can be toggled using MQTT.""" def __init__(self, name, state_topic, command_topic, qos, retain, payload_lock, payload_unlock, optimistic, value_template): @@ -79,13 +78,13 @@ class MqttLock(LockDevice): @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) @@ -110,7 +109,7 @@ class MqttLock(LockDevice): @property def name(self): - """The name of the lock.""" + """Return the name of the lock.""" return self._name @property diff --git a/homeassistant/components/lock/nuki.py b/homeassistant/components/lock/nuki.py index bb3f2f030a2..144bfc3ec45 100644 --- a/homeassistant/components/lock/nuki.py +++ b/homeassistant/components/lock/nuki.py @@ -6,13 +6,13 @@ https://home-assistant.io/components/lock.nuki/ """ from datetime import timedelta import logging + import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.lock import (LockDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_TOKEN) from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv - REQUIREMENTS = ['pynuki==1.2.2'] @@ -33,7 +33,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=5) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo lock platform.""" + """Set up the Nuki lock platform.""" from pynuki import NukiBridge bridge = NukiBridge(config.get(CONF_HOST), config.get(CONF_TOKEN)) add_devices([NukiLock(lock) for lock in bridge.locks]) diff --git a/homeassistant/components/lock/vera.py b/homeassistant/components/lock/vera.py index da2a465d570..04962566821 100644 --- a/homeassistant/components/lock/vera.py +++ b/homeassistant/components/lock/vera.py @@ -48,6 +48,6 @@ class VeraLock(VeraDevice, LockDevice): return self._state == STATE_LOCKED def update(self): - """Called by the Vera device callback to update state.""" + """Update state by the Vera device callback.""" self._state = (STATE_LOCKED if self.vera_device.is_locked(True) else STATE_UNLOCKED) diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/lock/verisure.py index 7e73ceb680e..e4fa2104da6 100644 --- a/homeassistant/components/lock/verisure.py +++ b/homeassistant/components/lock/verisure.py @@ -16,13 +16,13 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Verisure platform.""" + """Set up the Verisure platform.""" locks = [] if int(hub.config.get(CONF_LOCKS, 1)): hub.update_locks() locks.extend([ VerisureDoorlock(device_id) - for device_id in hub.lock_status.keys() + for device_id in hub.lock_status ]) add_devices(locks) @@ -31,7 +31,7 @@ class VerisureDoorlock(LockDevice): """Representation of a Verisure doorlock.""" def __init__(self, device_id): - """Initialize the lock.""" + """Initialize the Verisure lock.""" self._id = device_id self._state = STATE_UNKNOWN self._digits = hub.config.get(CONF_CODE_DIGITS) @@ -72,8 +72,7 @@ class VerisureDoorlock(LockDevice): self._state = STATE_LOCKED elif hub.lock_status[self._id].status != 'pending': _LOGGER.error( - 'Unknown lock state %s', - hub.lock_status[self._id].status) + "Unknown lock state %s", hub.lock_status[self._id].status) self._changed_by = hub.lock_status[self._id].name @property @@ -84,13 +83,13 @@ class VerisureDoorlock(LockDevice): def unlock(self, **kwargs): """Send unlock command.""" hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED') - _LOGGER.info('verisure doorlock unlocking') + _LOGGER.debug("Verisure doorlock unlocking") hub.my_pages.lock.wait_while_pending() self.update() def lock(self, **kwargs): """Send lock command.""" hub.my_pages.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED') - _LOGGER.info('verisure doorlock locking') + _LOGGER.debug("Verisure doorlock locking") hub.my_pages.lock.wait_while_pending() self.update() diff --git a/homeassistant/components/lock/volvooncall.py b/homeassistant/components/lock/volvooncall.py index b308ec61fba..ab1d2fabefe 100644 --- a/homeassistant/components/lock/volvooncall.py +++ b/homeassistant/components/lock/volvooncall.py @@ -1,5 +1,5 @@ """ -Support for Volvo locks. +Support for Volvo On Call locks. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/lock.volvooncall/ @@ -14,9 +14,10 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the lock.""" + """Set up the Volvo On Call lock.""" if discovery_info is None: return + add_devices([VolvoLock(hass, *discovery_info)]) diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 76922cf9d62..9ac5c579ab5 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -4,7 +4,6 @@ Support for Wink locks. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/lock.wink/ """ - from homeassistant.components.lock import LockDevice from homeassistant.components.wink import WinkDevice, DOMAIN @@ -12,7 +11,7 @@ DEPENDENCIES = ['wink'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink platform.""" + """Set up the Wink platform.""" import pywink for lock in pywink.get_locks(): diff --git a/homeassistant/components/lock/zwave.py b/homeassistant/components/lock/zwave.py index 9a3e2e34fcc..a46406e8361 100644 --- a/homeassistant/components/lock/zwave.py +++ b/homeassistant/components/lock/zwave.py @@ -1,5 +1,5 @@ """ -Zwave platform that handles simple door locks. +Z-Wave platform that handles simple door locks. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/lock.zwave/ @@ -122,7 +122,7 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Generic Z-Wave platform setup.""" + """Set up the Z-Wave Lock platform.""" yield from zwave.async_setup_platform( hass, config, async_add_devices, discovery_info) @@ -142,8 +142,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value.index != code_slot: continue if len(str(usercode)) > 4: - _LOGGER.error('Invalid code provided: (%s)' - ' usercode must %s or less digits', + _LOGGER.error("Invalid code provided: (%s) " + "usercode must %s or less digits", usercode, len(value.data)) break value.data = str(usercode) @@ -159,7 +159,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class_id=zwave.const.COMMAND_CLASS_USER_CODE).values(): if value.index != code_slot: continue - _LOGGER.info('Usercode at slot %s is: %s', value.index, value.data) + _LOGGER.info("Usercode at slot %s is: %s", value.index, value.data) break def clear_usercode(service): @@ -178,28 +178,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): i += 1 _LOGGER.debug('Data to clear lock: %s', data) value.data = data - _LOGGER.info('Usercode at slot %s is cleared', value.index) + _LOGGER.info("Usercode at slot %s is cleared", value.index) break - hass.services.async_register(DOMAIN, - SERVICE_SET_USERCODE, - set_usercode, - descriptions.get(SERVICE_SET_USERCODE), - schema=SET_USERCODE_SCHEMA) - hass.services.async_register(DOMAIN, - SERVICE_GET_USERCODE, - get_usercode, - descriptions.get(SERVICE_GET_USERCODE), - schema=GET_USERCODE_SCHEMA) - hass.services.async_register(DOMAIN, - SERVICE_CLEAR_USERCODE, - clear_usercode, - descriptions.get(SERVICE_CLEAR_USERCODE), - schema=CLEAR_USERCODE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_SET_USERCODE, set_usercode, + descriptions.get(SERVICE_SET_USERCODE), schema=SET_USERCODE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_GET_USERCODE, get_usercode, + descriptions.get(SERVICE_GET_USERCODE), schema=GET_USERCODE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_CLEAR_USERCODE, clear_usercode, + descriptions.get(SERVICE_CLEAR_USERCODE), schema=CLEAR_USERCODE_SCHEMA) def get_device(node, values, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" return ZwaveLock(values) @@ -228,10 +222,9 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): self.update_properties() def update_properties(self): - """Callback on data changes for node values.""" + """Handle data changes for node values.""" self._state = self.values.primary.data - _LOGGER.debug('Lock state set from Bool value and' - ' is %s', self._state) + _LOGGER.debug("Lock state set from Bool value and is %s", self._state) if self.values.access_control: notification_data = self.values.access_control.data self._notification = LOCK_NOTIFICATION.get(str(notification_data)) @@ -240,21 +233,20 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): if self.values.v2btze_advanced and \ self.values.v2btze_advanced.data == CONFIG_ADVANCED: self._state = LOCK_STATUS.get(str(notification_data)) - _LOGGER.debug('Lock state set from Access Control ' - 'value and is %s, get=%s', - str(notification_data), - self.state) + _LOGGER.debug( + "Lock state set from Access Control value and is %s, " + "get=%s", str(notification_data), self.state) if not self.values.alarm_type: return alarm_type = self.values.alarm_type.data - _LOGGER.debug('Lock alarm_type is %s', str(alarm_type)) + _LOGGER.debug("Lock alarm_type is %s", str(alarm_type)) if self.values.alarm_level: alarm_level = self.values.alarm_level.data else: alarm_level = None - _LOGGER.debug('Lock alarm_level is %s', str(alarm_level)) + _LOGGER.debug("Lock alarm_level is %s", str(alarm_level)) if not alarm_type: return diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 92f99887867..98a0973a807 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -17,13 +17,12 @@ import homeassistant.util.dt as dt_util from homeassistant.components import sun from homeassistant.components.frontend import register_built_in_panel from homeassistant.components.http import HomeAssistantView -from homeassistant.const import (EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, - STATE_NOT_HOME, STATE_OFF, STATE_ON, - ATTR_HIDDEN, HTTP_BAD_REQUEST) +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, + STATE_NOT_HOME, STATE_OFF, STATE_ON, ATTR_HIDDEN, HTTP_BAD_REQUEST) from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN -DOMAIN = "logbook" +DOMAIN = 'logbook' DEPENDENCIES = ['recorder', 'frontend'] _LOGGER = logging.getLogger(__name__) @@ -100,11 +99,11 @@ def setup(hass, config): hass.http.register_view(LogbookView(config.get(DOMAIN, {}))) - register_built_in_panel(hass, 'logbook', 'Logbook', - 'mdi:format-list-bulleted-type') + register_built_in_panel( + hass, 'logbook', 'Logbook', 'mdi:format-list-bulleted-type') - hass.services.register(DOMAIN, 'log', log_message, - schema=LOG_MESSAGE_SCHEMA) + hass.services.register( + DOMAIN, 'log', log_message, schema=LOG_MESSAGE_SCHEMA) return True @@ -164,11 +163,11 @@ class Entry(object): def humanify(events): - """Generator that converts a list of events into Entry objects. + """Generate a converted list of events into Entry objects. Will try to group events if possible: - - if 2+ sensor updates in GROUP_BY_MINUTES, show last - - if home assistant stop and start happen in same minute call it restarted + - if 2+ sensor updates in GROUP_BY_MINUTES, show last + - if home assistant stop and start happen in same minute call it restarted """ # Group events in batches of GROUP_BY_MINUTES for _, g_events in groupby( diff --git a/homeassistant/components/logentries.py b/homeassistant/components/logentries.py index ef79b033922..6dc76d8d932 100644 --- a/homeassistant/components/logentries.py +++ b/homeassistant/components/logentries.py @@ -10,9 +10,9 @@ import requests import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import (CONF_TOKEN, EVENT_STATE_CHANGED) from homeassistant.helpers import state as state_helper -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -28,7 +28,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Logentries component.""" + """Set up the Logentries component.""" conf = config[DOMAIN] token = conf.get(CONF_TOKEN) le_wh = '{}{}'.format(DEFAULT_HOST, token) @@ -52,11 +52,13 @@ def setup(hass, config): } ] try: - payload = {"host": le_wh, - "event": json_body} + payload = { + "host": le_wh, + "event": json_body + } requests.post(le_wh, data=json.dumps(payload), timeout=10) except requests.exceptions.RequestException as error: - _LOGGER.exception('Error sending to Logentries: %s', error) + _LOGGER.exception("Error sending to Logentries: %s", error) hass.bus.listen(EVENT_STATE_CHANGED, logentries_event_listener) diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger.py index 8572bbc044a..94a3ad902da 100644 --- a/homeassistant/components/logger.py +++ b/homeassistant/components/logger.py @@ -62,7 +62,7 @@ class HomeAssistantLogFilter(logging.Filter): self.logfilter = logfilter def filter(self, record): - """A filter to use.""" + """Filter the log entries.""" # Log with filtered severity if LOGGER_LOGS in self.logfilter: for filtername in self.logfilter[LOGGER_LOGS]: @@ -77,7 +77,7 @@ class HomeAssistantLogFilter(logging.Filter): @asyncio.coroutine def async_setup(hass, config): - """Setup the logger component.""" + """Set up the logger component.""" logfilter = {} # Set default log severity diff --git a/homeassistant/components/lutron.py b/homeassistant/components/lutron.py index 3bd1682bc41..ed3134c275a 100644 --- a/homeassistant/components/lutron.py +++ b/homeassistant/components/lutron.py @@ -22,7 +22,7 @@ LUTRON_DEVICES = 'lutron_devices' def setup(hass, base_config): - """Setup the Lutron component.""" + """Set up the Lutron component.""" from pylutron import Lutron hass.data[LUTRON_CONTROLLER] = None @@ -66,7 +66,7 @@ class LutronDevice(Entity): ) def _update_callback(self, _device): - """Callback invoked by pylutron when the device state changes.""" + """Run when invoked by pylutron when the device state changes.""" self.schedule_update_ha_state() @property diff --git a/homeassistant/components/lutron_caseta.py b/homeassistant/components/lutron_caseta.py index 1e19083e959..375e2930a4f 100644 --- a/homeassistant/components/lutron_caseta.py +++ b/homeassistant/components/lutron_caseta.py @@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, base_config): - """Setup the Lutron component.""" + """Set up the Lutron component.""" from pylutron_caseta.smartbridge import Smartbridge config = base_config.get(DOMAIN) @@ -44,8 +44,7 @@ def setup(hass, base_config): config[CONF_HOST]) return False - _LOGGER.info("Connected to Lutron smartbridge at %s", - config[CONF_HOST]) + _LOGGER.info("Connected to Lutron smartbridge at %s", config[CONF_HOST]) for component in ('light', 'switch'): discovery.load_platform(hass, component, DOMAIN, {}, config) @@ -88,8 +87,10 @@ class LutronCasetaDevice(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {'Device ID': self._device_id, - 'Zone ID': self._device_zone} + attr = { + 'Device ID': self._device_id, + 'Zone ID': self._device_zone, + } return attr @property diff --git a/homeassistant/components/maxcube.py b/homeassistant/components/maxcube.py index c0c9bd16674..a0a8db6ba4d 100644 --- a/homeassistant/components/maxcube.py +++ b/homeassistant/components/maxcube.py @@ -4,25 +4,25 @@ Platform for the MAX! Cube LAN Gateway. For more details about this component, please refer to the documentation https://home-assistant.io/components/maxcube/ """ - -from socket import timeout import logging import time +from socket import timeout from threading import Lock +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.const import CONF_HOST, CONF_PORT -import homeassistant.helpers.config_validation as cv -import voluptuous as vol REQUIREMENTS = ['maxcube-api==0.1.0'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'maxcube' -MAXCUBE_HANDLE = 'maxcube' - DEFAULT_PORT = 62910 +DOMAIN = 'maxcube' + +MAXCUBE_HANDLE = 'maxcube' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -37,11 +37,9 @@ def setup(hass, config): from maxcube.connection import MaxCubeConnection from maxcube.cube import MaxCube - # Read Config host = config.get(DOMAIN).get(CONF_HOST) port = config.get(DOMAIN).get(CONF_PORT) - # Assign Cube Handle to global variable try: cube = MaxCube(MaxCubeConnection(host, port)) except timeout: @@ -51,13 +49,9 @@ def setup(hass, config): hass.data[MAXCUBE_HANDLE] = MaxCubeHandle(cube) - # Load Climate (for Thermostats) load_platform(hass, 'climate', DOMAIN) - - # Load BinarySensor (for Window Shutter) load_platform(hass, 'binary_sensor', DOMAIN) - # Initialization successfull return True @@ -66,13 +60,8 @@ class MaxCubeHandle(object): def __init__(self, cube): """Initialize the Cube Handle.""" - # Cube handle self.cube = cube - - # Instantiate Mutex self.mutex = Lock() - - # Update Timestamp self._updatets = time.time() def update(self): @@ -81,7 +70,7 @@ class MaxCubeHandle(object): with self.mutex: # Only update every 60s if (time.time() - self._updatets) >= 60: - _LOGGER.debug("UPDATE: Updating") + _LOGGER.debug("Updating") try: self.cube.update() @@ -91,4 +80,4 @@ class MaxCubeHandle(object): self._updatets = time.time() else: - _LOGGER.debug("UPDATE: Skipping") + _LOGGER.debug("Skipping update") diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 511cb8208a5..97712e1c0ad 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -31,7 +31,8 @@ from homeassistant.const import ( SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, SERVICE_VOLUME_MUTE, SERVICE_TOGGLE, SERVICE_MEDIA_STOP, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, + SERVICE_SHUFFLE_SET) _LOGGER = logging.getLogger(__name__) _RND = SystemRandom() @@ -81,6 +82,7 @@ ATTR_APP_NAME = 'app_name' ATTR_INPUT_SOURCE = 'source' ATTR_INPUT_SOURCE_LIST = 'source_list' ATTR_MEDIA_ENQUEUE = 'enqueue' +ATTR_MEDIA_SHUFFLE = 'shuffle' MEDIA_TYPE_MUSIC = 'music' MEDIA_TYPE_TVSHOW = 'tvshow' @@ -104,6 +106,7 @@ SUPPORT_SELECT_SOURCE = 2048 SUPPORT_STOP = 4096 SUPPORT_CLEAR_PLAYLIST = 8192 SUPPORT_PLAY = 16384 +SUPPORT_SHUFFLE_SET = 32768 # Service call validation schemas MEDIA_PLAYER_SCHEMA = vol.Schema({ @@ -133,6 +136,10 @@ MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean, }) +MEDIA_PLAYER_SET_SHUFFLE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean, +}) + SERVICE_TO_METHOD = { SERVICE_TURN_ON: {'method': 'async_turn_on'}, SERVICE_TURN_OFF: {'method': 'async_turn_off'}, @@ -161,6 +168,9 @@ SERVICE_TO_METHOD = { SERVICE_PLAY_MEDIA: { 'method': 'async_play_media', 'schema': MEDIA_PLAYER_PLAY_MEDIA_SCHEMA}, + SERVICE_SHUFFLE_SET: { + 'method': 'async_set_shuffle', + 'schema': MEDIA_PLAYER_SET_SHUFFLE_SCHEMA}, } ATTR_TO_PROPERTY = [ @@ -185,6 +195,7 @@ ATTR_TO_PROPERTY = [ ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, + ATTR_MEDIA_SHUFFLE, ] @@ -322,6 +333,16 @@ def clear_playlist(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data) +def set_shuffle(hass, shuffle, entity_id=None): + """Send the media player the command to enable/disable shuffle mode.""" + data = {ATTR_MEDIA_SHUFFLE: shuffle} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_SHUFFLE_SET, data) + + @asyncio.coroutine def async_setup(hass, config): """Track states and offer events for media_players.""" @@ -358,6 +379,9 @@ def async_setup(hass, config): params['media_id'] = service.data.get(ATTR_MEDIA_CONTENT_ID) params[ATTR_MEDIA_ENQUEUE] = \ service.data.get(ATTR_MEDIA_ENQUEUE) + elif service.service == SERVICE_SHUFFLE_SET: + params[ATTR_MEDIA_SHUFFLE] = \ + service.data.get(ATTR_MEDIA_SHUFFLE) target_players = component.async_extract_from_service(service) update_tasks = [] @@ -539,6 +563,11 @@ class MediaPlayerDevice(Entity): """List of available input sources.""" return None + @property + def shuffle(self): + """Boolean if shuffle is enabled.""" + return None + @property @deprecated_substitute('supported_media_commands') def supported_features(self): @@ -701,6 +730,18 @@ class MediaPlayerDevice(Entity): return self.hass.loop.run_in_executor( None, self.clear_playlist) + def set_shuffle(self, shuffle): + """Enable/disable shuffle mode.""" + raise NotImplementedError() + + def async_set_shuffle(self, shuffle): + """Enable/disable shuffle mode. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.loop.run_in_executor( + None, self.set_shuffle, shuffle) + # No need to overwrite these. @property def support_play(self): @@ -757,6 +798,11 @@ class MediaPlayerDevice(Entity): """Boolean if clear playlist command supported.""" return bool(self.supported_features & SUPPORT_CLEAR_PLAYLIST) + @property + def support_shuffle_set(self): + """Boolean if shuffle is supported.""" + return bool(self.supported_features & SUPPORT_SHUFFLE_SET) + def async_toggle(self): """Toggle the power on the media player. @@ -851,7 +897,7 @@ class MediaPlayerDevice(Entity): @asyncio.coroutine def _async_fetch_image(hass, url): - """Helper method to fetch image. + """Fetch image. Images are cached in memory (the images are typically 10-100kB in size). """ @@ -871,6 +917,8 @@ def _async_fetch_image(hass, url): if response.status == 200: content = yield from response.read() content_type = response.headers.get(CONTENT_TYPE_HEADER) + if content_type: + content_type = content_type.split(';')[0] except asyncio.TimeoutError: pass @@ -896,8 +944,8 @@ class MediaPlayerImageView(HomeAssistantView): """Media player view to serve an image.""" requires_auth = False - url = "/api/media_player_proxy/{entity_id}" - name = "api:media_player:image" + url = '/api/media_player_proxy/{entity_id}' + name = 'api:media_player:image' def __init__(self, entities): """Initialize a media player view.""" diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py index e6fd4e286ab..64b8e826cfb 100644 --- a/homeassistant/components/media_player/anthemav.py +++ b/homeassistant/components/media_player/anthemav.py @@ -45,11 +45,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): name = config.get(CONF_NAME) device = None - _LOGGER.info('Provisioning Anthem AVR device at %s:%d', host, port) + _LOGGER.info("Provisioning Anthem AVR device at %s:%d", host, port) def async_anthemav_update_callback(message): """Receive notification from transport that new data exists.""" - _LOGGER.info('Received update calback from AVR: %s', message) + _LOGGER.info("Received update calback from AVR: %s", message) hass.async_add_job(device.async_update_ha_state()) avr = yield from anthemav.Connection.create( @@ -58,9 +58,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): device = AnthemAVR(avr, name) - _LOGGER.debug('dump_devicedata: '+device.dump_avrdata) - _LOGGER.debug('dump_conndata: '+avr.dump_conndata) - _LOGGER.debug('dump_rawdata: '+avr.protocol.dump_rawdata) + _LOGGER.debug("dump_devicedata: %s", device.dump_avrdata) + _LOGGER.debug("dump_conndata: %s", avr.dump_conndata) + _LOGGER.debug("dump_rawdata: %s", avr.protocol.dump_rawdata) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.avr.close) async_add_devices([device]) @@ -70,7 +70,7 @@ class AnthemAVR(MediaPlayerDevice): """Entity reading values from Anthem AVR protocol.""" def __init__(self, avr, name): - """"Initialize entity with transport.""" + """Initialize entity with transport.""" super().__init__() self.avr = avr self._name = name @@ -163,7 +163,8 @@ class AnthemAVR(MediaPlayerDevice): def _update_avr(self, propname, value): """Update a property in the AVR.""" - _LOGGER.info('Sending command to AVR: set '+propname+' to '+str(value)) + _LOGGER.info( + "Sending command to AVR: set %s to %s", propname, str(value)) setattr(self.avr.protocol, propname, value) @property diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py index 580870cf375..97114a6bc84 100644 --- a/homeassistant/components/media_player/apple_tv.py +++ b/homeassistant/components/media_player/apple_tv.py @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Apple TV platform.""" + """Set up the Apple TV platform.""" import pyatv if discovery_info is not None: @@ -95,12 +95,13 @@ class AppleTvDevice(MediaPlayerDevice): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity is about to be added to HASS.""" + """Handle when an entity is about to be added to Home Assistant.""" if not self._is_off: self._atv.push_updater.start() @callback def _set_power_off(self, is_off): + """Set the power to off.""" self._playing = None self._artwork_hash = None self._is_off = is_off diff --git a/homeassistant/components/media_player/aquostv.py b/homeassistant/components/media_player/aquostv.py index 65d3e2c93fd..2d51cacadff 100644 --- a/homeassistant/components/media_player/aquostv.py +++ b/homeassistant/components/media_player/aquostv.py @@ -61,7 +61,7 @@ SOURCES = {0: 'TV / Antenna', # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Sharp Aquos TV platform.""" + """Set up the Sharp Aquos TV platform.""" import sharp_aquos_rc name = config.get(CONF_NAME) @@ -77,30 +77,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): port = vals[1] host = vals[0] - remote = sharp_aquos_rc.TV(host, - port, - username, - password, - timeout=20) + remote = sharp_aquos_rc.TV(host, port, username, password, timeout=20) add_devices([SharpAquosTVDevice(name, remote, power_on_enabled)]) return True host = config.get(CONF_HOST) - remote = sharp_aquos_rc.TV(host, - port, - username, - password, - 15, - 1) + remote = sharp_aquos_rc.TV(host, port, username, password, 15, 1) add_devices([SharpAquosTVDevice(name, remote, power_on_enabled)]) return True def _retry(func): - """Decorator to handle query retries.""" + """Handle query retries.""" def wrapper(obj, *args, **kwargs): - """Wrapper for all query functions.""" + """Wrap all query functions.""" update_retries = 5 while update_retries > 0: try: diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py index fbfc207c59a..1c8419ba144 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -76,7 +76,7 @@ def _config_from_file(filename, config=None): with open(filename, 'w') as fdesc: fdesc.write(json.dumps(new_config)) except IOError as error: - _LOGGER.error('Saving config file failed: %s', error) + _LOGGER.error("Saving config file failed: %s", error) return False return True else: @@ -88,7 +88,7 @@ def _config_from_file(filename, config=None): except ValueError as error: return {} except IOError as error: - _LOGGER.error('Reading config file failed: %s', error) + _LOGGER.error("Reading config file failed: %s", error) # This won't work yet return False else: @@ -97,16 +97,16 @@ def _config_from_file(filename, config=None): # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Sony Bravia TV platform.""" + """Set up the Sony Bravia TV platform.""" host = config.get(CONF_HOST) if host is None: - return # if no host configured, do not continue + return pin = None bravia_config = _config_from_file(hass.config.path(BRAVIA_CONFIG_FILE)) - while len(bravia_config): - # Setup a configured TV + while bravia_config: + # Set up a configured TV host_ip, host_config = bravia_config.popitem() if host_ip == host: pin = host_config['pin'] @@ -119,7 +119,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def setup_bravia(config, pin, hass, add_devices): - """Setup a Sony Bravia TV based on host parameter.""" + """Set up a Sony Bravia TV based on host parameter.""" host = config.get(CONF_HOST) name = config.get(CONF_NAME) @@ -135,13 +135,13 @@ def setup_bravia(config, pin, hass, add_devices): request_id = _CONFIGURING.pop(host) configurator = get_component('configurator') configurator.request_done(request_id) - _LOGGER.info('Discovery configuration done!') + _LOGGER.info("Discovery configuration done") # Save config if not _config_from_file( hass.config.path(BRAVIA_CONFIG_FILE), {host: {'pin': pin, 'host': host, 'mac': mac}}): - _LOGGER.error('failed to save config file') + _LOGGER.error("Failed to save configuration file") add_devices([BraviaTVDevice(host, mac, name, pin)]) @@ -160,7 +160,7 @@ def request_configuration(config, hass, add_devices): return def bravia_configuration_callback(data): - """Callback after user enter PIN.""" + """Handle the entry of user PIN.""" from braviarc import braviarc pin = data.get('pin') @@ -236,7 +236,7 @@ class BraviaTVDevice(MediaPlayerDevice): self._state = STATE_ON playing_info = self._braviarc.get_playing_info() self._reset_playing_info() - if playing_info is None or len(playing_info) == 0: + if playing_info is None or not playing_info: self._channel_name = 'App' else: self._program_name = playing_info.get('programTitle') @@ -275,7 +275,7 @@ class BraviaTVDevice(MediaPlayerDevice): self._muted = volume_info.get('mute') def _refresh_channels(self): - if len(self._source_list) == 0: + if not self._source_list: self._content_mapping = self._braviarc. \ load_source_list() self._source_list = [] diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 148cdee1d48..e4ecd1bd37d 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -43,10 +43,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the cast platform.""" + """Set up the cast platform.""" import pychromecast - # import CEC IGNORE attributes + # Import CEC IGNORE attributes pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, []) hosts = [] @@ -68,8 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): casts = [] - # get_chromecasts() returns Chromecast objects - # with the correct friendly name for grouped devices + # get_chromecasts() returns Chromecast objects with the correct friendly + # name for grouped devices all_chromecasts = pychromecast.get_chromecasts() for host in hosts: @@ -204,7 +204,7 @@ class CastDevice(MediaPlayerDevice): @property def media_series_title(self): - """The title of the series of current playing media (TV Show only).""" + """Return the title of the series of current playing media.""" return self.media_status.series_title if self.media_status else None @property @@ -310,12 +310,12 @@ class CastDevice(MediaPlayerDevice): # Implementation of chromecast status_listener methods def new_cast_status(self, status): - """Called when a new cast status is received.""" + """Handle updates of the cast status.""" self.cast_status = status self.schedule_update_ha_state() def new_media_status(self, status): - """Called when a new media status is received.""" + """Handle updates of the media status.""" self.media_status = status self.media_status_received = dt_util.utcnow() self.schedule_update_ha_state() diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/media_player/clementine.py index e85019b7991..d9688badcd1 100644 --- a/homeassistant/components/media_player/clementine.py +++ b/homeassistant/components/media_player/clementine.py @@ -28,6 +28,7 @@ SCAN_INTERVAL = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Clementine Remote' +DEFAULT_PORT = 5500 SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \ @@ -35,19 +36,22 @@ SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ SUPPORT_SELECT_SOURCE | SUPPORT_PLAY PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=5500): cv.positive_int, vol.Optional(CONF_ACCESS_TOKEN, default=None): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Clementine platform.""" + """Set up the Clementine platform.""" from clementineremote import ClementineRemote - client = ClementineRemote(config.get(CONF_HOST), config.get(CONF_PORT), - config.get(CONF_ACCESS_TOKEN), reconnect=True) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + token = config.get(CONF_ACCESS_TOKEN) + + client = ClementineRemote(host, port, token, reconnect=True) add_devices([ClementineDevice(client, config[CONF_NAME])]) diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/media_player/cmus.py index 136d26974fe..aefe5bced18 100644 --- a/homeassistant/components/media_player/cmus.py +++ b/homeassistant/components/media_player/cmus.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discover_info=None): - """Setup the CMUS platform.""" + """Set up the CMUS platform.""" from pycmus import exceptions host = config.get(CONF_HOST) @@ -166,7 +166,7 @@ class CmusDevice(MediaPlayerDevice): self.cmus.set_volume(int(volume * 100)) def volume_up(self): - """Function to send CMUS the command for volume up.""" + """Set the volume up.""" left = self.status['set'].get('vol_left') right = self.status['set'].get('vol_right') if left != right: @@ -178,7 +178,7 @@ class CmusDevice(MediaPlayerDevice): self.cmus.set_volume(int(current_volume) + 5) def volume_down(self): - """Function to send CMUS the command for volume down.""" + """Set the volume down.""" left = self.status['set'].get('vol_left') right = self.status['set'].get('vol_right') if left != right: diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index aab75f702d4..9e4e912f314 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -9,14 +9,14 @@ from homeassistant.components.media_player import ( SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_PLAY, - MediaPlayerDevice) + SUPPORT_SHUFFLE_SET, MediaPlayerDevice) from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING import homeassistant.util.dt as dt_util # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the media player demo platform.""" + """Set up the media player demo platform.""" add_devices([ DemoYoutubePlayer( 'Living Room', 'eyU3bRy2x44', @@ -31,15 +31,17 @@ YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/hqdefault.jpg' YOUTUBE_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \ + SUPPORT_SHUFFLE_SET MUSIC_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST | SUPPORT_PLAY + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST | \ + SUPPORT_PLAY | SUPPORT_SHUFFLE_SET NETFLIX_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_SELECT_SOURCE | SUPPORT_PLAY + SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET class AbstractDemoPlayer(MediaPlayerDevice): @@ -53,6 +55,7 @@ class AbstractDemoPlayer(MediaPlayerDevice): self._player_state = STATE_PLAYING self._volume_level = 1.0 self._volume_muted = False + self._shuffle = False @property def should_poll(self): @@ -79,6 +82,11 @@ class AbstractDemoPlayer(MediaPlayerDevice): """Return boolean if volume is currently muted.""" return self._volume_muted + @property + def shuffle(self): + """Boolean if shuffling is enabled.""" + return self._shuffle + def turn_on(self): """Turn the media player on.""" self._player_state = STATE_PLAYING @@ -109,6 +117,11 @@ class AbstractDemoPlayer(MediaPlayerDevice): self._player_state = STATE_PAUSED self.schedule_update_ha_state() + def set_shuffle(self, shuffle): + """Enable/disable shuffle mode.""" + self._shuffle = shuffle + self.schedule_update_ha_state() + class DemoYoutubePlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" @@ -250,12 +263,12 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_title(self): """Return the title of current playing media.""" - return self.tracks[self._cur_track][1] if len(self.tracks) > 0 else "" + return self.tracks[self._cur_track][1] if self.tracks else "" @property def media_artist(self): """Return the artist of current playing media (Music track only).""" - return self.tracks[self._cur_track][0] if len(self.tracks) > 0 else "" + return self.tracks[self._cur_track][0] if self.tracks else "" @property def media_album_name(self): diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/media_player/denon.py index 4b3347a832a..08e08dd1650 100755 --- a/homeassistant/components/media_player/denon.py +++ b/homeassistant/components/media_player/denon.py @@ -50,7 +50,7 @@ MEDIA_MODES = {'Tuner': 'TUNER', 'Media server': 'SERVER', def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Denon platform.""" + """Set up the Denon platform.""" denon = DenonDevice(config.get(CONF_NAME), config.get(CONF_HOST)) if denon.update(): @@ -101,7 +101,7 @@ class DenonDevice(MediaPlayerDevice): @classmethod def telnet_request(cls, telnet, command, all_lines=False): """Execute `command` and return the response.""" - _LOGGER.debug('Sending: "%s"', command) + _LOGGER.debug("Sending: %s", command) telnet.write(command.encode('ASCII') + b'\r') lines = [] while True: @@ -109,7 +109,7 @@ class DenonDevice(MediaPlayerDevice): if not line: break lines.append(line.decode('ASCII').strip()) - _LOGGER.debug('Recived: "%s"', line) + _LOGGER.debug("Recived: %s", line) if all_lines: return lines @@ -118,7 +118,7 @@ class DenonDevice(MediaPlayerDevice): def telnet_command(self, command): """Establish a telnet connection and sends `command`.""" telnet = telnetlib.Telnet(self._host) - _LOGGER.debug('Sending: "%s"', command) + _LOGGER.debug("Sending: %s", command) telnet.write(command.encode('ASCII') + b'\r') telnet.read_very_eager() # skip response telnet.close() @@ -179,17 +179,17 @@ class DenonDevice(MediaPlayerDevice): @property def is_volume_muted(self): - """Boolean if volume is currently muted.""" + """Return boolean if volume is currently muted.""" return self._muted @property def source_list(self): - """List of available input sources.""" + """Return the list of available input sources.""" return sorted(list(self._source_list.keys())) @property def media_title(self): - """Current media info.""" + """Return the current media info.""" return self._mediainfo @property diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 5e91a5418e2..494a3f69e6d 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -19,7 +19,7 @@ from homeassistant.const import ( CONF_NAME, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['denonavr==0.3.1'] +REQUIREMENTS = ['denonavr==0.4.0'] _LOGGER = logging.getLogger(__name__) @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Denon platform.""" + """Set up the Denon platform.""" import denonavr # Initialize list with receivers to be started @@ -116,9 +116,7 @@ class DenonDevice(MediaPlayerDevice): def update(self): """Get the latest status information from device.""" - # Update denonavr self._receiver.update() - # Refresh own data self._name = self._receiver.name self._muted = self._receiver.muted self._volume = self._receiver.volume @@ -146,7 +144,7 @@ class DenonDevice(MediaPlayerDevice): @property def is_volume_muted(self): - """Boolean if volume is currently muted.""" + """Return boolean if volume is currently muted.""" return self._muted @property @@ -163,7 +161,7 @@ class DenonDevice(MediaPlayerDevice): @property def source_list(self): - """List of available input sources.""" + """Return a list of available input sources.""" return self._source_list @property diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py index ef3333d4da3..c1b690bc370 100644 --- a/homeassistant/components/media_player/directv.py +++ b/homeassistant/components/media_player/directv.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the DirecTV platform.""" + """Set up the DirecTV platform.""" hosts = [] if discovery_info: @@ -94,13 +94,13 @@ class DirecTvDevice(MediaPlayerDevice): """Return the state of the device.""" if self._is_standby: return STATE_OFF - # haven't determined a way to see if the content is paused + # Haven't determined a way to see if the content is paused else: return STATE_PLAYING @property def media_content_id(self): - """Content ID of current playing media.""" + """Return the content ID of current playing media.""" if self._is_standby: return None else: @@ -108,7 +108,7 @@ class DirecTvDevice(MediaPlayerDevice): @property def media_duration(self): - """Duration of current playing media in seconds.""" + """Return the duration of current playing media in seconds.""" if self._is_standby: return None else: @@ -116,7 +116,7 @@ class DirecTvDevice(MediaPlayerDevice): @property def media_title(self): - """Title of current playing media.""" + """Return the title of current playing media.""" if self._is_standby: return None else: @@ -124,7 +124,7 @@ class DirecTvDevice(MediaPlayerDevice): @property def media_series_title(self): - """Title of current episode of TV show.""" + """Return the title of current episode of TV show.""" if self._is_standby: return None else: @@ -140,7 +140,7 @@ class DirecTvDevice(MediaPlayerDevice): @property def media_content_type(self): - """Content type of current playing media.""" + """Return the content type of current playing media.""" if 'episodeTitle' in self._current: return MEDIA_TYPE_TVSHOW else: @@ -148,38 +148,38 @@ class DirecTvDevice(MediaPlayerDevice): @property def media_channel(self): - """Channel current playing media.""" + """Return the channel current playing media.""" if self._is_standby: return None else: - chan = "{} ({})".format(self._current['callsign'], - self._current['major']) + chan = "{} ({})".format( + self._current['callsign'], self._current['major']) return chan def turn_on(self): - """Turn on the reciever.""" + """Turn on the receiver.""" self.dtv.key_press('poweron') def turn_off(self): - """Turn off the reciever.""" + """Turn off the receiver.""" self.dtv.key_press('poweroff') def media_play(self): - """Send play commmand.""" + """Send play command.""" self.dtv.key_press('play') def media_pause(self): - """Send pause commmand.""" + """Send pause command.""" self.dtv.key_press('pause') def media_stop(self): - """Send stop commmand.""" + """Send stop command.""" self.dtv.key_press('stop') def media_previous_track(self): - """Send rewind commmand.""" + """Send rewind command.""" self.dtv.key_press('rew') def media_next_track(self): - """Send fast forward commmand.""" + """Send fast forward command.""" self.dtv.key_press('ffwd') diff --git a/homeassistant/components/media_player/dunehd.py b/homeassistant/components/media_player/dunehd.py index 1facb523da6..80f4795ccbe 100644 --- a/homeassistant/components/media_player/dunehd.py +++ b/homeassistant/components/media_player/dunehd.py @@ -22,7 +22,7 @@ CONF_SOURCES = 'sources' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_SOURCES): cv.ordered_dict(cv.string, cv.string), + vol.Optional(CONF_SOURCES): vol.Schema({cv.string: cv.string}), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) @@ -34,7 +34,7 @@ DUNEHD_PLAYER_SUPPORT = \ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the media player demo platform.""" + """Set up the DuneHD media player platform.""" sources = config.get(CONF_SOURCES, {}) from pdunehd import DuneHDPlayer @@ -48,7 +48,7 @@ class DuneHDPlayerEntity(MediaPlayerDevice): """Implementation of the Dune HD player.""" def __init__(self, player, name, sources): - """Setup entity to control Dune HD.""" + """Initialize entity to control Dune HD.""" self._player = player self._name = name self._sources = sources @@ -83,17 +83,17 @@ class DuneHDPlayerEntity(MediaPlayerDevice): @property def volume_level(self): - """Volume level of the media player (0..1).""" + """Return the volume level of the media player (0..1).""" return int(self._state.get('playback_volume', 0)) / 100 @property def is_volume_muted(self): - """Boolean if volume is currently muted.""" + """Return a boolean if volume is currently muted.""" return int(self._state.get('playback_mute', 0)) == 1 @property def source_list(self): - """List of available input sources.""" + """Return a list of available input sources.""" return list(self._sources.keys()) @property @@ -136,7 +136,7 @@ class DuneHDPlayerEntity(MediaPlayerDevice): @property def media_title(self): - """Current media source.""" + """Return the current media source.""" self.__update_title() if self._media_title: return self._media_title diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py index 3ed6d42e76a..7fc18ef8fee 100644 --- a/homeassistant/components/media_player/emby.py +++ b/homeassistant/components/media_player/emby.py @@ -52,7 +52,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Emby platform.""" + """Set up the Emby platform.""" from pyemby import EmbyServer host = config.get(CONF_HOST) @@ -64,7 +64,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if port is None: port = DEFAULT_SSL_PORT if ssl else DEFAULT_PORT - _LOGGER.debug('Setting up Emby server at: %s:%s', host, port) + _LOGGER.debug("Setting up Emby server at: %s:%s", host, port) emby = EmbyServer(host, key, port, ssl, hass.loop) @@ -73,7 +73,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): @callback def device_update_callback(data): - """Callback for when devices are added to emby.""" + """Handle devices which are added to Emby.""" new_devices = [] active_devices = [] for dev_id in emby.devices: @@ -93,12 +93,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): add.set_hidden(False) if new_devices: - _LOGGER.debug("Adding new devices to HASS: %s", new_devices) + _LOGGER.debug("Adding new devices: %s", new_devices) async_add_devices(new_devices, update_before_add=True) @callback def device_removal_callback(data): - """Callback for when devices are removed from emby.""" + """Handle the removal of devices from Emby.""" if data in active_emby_devices: rem = active_emby_devices.pop(data) inactive_emby_devices[data] = rem @@ -109,12 +109,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): @callback def start_emby(event): - """Start emby connection.""" + """Start Emby connection.""" emby.start() @asyncio.coroutine def stop_emby(event): - """Stop emby connection.""" + """Stop Emby connection.""" yield from emby.stop() emby.add_new_devices_callback(device_update_callback) @@ -129,7 +129,7 @@ class EmbyDevice(MediaPlayerDevice): def __init__(self, emby, device_id): """Initialize the Emby device.""" - _LOGGER.debug('New Emby Device initialized with ID: %s', device_id) + _LOGGER.debug("New Emby Device initialized with ID: %s", device_id) self.emby = emby self.device_id = device_id self.device = self.emby.devices[self.device_id] @@ -143,12 +143,12 @@ class EmbyDevice(MediaPlayerDevice): @asyncio.coroutine def async_added_to_hass(self): """Register callback.""" - self.emby.add_update_callback(self.async_update_callback, - self.device_id) + self.emby.add_update_callback( + self.async_update_callback, self.device_id) @callback def async_update_callback(self, msg): - """Callback for device updates.""" + """Handle device updates.""" # Check if we should update progress if self.device.media_position: if self.device.media_position != self.media_status_last_position: @@ -244,12 +244,12 @@ class EmbyDevice(MediaPlayerDevice): @property def media_duration(self): - """Duration of current playing media in seconds.""" + """Return the duration of current playing media in seconds.""" return self.device.media_runtime @property def media_position(self): - """Position of current playing media in seconds.""" + """Return the position of current playing media in seconds.""" return self.media_status_last_position @property @@ -263,12 +263,12 @@ class EmbyDevice(MediaPlayerDevice): @property def media_image_url(self): - """Image url of current playing media.""" + """Return the image URL of current playing media.""" return self.device.media_image_url @property def media_title(self): - """Title of current playing media.""" + """Return the title of current playing media.""" return self.device.media_title @property @@ -278,27 +278,27 @@ class EmbyDevice(MediaPlayerDevice): @property def media_series_title(self): - """The title of the series of current playing media (TV Show only).""" + """Return the title of the series of current playing media (TV).""" return self.device.media_series_title @property def media_episode(self): - """Episode of current playing media (TV Show only).""" + """Return the episode of current playing media (TV only).""" return self.device.media_episode @property def media_album_name(self): - """Album name of current playing media (Music track only).""" + """Return the album name of current playing media (Music only).""" return self.device.media_album_name @property def media_artist(self): - """Artist of current playing media (Music track only).""" + """Return the artist of current playing media (Music track only).""" return self.device.media_artist @property def media_album_artist(self): - """Album artist of current playing media (Music track only).""" + """Return the album artist of current playing media (Music only).""" return self.device.media_album_artist @property diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index 237faddaaea..fbf1d1fd03b 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the FireTV platform.""" + """Set up the FireTV platform.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -52,13 +52,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): response = requests.get(DEVICE_LIST_URL.format(host, port)).json() if device_id in response[CONF_DEVICES].keys(): add_devices([FireTVDevice(host, port, device_id, name)]) - _LOGGER.info('Device %s accessible and ready for control', + _LOGGER.info("Device %s accessible and ready for control", device_id) else: - _LOGGER.warning('Device %s is not registered with firetv-server', + _LOGGER.warning("Device %s is not registered with firetv-server", device_id) except requests.exceptions.RequestException: - _LOGGER.error('Could not connect to firetv-server at %s', host) + _LOGGER.error("Could not connect to firetv-server at %s", host) class FireTV(object): @@ -88,7 +88,7 @@ class FireTV(object): return response.get('state', STATE_UNKNOWN) except requests.exceptions.RequestException: _LOGGER.error( - 'Could not retrieve device state for %s', self.device_id) + "Could not retrieve device state for %s", self.device_id) return STATE_UNKNOWN def action(self, action_id): @@ -98,7 +98,7 @@ class FireTV(object): self.host, self.port, self.device_id, action_id), timeout=10) except requests.exceptions.RequestException: _LOGGER.error( - 'Action request for %s was not accepted for device %s', + "Action request for %s was not accepted for device %s", action_id, self.device_id) diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/media_player/gpmdp.py index c3283aca382..88a8a949ca4 100644 --- a/homeassistant/components/media_player/gpmdp.py +++ b/homeassistant/components/media_player/gpmdp.py @@ -62,7 +62,7 @@ def request_configuration(hass, config, url, add_devices_callback): # pylint: disable=unused-argument def gpmdp_configuration_callback(callback_data): - """The actions to do when our configuration callback is called.""" + """Handle configuration changes.""" while True: from websocket import _exceptions try: @@ -79,8 +79,8 @@ def request_configuration(hass, config, url, add_devices_callback): 'arguments': ['Home Assistant', pin]})) tmpmsg = json.loads(websocket.recv()) if tmpmsg['channel'] == 'time': - _LOGGER.error('Error setting up GPMDP. Please pause' - ' the desktop player and try again.') + _LOGGER.error("Error setting up GPMDP. Please pause " + "the desktop player and try again") break code = tmpmsg['payload'] if code == 'CODE_REQUIRED': @@ -106,7 +106,7 @@ def request_configuration(hass, config, url, add_devices_callback): def setup_gpmdp(hass, config, code, add_devices): - """Setup gpmdp.""" + """Set up gpmdp.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -154,9 +154,9 @@ def _save_config(filename, config): def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the GPMDP platform.""" + """Set up the GPMDP platform.""" codeconfig = _load_config(hass.config.path(GPMDP_CONFIG_FILE)) - if len(codeconfig): + if codeconfig: code = codeconfig.get('CODE') elif discovery_info is not None: if 'gpmdp' in _CONFIGURING: diff --git a/homeassistant/components/media_player/gstreamer.py b/homeassistant/components/media_player/gstreamer.py index f9c20c4d4ea..064ca68ea95 100644 --- a/homeassistant/components/media_player/gstreamer.py +++ b/homeassistant/components/media_player/gstreamer.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Gstreamer platform.""" + """Set up the Gstreamer platform.""" from gsp import GstreamerPlayer name = config.get(CONF_NAME) pipeline = config.get(CONF_PIPELINE) diff --git a/homeassistant/components/media_player/hdmi_cec.py b/homeassistant/components/media_player/hdmi_cec.py index aa3b44e9e90..7054c83d36a 100644 --- a/homeassistant/components/media_player/hdmi_cec.py +++ b/homeassistant/components/media_player/hdmi_cec.py @@ -134,7 +134,7 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): @property def state(self) -> str: - """Cached state of device.""" + """Cache state of device.""" return self._state def _update(self, device=None): diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index 5d53518256e..514bf24a21a 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -124,7 +124,7 @@ class Itunes(object): [playlist for playlist in playlists if (playlist_id_or_name in [playlist["name"], playlist["id"]])] - if len(found_playlists) > 0: + if found_playlists: playlist = found_playlists[0] path = '/playlists/' + playlist['id'] + '/play' return self._request('PUT', path) @@ -154,7 +154,7 @@ class Itunes(object): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the iTunes platform.""" + """Set up the iTunes platform.""" add_devices([ ItunesDevice( config.get(CONF_NAME), diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index e10886d6916..10d13002625 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -47,17 +47,17 @@ TURN_OFF_ACTION = [None, 'quit', 'hibernate', 'suspend', 'reboot', 'shutdown'] # https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h MEDIA_TYPES = { - "music": MEDIA_TYPE_MUSIC, - "artist": MEDIA_TYPE_MUSIC, - "album": MEDIA_TYPE_MUSIC, - "song": MEDIA_TYPE_MUSIC, - "video": MEDIA_TYPE_VIDEO, - "set": MEDIA_TYPE_PLAYLIST, - "musicvideo": MEDIA_TYPE_VIDEO, - "movie": MEDIA_TYPE_VIDEO, - "tvshow": MEDIA_TYPE_TVSHOW, - "season": MEDIA_TYPE_TVSHOW, - "episode": MEDIA_TYPE_TVSHOW, + 'music': MEDIA_TYPE_MUSIC, + 'artist': MEDIA_TYPE_MUSIC, + 'album': MEDIA_TYPE_MUSIC, + 'song': MEDIA_TYPE_MUSIC, + 'video': MEDIA_TYPE_VIDEO, + 'set': MEDIA_TYPE_PLAYLIST, + 'musicvideo': MEDIA_TYPE_VIDEO, + 'movie': MEDIA_TYPE_VIDEO, + 'tvshow': MEDIA_TYPE_TVSHOW, + 'season': MEDIA_TYPE_TVSHOW, + 'episode': MEDIA_TYPE_TVSHOW, } SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ @@ -108,7 +108,7 @@ SERVICE_TO_METHOD = { @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Kodi platform.""" + """Set up the Kodi platform.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) tcp_port = config.get(CONF_TCP_PORT) @@ -161,11 +161,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def cmd(func): - """Decorator to catch command exceptions.""" + """Catch command exceptions.""" @wraps(func) @asyncio.coroutine def wrapper(obj, *args, **kwargs): - """Wrapper for all command methods.""" + """Wrap all command methods.""" import jsonrpc_base try: yield from func(obj, *args, **kwargs) @@ -245,7 +245,7 @@ class KodiDevice(MediaPlayerDevice): @callback def async_on_speed_event(self, sender, data): - """Called when player changes between playing and paused.""" + """Handle player changes between playing and paused.""" self._properties['speed'] = data['player']['speed'] if not hasattr(data['item'], 'id'): @@ -259,7 +259,7 @@ class KodiDevice(MediaPlayerDevice): @callback def async_on_stop(self, sender, data): - """Called when the player stops playback.""" + """Handle the stop of the player playback.""" # Prevent stop notifications which are sent after quit notification if self._players is None: return @@ -271,14 +271,14 @@ class KodiDevice(MediaPlayerDevice): @callback def async_on_volume_changed(self, sender, data): - """Called when the volume is changed.""" + """Handle the volume changes.""" self._app_properties['volume'] = data['volume'] self._app_properties['muted'] = data['muted'] self.hass.async_add_job(self.async_update_ha_state()) @callback def async_on_quit(self, sender, data): - """Called when the volume is changed.""" + """Handle the muted volume.""" self._players = None self._properties = {} self._item = {} @@ -293,8 +293,8 @@ class KodiDevice(MediaPlayerDevice): return (yield from self.server.Player.GetActivePlayers()) except jsonrpc_base.jsonrpc.TransportError: if self._players is not None: - _LOGGER.info('Unable to fetch kodi data') - _LOGGER.debug('Unable to fetch kodi data', exc_info=True) + _LOGGER.info("Unable to fetch kodi data") + _LOGGER.debug("Unable to fetch kodi data", exc_info=True) return None @property @@ -303,7 +303,7 @@ class KodiDevice(MediaPlayerDevice): if self._players is None: return STATE_OFF - if len(self._players) == 0: + if not self._players: return STATE_IDLE if self._properties['speed'] == 0 and not self._properties['live']: @@ -356,7 +356,7 @@ class KodiDevice(MediaPlayerDevice): ['volume', 'muted'] ) - if len(self._players) > 0: + if self._players: player_id = self._players[0]['playerid'] assert isinstance(player_id, int) @@ -475,7 +475,7 @@ class KodiDevice(MediaPlayerDevice): def media_artist(self): """Artist of current playing media, music track only.""" artists = self._item.get('artist', []) - if len(artists) > 0: + if artists: return artists[0] else: return None @@ -484,7 +484,7 @@ class KodiDevice(MediaPlayerDevice): def media_album_artist(self): """Album artist of current playing media, music track only.""" artists = self._item.get('albumartist', []) - if len(artists) > 0: + if artists: return artists[0] else: return None @@ -548,10 +548,10 @@ class KodiDevice(MediaPlayerDevice): @asyncio.coroutine def async_set_play_state(self, state): - """Helper method for play/pause/toggle.""" + """Handle play/pause/toggle.""" players = yield from self._get_players() - if players is not None and len(players) != 0: + if players is not None and players: yield from self.server.Player.PlayPause( players[0]['playerid'], state) @@ -585,17 +585,17 @@ class KodiDevice(MediaPlayerDevice): """Stop the media player.""" players = yield from self._get_players() - if len(players) != 0: + if players: yield from self.server.Player.Stop(players[0]['playerid']) @asyncio.coroutine def _goto(self, direction): - """Helper method used for previous/next track.""" + """Handle for previous/next track.""" players = yield from self._get_players() - if len(players) != 0: + if players: if direction == 'previous': - # first seek to position 0. Kodi goes to the beginning of the + # First seek to position 0. Kodi goes to the beginning of the # current track if the current track is not at the beginning. yield from self.server.Player.Seek(players[0]['playerid'], 0) @@ -637,7 +637,7 @@ class KodiDevice(MediaPlayerDevice): time['hours'] = int(position) - if len(players) != 0: + if players: yield from self.server.Player.Seek(players[0]['playerid'], time) @cmd diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py index 00e405c17b2..9e8fcbf2462 100644 --- a/homeassistant/components/media_player/lg_netcast.py +++ b/homeassistant/components/media_player/lg_netcast.py @@ -45,10 +45,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the LG TV platform.""" + """Set up the LG TV platform.""" from pylgnetcast import LgNetCastClient - client = LgNetCastClient(config.get(CONF_HOST), - config.get(CONF_ACCESS_TOKEN)) + client = LgNetCastClient( + config.get(CONF_HOST), config.get(CONF_ACCESS_TOKEN)) add_devices([LgTVDevice(client, config[CONF_NAME])]) @@ -110,7 +110,7 @@ class LgTVDevice(MediaPlayerDevice): self._sources = dict(zip(channel_names, channel_list)) # sort source names by the major channel number source_tuples = [(k, self._sources[k].find('major').text) - for k in self._sources.keys()] + for k in self._sources] sorted_sources = sorted( source_tuples, key=lambda channel: int(channel[1])) self._source_names = [n for n, k in sorted_sources] diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py index 52a37eb8faa..eef5a890e8e 100644 --- a/homeassistant/components/media_player/liveboxplaytv.py +++ b/homeassistant/components/media_player/liveboxplaytv.py @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Orange Livebox Play TV platform.""" + """Set up the Orange Livebox Play TV platform.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) name = config.get(CONF_NAME) @@ -56,8 +56,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device = LiveboxPlayTvDevice(host, port, name) livebox_devices.append(device) except IOError: - _LOGGER.error('Failed to connect to Livebox Play TV at %s:%s. ' - 'Please check your configuration.', host, port) + _LOGGER.error("Failed to connect to Livebox Play TV at %s:%s. " + "Please check your configuration", host, port) add_devices(livebox_devices, True) diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/media_player/mpchc.py index 168497c4052..964c2b5d009 100644 --- a/homeassistant/components/media_player/mpchc.py +++ b/homeassistant/components/media_player/mpchc.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the MPC-HC platform.""" + """Set up the MPC-HC platform.""" name = config.get(CONF_NAME) url = '{}:{}'.format(config.get(CONF_HOST), config.get(CONF_PORT)) @@ -59,8 +59,8 @@ class MpcHcDevice(MediaPlayerDevice): self._player_variables = dict() try: - response = requests.get('{}/variables.html'.format(self._url), - data=None, timeout=3) + response = requests.get( + '{}/variables.html'.format(self._url), data=None, timeout=3) mpchc_variables = re.findall(r'

(.+?)

', response.text) @@ -102,24 +102,24 @@ class MpcHcDevice(MediaPlayerDevice): @property def media_title(self): - """Title of current playing media.""" + """Return the title of current playing media.""" return self._player_variables.get('file', None) @property def volume_level(self): - """Volume level of the media player (0..1).""" + """Return the volume level of the media player (0..1).""" return int(self._player_variables.get('volumelevel', 0)) / 100.0 @property def is_volume_muted(self): - """Boolean if volume is currently muted.""" + """Return boolean if volume is currently muted.""" return self._player_variables.get('muted', '0') == '1' @property def media_duration(self): - """Duration of current playing media in seconds.""" - duration = self._player_variables.get('durationstring', - "00:00:00").split(':') + """Return the duration of the current playing media in seconds.""" + duration = self._player_variables.get( + 'durationstring', "00:00:00").split(':') return \ int(duration[0]) * 3600 + \ int(duration[1]) * 60 + \ diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index f6891d23f3f..572898dd60a 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the MPD platform.""" + """Set up the MPD platform.""" daemon = config.get(CONF_HOST) port = config.get(CONF_PORT) name = config.get(CONF_NAME) @@ -136,23 +136,23 @@ class MpdDevice(MediaPlayerDevice): @property def media_content_id(self): - """Content ID of current playing media.""" + """Return the content ID of current playing media.""" return self.currentsong.get('file') @property def media_content_type(self): - """Content type of current playing media.""" + """Return the content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): - """Duration of current playing media in seconds.""" + """Return the duration of current playing media in seconds.""" # Time does not exist for streams return self.currentsong.get('time') @property def media_title(self): - """Title of current playing media.""" + """Return the title of current playing media.""" name = self.currentsong.get('name', None) title = self.currentsong.get('title', None) @@ -167,12 +167,12 @@ class MpdDevice(MediaPlayerDevice): @property def media_artist(self): - """Artist of current playing media (Music track only).""" + """Return the artist of current playing media (Music track only).""" return self.currentsong.get('artist') @property def media_album_name(self): - """Album of current playing media (Music track only).""" + """Return the album of current playing media (Music track only).""" return self.currentsong.get('album') @property @@ -192,7 +192,7 @@ class MpdDevice(MediaPlayerDevice): @property def source_list(self): - """List of available input sources.""" + """Return the list of available input sources.""" return self.playlists def select_source(self, source): diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py index 332aed4a839..c1b7b867c3b 100644 --- a/homeassistant/components/media_player/nad.py +++ b/homeassistant/components/media_player/nad.py @@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the NAD platform.""" + """Set up the NAD platform.""" from nad_receiver import NADReceiver add_devices([NAD( config.get(CONF_NAME), diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py index 67197d7d0da..18690cca871 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Onkyo platform.""" + """Set up the Onkyo platform.""" import eiscp from eiscp import eISCP @@ -52,12 +52,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if CONF_HOST in config and host not in KNOWN_HOSTS: try: - hosts.append(OnkyoDevice(eiscp.eISCP(host), - config.get(CONF_SOURCES), - name=config.get(CONF_NAME))) + hosts.append(OnkyoDevice( + eiscp.eISCP(host), config.get(CONF_SOURCES), + name=config.get(CONF_NAME))) KNOWN_HOSTS.append(host) except OSError: - _LOGGER.error('Unable to connect to receiver at %s.', host) + _LOGGER.error("Unable to connect to receiver at %s", host) else: for receiver in eISCP.discover(): if receiver.host not in KNOWN_HOSTS: @@ -90,9 +90,9 @@ class OnkyoDevice(MediaPlayerDevice): except (ValueError, OSError, AttributeError, AssertionError): if self._receiver.command_socket: self._receiver.command_socket = None - _LOGGER.info('Resetting connection to %s.', self._name) + _LOGGER.info("Resetting connection to %s", self._name) else: - _LOGGER.info('%s is disconnected. Attempting to reconnect.', + _LOGGER.info("%s is disconnected. Attempting to reconnect", self._name) return False return result @@ -142,7 +142,7 @@ class OnkyoDevice(MediaPlayerDevice): @property def volume_level(self): - """Volume level of the media player (0..1).""" + """Return the volume level of the media player (0..1).""" return self._volume @property @@ -157,7 +157,7 @@ class OnkyoDevice(MediaPlayerDevice): @property def source(self): - """"Return the current input source of the device.""" + """Return the current input source of the device.""" return self._current_source @property diff --git a/homeassistant/components/media_player/openhome.py b/homeassistant/components/media_player/openhome.py index c70822381a4..59d6a8c169f 100644 --- a/homeassistant/components/media_player/openhome.py +++ b/homeassistant/components/media_player/openhome.py @@ -22,13 +22,12 @@ SUPPORT_OPENHOME = SUPPORT_SELECT_SOURCE | \ _LOGGER = logging.getLogger(__name__) -# List of devices that have been registered DEVICES = [] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Openhome Platform.""" + """Set up the Openhome platform.""" from openhomedevice.Device import Device if not discovery_info: @@ -36,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): name = discovery_info.get('name') description = discovery_info.get('ssdp_description') - _LOGGER.info('Openhome device found, (%s)', name) + _LOGGER.info("Openhome device found: %s", name) device = Device(description) # if device has already been discovered @@ -153,7 +152,7 @@ class OpenhomeDevice(MediaPlayerDevice): @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return True @property diff --git a/homeassistant/components/media_player/panasonic_viera.py b/homeassistant/components/media_player/panasonic_viera.py index a5f9979d2d4..8c946ec0f0f 100644 --- a/homeassistant/components/media_player/panasonic_viera.py +++ b/homeassistant/components/media_player/panasonic_viera.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Panasonic Viera TV platform.""" + """Set up the Panasonic Viera TV platform.""" from panasonic_viera import RemoteControl mac = config.get(CONF_MAC) diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py index f742618822a..4e36fdab5a2 100644 --- a/homeassistant/components/media_player/pandora.py +++ b/homeassistant/components/media_player/pandora.py @@ -4,7 +4,6 @@ Component for controlling Pandora stations through the pianobar client. For more details about this platform, please refer to the documentation https://home-assistant.io/components/media_player.pandora/ """ - import logging import re import os @@ -46,12 +45,12 @@ STATION_PATTERN = re.compile(r'Station\s"(.+?)"', re.MULTILINE) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the media player pandora platform.""" + """Set up the Pandora media player platform.""" if not _pianobar_exists(): return False pandora = PandoraMediaPlayer('Pandora') - # make sure we end the pandora subprocess on exit in case user doesn't + # Make sure we end the pandora subprocess on exit in case user doesn't # power it down. def _stop_pianobar(_event): pandora.turn_off() @@ -64,7 +63,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): """A media player that uses the Pianobar interface to Pandora.""" def __init__(self, name): - """Initialize the demo device.""" + """Initialize the Pandora device.""" MediaPlayerDevice.__init__(self) self._name = name self._player_state = STATE_OFF @@ -79,7 +78,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): @property def should_poll(self): - """Should be polled for current state.""" + """Return the polling state.""" return True @property @@ -98,7 +97,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): if self._player_state != STATE_OFF: return self._pianobar = pexpect.spawn('pianobar') - _LOGGER.info('Started pianobar subprocess') + _LOGGER.info("Started pianobar subprocess") mode = self._pianobar.expect(['Receiving new playlist', 'Select station:', 'Email:']) @@ -106,10 +105,10 @@ class PandoraMediaPlayer(MediaPlayerDevice): # station list was presented. dismiss it. self._pianobar.sendcontrol('m') elif mode == 2: - _LOGGER.warning('The pianobar client is not configured to log in. ' - 'Please create a config file for it as described ' - 'at https://home-assistant.io' - '/components/media_player.pandora/') + _LOGGER.warning( + "The pianobar client is not configured to log in. " + "Please create a config file for it as described at " + "https://home-assistant.io/components/media_player.pandora/") # pass through the email/password prompts to quit cleanly self._pianobar.sendcontrol('m') self._pianobar.sendcontrol('m') @@ -126,16 +125,16 @@ class PandoraMediaPlayer(MediaPlayerDevice): """Turn the media player off.""" import pexpect if self._pianobar is None: - _LOGGER.info('Pianobar subprocess already stopped') + _LOGGER.info("Pianobar subprocess already stopped") return self._pianobar.send('q') try: - _LOGGER.info('Stopped Pianobar subprocess') + _LOGGER.debug("Stopped Pianobar subprocess") self._pianobar.terminate() except pexpect.exceptions.TIMEOUT: # kill the process group os.killpg(os.getpgid(self._pianobar.pid), signal.SIGTERM) - _LOGGER.info('Killed Pianobar subprocess') + _LOGGER.debug("Killed Pianobar subprocess") self._pianobar = None self._player_state = STATE_OFF self.schedule_update_ha_state() @@ -203,9 +202,9 @@ class PandoraMediaPlayer(MediaPlayerDevice): try: station_index = self._stations.index(source) except ValueError: - _LOGGER.warning('Station `%s` is not in list', source) + _LOGGER.warning("Station %s is not in list", source) return - _LOGGER.info('Setting station %s, %d', source, station_index) + _LOGGER.debug("Setting station %s, %d", source, station_index) self._send_station_list_command() self._pianobar.sendline('{}'.format(station_index)) self._pianobar.expect('\r\n') @@ -243,7 +242,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): 'Select station', 'Receiving new playlist']) except pexpect.exceptions.EOF: - _LOGGER.info('Pianobar process already exited.') + _LOGGER.info("Pianobar process already exited") return None self._log_match() @@ -252,12 +251,12 @@ class PandoraMediaPlayer(MediaPlayerDevice): response = None elif match_idx == 2: # stuck on a station selection dialog. Clear it. - _LOGGER.warning('On unexpected station list page.') + _LOGGER.warning("On unexpected station list page") self._pianobar.sendcontrol('m') # press enter self._pianobar.sendcontrol('m') # do it again b/c an 'i' got in response = self.update_playing_status() elif match_idx == 3: - _LOGGER.debug('Received new playlist list.') + _LOGGER.debug("Received new playlist list") response = self.update_playing_status() else: response = self._pianobar.before.decode('utf-8') @@ -268,9 +267,9 @@ class PandoraMediaPlayer(MediaPlayerDevice): station_match = re.search(STATION_PATTERN, response) if station_match: self._station = station_match.group(1) - _LOGGER.debug('Got station as: %s', self._station) + _LOGGER.debug("Got station as: %s", self._station) else: - _LOGGER.warning('No station match. ') + _LOGGER.warning("No station match") def _update_current_song(self, response): """Update info about current song.""" @@ -278,9 +277,9 @@ class PandoraMediaPlayer(MediaPlayerDevice): if song_match: (self._media_title, self._media_artist, self._media_album) = song_match.groups() - _LOGGER.debug('Got song as: %s', self._media_title) + _LOGGER.debug("Got song as: %s", self._media_title) else: - _LOGGER.warning('No song match.') + _LOGGER.warning("No song match") @util.Throttle(MIN_TIME_BETWEEN_UPDATES) def _update_song_position(self): @@ -305,7 +304,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): def _log_match(self): """Log grabbed values from console.""" - _LOGGER.debug('Before: %s\nMatch: %s\nAfter: %s', + _LOGGER.debug("Before: %s\nMatch: %s\nAfter: %s", repr(self._pianobar.before), repr(self._pianobar.match), repr(self._pianobar.after)) @@ -313,10 +312,10 @@ class PandoraMediaPlayer(MediaPlayerDevice): def _send_pianobar_command(self, service_cmd): """Send a command to Pianobar.""" command = CMD_MAP.get(service_cmd) - _LOGGER.debug('Sending pinaobar command %s for %s', - command, service_cmd) + _LOGGER.debug( + "Sending pinaobar command %s for %s", command, service_cmd) if command is None: - _LOGGER.info('Command %s not supported yet', service_cmd) + _LOGGER.info("Command %s not supported yet", service_cmd) self._clear_buffer() self._pianobar.sendline(command) @@ -324,16 +323,16 @@ class PandoraMediaPlayer(MediaPlayerDevice): """List defined Pandora stations.""" self._send_station_list_command() station_lines = self._pianobar.before.decode('utf-8') - _LOGGER.debug('Getting stations: %s', station_lines) + _LOGGER.debug("Getting stations: %s", station_lines) self._stations = [] for line in station_lines.split('\r\n'): match = re.search(r'\d+\).....(.+)', line) if match: station = match.group(1).strip() - _LOGGER.debug('Found station %s', station) + _LOGGER.debug("Found station %s", station) self._stations.append(station) else: - _LOGGER.debug('No station match on `%s`', line) + _LOGGER.debug("No station match on %s", line) self._pianobar.sendcontrol('m') # press enter with blank line self._pianobar.sendcontrol('m') # do it twice in case an 'i' got in @@ -360,9 +359,8 @@ def _pianobar_exists(): if pianobar_exe: return True else: - _LOGGER.warning('The Pandora component depends on the Pianobar ' - 'client, which cannot be found. Please install ' - 'using instructions at' - 'https://home-assistant.io' - '/components/media_player.pandora/') + _LOGGER.warning( + "The Pandora component depends on the Pianobar client, which " + "cannot be found. Please install using instructions at " + "https://home-assistant.io/components/media_player.pandora/") return False diff --git a/homeassistant/components/media_player/philips_js.py b/homeassistant/components/media_player/philips_js.py index a4d3b02381a..b79f3eeeb89 100644 --- a/homeassistant/components/media_player/philips_js.py +++ b/homeassistant/components/media_player/philips_js.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Philips TV platform.""" + """Set up the Philips TV platform.""" import haphilipsjs name = config.get(CONF_NAME) diff --git a/homeassistant/components/media_player/pioneer.py b/homeassistant/components/media_player/pioneer.py index 73f0d18f597..eb346502d95 100644 --- a/homeassistant/components/media_player/pioneer.py +++ b/homeassistant/components/media_player/pioneer.py @@ -40,11 +40,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Pioneer platform.""" - pioneer = PioneerDevice(config.get(CONF_NAME), - config.get(CONF_HOST), - config.get(CONF_PORT), - config.get(CONF_TIMEOUT)) + """Set up the Pioneer platform.""" + pioneer = PioneerDevice( + config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT), + config.get(CONF_TIMEOUT)) if pioneer.update(): add_devices([pioneer]) @@ -126,9 +125,8 @@ class PioneerDevice(MediaPlayerDevice): # Build the source name dictionaries if necessary if not self._source_name_to_number: for i in range(MAX_SOURCE_NUMBERS): - result = self.telnet_request(telnet, - "?RGB" + str(i).zfill(2), - "RGB") + result = self.telnet_request( + telnet, "?RGB" + str(i).zfill(2), "RGB") if not result: continue diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index ee8ae4840b2..e2f322a8ec8 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -12,34 +12,24 @@ from urllib.parse import urlparse import requests import voluptuous as vol + from homeassistant import util from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, - MEDIA_TYPE_TVSHOW, - MEDIA_TYPE_VIDEO, - PLATFORM_SCHEMA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - MediaPlayerDevice, -) + MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, PLATFORM_SCHEMA, + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + MediaPlayerDevice) from homeassistant.const import ( - DEVICE_DEFAULT_NAME, - STATE_IDLE, - STATE_OFF, - STATE_PAUSED, - STATE_PLAYING, -) + DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import track_utc_time_change from homeassistant.loader import get_component REQUIREMENTS = ['plexapi==2.0.2'] + +_CONFIGURING = {} +_LOGGER = logging.getLogger(__name__) + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) @@ -59,10 +49,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ cv.boolean, }) -# Map ip to request id for configuring -_CONFIGURING = {} -_LOGGER = logging.getLogger(__name__) - def config_from_file(filename, config=None): """Small configuration file management function.""" @@ -72,7 +58,7 @@ def config_from_file(filename, config=None): with open(filename, 'w') as fdesc: fdesc.write(json.dumps(config)) except IOError as error: - _LOGGER.error('Saving config file failed: %s', error) + _LOGGER.error("Saving config file failed: %s", error) return False return True else: @@ -82,7 +68,7 @@ def config_from_file(filename, config=None): with open(filename, 'r') as fdesc: return json.loads(fdesc.read()) except IOError as error: - _LOGGER.error('Reading config file failed: %s', error) + _LOGGER.error("Reading config file failed: %s", error) # This won't work yet return False else: @@ -90,11 +76,11 @@ def config_from_file(filename, config=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the Plex platform.""" + """Set up the Plex platform.""" # get config from plex.conf file_config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) - if len(file_config): + if file_config: # Setup a configured PlexServer host, token = file_config.popitem() token = token['token'] @@ -102,7 +88,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): elif discovery_info is not None: # Parse discovery data host = discovery_info.get('host') - _LOGGER.info('Discovered PLEX server: %s', host) + _LOGGER.info("Discovered PLEX server: %s", host) if host in _CONFIGURING: return @@ -114,7 +100,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_plexserver(host, token, hass, config, add_devices_callback): - """Setup a plexserver based on host parameter.""" + """Set up a plexserver based on host parameter.""" import plexapi.server import plexapi.exceptions @@ -124,8 +110,7 @@ def setup_plexserver(host, token, hass, config, add_devices_callback): plexapi.exceptions.NotFound) as error: _LOGGER.info(error) # No token or wrong token - request_configuration(host, hass, config, - add_devices_callback) + request_configuration(host, hass, config, add_devices_callback) return # If we came here and configuring this host, mark as done @@ -133,14 +118,14 @@ def setup_plexserver(host, token, hass, config, add_devices_callback): request_id = _CONFIGURING.pop(host) configurator = get_component('configurator') configurator.request_done(request_id) - _LOGGER.info('Discovery configuration done!') + _LOGGER.info("Discovery configuration done") # Save config if not config_from_file( hass.config.path(PLEX_CONFIG_FILE), {host: { 'token': token }}): - _LOGGER.error('failed to save config file') + _LOGGER.error("Failed to save configuration file") _LOGGER.info('Connected to: http://%s', host) @@ -155,10 +140,10 @@ def setup_plexserver(host, token, hass, config, add_devices_callback): try: devices = plexserver.clients() except plexapi.exceptions.BadRequest: - _LOGGER.exception('Error listing plex devices') + _LOGGER.exception("Error listing plex devices") return except OSError: - _LOGGER.error('Could not connect to plex server at http://%s', + _LOGGER.error("Could not connect to plex server at http://%s", host) return @@ -204,7 +189,7 @@ def setup_plexserver(host, token, hass, config, add_devices_callback): try: sessions = plexserver.sessions() except plexapi.exceptions.BadRequest: - _LOGGER.exception('Error listing plex sessions') + _LOGGER.exception("Error listing plex sessions") return plex_sessions.clear() @@ -228,10 +213,9 @@ def request_configuration(host, hass, config, add_devices_callback): return def plex_configuration_callback(data): - """The actions to do when our configuration callback is called.""" - setup_plexserver(host, - data.get('token'), hass, config, - add_devices_callback) + """Handle configuration changes.""" + setup_plexserver( + host, data.get('token'), hass, config, add_devices_callback) _CONFIGURING[host] = configurator.request_config( hass, @@ -386,9 +370,8 @@ class PlexClient(MediaPlayerDevice): # media type if self._session_type == 'clip': - _LOGGER.debug('Clip content type detected, ' - 'compatibility may vary: %s', - self.entity_id) + _LOGGER.debug("Clip content type detected, compatibility may " + "vary: %s", self.entity_id) self._media_content_type = MEDIA_TYPE_TVSHOW elif self._session_type == 'episode': self._media_content_type = MEDIA_TYPE_TVSHOW @@ -447,9 +430,8 @@ class PlexClient(MediaPlayerDevice): self._session.originalTitle) # use album artist if track artist is missing if self._media_artist is None: - _LOGGER.debug( - 'Using album artist because track artist ' - 'was not found: %s', self.entity_id) + _LOGGER.debug("Using album artist because track artist was " + "not found: %s", self.entity_id) self._media_artist = self._media_album_artist else: self._media_album_name = None @@ -475,8 +457,8 @@ class PlexClient(MediaPlayerDevice): self._session.grandparentThumb) if thumb_url is None: - _LOGGER.debug('Using media art because media thumb ' - 'was not found: %s', self.entity_id) + _LOGGER.debug("Using media art because media thumb " + "was not found: %s", self.entity_id) thumb_url = self._get_thumbnail_url(self._session.art) self._media_image_url = thumb_url @@ -563,16 +545,15 @@ class PlexClient(MediaPlayerDevice): @property def media_content_id(self): - """Content ID of current playing media.""" + """Return the content ID of current playing media.""" return self._media_content_id @property def media_content_type(self): - """Content type of current playing media.""" + """Return the content type of current playing media.""" if self._session_type == 'clip': - _LOGGER.debug('Clip content type detected, ' - 'compatibility may vary: %s', - self.entity_id) + _LOGGER.debug("Clip content type detected, " + "compatibility may vary: %s", self.entity_id) return MEDIA_TYPE_TVSHOW elif self._session_type == 'episode': return MEDIA_TYPE_TVSHOW @@ -585,57 +566,57 @@ class PlexClient(MediaPlayerDevice): @property def media_artist(self): - """Artist of current playing media, music track only.""" + """Return the artist of current playing media, music track only.""" return self._media_artist @property def media_album_name(self): - """Album name of current playing media, music track only.""" + """Return the album name of current playing media, music track only.""" return self._media_album_name @property def media_album_artist(self): - """Album artist of current playing media, music track only.""" + """Return the album artist of current playing media, music only.""" return self._media_album_artist @property def media_track(self): - """Track number of current playing media, music track only.""" + """Return the track number of current playing media, music only.""" return self._media_track @property def media_duration(self): - """Duration of current playing media in seconds.""" + """Return the duration of current playing media in seconds.""" return self._media_duration @property def media_image_url(self): - """Image url of current playing media.""" + """Return the image URL of current playing media.""" return self._media_image_url @property def media_title(self): - """Title of current playing media.""" + """Return the title of current playing media.""" return self._media_title @property def media_season(self): - """Season of curent playing media (TV Show only).""" + """Return the season of current playing media (TV Show only).""" return self._media_season @property def media_series_title(self): - """The title of the series of current playing media (TV Show only).""" + """Return the title of the series of current playing media.""" return self._media_series_title @property def media_episode(self): - """Episode of current playing media (TV Show only).""" + """Return the episode of current playing media (TV Show only).""" return self._media_episode @property def make(self): - """The make of the device (ex. SHIELD Android TV).""" + """Return the make of the device (ex. SHIELD Android TV).""" return self._make @property @@ -657,8 +638,8 @@ class PlexClient(MediaPlayerDevice): # no mute support elif self.make.lower() == "shield android tv": _LOGGER.debug( - 'Shield Android TV client detected, disabling mute ' - 'controls: %s', self.entity_id) + "Shield Android TV client detected, disabling mute " + "controls: %s", self.entity_id) return (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_STOP | SUPPORT_VOLUME_SET | SUPPORT_PLAY | @@ -666,8 +647,8 @@ class PlexClient(MediaPlayerDevice): # Only supports play,pause,stop (and off which really is stop) elif self.make.lower().startswith("tivo"): _LOGGER.debug( - 'Tivo client detected, only enabling pause, play, ' - 'stop, and off controls: %s', self.entity_id) + "Tivo client detected, only enabling pause, play, " + "stop, and off controls: %s", self.entity_id) return (SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP | SUPPORT_TURN_OFF) # Not all devices support playback functionality @@ -693,8 +674,8 @@ class PlexClient(MediaPlayerDevice): # point controls to server since that's where the # playback is occuring _LOGGER.debug( - 'Local client detected, redirecting controls to ' - 'Plex server: %s', self.entity_id) + "Local client detected, redirecting controls to " + "Plex server: %s", self.entity_id) server_url = self.device.server.baseurl client_url = self.device.baseurl self.device.baseurl = "{}://{}:{}".format( @@ -830,7 +811,7 @@ class PlexClient(MediaPlayerDevice): break if target_season is None: - _LOGGER.error('Season not found: %s\\%s - S%sE%s', library_name, + _LOGGER.error("Season not found: %s\\%s - S%sE%s", library_name, show_name, str(season_number).zfill(2), str(episode_number).zfill(2)) @@ -847,7 +828,7 @@ class PlexClient(MediaPlayerDevice): break if target_episode is None: - _LOGGER.error('Episode not found: %s\\%s - S%sE%s', + _LOGGER.error("Episode not found: %s\\%s - S%sE%s", library_name, show_name, str(season_number).zfill(2), str(episode_number).zfill(2)) @@ -858,14 +839,14 @@ class PlexClient(MediaPlayerDevice): """Instruct Plex client to play a piece of media.""" if not (self.device and 'playback' in self._device_protocol_capabilities): - _LOGGER.error('Client cannot play media: %s', self.entity_id) + _LOGGER.error("Client cannot play media: %s", self.entity_id) return import plexapi.playqueue - playqueue = plexapi.playqueue.PlayQueue.create(self.device.server, - media, **params) + playqueue = plexapi.playqueue.PlayQueue.create( + self.device.server, media, **params) - # delete dynamic playlists used to build playqueue (ex. play tv season) + # Delete dynamic playlists used to build playqueue (ex. play tv season) if delete: media.delete() @@ -873,16 +854,13 @@ class PlexClient(MediaPlayerDevice): server_url = self.device.server.baseurl.split(':') self.device.sendCommand('playback/playMedia', **dict({ - 'machineIdentifier': - self.device.server.machineIdentifier, - 'address': - server_url[1].strip('/'), - 'port': - server_url[-1], - 'key': - media.key, + 'machineIdentifier': self.device.server.machineIdentifier, + 'address': server_url[1].strip('/'), + 'port': server_url[-1], + 'key': media.key, 'containerKey': - '/playQueues/%s?window=100&own=1' % playqueue.playQueueID, + '/playQueues/{}?window=100&own=1'.format( + playqueue.playQueueID), }, **params)) @property diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py index 59e6a19bff8..97f7ba2e716 100644 --- a/homeassistant/components/media_player/roku.py +++ b/homeassistant/components/media_player/roku.py @@ -39,17 +39,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Roku platform.""" + """Set up the Roku platform.""" hosts = [] if discovery_info: - host = discovery_info[0] + host = discovery_info.get("host") if host in KNOWN_HOSTS: return - _LOGGER.debug('Discovered Roku: %s', host) - hosts.append(discovery_info[0]) + _LOGGER.debug("Discovered Roku: %s", host) + hosts.append(discovery_info.get("host")) elif CONF_HOST in config: hosts.append(config.get(CONF_HOST)) @@ -175,9 +175,8 @@ class RokuDevice(MediaPlayerDevice): elif self.current_app.id is None: return None - return 'http://{0}:{1}/query/icon/{2}'.format(self.ip_address, - DEFAULT_PORT, - self.current_app.id) + return 'http://{0}:{1}/query/icon/{2}'.format( + self.ip_address, DEFAULT_PORT, self.current_app.id) @property def app_name(self): diff --git a/homeassistant/components/media_player/russound_rnet.py b/homeassistant/components/media_player/russound_rnet.py index 7ae16adcce3..6615f85db65 100644 --- a/homeassistant/components/media_player/russound_rnet.py +++ b/homeassistant/components/media_player/russound_rnet.py @@ -4,8 +4,8 @@ Support for interfacing with Russound via RNET Protocol. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.russound_rnet/ """ - import logging + import voluptuous as vol from homeassistant.components.media_player import ( @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Russound RNET platform.""" + """Set up the Russound RNET platform.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -163,5 +163,5 @@ class RussoundRNETDevice(MediaPlayerDevice): @property def source_list(self): - """List of available input sources.""" + """Return a list of available input sources.""" return self._sources diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index c99ad49577c..0153eb687ff 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Samsung TV platform.""" + """Set up the Samsung TV platform.""" known_devices = hass.data.get(KNOWN_DEVICES_KEY) if known_devices is None: known_devices = set() @@ -66,8 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): timeout = DEFAULT_TIMEOUT mac = None else: - _LOGGER.warning( - 'Internal error on samsungtv component. Cannot determine device') + _LOGGER.warning("Cannot determine device") return # Only add a device once, so discovered devices do not override manual diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index e23616a47a9..ae90e141289 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -154,6 +154,17 @@ clear_playlist: description: Name(s) of entites to change source on example: 'media_player.living_room_chromecast' +shuffle_set: + description: Set shuffling state + + fields: + entity_id: + description: Name(s) of entities to set + example: 'media_player.spotify' + shuffle: + description: True/false for enabling/disabling shuffle + example: true + sonos_join: description: Group player together. diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index 3e06cca38a5..f893dcdeed1 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Snapcast platform.""" + """Set up the Snapcast platform.""" import snapcast.control host = config.get(CONF_HOST) port = config.get(CONF_PORT, snapcast.control.CONTROL_PORT) @@ -42,8 +42,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: server = snapcast.control.Snapserver(host, port) except socket.gaierror: - _LOGGER.error('Could not connect to Snapcast server at %s:%d', - host, port) + _LOGGER.error( + "Could not connect to Snapcast server at %s:%d", host, port) return False add_devices([SnapcastDevice(client) for client in server.clients]) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 00cc52ab3a1..da75b89c19d 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -92,7 +92,7 @@ SONOS_SET_TIMER_SCHEMA = SONOS_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Sonos platform.""" + """Set up the Sonos platform.""" import soco if DATA_SONOS not in hass.data: @@ -105,7 +105,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info: player = soco.SoCo(discovery_info.get('host')) - # if device allready exists by config + # if device already exists by config if player.uid in [x.unique_id for x in hass.data[DATA_SONOS]]: return @@ -132,18 +132,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): interface_addr=config.get(CONF_INTERFACE_ADDR)) if not players: - _LOGGER.warning('No Sonos speakers found.') + _LOGGER.warning("No Sonos speakers found") return hass.data[DATA_SONOS] = [SonosDevice(p) for p in players] add_devices(hass.data[DATA_SONOS], True) - _LOGGER.info('Added %s Sonos speakers', len(players)) + _LOGGER.info("Added %s Sonos speakers", len(players)) descriptions = load_yaml_config_file( path.join(path.dirname(__file__), 'services.yaml')) def service_handle(service): - """Internal func for applying a service.""" + """Handle for services.""" entity_ids = service.data.get('entity_id') if entity_ids: @@ -221,28 +221,28 @@ def _get_entity_from_soco(hass, soco): for device in hass.data[DATA_SONOS]: if soco == device.soco: return device - raise ValueError("No entity for SoCo device!") + raise ValueError("No entity for SoCo device") def soco_error(funct): - """Decorator to catch soco exceptions.""" + """Catch soco exceptions.""" @ft.wraps(funct) def wrapper(*args, **kwargs): - """Wrapper for all soco exception.""" + """Wrap for all soco exception.""" from soco.exceptions import SoCoException try: return funct(*args, **kwargs) except SoCoException as err: - _LOGGER.error("Error on %s with %s.", funct.__name__, err) + _LOGGER.error("Error on %s with %s", funct.__name__, err) return wrapper def soco_coordinator(funct): - """Decorator to call funct on coordinator.""" + """Call function on coordinator.""" @ft.wraps(funct) def wrapper(device, *args, **kwargs): - """Wrapper for call to coordinator.""" + """Wrap for call to coordinator.""" if device.is_coordinator: return funct(device, *args, **kwargs) return funct(device.coordinator, *args, **kwargs) @@ -296,7 +296,7 @@ class SonosDevice(MediaPlayerDevice): @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return True @property @@ -340,8 +340,7 @@ class SonosDevice(MediaPlayerDevice): def _is_available(self): try: sock = socket.create_connection( - address=(self._player.ip_address, 1443), - timeout=3) + address=(self._player.ip_address, 1443), timeout=3) sock.close() return True except socket.error: diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py index fb2e02494df..6a118844727 100644 --- a/homeassistant/components/media_player/soundtouch.py +++ b/homeassistant/components/media_player/soundtouch.py @@ -1,4 +1,9 @@ -"""Support for interface with a Bose Soundtouch.""" +""" +Support for interface with a Bose Soundtouch. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.soundtouch/ +""" import logging from os import path @@ -69,14 +74,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Bose Soundtouch platform.""" + """Set up the Bose Soundtouch platform.""" if DATA_SOUNDTOUCH not in hass.data: hass.data[DATA_SOUNDTOUCH] = [] if discovery_info: - # Discovery - host = discovery_info["host"] - port = int(discovery_info["port"]) + host = discovery_info['host'] + port = int(discovery_info['port']) # if device already exists by config if host in [device.config['host'] for device in @@ -92,7 +96,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.data[DATA_SOUNDTOUCH].append(soundtouch_device) add_devices([soundtouch_device]) else: - # Config name = config.get(CONF_NAME) remote_config = { 'id': 'ha.component.soundtouch', @@ -107,7 +110,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): path.join(path.dirname(__file__), 'services.yaml')) def service_handle(service): - """Internal func for applying a service.""" + """Handle the applying of a service.""" master_device_id = service.data.get('master') slaves_ids = service.data.get('slaves') slaves = [] @@ -119,8 +122,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device.entity_id == master_device_id].__iter__(), None) if master is None: - _LOGGER.warning("Unable to find master with entity_id:" + str( - master_device_id)) + _LOGGER.warning("Unable to find master with entity_id: %s", + str(master_device_id)) return if service.service == SERVICE_PLAY_EVERYWHERE: diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index ce4230bf92c..a73a4a922ca 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -4,7 +4,6 @@ Support for interacting with Spotify Connect. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.spotify/ """ - import logging from datetime import timedelta @@ -16,13 +15,12 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET, SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, PLATFORM_SCHEMA, - MediaPlayerDevice) + SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, + PLATFORM_SCHEMA, MediaPlayerDevice) from homeassistant.const import ( CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv - COMMIT = '544614f4b1d508201d363e84e871f86c90aa26b2' REQUIREMENTS = ['https://github.com/happyleavesaoc/spotipy/' 'archive/%s.zip#spotipy==2.4.4' % COMMIT] @@ -33,7 +31,7 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_SPOTIFY = SUPPORT_VOLUME_SET | SUPPORT_PAUSE | SUPPORT_PLAY |\ SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\ - SUPPORT_PLAY_MEDIA + SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET SCOPE = 'user-read-playback-state user-modify-playback-state' DEFAULT_CACHE_PATH = '.spotify-token-cache' @@ -72,7 +70,7 @@ def request_configuration(hass, config, add_devices, oauth): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Spotify platform.""" + """Set up the Spotify platform.""" import spotipy.oauth2 callback_url = '{}{}'.format(hass.config.api.base_url, AUTH_CALLBACK_PATH) cache = config.get(CONF_CACHE_PATH, hass.config.path(DEFAULT_CACHE_PATH)) @@ -82,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): cache_path=cache) token_info = oauth.get_cached_token() if not token_info: - _LOGGER.info('no token; requesting authorization') + _LOGGER.info("no token; requesting authorization") hass.http.register_view(SpotifyAuthCallbackView( config, add_devices, oauth)) request_configuration(hass, config, add_devices, oauth) @@ -132,6 +130,7 @@ class SpotifyMediaPlayer(MediaPlayerDevice): self._current_device = None self._devices = None self._volume = None + self._shuffle = False self._player = None self._token_info = self._oauth.get_cached_token() @@ -185,11 +184,17 @@ class SpotifyMediaPlayer(MediaPlayerDevice): self._volume = device.get('volume_percent') / 100 if device.get('name'): self._current_device = device.get('name') + if device.get('shuffle_state'): + self._shuffle = device.get('shuffle_state') def set_volume_level(self, volume): """Set the volume level.""" self._player.volume(int(volume * 100)) + def set_shuffle(self, shuffle): + """Enable/Disable shuffle mode.""" + self._player.shuffle(shuffle) + def media_next_track(self): """Skip to next track.""" self._player.next_track() @@ -218,69 +223,74 @@ class SpotifyMediaPlayer(MediaPlayerDevice): elif media_type == MEDIA_TYPE_PLAYLIST: kwargs['context_uri'] = media_id else: - _LOGGER.error('media type %s is not supported', media_type) + _LOGGER.error("media type %s is not supported", media_type) return if not media_id.startswith('spotify:'): - _LOGGER.error('media id must be spotify uri') + _LOGGER.error("media id must be spotify uri") return self._player.start_playback(**kwargs) @property def name(self): - """Name.""" + """Return the name.""" return self._name @property def icon(self): - """Icon.""" + """Return the icon.""" return ICON @property def state(self): - """Playback state.""" + """Return the playback state.""" return self._state @property def volume_level(self): - """Device volume.""" + """Return the device volume.""" return self._volume + @property + def shuffle(self): + """Shuffling state.""" + return self._shuffle + @property def source_list(self): - """Playback devices.""" + """Return a list of source devices.""" return list(self._devices.keys()) @property def source(self): - """Current playback device.""" + """Return the current playback device.""" return self._current_device @property def media_content_id(self): - """Media URL.""" + """Return the media URL.""" return self._uri @property def media_image_url(self): - """Media image url.""" + """Return the media image URL.""" return self._image_url @property def media_artist(self): - """Media artist.""" + """Return the media artist.""" return self._artist @property def media_album_name(self): - """Media album.""" + """Return the media album.""" return self._album @property def media_title(self): - """Media title.""" + """Return the media title.""" return self._title @property def supported_features(self): - """Media player features that are supported.""" + """Return the media player features that are supported.""" return SUPPORT_SPOTIFY diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 40c48f55215..42efe183421 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the squeezebox platform.""" + """Set up the squeezebox platform.""" import socket username = config.get(CONF_USERNAME) @@ -65,8 +65,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): try: ipaddr = socket.gethostbyname(host) except (OSError) as error: - _LOGGER.error("Could not communicate with %s:%d: %s", - host, port, error) + _LOGGER.error( + "Could not communicate with %s:%d: %s", host, port, error) return False _LOGGER.debug("Creating LMS object for %s", ipaddr) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 5dfe007976f..68c2a06cf23 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -17,19 +17,19 @@ from homeassistant.components.media_player import ( ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_INPUT_SOURCE_LIST, - ATTR_MEDIA_POSITION, + ATTR_MEDIA_POSITION, ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_POSITION_UPDATED_AT, DOMAIN, SERVICE_PLAY_MEDIA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, - ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE, SERVICE_CLEAR_PLAYLIST, - MediaPlayerDevice) + SUPPORT_SHUFFLE_SET, ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE, + SERVICE_CLEAR_PLAYLIST, MediaPlayerDevice) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, - SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE, STATE_OFF, STATE_ON, - SERVICE_MEDIA_STOP, ATTR_SUPPORTED_FEATURES) + SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, SERVICE_SHUFFLE_SET, STATE_IDLE, + STATE_OFF, STATE_ON, SERVICE_MEDIA_STOP, ATTR_SUPPORTED_FEATURES) from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.service import async_call_from_config @@ -51,7 +51,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the universal media players.""" + """Set up the universal media players.""" if not validate_config(config): return @@ -72,7 +72,7 @@ def validate_config(config): # Validate name if CONF_NAME not in config: - _LOGGER.error('Universal Media Player configuration requires name') + _LOGGER.error("Universal Media Player configuration requires name") return False validate_children(config) @@ -83,7 +83,7 @@ def validate_config(config): for key in config: if key not in [CONF_NAME, CONF_CHILDREN, CONF_COMMANDS, CONF_ATTRS]: _LOGGER.warning( - 'Universal Media Player (%s) unrecognized parameter %s', + "Universal Media Player (%s) unrecognized parameter %s", config[CONF_NAME], key) del_keys.append(key) for key in del_keys: @@ -96,13 +96,12 @@ def validate_children(config): """Validate children.""" if CONF_CHILDREN not in config: _LOGGER.info( - 'No children under Universal Media Player (%s)', config[CONF_NAME]) + "No children under Universal Media Player (%s)", config[CONF_NAME]) config[CONF_CHILDREN] = [] elif not isinstance(config[CONF_CHILDREN], list): _LOGGER.warning( - 'Universal Media Player (%s) children not list in config. ' - 'They will be ignored.', - config[CONF_NAME]) + "Universal Media Player (%s) children not list in config. " + "They will be ignored", config[CONF_NAME]) config[CONF_CHILDREN] = [] @@ -112,9 +111,8 @@ def validate_commands(config): config[CONF_COMMANDS] = {} elif not isinstance(config[CONF_COMMANDS], dict): _LOGGER.warning( - 'Universal Media Player (%s) specified commands not dict in ' - 'config. They will be ignored.', - config[CONF_NAME]) + "Universal Media Player (%s) specified commands not dict in " + "config. They will be ignored", config[CONF_NAME]) config[CONF_COMMANDS] = {} @@ -124,9 +122,8 @@ def validate_attributes(config): config[CONF_ATTRS] = {} elif not isinstance(config[CONF_ATTRS], dict): _LOGGER.warning( - 'Universal Media Player (%s) specified attributes ' - 'not dict in config. They will be ignored.', - config[CONF_NAME]) + "Universal Media Player (%s) specified attributes " + "not dict in config. They will be ignored", config[CONF_NAME]) config[CONF_ATTRS] = {} for key, val in config[CONF_ATTRS].items(): @@ -173,8 +170,8 @@ class UniversalMediaPlayer(MediaPlayerDevice): def _override_or_child_attr(self, 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]) + return self._entity_lkp( + self._attrs[attr_name][0], self._attrs[attr_name][1]) return self._child_attr(attr_name) @@ -215,8 +212,8 @@ class UniversalMediaPlayer(MediaPlayerDevice): def master_state(self): """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]) + master_state = self._entity_lkp( + self._attrs[CONF_STATE][0], self._attrs[CONF_STATE][1]) return master_state if master_state else STATE_OFF else: return None @@ -228,7 +225,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def state(self): - """Current state of media player. + """Return the current state of media player. Off if master state is off else Status of first active child @@ -257,17 +254,17 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def media_content_id(self): - """Content ID of current playing media.""" + """Return the 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.""" + """Return the 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.""" + """Return the duration of current playing media in seconds.""" return self._child_attr(ATTR_MEDIA_DURATION) @property @@ -313,7 +310,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def media_series_title(self): - """The title of the series of current playing media (TV Show only).""" + """Return the title of the series of current playing media (TV).""" return self._child_attr(ATTR_MEDIA_SERIES_TITLE) @property @@ -348,7 +345,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def source(self): - """"Return the current input source of the device.""" + """Return the current input source of the device.""" return self._override_or_child_attr(ATTR_INPUT_SOURCE) @property @@ -356,6 +353,11 @@ class UniversalMediaPlayer(MediaPlayerDevice): """List of available input sources.""" return self._override_or_child_attr(ATTR_INPUT_SOURCE_LIST) + @property + def shuffle(self): + """Boolean if shuffling is enabled.""" + return self._override_or_child_attr(ATTR_MEDIA_SHUFFLE) + @property def supported_features(self): """Flag media player features that are supported.""" @@ -383,6 +385,10 @@ class UniversalMediaPlayer(MediaPlayerDevice): if SERVICE_CLEAR_PLAYLIST in self._cmds: flags |= SUPPORT_CLEAR_PLAYLIST + if SERVICE_SHUFFLE_SET in self._cmds and \ + ATTR_MEDIA_SHUFFLE in self._attrs: + flags |= SUPPORT_SHUFFLE_SET + return flags @property @@ -524,6 +530,15 @@ class UniversalMediaPlayer(MediaPlayerDevice): """ return self._async_call_service(SERVICE_CLEAR_PLAYLIST) + def async_set_shuffle(self, shuffle): + """Enable/disable shuffling. + + This method must be run in the event loop and returns a coroutine. + """ + data = {ATTR_MEDIA_SHUFFLE: shuffle} + return self._async_call_service( + SERVICE_SHUFFLE_SET, data, allow_override=True) + @asyncio.coroutine def async_update(self): """Update state in HA.""" diff --git a/homeassistant/components/media_player/vlc.py b/homeassistant/components/media_player/vlc.py index 9439e6da5ad..f77b06054e1 100644 --- a/homeassistant/components/media_player/vlc.py +++ b/homeassistant/components/media_player/vlc.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the vlc platform.""" + """Set up the vlc platform.""" add_devices([VlcDevice(config.get(CONF_NAME, DEFAULT_NAME), config.get(CONF_ARGUMENTS))]) diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py index 5944921f94f..9bf0351d200 100755 --- a/homeassistant/components/media_player/volumio.py +++ b/homeassistant/components/media_player/volumio.py @@ -140,7 +140,7 @@ class Volumio(MediaPlayerDevice): if str(url[0:2]).lower() == 'ht': mediaurl = url else: - mediaurl = "http://" + self.host + ":" + str(self.port) + url + mediaurl = "http://{}:{}{}".format(self.host, self.port, url) return mediaurl @property diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py index c1b51e2d32a..112b84ec5f0 100644 --- a/homeassistant/components/media_player/webostv.py +++ b/homeassistant/components/media_player/webostv.py @@ -25,7 +25,7 @@ from homeassistant.const import ( from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pylgtv==0.1.6', +REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==3.2', 'wakeonlan==0.2.2'] @@ -62,7 +62,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the LG WebOS TV platform.""" + """Set up the LG WebOS TV platform.""" if discovery_info is not None: host = urlparse(discovery_info[1]).hostname else: @@ -84,7 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def setup_tv(host, mac, name, customize, config, hass, add_devices): - """Setup a LG WebOS TV based on host parameter.""" + """Set up a LG WebOS TV based on host parameter.""" from pylgtv import WebOsClient from pylgtv import PyLGTVPairException from websockets.exceptions import ConnectionClosed @@ -133,7 +133,7 @@ def request_configuration( # pylint: disable=unused-argument def lgtv_configuration_callback(data): - """The actions to do when our configuration callback is called.""" + """Handle configuration changes.""" setup_tv(host, mac, name, customize, config, hass, add_devices) _CONFIGURING[host] = configurator.request_config( @@ -173,34 +173,55 @@ class LgWebOSDevice(MediaPlayerDevice): """Retrieve the latest data.""" from websockets.exceptions import ConnectionClosed try: - self._state = STATE_PLAYING - self._muted = self._client.get_muted() - self._volume = self._client.get_volume() - self._current_source_id = self._client.get_input() - self._source_list = {} - self._app_list = {} + current_input = self._client.get_input() + if current_input is not None: + self._current_source_id = current_input + if self._state in (STATE_UNKNOWN, STATE_OFF): + self._state = STATE_PLAYING + else: + self._state = STATE_OFF + self._current_source = None + self._current_source_id = None - custom_sources = self._customize.get(CONF_SOURCES, []) + if self._state is not STATE_OFF: + self._muted = self._client.get_muted() + self._volume = self._client.get_volume() - for app in self._client.get_apps(): - self._app_list[app['id']] = app - if app['id'] == self._current_source_id: - self._current_source = app['title'] - self._source_list[app['title']] = app - elif (app['id'] in custom_sources or - any(word in app['title'] for word in custom_sources) or - any(word in app['id'] for word in custom_sources)): - self._source_list[app['title']] = app + self._source_list = {} + self._app_list = {} + conf_sources = self._customize.get(CONF_SOURCES, []) - for source in self._client.get_inputs(): - if not source['connected']: - continue - app = self._app_list[source['appId']] - self._source_list[app['title']] = app + for app in self._client.get_apps(): + self._app_list[app['id']] = app + if conf_sources: + if app['id'] == self._current_source_id: + self._current_source = app['title'] + self._source_list[app['title']] = app + elif (app['id'] in conf_sources or + any(word in app['title'] + for word in conf_sources) or + any(word in app['id'] + for word in conf_sources)): + self._source_list[app['title']] = app + else: + self._current_source = app['title'] + self._source_list[app['title']] = app + for source in self._client.get_inputs(): + if conf_sources: + if source['id'] == self._current_source_id: + self._source_list[source['label']] = source + elif (source['label'] in conf_sources or + any(source['label'].find(word) != -1 + for word in conf_sources)): + self._source_list[source['label']] = source + else: + self._source_list[source['label']] = source except (OSError, ConnectionClosed, TypeError, asyncio.TimeoutError): self._state = STATE_OFF + self._current_source = None + self._current_source_id = None @property def name(self): @@ -296,9 +317,14 @@ class LgWebOSDevice(MediaPlayerDevice): def select_source(self, source): """Select input source.""" - self._current_source_id = self._source_list[source]['id'] - self._current_source = self._source_list[source]['title'] - self._client.launch_app(self._source_list[source]['id']) + if self._source_list.get(source).get('title'): + self._current_source_id = self._source_list[source]['id'] + self._current_source = self._source_list[source]['title'] + self._client.launch_app(self._source_list[source]['id']) + elif self._source_list.get(source).get('label'): + self._current_source_id = self._source_list[source]['id'] + self._current_source = self._source_list[source]['label'] + self._client.set_input(self._source_list[source]['id']) def media_play(self): """Send play command.""" diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 243b96220c9..de19ab238b5 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Yamaha platform.""" + """Set up the Yamaha platform.""" import rxv # keep track of configured receivers so that we don't end up # discovering a receiver dynamically that we have static config @@ -241,7 +241,7 @@ class YamahaDevice(MediaPlayerDevice): function() except rxv.exceptions.ResponseException: _LOGGER.warning( - 'Failed to execute %s on %s', function_text, self._name) + "Failed to execute %s on %s", function_text, self._name) def select_source(self, source): """Select input source.""" diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face.py index 5d98f04d01e..a0ff2ed99e7 100644 --- a/homeassistant/components/microsoft_face.py +++ b/homeassistant/components/microsoft_face.py @@ -23,11 +23,11 @@ from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component from homeassistant.util import slugify +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'microsoft_face' DEPENDENCIES = ['camera'] -_LOGGER = logging.getLogger(__name__) - FACE_API_URL = "https://westus.api.cognitive.microsoft.com/face/v1.0/{0}" DATA_MICROSOFT_FACE = 'microsoft_face' @@ -111,7 +111,7 @@ def face_person(hass, group, person, camera_entity): @asyncio.coroutine def async_setup(hass, config): - """Setup microsoft face.""" + """Set up microsoft face.""" entities = {} face = MicrosoftFace( hass, diff --git a/homeassistant/components/mochad.py b/homeassistant/components/mochad.py index 83665a3c6d1..a42d142112d 100644 --- a/homeassistant/components/mochad.py +++ b/homeassistant/components/mochad.py @@ -4,15 +4,14 @@ Support for CM15A/CM19A X10 Controller using mochad daemon. For more details about this component, please refer to the documentation at https://home-assistant.io/components/mochad/ """ - import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.const import (CONF_HOST, CONF_PORT) -from homeassistant.helpers import config_validation as cv REQUIREMENTS = ['pymochad==0.1.1'] @@ -31,7 +30,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the mochad platform.""" + """Set up the mochad component.""" conf = config[DOMAIN] host = conf.get(CONF_HOST) port = conf.get(CONF_PORT) @@ -72,12 +71,12 @@ class MochadCtrl(object): @property def host(self): - """The server where mochad is running.""" + """Return the server where mochad is running.""" return self._host @property def port(self): - """The port mochad is running on.""" + """Return the port mochad is running on.""" return self._port def disconnect(self): diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 3ed9fff9cf0..b7b6193f6c0 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -9,22 +9,22 @@ import threading import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_METHOD, CONF_PORT) -import homeassistant.helpers.config_validation as cv -DOMAIN = "modbus" +DOMAIN = 'modbus' REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' 'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0'] # Type of network -CONF_BAUDRATE = "baudrate" -CONF_BYTESIZE = "bytesize" -CONF_STOPBITS = "stopbits" -CONF_TYPE = "type" -CONF_PARITY = "parity" +CONF_BAUDRATE = 'baudrate' +CONF_BYTESIZE = 'bytesize' +CONF_STOPBITS = 'stopbits' +CONF_TYPE = 'type' +CONF_PARITY = 'parity' SERIAL_SCHEMA = { vol.Required(CONF_BAUDRATE): cv.positive_int, @@ -50,11 +50,11 @@ CONFIG_SCHEMA = vol.Schema({ _LOGGER = logging.getLogger(__name__) -SERVICE_WRITE_REGISTER = "write_register" +SERVICE_WRITE_REGISTER = 'write_register' -ATTR_ADDRESS = "address" -ATTR_UNIT = "unit" -ATTR_VALUE = "value" +ATTR_ADDRESS = 'address' +ATTR_UNIT = 'unit' +ATTR_VALUE = 'value' SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ vol.Required(ATTR_UNIT): cv.positive_int, @@ -67,7 +67,7 @@ HUB = None def setup(hass, config): - """Setup Modbus component.""" + """Set up Modbus component.""" # Modbus connection type # pylint: disable=global-statement, import-error client_type = config[DOMAIN][CONF_TYPE] @@ -75,7 +75,7 @@ def setup(hass, config): # Connect to Modbus network # pylint: disable=global-statement, import-error - if client_type == "serial": + if client_type == 'serial': from pymodbus.client.sync import ModbusSerialClient as ModbusClient client = ModbusClient(method=config[DOMAIN][CONF_METHOD], port=config[DOMAIN][CONF_PORT], @@ -83,11 +83,11 @@ def setup(hass, config): stopbits=config[DOMAIN][CONF_STOPBITS], bytesize=config[DOMAIN][CONF_BYTESIZE], parity=config[DOMAIN][CONF_PARITY]) - elif client_type == "tcp": + elif client_type == 'tcp': from pymodbus.client.sync import ModbusTcpClient as ModbusClient client = ModbusClient(host=config[DOMAIN][CONF_HOST], port=config[DOMAIN][CONF_PORT]) - elif client_type == "udp": + elif client_type == 'udp': from pymodbus.client.sync import ModbusUdpClient as ModbusClient client = ModbusClient(host=config[DOMAIN][CONF_HOST], port=config[DOMAIN][CONF_PORT]) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 458c5952a69..b4701ad4690 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -10,6 +10,7 @@ import os import socket import time import ssl +import re import requests.certs import voluptuous as vol @@ -213,8 +214,7 @@ def async_subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS, if encoding is not None: try: payload = dp_payload.decode(encoding) - _LOGGER.debug("Received message on %s: %s", - dp_topic, payload) + _LOGGER.debug("Received message on %s: %s", dp_topic, payload) except (AttributeError, UnicodeDecodeError): _LOGGER.error("Illegal payload encoding %s from " "MQTT topic: %s, Payload: %s", @@ -639,12 +639,20 @@ def _raise_on_error(result): def _match_topic(subscription, topic): """Test if topic matches subscription.""" + reg_ex_parts = [] + suffix = "" if subscription.endswith('#'): - return (subscription[:-2] == topic or - topic.startswith(subscription[:-1])) - + subscription = subscription[:-2] + suffix = "(.*)" sub_parts = subscription.split('/') - topic_parts = topic.split('/') + for sub_part in sub_parts: + if sub_part == "+": + reg_ex_parts.append(r"([^\/]+)") + else: + reg_ex_parts.append(sub_part) - return (len(sub_parts) == len(topic_parts) and - all(a == b for a, b in zip(sub_parts, topic_parts) if a != '+')) + reg_ex = "^" + (r'\/'.join(reg_ex_parts)) + suffix + "$" + + reg = re.compile(reg_ex) + + return reg.match(topic) is not None diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index c3ff0f9dce5..dbee9dce571 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -31,7 +31,7 @@ ALLOWED_PLATFORMS = { @asyncio.coroutine def async_start(hass, discovery_topic, hass_config): - """Initialization of MQTT Discovery.""" + """Initialize of MQTT Discovery.""" # pylint: disable=unused-variable @asyncio.coroutine def async_device_message_received(topic, payload, qos): diff --git a/homeassistant/components/mqtt/server.py b/homeassistant/components/mqtt/server.py index c51649a3bef..0e866723b34 100644 --- a/homeassistant/components/mqtt/server.py +++ b/homeassistant/components/mqtt/server.py @@ -47,7 +47,7 @@ def async_start(hass, server_config): broker = Broker(server_config, hass.loop) yield from broker.start() except BrokerException: - logging.getLogger(__name__).exception('Error initializing MQTT server') + logging.getLogger(__name__).exception("Error initializing MQTT server") return False, None finally: passwd.close() diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py index bd149b6397d..40a752807ed 100644 --- a/homeassistant/components/mqtt_eventstream.py +++ b/homeassistant/components/mqtt_eventstream.py @@ -20,7 +20,7 @@ from homeassistant.core import EventOrigin, State import homeassistant.helpers.config_validation as cv from homeassistant.remote import JSONEncoder -DOMAIN = "mqtt_eventstream" +DOMAIN = 'mqtt_eventstream' DEPENDENCIES = ['mqtt'] CONF_PUBLISH_TOPIC = 'publish_topic' @@ -39,7 +39,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Setup the MQTT eventstream component.""" + """Set up the MQTT eventstream component.""" mqtt = loader.get_component('mqtt') conf = config.get(DOMAIN, {}) pub_topic = conf.get(CONF_PUBLISH_TOPIC) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index dbf66c2288b..984ff8a4606 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -13,41 +13,45 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.setup import setup_component -from homeassistant.components.mqtt import (valid_publish_topic, - valid_subscribe_topic) -from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_NAME, - CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) +from homeassistant.components.mqtt import ( + valid_publish_topic, valid_subscribe_topic) +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, CONF_NAME, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) from homeassistant.helpers import discovery from homeassistant.loader import get_component -_LOGGER = logging.getLogger(__name__) - -ATTR_NODE_ID = 'node_id' -ATTR_CHILD_ID = 'child_id' -ATTR_DESCRIPTION = 'description' -ATTR_DEVICE = 'device' -CONF_BAUD_RATE = 'baud_rate' -CONF_DEVICE = 'device' -CONF_DEBUG = 'debug' -CONF_GATEWAYS = 'gateways' -CONF_PERSISTENCE = 'persistence' -CONF_PERSISTENCE_FILE = 'persistence_file' -CONF_TCP_PORT = 'tcp_port' -CONF_TOPIC_IN_PREFIX = 'topic_in_prefix' -CONF_TOPIC_OUT_PREFIX = 'topic_out_prefix' -CONF_RETAIN = 'retain' -CONF_VERSION = 'version' -DEFAULT_VERSION = 1.4 -DEFAULT_BAUD_RATE = 115200 -DEFAULT_TCP_PORT = 5003 -DOMAIN = 'mysensors' -MYSENSORS_GATEWAYS = 'mysensors_gateways' -MQTT_COMPONENT = 'mqtt' REQUIREMENTS = [ 'https://github.com/theolind/pymysensors/archive/' 'c6990eaaa741444a638608e6e00488195e2ca74c.zip#pymysensors==0.9.1'] +_LOGGER = logging.getLogger(__name__) + +ATTR_CHILD_ID = 'child_id' +ATTR_DESCRIPTION = 'description' +ATTR_DEVICE = 'device' +ATTR_NODE_ID = 'node_id' + +CONF_BAUD_RATE = 'baud_rate' +CONF_DEBUG = 'debug' +CONF_DEVICE = 'device' +CONF_GATEWAYS = 'gateways' +CONF_PERSISTENCE = 'persistence' +CONF_PERSISTENCE_FILE = 'persistence_file' +CONF_RETAIN = 'retain' +CONF_TCP_PORT = 'tcp_port' +CONF_TOPIC_IN_PREFIX = 'topic_in_prefix' +CONF_TOPIC_OUT_PREFIX = 'topic_out_prefix' +CONF_VERSION = 'version' + +DEFAULT_BAUD_RATE = 115200 +DEFAULT_TCP_PORT = 5003 +DEFAULT_VERSION = 1.4 +DOMAIN = 'mysensors' + +MQTT_COMPONENT = 'mqtt' +MYSENSORS_GATEWAYS = 'mysensors_gateways' + def is_socket_address(value): """Validate that value is a valid address.""" @@ -148,7 +152,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the MySensors component.""" + """Set up the MySensors component.""" import mysensors.mysensors as mysensors version = config[DOMAIN].get(CONF_VERSION) @@ -164,11 +168,11 @@ def setup(hass, config): retain = config[DOMAIN].get(CONF_RETAIN) def pub_callback(topic, payload, qos, retain): - """Call mqtt publish function.""" + """Call MQTT publish function.""" mqtt.publish(hass, topic, payload, qos, retain) def sub_callback(topic, callback, qos): - """Call mqtt subscribe function.""" + """Call MQTT subscribe function.""" mqtt.subscribe(hass, topic, callback, qos) gateway = mysensors.MQTTGateway( pub_callback, sub_callback, @@ -201,7 +205,7 @@ def setup(hass, config): gateway.event_callback = gateway.callback_factory() def gw_start(event): - """Callback to trigger start of gateway and any persistence.""" + """Trigger to start of the gateway and any persistence.""" if persistence: for node_id in gateway.sensors: node = gateway.sensors[node_id] @@ -220,7 +224,7 @@ def setup(hass, config): gateways = hass.data.get(MYSENSORS_GATEWAYS) if gateways is not None: _LOGGER.error( - '%s already exists in %s, will not setup %s component', + "%s already exists in %s, will not setup %s component", MYSENSORS_GATEWAYS, hass.data, DOMAIN) return False @@ -245,7 +249,7 @@ def setup(hass, config): if not gateways: _LOGGER.error( - 'No devices could be setup as gateways, check your configuration') + "No devices could be setup as gateways, check your configuration") return False hass.data[MYSENSORS_GATEWAYS] = gateways @@ -266,9 +270,9 @@ def setup(hass, config): def pf_callback_factory(map_sv_types, devices, entity_class, add_devices=None): """Return a new callback for the platform.""" def mysensors_callback(gateway, msg): - """Callback for mysensors platform.""" + """Run when a message from the gateway arrives.""" if gateway.sensors[msg.node_id].sketch_name is None: - _LOGGER.debug('No sketch_name: node %s', msg.node_id) + _LOGGER.debug("No sketch_name: node %s", msg.node_id) return child = gateway.sensors[msg.node_id].children.get(msg.child_id) if child is None: @@ -294,7 +298,7 @@ def pf_callback_factory(map_sv_types, devices, entity_class, add_devices=None): devices[key] = device_class( gateway, msg.node_id, child.id, name, value_type) if add_devices: - _LOGGER.info('Adding new devices: %s', [devices[key]]) + _LOGGER.info("Adding new devices: %s", [devices[key]]) add_devices([devices[key]], True) else: devices[key].update() @@ -305,7 +309,7 @@ class GatewayWrapper(object): """Gateway wrapper class.""" def __init__(self, gateway, optimistic, device): - """Setup class attributes on instantiation. + """Set up the class attributes on instantiation. Args: gateway (mysensors.SerialGateway): Gateway to wrap. @@ -318,6 +322,7 @@ class GatewayWrapper(object): optimistic (bool): Send values to actuators without feedback state. device (str): Device configured as gateway. __initialised (bool): True if GatewayWrapper is initialised. + """ self._wrapped_gateway = gateway self.platform_callbacks = [] @@ -346,9 +351,9 @@ class GatewayWrapper(object): def callback_factory(self): """Return a new callback function.""" def node_update(msg): - """Callback for node updates from the MySensors gateway.""" + """Handle node updates from the MySensors gateway.""" _LOGGER.debug( - 'Update: node %s, child %s sub_type %s', + "Update: node %s, child %s sub_type %s", msg.node_id, msg.child_id, msg.sub_type) for callback in self.platform_callbacks: callback(self, msg) @@ -357,10 +362,10 @@ class GatewayWrapper(object): class MySensorsDeviceEntity(object): - """Represent a MySensors entity.""" + """Representation of a MySensors entity.""" def __init__(self, gateway, node_id, child_id, name, value_type): - """Set up MySensors device.""" + """Set up the MySensors device.""" self.gateway = gateway self.node_id = node_id self.child_id = child_id @@ -377,7 +382,7 @@ class MySensorsDeviceEntity(object): @property def name(self): - """The name of this entity.""" + """Return the name of this entity.""" return self._name @property @@ -399,14 +404,14 @@ class MySensorsDeviceEntity(object): try: attr[set_req(value_type).name] = value except ValueError: - _LOGGER.error('Value_type %s is not valid for mysensors ' - 'version %s', value_type, + _LOGGER.error("Value_type %s is not valid for mysensors " + "version %s", value_type, self.gateway.protocol_version) return attr @property def available(self): - """Return True if entity is available.""" + """Return true if entity is available.""" return self.value_type in self._values def update(self): diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index 67716c6a2e5..2401bc6604f 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -10,10 +10,10 @@ from urllib.error import HTTPError import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import discovery from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -81,13 +81,13 @@ ALERTS = { def setup(hass, config): - """Setup the Neato component.""" + """Set up the Neato component.""" from pybotvac import Account hass.data[NEATO_LOGIN] = NeatoHub(hass, config[DOMAIN], Account) hub = hass.data[NEATO_LOGIN] if not hub.login(): - _LOGGER.debug('Failed to login to Neato API') + _LOGGER.debug("Failed to login to Neato API") return False hub.update_robots() for component in ('camera', 'sensor', 'switch'): @@ -114,9 +114,9 @@ class NeatoHub(object): def login(self): """Login to My Neato.""" try: - _LOGGER.debug('Trying to connect to Neato API') - self.my_neato = self._neato(self.config[CONF_USERNAME], - self.config[CONF_PASSWORD]) + _LOGGER.debug("Trying to connect to Neato API") + self.my_neato = self._neato( + self.config[CONF_USERNAME], self.config[CONF_PASSWORD]) return True except HTTPError: _LOGGER.error("Unable to connect to Neato API") @@ -125,7 +125,7 @@ class NeatoHub(object): @Throttle(timedelta(seconds=1)) def update_robots(self): """Update the robot states.""" - _LOGGER.debug('Running HUB.update_robots %s', + _LOGGER.debug("Running HUB.update_robots %s", self._hass.data[NEATO_ROBOTS]) self._hass.data[NEATO_ROBOTS] = self.my_neato.robots self._hass.data[NEATO_MAP_DATA] = self.my_neato.maps diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index 94a0db35f4d..8d99a4d0327 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -11,16 +11,16 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery -from homeassistant.const import (CONF_STRUCTURE, CONF_FILENAME, - CONF_BINARY_SENSORS, CONF_SENSORS, - CONF_MONITORED_CONDITIONS) +from homeassistant.const import ( + CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS, + CONF_MONITORED_CONDITIONS) from homeassistant.loader import get_component +REQUIREMENTS = ['python-nest==3.1.0'] + _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['python-nest==3.1.0'] - DOMAIN = 'nest' DATA_NEST = 'nest' @@ -54,7 +54,7 @@ def request_configuration(nest, hass, config): return def nest_configuration_callback(data): - """The actions to do when our configuration callback is called.""" + """Run when the configuration callback is called.""" _LOGGER.debug("configurator callback") pin = data.get('pin') setup_nest(hass, nest, config, pin=pin) @@ -72,7 +72,7 @@ def request_configuration(nest, hass, config): def setup_nest(hass, nest, config, pin=None): - """Setup Nest Devices.""" + """Set up the Nest devices.""" if pin is not None: _LOGGER.debug("pin acquired, requesting access token") nest.request_token(pin) @@ -108,7 +108,7 @@ def setup_nest(hass, nest, config, pin=None): def setup(hass, config): - """Setup the Nest thermostat component.""" + """Set up the Nest thermostat component.""" import nest if 'nest' in _CONFIGURING: @@ -144,7 +144,7 @@ class NestDevice(object): _LOGGER.debug("Structures to include: %s", self._structure) def thermostats(self): - """Generator returning list of thermostats and their location.""" + """Generate a list of thermostats and their location.""" try: for structure in self.nest.structures: if structure.name in self._structure: @@ -158,7 +158,7 @@ class NestDevice(object): "Connection error logging into the nest web service.") def smoke_co_alarms(self): - """Generator returning list of smoke co alarams.""" + """Generate a list of smoke co alarams.""" try: for structure in self.nest.structures: if structure.name in self._structure: @@ -172,7 +172,7 @@ class NestDevice(object): "Connection error logging into the nest web service.") def cameras(self): - """Generator returning list of cameras.""" + """Generate a list of cameras.""" try: for structure in self.nest.structures: if structure.name in self._structure: diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 35a01e25475..cf05629ce1b 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -68,7 +68,7 @@ def send_message(hass, message, title=None, data=None): @asyncio.coroutine def async_setup(hass, config): - """Setup the notify services.""" + """Set up the notify services.""" descriptions = yield from hass.loop.run_in_executor( None, load_yaml_config_file, os.path.join(os.path.dirname(__file__), 'services.yaml')) @@ -166,7 +166,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_platform_discovered(platform, info): - """Callback to load a platform.""" + """Handle for discovered platform.""" yield from async_setup_platform(platform, discovery_info=info) discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) diff --git a/homeassistant/components/notify/apns.py b/homeassistant/components/notify/apns.py index 50842c69a61..136d5300183 100644 --- a/homeassistant/components/notify/apns.py +++ b/homeassistant/components/notify/apns.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/notify.apns/ """ import logging import os + import voluptuous as vol from homeassistant.helpers.event import track_state_change @@ -16,15 +17,17 @@ from homeassistant.const import CONF_NAME, CONF_PLATFORM import homeassistant.helpers.config_validation as cv from homeassistant.helpers import template as template_helper -APNS_DEVICES = "apns.yaml" -CONF_CERTFILE = "cert_file" -CONF_TOPIC = "topic" -CONF_SANDBOX = "sandbox" -DEVICE_TRACKER_DOMAIN = "device_tracker" -SERVICE_REGISTER = "apns_register" +REQUIREMENTS = ['apns2==0.1.1'] -ATTR_PUSH_ID = "push_id" -ATTR_NAME = "name" +APNS_DEVICES = 'apns.yaml' +CONF_CERTFILE = 'cert_file' +CONF_TOPIC = 'topic' +CONF_SANDBOX = 'sandbox' +DEVICE_TRACKER_DOMAIN = 'device_tracker' +SERVICE_REGISTER = 'apns_register' + +ATTR_PUSH_ID = 'push_id' +ATTR_NAME = 'name' PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): 'apns', @@ -39,8 +42,6 @@ REGISTER_SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_NAME, default=None): cv.string, }) -REQUIREMENTS = ["apns2==0.1.1"] - def get_service(hass, config, discovery_info=None): """Return push service.""" @@ -53,20 +54,18 @@ def get_service(hass, config, discovery_info=None): sandbox = config.get(CONF_SANDBOX) service = ApnsNotificationService(hass, name, topic, sandbox, cert_file) - hass.services.register(DOMAIN, - 'apns_{}'.format(name), - service.register, - descriptions.get(SERVICE_REGISTER), - schema=REGISTER_SERVICE_SCHEMA) + hass.services.register( + DOMAIN, 'apns_{}'.format(name), service.register, + descriptions.get(SERVICE_REGISTER), schema=REGISTER_SERVICE_SCHEMA) return service class ApnsDevice(object): """ - Apns Device class. + The APNS Device class. - Stores information about a device that is - registered for push notifications. + Stores information about a device that is registered for push + notifications. """ def __init__(self, push_id, name, tracking_device_id=None, disabled=False): @@ -78,18 +77,18 @@ class ApnsDevice(object): @property def push_id(self): - """The apns id for the device.""" + """Return the APNS id for the device.""" return self.device_push_id @property def name(self): - """The friendly name for the device.""" + """Return the friendly name for the device.""" return self.device_name @property def tracking_device_id(self): """ - Device Id. + Return the device Id. The id of a device that is tracked by the device tracking component. @@ -99,16 +98,16 @@ class ApnsDevice(object): @property def full_tracking_device_id(self): """ - Fully qualified device id. + Return the fully qualified device id. The full id of a device that is tracked by the device tracking component. """ - return DEVICE_TRACKER_DOMAIN + '.' + self.tracking_id + return '{}.{}'.format(DEVICE_TRACKER_DOMAIN, self.tracking_id) @property def disabled(self): - """Should receive notifications.""" + """Return the .""" return self.device_disabled def disable(self): @@ -140,7 +139,7 @@ def _write_device(out, device): out.write(device.push_id) out.write(": {") - if len(attributes) > 0: + if attributes: separator = ", " out.write(separator.join(attributes)) @@ -178,16 +177,13 @@ class ApnsNotificationService(BaseNotificationService): if device.tracking_device_id is not None ] track_state_change( - hass, - tracking_ids, - self.device_state_changed_listener) + hass, tracking_ids, self.device_state_changed_listener) def device_state_changed_listener(self, entity_id, from_s, to_s): """ - Listener for sate change. + Listen for sate change. - Track device state change if a device - has a tracking id specified. + Track device state change if a device has a tracking id specified. """ self.device_states[entity_id] = str(to_s.state) @@ -206,10 +202,7 @@ class ApnsNotificationService(BaseNotificationService): current_tracking_id = None if current_device is None \ else current_device.tracking_device_id - device = ApnsDevice( - push_id, - device_name, - current_tracking_id) + device = ApnsDevice(push_id, device_name, current_tracking_id) if current_device is None: self.devices[push_id] = device @@ -245,15 +238,15 @@ class ApnsNotificationService(BaseNotificationService): elif isinstance(message, template_helper.Template): rendered_message = message.render() else: - rendered_message = "" + rendered_message = '' payload = Payload( alert=rendered_message, - badge=message_data.get("badge"), - sound=message_data.get("sound"), - category=message_data.get("category"), - custom=message_data.get("custom", {}), - content_available=message_data.get("content_available", False)) + badge=message_data.get('badge'), + sound=message_data.get('sound'), + category=message_data.get('category'), + custom=message_data.get('custom', {}), + content_available=message_data.get('content_available', False)) device_update = False @@ -267,13 +260,9 @@ class ApnsNotificationService(BaseNotificationService): if device_state is None or state == str(device_state): try: apns.send_notification( - push_id, - payload, - topic=self.topic) + push_id, payload, topic=self.topic) except Unregistered: - logging.error( - "Device %s has unregistered.", - push_id) + logging.error("Device %s has unregistered", push_id) device_update = True device.disable() diff --git a/homeassistant/components/notify/aws_lambda.py b/homeassistant/components/notify/aws_lambda.py index 801537fbba6..7bdc103523d 100644 --- a/homeassistant/components/notify/aws_lambda.py +++ b/homeassistant/components/notify/aws_lambda.py @@ -17,8 +17,9 @@ from homeassistant.components.notify import ( import homeassistant.helpers.config_validation as cv from homeassistant.remote import JSONEncoder +REQUIREMENTS = ['boto3==1.4.3'] + _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["boto3==1.4.3"] CONF_REGION = 'region_name' CONF_ACCESS_KEY_ID = 'aws_access_key_id' @@ -28,7 +29,7 @@ CONF_CONTEXT = 'context' ATTR_CREDENTIALS = 'credentials' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_REGION, default="us-east-1"): cv.string, + vol.Optional(CONF_REGION, default='us-east-1'): cv.string, vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, @@ -40,8 +41,8 @@ def get_service(hass, config, discovery_info=None): """Get the AWS Lambda notification service.""" context_str = json.dumps({'hass': hass.config.as_dict(), 'custom': config[CONF_CONTEXT]}, cls=JSONEncoder) - context_b64 = base64.b64encode(context_str.encode("utf-8")) - context = context_b64.decode("utf-8") + context_b64 = base64.b64encode(context_str.encode('utf-8')) + context = context_b64.decode('utf-8') # pylint: disable=import-error import boto3 diff --git a/homeassistant/components/notify/aws_sns.py b/homeassistant/components/notify/aws_sns.py index 9b95c486b4d..27fa7ac41c2 100644 --- a/homeassistant/components/notify/aws_sns.py +++ b/homeassistant/components/notify/aws_sns.py @@ -26,7 +26,7 @@ CONF_PROFILE_NAME = 'profile_name' ATTR_CREDENTIALS = 'credentials' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_REGION, default="us-east-1"): cv.string, + vol.Optional(CONF_REGION, default='us-east-1'): cv.string, vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, diff --git a/homeassistant/components/notify/aws_sqs.py b/homeassistant/components/notify/aws_sqs.py index 76a137734d3..227dba14b43 100644 --- a/homeassistant/components/notify/aws_sqs.py +++ b/homeassistant/components/notify/aws_sqs.py @@ -25,7 +25,7 @@ CONF_PROFILE_NAME = 'profile_name' ATTR_CREDENTIALS = 'credentials' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_REGION, default="us-east-1"): cv.string, + vol.Optional(CONF_REGION, default='us-east-1'): cv.string, vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, diff --git a/homeassistant/components/notify/ciscospark.py b/homeassistant/components/notify/ciscospark.py index ee6115c3f79..0bf184023d7 100644 --- a/homeassistant/components/notify/ciscospark.py +++ b/homeassistant/components/notify/ciscospark.py @@ -34,29 +34,17 @@ def get_service(hass, config, discovery_info=None): class CiscoSparkNotificationService(BaseNotificationService): - """CiscoSparkNotificationService.""" + """The Cisco Spark Notification Service.""" def __init__(self, token, default_room): - """ - Initialize the service. - - Args: - token: Cisco Spark Developer's Token - default_room: Cisco Spark Room ID - """ + """Initialize the service.""" from ciscosparkapi import CiscoSparkAPI self._default_room = default_room self._token = token self._spark = CiscoSparkAPI(access_token=self._token) def send_message(self, message="", **kwargs): - """ - Send a message to a user. - - Args: - message: notificaiton text - kwargs: attributes used - 'title' - """ + """Send a message to a user.""" from ciscosparkapi import SparkApiError try: title = "" diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index cd3bdfb16f3..4a7483d6e4d 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -43,6 +43,6 @@ class CommandLineNotificationService(BaseNotificationService): stdin=subprocess.PIPE, shell=True) proc.communicate(input=message) if proc.returncode != 0: - _LOGGER.error('Command failed: %s', self.command) + _LOGGER.error("Command failed: %s", self.command) except subprocess.SubprocessError: - _LOGGER.error('Error trying to exec Command: %s', self.command) + _LOGGER.error("Error trying to exec Command: %s", self.command) diff --git a/homeassistant/components/notify/facebook.py b/homeassistant/components/notify/facebook.py index 8f8bb98bbe1..ef85450ca63 100644 --- a/homeassistant/components/notify/facebook.py +++ b/homeassistant/components/notify/facebook.py @@ -69,6 +69,6 @@ class FacebookNotificationService(BaseNotificationService): obj = resp.json() error_message = obj['error']['message'] error_code = obj['error']['code'] - _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - error_message, - error_code) + _LOGGER.error( + "Error %s : %s (Code %s)", resp.status_code, error_message, + error_code) diff --git a/homeassistant/components/notify/free_mobile.py b/homeassistant/components/notify/free_mobile.py index 74d9a80ad86..92ea75a79dc 100644 --- a/homeassistant/components/notify/free_mobile.py +++ b/homeassistant/components/notify/free_mobile.py @@ -13,9 +13,9 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['freesms==0.1.1'] +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, @@ -25,8 +25,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Free Mobile SMS notification service.""" - return FreeSMSNotificationService(config[CONF_USERNAME], - config[CONF_ACCESS_TOKEN]) + return FreeSMSNotificationService( + config[CONF_USERNAME], config[CONF_ACCESS_TOKEN]) class FreeSMSNotificationService(BaseNotificationService): diff --git a/homeassistant/components/notify/gntp.py b/homeassistant/components/notify/gntp.py index 5aaaf64577c..b7e5b1b813a 100644 --- a/homeassistant/components/notify/gntp.py +++ b/homeassistant/components/notify/gntp.py @@ -73,7 +73,7 @@ class GNTPNotificationService(BaseNotificationService): try: self.gntp.register() except gntp.errors.NetworkError: - _LOGGER.error('Unable to register with the GNTP host.') + _LOGGER.error("Unable to register with the GNTP host") return def send_message(self, message="", **kwargs): diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/notify/group.py index 07cc7b1146a..a98bb6c2317 100644 --- a/homeassistant/components/notify/group.py +++ b/homeassistant/components/notify/group.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_SERVICES = "services" +CONF_SERVICES = 'services' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_SERVICES): vol.All(cv.ensure_list, [{ diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py index 419e5aba2f8..3aa5db7faba 100644 --- a/homeassistant/components/notify/html5.py +++ b/homeassistant/components/notify/html5.py @@ -90,12 +90,10 @@ CALLBACK_EVENT_PAYLOAD_SCHEMA = vol.Schema({ NOTIFY_CALLBACK_EVENT = 'html5_notification' -# badge and timestamp are Chrome specific (not in official spec) - -HTML5_SHOWNOTIFICATION_PARAMETERS = ('actions', 'badge', 'body', 'dir', - 'icon', 'lang', 'renotify', - 'requireInteraction', 'tag', 'timestamp', - 'vibrate') +# Badge and timestamp are Chrome specific (not in official spec) +HTML5_SHOWNOTIFICATION_PARAMETERS = ( + 'actions', 'badge', 'body', 'dir', 'icon', 'lang', 'renotify', + 'requireInteraction', 'tag', 'timestamp', 'vibrate') def get_service(hass, config, discovery_info=None): @@ -115,8 +113,8 @@ def get_service(hass, config, discovery_info=None): gcm_sender_id = config.get(ATTR_GCM_SENDER_ID) if gcm_sender_id is not None: - add_manifest_json_key(ATTR_GCM_SENDER_ID, - config.get(ATTR_GCM_SENDER_ID)) + add_manifest_json_key( + ATTR_GCM_SENDER_ID, config.get(ATTR_GCM_SENDER_ID)) return HTML5NotificationService(gcm_api_key, registrations) @@ -136,7 +134,7 @@ def _load_config(filename): return json.loads(inp) except (IOError, ValueError) as error: - _LOGGER.error('Reading config file %s failed: %s', filename, error) + _LOGGER.error("Reading config file %s failed: %s", filename, error) return None @@ -158,7 +156,7 @@ def _save_config(filename, config): fdesc.write(json.dumps( config, cls=JSONBytesDecoder, indent=4, sort_keys=True)) except (IOError, TypeError) as error: - _LOGGER.error('Saving config file failed: %s', error) + _LOGGER.error("Saving config file failed: %s", error) return False return True @@ -185,17 +183,16 @@ class HTML5PushRegistrationView(HomeAssistantView): try: data = REGISTER_SCHEMA(data) except vol.Invalid as ex: - return self.json_message(humanize_error(data, ex), - HTTP_BAD_REQUEST) + return self.json_message( + humanize_error(data, ex), HTTP_BAD_REQUEST) - name = ensure_unique_string('unnamed device', - self.registrations.keys()) + name = ensure_unique_string('unnamed device', self.registrations) self.registrations[name] = data if not _save_config(self.json_path, self.registrations): - return self.json_message('Error saving registration.', - HTTP_INTERNAL_SERVER_ERROR) + return self.json_message( + 'Error saving registration.', HTTP_INTERNAL_SERVER_ERROR) return self.json_message('Push notification subscriber registered.') @@ -224,8 +221,8 @@ class HTML5PushRegistrationView(HomeAssistantView): if not _save_config(self.json_path, self.registrations): self.registrations[found] = reg - return self.json_message('Error saving registration.', - HTTP_INTERNAL_SERVER_ERROR) + return self.json_message( + 'Error saving registration.', HTTP_INTERNAL_SERVER_ERROR) return self.json_message('Push notification subscriber unregistered.') @@ -318,7 +315,7 @@ class HTML5PushCallbackView(HomeAssistantView): try: event_payload = CALLBACK_EVENT_PAYLOAD_SCHEMA(event_payload) except vol.Invalid as ex: - _LOGGER.warning('Callback event payload is not valid! %s', + _LOGGER.warning("Callback event payload is not valid: %s", humanize_error(event_payload, ex)) event_name = '{}.{}'.format(NOTIFY_CALLBACK_EVENT, @@ -390,8 +387,8 @@ class HTML5NotificationService(BaseNotificationService): for target in targets: info = self.registrations.get(target) if info is None: - _LOGGER.error('%s is not a valid HTML5 push notification' - ' target!', target) + _LOGGER.error("%s is not a valid HTML5 push notification" + " target", target) continue jwt_exp = (datetime.datetime.fromtimestamp(timestamp) + diff --git a/homeassistant/components/notify/instapush.py b/homeassistant/components/notify/instapush.py index 1af08420726..39cdf0fc475 100644 --- a/homeassistant/components/notify/instapush.py +++ b/homeassistant/components/notify/instapush.py @@ -13,7 +13,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_API_KEY +from homeassistant.const import ( + CONF_API_KEY, HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://api.instapush.im/v1/' @@ -24,6 +25,9 @@ CONF_TRACKER = 'tracker' DEFAULT_TIMEOUT = 10 +HTTP_HEADER_APPID = 'x-instapush-appid' +HTTP_HEADER_APPSECRET = 'x-instapush-appsecret' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_APP_SECRET): cv.string, @@ -34,25 +38,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Instapush notification service.""" - headers = {'x-instapush-appid': config[CONF_API_KEY], - 'x-instapush-appsecret': config[CONF_APP_SECRET]} + headers = { + HTTP_HEADER_APPID: config[CONF_API_KEY], + HTTP_HEADER_APPSECRET: config[CONF_APP_SECRET], + } try: response = requests.get( '{}{}'.format(_RESOURCE, 'events/list'), headers=headers, timeout=DEFAULT_TIMEOUT).json() except ValueError: - _LOGGER.error('Unexpected answer from Instapush API.') + _LOGGER.error("Unexpected answer from Instapush API") return None if 'error' in response: _LOGGER.error(response['msg']) return None - if len([app for app in response - if app['title'] == config[CONF_EVENT]]) == 0: - _LOGGER.error("No app match your given value. " - "Please create an app at https://instapush.im") + if not [app for app in response if app['title'] == config[CONF_EVENT]]: + _LOGGER.error("No app match your given value") return None return InstapushNotificationService( @@ -70,16 +74,17 @@ class InstapushNotificationService(BaseNotificationService): self._event = event self._tracker = tracker self._headers = { - 'x-instapush-appid': self._api_key, - 'x-instapush-appsecret': self._app_secret, - 'Content-Type': 'application/json'} + HTTP_HEADER_APPID: self._api_key, + HTTP_HEADER_APPSECRET: self._app_secret, + HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_JSON, + } def send_message(self, message="", **kwargs): """Send a message to a user.""" title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) data = { 'event': self._event, - 'trackers': {self._tracker: title + ' : ' + message} + 'trackers': {self._tracker: '{} : {}'.format(title, message)} } response = requests.post( diff --git a/homeassistant/components/notify/ios.py b/homeassistant/components/notify/ios.py index 6486eed1512..469f1f3e61f 100644 --- a/homeassistant/components/notify/ios.py +++ b/homeassistant/components/notify/ios.py @@ -46,10 +46,10 @@ def get_service(hass, config, discovery_info=None): hass.config.components.add("notify.ios") if not ios.devices_with_push(): - _LOGGER.error(("The notify.ios platform was loaded but no " - "devices exist! Please check the documentation at " - "https://home-assistant.io/ecosystem/ios/notifications" - "/ for more information")) + _LOGGER.error("The notify.ios platform was loaded but no " + "devices exist! Please check the documentation at " + "https://home-assistant.io/ecosystem/ios/notifications" + "/ for more information") return None return iOSNotificationService() diff --git a/homeassistant/components/notify/joaoapps_join.py b/homeassistant/components/notify/joaoapps_join.py index f79c7186359..e391d6559e5 100644 --- a/homeassistant/components/notify/joaoapps_join.py +++ b/homeassistant/components/notify/joaoapps_join.py @@ -12,49 +12,59 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv -REQUIREMENTS = [ - 'https://github.com/nkgilley/python-join-api/archive/' - '3e1e849f1af0b4080f551b62270c6d244d5fbcbd.zip#python-join-api==0.0.1'] +REQUIREMENTS = ['python-join-api==0.0.2'] _LOGGER = logging.getLogger(__name__) CONF_DEVICE_ID = 'device_id' +CONF_DEVICE_IDS = 'device_ids' +CONF_DEVICE_NAMES = 'device_names' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICE_ID): cv.string, - vol.Optional(CONF_API_KEY): cv.string + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_DEVICE_ID): cv.string, + vol.Optional(CONF_DEVICE_IDS): cv.string, + vol.Optional(CONF_DEVICE_NAMES): cv.string, }) # pylint: disable=unused-variable def get_service(hass, config, discovery_info=None): """Get the Join notification service.""" - device_id = config.get(CONF_DEVICE_ID) api_key = config.get(CONF_API_KEY) + device_id = config.get(CONF_DEVICE_ID) + device_ids = config.get(CONF_DEVICE_IDS) + device_names = config.get(CONF_DEVICE_NAMES) if api_key: from pyjoin import get_devices if not get_devices(api_key): - _LOGGER.error("Error connecting to Join, check API key") + _LOGGER.error("Error connecting to Join. Check the API key") return False - return JoinNotificationService(device_id, api_key) + if device_id is None and device_ids is None and device_names is None: + _LOGGER.error("No device was provided. Please specify device_id" + ", device_ids, or device_names") + return False + return JoinNotificationService(api_key, device_id, + device_ids, device_names) class JoinNotificationService(BaseNotificationService): """Implement the notification service for Join.""" - def __init__(self, device_id, api_key=None): + def __init__(self, api_key, device_id, device_ids, device_names): """Initialize the service.""" - self._device_id = device_id self._api_key = api_key + self._device_id = device_id + self._device_ids = device_ids + self._device_names = device_names def send_message(self, message="", **kwargs): """Send a message to a user.""" from pyjoin import send_notification title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) data = kwargs.get(ATTR_DATA) or {} - send_notification(device_id=self._device_id, - text=message, - title=title, - icon=data.get('icon'), - smallicon=data.get('smallicon'), - api_key=self._api_key) + send_notification( + device_id=self._device_id, device_ids=self._device_ids, + device_names=self._device_names, text=message, title=title, + icon=data.get('icon'), smallicon=data.get('smallicon'), + vibration=data.get('vibration'), api_key=self._api_key) diff --git a/homeassistant/components/notify/kodi.py b/homeassistant/components/notify/kodi.py index db72fff37d5..eda01c13086 100644 --- a/homeassistant/components/notify/kodi.py +++ b/homeassistant/components/notify/kodi.py @@ -100,4 +100,4 @@ class KodiNotificationService(BaseNotificationService): title, message, icon, displaytime) except jsonrpc_async.TransportError: - _LOGGER.warning('Unable to fetch Kodi data, Is Kodi online?') + _LOGGER.warning("Unable to fetch Kodi data. Is Kodi online?") diff --git a/homeassistant/components/notify/lannouncer.py b/homeassistant/components/notify/lannouncer.py index 4d038faeb9a..5677f38b06c 100644 --- a/homeassistant/components/notify/lannouncer.py +++ b/homeassistant/components/notify/lannouncer.py @@ -74,12 +74,12 @@ class LannouncerNotificationService(BaseNotificationService): # Check response buffer = sock.recv(1024) if buffer != b'LANnouncer: OK': - _LOGGER.error('Error sending data to Lannnouncer: %s', + _LOGGER.error("Error sending data to Lannnouncer: %s", buffer.decode()) # Close socket sock.close() except socket.gaierror: - _LOGGER.error('Unable to connect to host %s', self._host) + _LOGGER.error("Unable to connect to host %s", self._host) except socket.error: - _LOGGER.exception('Failed to send data to Lannnouncer') + _LOGGER.exception("Failed to send data to Lannnouncer") diff --git a/homeassistant/components/notify/llamalab_automate.py b/homeassistant/components/notify/llamalab_automate.py index bf000171c12..606c0fafc8b 100644 --- a/homeassistant/components/notify/llamalab_automate.py +++ b/homeassistant/components/notify/llamalab_automate.py @@ -8,16 +8,16 @@ import logging import requests import voluptuous as vol -from homeassistant.components.notify import (BaseNotificationService, - PLATFORM_SCHEMA) -from homeassistant.const import CONF_API_KEY +from homeassistant.components.notify import ( + BaseNotificationService, PLATFORM_SCHEMA) +from homeassistant.const import CONF_API_KEY, CONF_DEVICE from homeassistant.helpers import config_validation as cv + _LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://llamalab.com/automate/cloud/message' CONF_TO = 'to' -CONF_DEVICE = 'device' -_RESOURCE = 'https://llamalab.com/automate/cloud/message' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, diff --git a/homeassistant/components/notify/mailgun.py b/homeassistant/components/notify/mailgun.py index 4a5a46f7e96..0e4254ae083 100644 --- a/homeassistant/components/notify/mailgun.py +++ b/homeassistant/components/notify/mailgun.py @@ -17,9 +17,7 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/pschmitt/pymailgun/' - 'archive/1.3.zip#' - 'pymailgun==1.3'] +REQUIREMENTS = ['pymailgunner==1.4'] # Images to attach to notification ATTR_IMAGES = 'images' @@ -65,7 +63,7 @@ class MailgunNotificationService(BaseNotificationService): def initialize_client(self): """Initialize the connection to Mailgun.""" - from pymailgun import Client + from pymailgunner import Client self._client = Client(self._token, self._domain, self._sandbox) _LOGGER.debug("Mailgun domain: %s", self._client.domain) self._domain = self._client.domain @@ -74,7 +72,7 @@ class MailgunNotificationService(BaseNotificationService): def connection_is_valid(self): """Check whether the provided credentials are valid.""" - from pymailgun import (MailgunCredentialsError, MailgunDomainError) + from pymailgunner import (MailgunCredentialsError, MailgunDomainError) try: self.initialize_client() except MailgunCredentialsError: @@ -87,7 +85,7 @@ class MailgunNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a mail to the recipient.""" - from pymailgun import MailgunError + from pymailgunner import MailgunError subject = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) data = kwargs.get(ATTR_DATA) files = data.get(ATTR_IMAGES) if data else None diff --git a/homeassistant/components/notify/matrix.py b/homeassistant/components/notify/matrix.py index 15da3f053d8..c3bdeae0280 100644 --- a/homeassistant/components/notify/matrix.py +++ b/homeassistant/components/notify/matrix.py @@ -7,12 +7,13 @@ https://home-assistant.io/components/notify.matrix/ import logging import json import os +from urllib.parse import urlparse import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_VERIFY_SSL REQUIREMENTS = ['matrix-client==0.0.6'] @@ -20,7 +21,6 @@ REQUIREMENTS = ['matrix-client==0.0.6'] _LOGGER = logging.getLogger(__name__) SESSION_FILE = 'matrix.conf' -AUTH_TOKENS = dict() CONF_HOMESERVER = 'homeserver' CONF_DEFAULT_ROOM = 'default_room' @@ -36,120 +36,160 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Matrix notification service.""" - if not AUTH_TOKENS: - load_token(hass.config.path(SESSION_FILE)) + from matrix_client.client import MatrixRequestError - return MatrixNotificationService( - config.get(CONF_HOMESERVER), - config.get(CONF_DEFAULT_ROOM), - config.get(CONF_VERIFY_SSL), - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD) - ) + try: + return MatrixNotificationService( + os.path.join(hass.config.path(), SESSION_FILE), + config.get(CONF_HOMESERVER), + config.get(CONF_DEFAULT_ROOM), + config.get(CONF_VERIFY_SSL), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD)) + + except MatrixRequestError: + return None class MatrixNotificationService(BaseNotificationService): - """Wrapper for the Matrix Notification Client.""" + """Send Notifications to a Matrix Room.""" - def __init__(self, homeserver, default_room, verify_ssl, + def __init__(self, config_file, homeserver, default_room, verify_ssl, username, password): - """Buffer configuration data for send_message.""" + """Set up the client.""" + self.session_filepath = config_file + self.auth_tokens = self.get_auth_tokens() + self.homeserver = homeserver self.default_room = default_room self.verify_tls = verify_ssl self.username = username self.password = password - def send_message(self, message, **kwargs): - """Wrapper function pass default parameters to actual send_message.""" - send_message( - message, - self.homeserver, - kwargs.get(ATTR_TARGET) or [self.default_room], - self.verify_tls, - self.username, - self.password - ) + self.mx_id = "{user}@{homeserver}".format( + user=username, homeserver=urlparse(homeserver).netloc) + # Login, this will raise a MatrixRequestError if login is unsuccessful + self.client = self.login() -def load_token(session_file): - """Load authentication tokens from persistent storage, if exists.""" - if not os.path.exists(session_file): - return + def get_auth_tokens(self): + """ + Read sorted authentication tokens from disk. - with open(session_file) as handle: - data = json.load(handle) + Returns the auth_tokens dictionary. + """ + if not os.path.exists(self.session_filepath): + return {} - for mx_id, token in data.items(): - AUTH_TOKENS[mx_id] = token - - -def store_token(mx_id, token): - """Store authentication token to session and persistent storage.""" - AUTH_TOKENS[mx_id] = token - - with open(SESSION_FILE, 'w') as handle: - handle.write(json.dumps(AUTH_TOKENS)) - - -def send_message(message, homeserver, target_rooms, verify_tls, - username, password): - """Do everything thats necessary to send a message to a Matrix room.""" - from matrix_client.client import MatrixClient, MatrixRequestError - - def login_by_token(): - """Login using authentication token.""" try: - return MatrixClient( - base_url=homeserver, - token=AUTH_TOKENS[mx_id], - user_id=username, - valid_cert_check=verify_tls - ) - except MatrixRequestError as ex: - _LOGGER.info("login_by_token: (%d) %s", ex.code, ex.content) + with open(self.session_filepath) as handle: + data = json.load(handle) + + auth_tokens = {} + for mx_id, token in data.items(): + auth_tokens[mx_id] = token + + return auth_tokens + + except (OSError, IOError, PermissionError) as ex: + _LOGGER.warning( + "Loading authentication tokens from file '%s' failed: %s", + self.session_filepath, str(ex)) + return {} + + def store_auth_token(self, token): + """Store authentication token to session and persistent storage.""" + self.auth_tokens[self.mx_id] = token - def login_by_password(): - """Login using password authentication.""" try: - _client = MatrixClient( - base_url=homeserver, valid_cert_check=verify_tls) - _client.login_with_password(username, password) - store_token(mx_id, _client.token) - return _client - except MatrixRequestError as ex: - _LOGGER.error("login_by_password: (%d) %s", ex.code, ex.content) + with open(self.session_filepath, 'w') as handle: + handle.write(json.dumps(self.auth_tokens)) - # This is as close as we can get to the mx_id, since there is no - # homeserver discovery protocol we have to fall back to the homeserver url - # instead of the actual domain it serves. - mx_id = "{user}@{homeserver}".format(user=username, homeserver=homeserver) + # Not saving the tokens to disk should not stop the client, we can just + # login using the password every time. + except (OSError, IOError, PermissionError) as ex: + _LOGGER.warning( + "Storing authentication tokens to file '%s' failed: %s", + self.session_filepath, str(ex)) - if mx_id in AUTH_TOKENS: - client = login_by_token() + def login(self): + """Login to the matrix homeserver and return the client instance.""" + from matrix_client.client import MatrixRequestError + + # Attempt to generate a valid client using either of the two possible + # login methods: + client = None + + # If we have an authentication token + if self.mx_id in self.auth_tokens: + try: + client = self.login_by_token() + _LOGGER.debug("Logged in using stored token.") + + except MatrixRequestError as ex: + _LOGGER.warning( + "Login by token failed, falling back to password. " + "login_by_token raised: (%d) %s", + ex.code, ex.content) + + # If we still don't have a client try password. if not client: - client = login_by_password() - if not client: + try: + client = self.login_by_password() + _LOGGER.debug("Logged in using password.") + + except MatrixRequestError as ex: _LOGGER.error( - "Login failed, both token and username/password invalid") - return - else: - client = login_by_password() - if not client: - _LOGGER.error("Login failed, username/password invalid") - return + "Login failed, both token and username/password invalid " + "login_by_password raised: (%d) %s", + ex.code, ex.content) - rooms = client.get_rooms() - for target_room in target_rooms: - try: - if target_room in rooms: - room = rooms[target_room] - else: - room = client.join_room(target_room) + # re-raise the error so the constructor can catch it. + raise - _LOGGER.debug(room.send_text(message)) - except MatrixRequestError as ex: - _LOGGER.error( - "Unable to deliver message to room '%s': (%d): %s", - target_room, ex.code, ex.content - ) + return client + + def login_by_token(self): + """Login using authentication token and return the client.""" + from matrix_client.client import MatrixClient + + return MatrixClient( + base_url=self.homeserver, + token=self.auth_tokens[self.mx_id], + user_id=self.username, + valid_cert_check=self.verify_tls) + + def login_by_password(self): + """Login using password authentication and return the client.""" + from matrix_client.client import MatrixClient + + _client = MatrixClient( + base_url=self.homeserver, + valid_cert_check=self.verify_tls) + + _client.login_with_password(self.username, self.password) + + self.store_auth_token(_client.token) + + return _client + + def send_message(self, message, **kwargs): + """Send the message to the matrix server.""" + from matrix_client.client import MatrixRequestError + + target_rooms = kwargs.get(ATTR_TARGET) or [self.default_room] + + rooms = self.client.get_rooms() + for target_room in target_rooms: + try: + if target_room in rooms: + room = rooms[target_room] + else: + room = self.client.join_room(target_room) + + _LOGGER.debug(room.send_text(message)) + + except MatrixRequestError as ex: + _LOGGER.error( + "Unable to deliver message to room '%s': (%d): %s", + target_room, ex.code, ex.content) diff --git a/homeassistant/components/notify/message_bird.py b/homeassistant/components/notify/message_bird.py index 7e9ce3b093f..b20abb52efc 100644 --- a/homeassistant/components/notify/message_bird.py +++ b/homeassistant/components/notify/message_bird.py @@ -34,7 +34,7 @@ def get_service(hass, config, discovery_info=None): # validates the api key client.balance() except messagebird.client.ErrorException: - _LOGGER.error('The specified MessageBird API key is invalid.') + _LOGGER.error("The specified MessageBird API key is invalid") return None return MessageBirdNotificationService(config.get(CONF_SENDER), client) @@ -54,15 +54,13 @@ class MessageBirdNotificationService(BaseNotificationService): targets = kwargs.get(ATTR_TARGET) if not targets: - _LOGGER.error('No target specified.') + _LOGGER.error("No target specified") return for target in targets: try: - self.client.message_create(self.sender, - target, - message, - {'reference': 'HA'}) + self.client.message_create( + self.sender, target, message, {'reference': 'HA'}) except ErrorException as exception: - _LOGGER.error('Failed to notify %s: %s', target, exception) + _LOGGER.error("Failed to notify %s: %s", target, exception) continue diff --git a/homeassistant/components/notify/mysensors.py b/homeassistant/components/notify/mysensors.py index a2708a51efd..d9576767f25 100644 --- a/homeassistant/components/notify/mysensors.py +++ b/homeassistant/components/notify/mysensors.py @@ -5,8 +5,8 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/notify.mysensors/ """ from homeassistant.components import mysensors -from homeassistant.components.notify import (ATTR_TARGET, - BaseNotificationService) +from homeassistant.components.notify import ( + ATTR_TARGET, BaseNotificationService) def get_service(hass, config, discovery_info=None): diff --git a/homeassistant/components/notify/nma.py b/homeassistant/components/notify/nma.py index 1116b5728fd..e81dc457a81 100644 --- a/homeassistant/components/notify/nma.py +++ b/homeassistant/components/notify/nma.py @@ -18,7 +18,6 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://www.notifymyandroid.com/publicapi/' - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, }) @@ -34,7 +33,7 @@ def get_service(hass, config, discovery_info=None): tree = ET.fromstring(response.content) if tree[0].tag == 'error': - _LOGGER.error("Wrong API key supplied. %s", tree[0].text) + _LOGGER.error("Wrong API key supplied: %s", tree[0].text) return None return NmaNotificationService(config[CONF_API_KEY]) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index fb6543afcb4..18f74ac01ae 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -14,9 +14,10 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pushbullet.py==0.10.0'] +_LOGGER = logging.getLogger(__name__) + ATTR_URL = 'url' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -33,9 +34,7 @@ def get_service(hass, config, discovery_info=None): try: pushbullet = PushBullet(config[CONF_API_KEY]) except InvalidKeyError: - _LOGGER.error( - "Wrong API key supplied. " - "Get it at https://www.pushbullet.com/account") + _LOGGER.error("Wrong API key supplied") return None return PushBulletNotificationService(pushbullet) diff --git a/homeassistant/components/notify/pushetta.py b/homeassistant/components/notify/pushetta.py index b1a71c57513..a29cd587105 100644 --- a/homeassistant/components/notify/pushetta.py +++ b/homeassistant/components/notify/pushetta.py @@ -29,9 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Pushetta notification service.""" - pushetta_service = PushettaNotificationService(config[CONF_API_KEY], - config[CONF_CHANNEL_NAME], - config[CONF_SEND_TEST_MSG]) + api_key = config[CONF_API_KEY] + channel_name = config[CONF_CHANNEL_NAME] + send_test_msg = config[CONF_SEND_TEST_MSG] + + pushetta_service = PushettaNotificationService( + api_key, channel_name, send_test_msg) if pushetta_service.is_valid: return pushetta_service diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index afaf4e6a7e9..3d8d62230ee 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -32,11 +32,10 @@ def get_service(hass, config, discovery_info=None): from pushover import InitError try: - return PushoverNotificationService(config[CONF_USER_KEY], - config[CONF_API_KEY]) + return PushoverNotificationService( + config[CONF_USER_KEY], config[CONF_API_KEY]) except InitError: - _LOGGER.error( - 'Wrong API key supplied. Get it at https://pushover.net') + _LOGGER.error("Wrong API key supplied") return None @@ -74,4 +73,4 @@ class PushoverNotificationService(BaseNotificationService): except ValueError as val_err: _LOGGER.error(str(val_err)) except RequestError: - _LOGGER.exception('Could not send pushover notification') + _LOGGER.exception("Could not send pushover notification") diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/notify/rest.py index 41d100b3a09..1a40e1f1833 100644 --- a/homeassistant/components/notify/rest.py +++ b/homeassistant/components/notify/rest.py @@ -71,8 +71,8 @@ class RestNotificationService(BaseNotificationService): } if self._title_param_name is not None: - data[self._title_param_name] = kwargs.get(ATTR_TITLE, - ATTR_TITLE_DEFAULT) + data[self._title_param_name] = kwargs.get( + ATTR_TITLE, ATTR_TITLE_DEFAULT) if self._target_param_name is not None and ATTR_TARGET in kwargs: # Target is a list as of 0.29 and we don't want to break existing diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/notify/sendgrid.py index 4db12a5b8d8..bb4a5078013 100644 --- a/homeassistant/components/notify/sendgrid.py +++ b/homeassistant/components/notify/sendgrid.py @@ -75,4 +75,4 @@ class SendgridNotificationService(BaseNotificationService): response = self._sg.client.mail.send.post(request_body=data) if response.status_code is not 202: - _LOGGER.error('Unable to send notification with SendGrid') + _LOGGER.error("Unable to send notification") diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index b9f33c95d43..8f63fcd7a2f 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -41,7 +41,7 @@ def get_service(hass, config, discovery_info=None): config.get(CONF_ICON, None)) except slacker.Error: - _LOGGER.exception("Slack authentication failed") + _LOGGER.exception("Authentication failed") return None @@ -77,12 +77,9 @@ class SlackNotificationService(BaseNotificationService): for target in targets: try: - self.slack.chat.post_message(target, message, - as_user=self._as_user, - username=self._username, - icon_emoji=self._icon, - attachments=attachments, - link_names=True) + self.slack.chat.post_message( + target, message, as_user=self._as_user, + username=self._username, icon_emoji=self._icon, + attachments=attachments, link_names=True) except slacker.Error as err: - _LOGGER.error("Could not send slack notification. Error: %s", - err) + _LOGGER.error("Could not send notification. Error: %s", err) diff --git a/homeassistant/components/notify/smtp.py b/homeassistant/components/notify/smtp.py index fbfa9e7a970..1a2e1bf5b4e 100644 --- a/homeassistant/components/notify/smtp.py +++ b/homeassistant/components/notify/smtp.py @@ -39,7 +39,7 @@ DEFAULT_STARTTLS = False # pylint: disable=no-value-for-parameter PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RECIPIENT): vol.Email(), + vol.Required(CONF_RECIPIENT): vol.All(cv.ensure_list, [vol.Email()]), vol.Optional(CONF_SERVER, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, @@ -74,7 +74,7 @@ class MailNotificationService(BaseNotificationService): """Implement the notification service for E-Mail messages.""" def __init__(self, server, port, timeout, sender, starttls, username, - password, recipient, debug): + password, recipients, debug): """Initialize the service.""" self._server = server self._port = port @@ -83,7 +83,7 @@ class MailNotificationService(BaseNotificationService): self.starttls = starttls self.username = username self.password = password - self.recipient = recipient + self.recipients = recipients self.debug = debug self.tries = 2 @@ -139,7 +139,7 @@ class MailNotificationService(BaseNotificationService): msg = _build_text_msg(message) msg['Subject'] = subject - msg['To'] = self.recipient + msg['To'] = ','.join(self.recipients) msg['From'] = self._sender msg['X-Mailer'] = 'HomeAssistant' msg['Date'] = email.utils.format_datetime(dt_util.now()) @@ -152,7 +152,7 @@ class MailNotificationService(BaseNotificationService): mail = self.connect() for _ in range(self.tries): try: - mail.sendmail(self._sender, self.recipient, + mail.sendmail(self._sender, self.recipients, msg.as_string()) break except smtplib.SMTPException: @@ -191,8 +191,8 @@ def _build_multipart_msg(message, images): msg.attach(attachment) attachment.add_header('Content-ID', '<{}>'.format(cid)) except TypeError: - _LOGGER.warning("Attachment %s has an unkown MIME type." - " Falling back to file", atch_name) + _LOGGER.warning("Attachment %s has an unkown MIME type. " + "Falling back to file", atch_name) attachment = MIMEApplication(file_bytes, Name=atch_name) attachment['Content-Disposition'] = ('attachment; ' 'filename="%s"' % diff --git a/homeassistant/components/notify/syslog.py b/homeassistant/components/notify/syslog.py index 31689bdc9f0..740148e28e5 100644 --- a/homeassistant/components/notify/syslog.py +++ b/homeassistant/components/notify/syslog.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) +_LOGGER = logging.getLogger(__name__) CONF_FACILITY = 'facility' CONF_OPTION = 'option' @@ -64,9 +65,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -_LOGGER = logging.getLogger(__name__) - - def get_service(hass, config, discovery_info=None): """Get the syslog notification service.""" import syslog diff --git a/homeassistant/components/notify/telstra.py b/homeassistant/components/notify/telstra.py index efe90dc51ba..7fabb51eac8 100644 --- a/homeassistant/components/notify/telstra.py +++ b/homeassistant/components/notify/telstra.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.components.notify import ( BaseNotificationService, ATTR_TITLE, PLATFORM_SCHEMA) -from homeassistant.const import CONTENT_TYPE_JSON +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_HEADER_CONTENT_TYPE import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -34,7 +34,7 @@ def get_service(hass, config, discovery_info=None): phone_number = config.get(CONF_PHONE_NUMBER) if _authenticate(consumer_key, consumer_secret) is False: - _LOGGER.exception('Error obtaining authorization from Telstra API') + _LOGGER.exception("Error obtaining authorization from Telstra API") return None return TelstraNotificationService( @@ -73,7 +73,7 @@ class TelstraNotificationService(BaseNotificationService): } message_resource = 'https://api.telstra.com/v1/sms/messages' message_headers = { - 'Content-Type': CONTENT_TYPE_JSON, + HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_JSON, 'Authorization': 'Bearer ' + token_response['access_token'], } message_response = requests.post( diff --git a/homeassistant/components/notify/twilio_call.py b/homeassistant/components/notify/twilio_call.py index f917d5cdab3..b517808d2ce 100644 --- a/homeassistant/components/notify/twilio_call.py +++ b/homeassistant/components/notify/twilio_call.py @@ -15,10 +15,10 @@ from homeassistant.components.notify import ( ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ["twilio"] +DEPENDENCIES = ['twilio'] -CONF_FROM_NUMBER = "from_number" +CONF_FROM_NUMBER = 'from_number' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_FROM_NUMBER): @@ -28,8 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Twilio Call notification service.""" - return TwilioCallNotificationService(hass.data[DATA_TWILIO], - config[CONF_FROM_NUMBER]) + return TwilioCallNotificationService( + hass.data[DATA_TWILIO], config[CONF_FROM_NUMBER]) class TwilioCallNotificationService(BaseNotificationService): @@ -58,8 +58,7 @@ class TwilioCallNotificationService(BaseNotificationService): for target in targets: try: - self.client.calls.create(to=target, - url=twimlet_url, - from_=self.from_number) + self.client.calls.create( + to=target, url=twimlet_url, from_=self.from_number) except TwilioRestException as exc: _LOGGER.error(exc) diff --git a/homeassistant/components/notify/twilio_sms.py b/homeassistant/components/notify/twilio_sms.py index 1bdfcb64407..52cfe2f436d 100644 --- a/homeassistant/components/notify/twilio_sms.py +++ b/homeassistant/components/notify/twilio_sms.py @@ -27,8 +27,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Twilio SMS notification service.""" - return TwilioSMSNotificationService(hass.data[DATA_TWILIO], - config[CONF_FROM_NUMBER]) + return TwilioSMSNotificationService( + hass.data[DATA_TWILIO], config[CONF_FROM_NUMBER]) class TwilioSMSNotificationService(BaseNotificationService): @@ -48,5 +48,5 @@ class TwilioSMSNotificationService(BaseNotificationService): return for target in targets: - self.client.messages.create(to=target, body=message, - from_=self.from_number) + self.client.messages.create( + to=target, body=message, from_=self.from_number) diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/notify/twitter.py index 21388c292eb..4bbe8a5d9e1 100644 --- a/homeassistant/components/notify/twitter.py +++ b/homeassistant/components/notify/twitter.py @@ -64,5 +64,4 @@ class TwitterNotificationService(BaseNotificationService): error_message = obj['errors'][0]['message'] error_code = obj['errors'][0]['code'] _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - error_message, - error_code) + error_message, error_code) diff --git a/homeassistant/components/notify/webostv.py b/homeassistant/components/notify/webostv.py index 0e91fc8698a..c70b198a333 100644 --- a/homeassistant/components/notify/webostv.py +++ b/homeassistant/components/notify/webostv.py @@ -14,7 +14,7 @@ from homeassistant.components.notify import ( ATTR_DATA, BaseNotificationService, PLATFORM_SCHEMA) from homeassistant.const import (CONF_FILENAME, CONF_HOST, CONF_ICON) -REQUIREMENTS = ['pylgtv==0.1.6'] +REQUIREMENTS = ['pylgtv==0.1.7'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index ee358ed069c..8faafbeab58 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -33,9 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Jabber (XMPP) notification service.""" return XmppNotificationService( - config.get('sender'), - config.get('password'), - config.get('recipient'), + config.get('sender'), config.get('password'), config.get('recipient'), config.get('tls')) @@ -86,7 +84,7 @@ def send_message(sender, password, recipient, use_tls, message): 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/nuimo_controller.py b/homeassistant/components/nuimo_controller.py index e9fd41bd098..ffd7a799413 100644 --- a/homeassistant/components/nuimo_controller.py +++ b/homeassistant/components/nuimo_controller.py @@ -7,13 +7,15 @@ https://home-assistant.io/components/nuimo_controller/ import logging import threading import time + import voluptuous as vol + import homeassistant.helpers.config_validation as cv from homeassistant.const import (CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STOP) REQUIREMENTS = [ '--only-binary=all ' # avoid compilation of gattlib - 'http://github.com/getSenic/nuimo-linux-python' + 'https://github.com/getSenic/nuimo-linux-python' '/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip' '#nuimo==1.0.0'] @@ -44,7 +46,7 @@ DEFAULT_ADAPTER = 'hci0' def setup(hass, config): - """Setup the Nuimo component.""" + """Set up the Nuimo component.""" conf = config[DOMAIN] mac = conf.get(CONF_MAC) name = conf.get(CONF_NAME) @@ -62,7 +64,7 @@ class NuimoLogger(object): def received_gesture_event(self, event): """Input Event received.""" - _LOGGER.debug("received event: name=%s, gesture_id=%s,value=%s", + _LOGGER.debug("Received event: name=%s, gesture_id=%s,value=%s", event.name, event.gesture, event.value) self._hass.bus.fire(EVENT_NUIMO, {'type': event.name, 'value': event.value, @@ -83,7 +85,7 @@ class NuimoThread(threading.Thread): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) def run(self): - """Setup connection or be idle.""" + """Set up the connection or be idle.""" while self._hass_is_running: if not self._nuimo or not self._nuimo.is_connected(): self._attach() @@ -102,7 +104,7 @@ class NuimoThread(threading.Thread): self._hass_is_running = False def _attach(self): - """Create a nuimo object from mac address or discovery.""" + """Create a Nuimo object from MAC address or discovery.""" # pylint: disable=import-error from nuimo import NuimoController, NuimoDiscoveryManager @@ -118,7 +120,7 @@ class NuimoThread(threading.Thread): nuimo_manager.start_discovery() # Were any Nuimos found? if not nuimo_manager.nuimos: - _LOGGER.debug('No Nuimos detected') + _LOGGER.debug("No Nuimo devices detected") return # Take the first Nuimo found. self._nuimo = nuimo_manager.nuimos[0] @@ -131,9 +133,9 @@ class NuimoThread(threading.Thread): try: self._nuimo.connect() - _LOGGER.debug('connected to %s', self._mac) + _LOGGER.debug("Connected to %s", self._mac) except RuntimeError as error: - _LOGGER.error('could not connect to %s: %s', self._mac, error) + _LOGGER.error("Could not connect to %s: %s", self._mac, error) time.sleep(1) return @@ -148,9 +150,9 @@ class NuimoThread(threading.Thread): if self._name == name and matrix: self._nuimo.write_matrix(matrix, interval) - self._hass.services.register(DOMAIN, SERVICE_NUIMO, - handle_write_matrix, - schema=SERVICE_NUIMO_SCHEMA) + self._hass.services.register( + DOMAIN, SERVICE_NUIMO, handle_write_matrix, + schema=SERVICE_NUIMO_SCHEMA) self._nuimo.write_matrix(HOMEASSIST_LOGO, 2.0) @@ -173,15 +175,15 @@ class DiscoveryLogger(object): # pylint: disable=no-self-use def discovery_started(self): - """Discovery startet.""" - _LOGGER.info("started discovery") + """Discovery started.""" + _LOGGER.info("Started discovery") # pylint: disable=no-self-use def discovery_finished(self): """Discovery finished.""" - _LOGGER.info("finished discovery") + _LOGGER.info("Finished discovery") # pylint: disable=no-self-use def controller_added(self, nuimo): - """Controller found.""" - _LOGGER.info("added Nuimo: %s", nuimo) + """Return that a controller was found.""" + _LOGGER.info("Added Nuimo: %s", nuimo) diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index 24f7039a41c..b06b15c7973 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -28,7 +28,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Set up OctoPrint API.""" + """Set up the OctoPrint component.""" base_url = 'http://{}/api/'.format(config[DOMAIN][CONF_HOST]) api_key = config[DOMAIN][CONF_API_KEY] @@ -75,9 +75,8 @@ class OctoPrintAPI(object): return self.printer_last_reading[0] url = self.api_url + endpoint try: - response = requests.get(url, - headers=self.headers, - timeout=30) + response = requests.get( + url, headers=self.headers, timeout=30) response.raise_for_status() if endpoint == "job": self.job_last_reading[0] = response.json() @@ -86,18 +85,16 @@ class OctoPrintAPI(object): self.printer_last_reading[0] = response.json() self.printer_last_reading[1] = time.time() return response.json() - except requests.exceptions.ConnectionError as conn_exc: + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError) as conn_exc: _LOGGER.error("Failed to update OctoPrint status. Error: %s", conn_exc) - raise def update(self, sensor_type, end_point, group, tool=None): """Return the value for sensor_type from the provided endpoint.""" - try: - return get_value_from_json(self.get(end_point), sensor_type, - group, tool) - except requests.exceptions.ConnectionError: - raise + response = self.get(end_point) + if response is not None: + return get_value_from_json(response, sensor_type, group, tool) # pylint: disable=unused-variable diff --git a/homeassistant/components/opencv.py b/homeassistant/components/opencv.py new file mode 100644 index 00000000000..634e8e156a1 --- /dev/null +++ b/homeassistant/components/opencv.py @@ -0,0 +1,183 @@ +""" +Support for OpenCV image/video processing. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/opencv/ +""" +import logging +import os +import voluptuous as vol + +import requests + +from homeassistant.const import ( + CONF_NAME, + CONF_ENTITY_ID, + CONF_FILE_PATH +) +from homeassistant.helpers import ( + discovery, + config_validation as cv, +) + +REQUIREMENTS = ['opencv-python==3.2.0.6', 'numpy==1.12.0'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_MATCHES = 'matches' + +BASE_PATH = os.path.realpath(__file__) + +CASCADE_URL = \ + 'https://raw.githubusercontent.com/opencv/opencv/master/data/' +\ + 'lbpcascades/lbpcascade_frontalface.xml' + +CONF_CLASSIFIER = 'classifier' +CONF_COLOR = 'color' +CONF_GROUPS = 'classifier_group' +CONF_MIN_SIZE = 'min_size' +CONF_NEIGHBORS = 'neighbors' +CONF_SCALE = 'scale' + +DATA_CLASSIFIER_GROUPS = 'classifier_groups' + +DEFAULT_COLOR = (255, 255, 0) +DEFAULT_CLASSIFIER_PATH = 'lbp_frontalface.xml' +DEFAULT_NAME = 'OpenCV' +DEFAULT_MIN_SIZE = (30, 30) +DEFAULT_NEIGHBORS = 4 +DEFAULT_SCALE = 1.1 + +DOMAIN = 'opencv' + +CLASSIFIER_GROUP_CONFIG = { + vol.Required(CONF_CLASSIFIER): vol.All( + cv.ensure_list, + [vol.Schema({ + vol.Optional(CONF_COLOR, default=DEFAULT_COLOR): + vol.Schema((int, int, int)), + vol.Optional(CONF_FILE_PATH, default=None): cv.isfile, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): + cv.string, + vol.Optional(CONF_MIN_SIZE, default=DEFAULT_MIN_SIZE): + vol.Schema((int, int)), + vol.Optional(CONF_NEIGHBORS, default=DEFAULT_NEIGHBORS): + cv.positive_int, + vol.Optional(CONF_SCALE, default=DEFAULT_SCALE): + float + })]), + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +} +CLASSIFIER_GROUP_SCHEMA = vol.Schema(CLASSIFIER_GROUP_CONFIG) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_GROUPS): vol.All( + cv.ensure_list, + [CLASSIFIER_GROUP_SCHEMA] + ), + }) +}, extra=vol.ALLOW_EXTRA) + + +# NOTE: +# pylint cannot find any of the members of cv2, using disable=no-member +# to pass linting + + +def cv_image_to_bytes(cv_image): + """Convert OpenCV image to bytes.""" + import cv2 # pylint: disable=import-error + + # pylint: disable=no-member + encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90] + # pylint: disable=no-member + success, data = cv2.imencode('.jpg', cv_image, encode_param) + + if success: + return data.tobytes() + + return None + + +def cv_image_from_bytes(image): + """Convert image bytes to OpenCV image.""" + import cv2 # pylint: disable=import-error + import numpy + + # pylint: disable=no-member + return cv2.imdecode(numpy.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) + + +def process_image(image, classifier_group, is_camera): + """Process the image given a classifier group.""" + import cv2 # pylint: disable=import-error + import numpy + + # pylint: disable=no-member + cv_image = cv2.imdecode(numpy.asarray(bytearray(image)), + cv2.IMREAD_UNCHANGED) + group_matches = {} + for classifier_config in classifier_group: + classifier_path = classifier_config[CONF_FILE_PATH] + classifier_name = classifier_config[CONF_NAME] + color = classifier_config[CONF_COLOR] + scale = classifier_config[CONF_SCALE] + neighbors = classifier_config[CONF_NEIGHBORS] + min_size = classifier_config[CONF_MIN_SIZE] + + # pylint: disable=no-member + classifier = cv2.CascadeClassifier(classifier_path) + + detections = classifier.detectMultiScale(cv_image, + scaleFactor=scale, + minNeighbors=neighbors, + minSize=min_size) + regions = [] + # pylint: disable=invalid-name + for (x, y, w, h) in detections: + if is_camera: + # pylint: disable=no-member + cv2.rectangle(cv_image, + (x, y), + (x + w, y + h), + color, + 2) + else: + regions.append((int(x), int(y), int(w), int(h))) + group_matches[classifier_name] = regions + + if is_camera: + return cv_image_to_bytes(cv_image) + else: + return group_matches + + +def setup(hass, config): + """Set up the OpenCV platform entities.""" + default_classifier = hass.config.path(DEFAULT_CLASSIFIER_PATH) + + if not os.path.isfile(default_classifier): + _LOGGER.info('Downloading default classifier') + + req = requests.get(CASCADE_URL, stream=True) + with open(default_classifier, 'wb') as fil: + for chunk in req.iter_content(chunk_size=1024): + if chunk: # filter out keep-alive new chunks + fil.write(chunk) + + for group in config[DOMAIN][CONF_GROUPS]: + grp = {} + + for classifier, config in group.items(): + config = dict(config) + + if config[CONF_FILE_PATH] is None: + config[CONF_FILE_PATH] = default_classifier + + grp[classifier] = config + + discovery.load_platform(hass, 'image_processing', DOMAIN, grp) + + return True diff --git a/homeassistant/components/panel_iframe.py b/homeassistant/components/panel_iframe.py index d0f9d20f838..50e764ba1f9 100644 --- a/homeassistant/components/panel_iframe.py +++ b/homeassistant/components/panel_iframe.py @@ -1,4 +1,9 @@ -"""Add an iframe panel to Home Assistant.""" +""" +Register an iFrame front end panel. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/panel_iframe/ +""" import voluptuous as vol import homeassistant.helpers.config_validation as cv @@ -22,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup iframe frontend panels.""" + """Set up the iFrame frontend panels.""" for url_path, info in config[DOMAIN].items(): register_built_in_panel( hass, 'iframe', info.get(CONF_TITLE), info.get(CONF_ICON), diff --git a/homeassistant/components/persistent_notification.py b/homeassistant/components/persistent_notification.py index d7eef848679..7173045c06d 100644 --- a/homeassistant/components/persistent_notification.py +++ b/homeassistant/components/persistent_notification.py @@ -17,13 +17,15 @@ from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.util import slugify from homeassistant.config import load_yaml_config_file +ATTR_MESSAGE = 'message' +ATTR_NOTIFICATION_ID = 'notification_id' +ATTR_TITLE = 'title' + DOMAIN = 'persistent_notification' + ENTITY_ID_FORMAT = DOMAIN + '.{}' SERVICE_CREATE = 'create' -ATTR_TITLE = 'title' -ATTR_MESSAGE = 'message' -ATTR_NOTIFICATION_ID = 'notification_id' SCHEMA_SERVICE_CREATE = vol.Schema({ vol.Required(ATTR_MESSAGE): cv.template, @@ -57,7 +59,7 @@ def async_create(hass, message, title=None, notification_id=None): @asyncio.coroutine def async_setup(hass, config): - """Setup the persistent notification component.""" + """Set up the persistent notification component.""" @callback def create_service(call): """Handle a create notification service call.""" diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight.py index e160d074bbe..3000820d28c 100644 --- a/homeassistant/components/pilight.py +++ b/homeassistant/components/pilight.py @@ -8,7 +8,6 @@ import logging import functools import socket import threading - from datetime import timedelta import voluptuous as vol @@ -24,8 +23,7 @@ REQUIREMENTS = ['pilight==0.1.1'] _LOGGER = logging.getLogger(__name__) - -CONF_SEND_DELAY = "send_delay" +CONF_SEND_DELAY = 'send_delay' DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 5000 @@ -55,29 +53,29 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Pilight component.""" + """Set up the Pilight component.""" from pilight import pilight host = config[DOMAIN][CONF_HOST] port = config[DOMAIN][CONF_PORT] - send_throttler = CallRateDelayThrottle(hass, - config[DOMAIN][CONF_SEND_DELAY]) + send_throttler = CallRateDelayThrottle( + hass, config[DOMAIN][CONF_SEND_DELAY]) try: pilight_client = pilight.Client(host=host, port=port) except (socket.error, socket.timeout) as err: - _LOGGER.error("Unable to connect to %s on port %s: %s", - host, port, err) + _LOGGER.error( + "Unable to connect to %s on port %s: %s", host, port, err) return False def start_pilight_client(_): - """Called once when Home Assistant starts.""" + """Run when Home Assistant starts.""" pilight_client.start() hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_pilight_client) def stop_pilight_client(_): - """Called once when Home Assistant stops.""" + """Run once when Home Assistant stops.""" pilight_client.stop() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_pilight_client) @@ -92,7 +90,7 @@ def setup(hass, config): try: pilight_client.send_code(message_data) except IOError: - _LOGGER.error('Pilight send failed for %s', str(message_data)) + _LOGGER.error("Pilight send failed for %s", str(message_data)) hass.services.register( DOMAIN, SERVICE_NAME, send_code, schema=RF_CODE_SCHEMA) @@ -102,7 +100,7 @@ def setup(hass, config): whitelist = config[DOMAIN].get(CONF_WHITELIST) def handle_received_code(data): - """Called when RF codes are received.""" + """Run when RF codes are received.""" # Unravel dict of dicts to make event_data cut in automation rule # possible data = dict({'protocol': data['protocol'], 'uuid': data['uuid']}, @@ -142,22 +140,22 @@ class CallRateDelayThrottle(object): self._schedule = functools.partial(track_point_in_utc_time, hass) def limited(self, method): - """Decorator to delay calls on a certain method.""" + """Decorate to delay calls on a certain method.""" @functools.wraps(method) def decorated(*args, **kwargs): - """The decorated function.""" + """Delay a call.""" if self._delay.total_seconds() == 0.0: method(*args, **kwargs) return def action(event): - """The action wrapper that gets scheduled.""" + """Wrap an action that gets scheduled.""" method(*args, **kwargs) with self._lock: self._next_ts = dt_util.utcnow() + self._delay - if len(self._queue) == 0: + if not self._queue: self._active = False else: next_action = self._queue.pop(0) diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant.py new file mode 100644 index 00000000000..2215d7c2f30 --- /dev/null +++ b/homeassistant/components/plant.py @@ -0,0 +1,241 @@ +""" +Component to monitor plants. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/plant/ +""" +import logging +import asyncio + +import voluptuous as vol + +from homeassistant.const import ( + STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_SENSORS, + ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.core import callback +from homeassistant.helpers.event import async_track_state_change + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'plant' + +READING_BATTERY = 'battery' +READING_TEMPERATURE = ATTR_TEMPERATURE +READING_MOISTURE = 'moisture' +READING_CONDUCTIVITY = 'conductivity' +READING_BRIGHTNESS = 'brightness' + +ATTR_PROBLEM = 'problem' +PROBLEM_NONE = 'none' + +CONF_MIN_BATTERY_LEVEL = 'min_' + READING_BATTERY +CONF_MIN_TEMPERATURE = 'min_' + READING_TEMPERATURE +CONF_MAX_TEMPERATURE = 'max_' + READING_TEMPERATURE +CONF_MIN_MOISTURE = 'min_' + READING_MOISTURE +CONF_MAX_MOISTURE = 'max_' + READING_MOISTURE +CONF_MIN_CONDUCTIVITY = 'min_' + READING_CONDUCTIVITY +CONF_MAX_CONDUCTIVITY = 'max_' + READING_CONDUCTIVITY +CONF_MIN_BRIGHTNESS = 'min_' + READING_BRIGHTNESS +CONF_MAX_BRIGHTNESS = 'max_' + READING_BRIGHTNESS + +CONF_SENSOR_BATTERY_LEVEL = READING_BATTERY +CONF_SENSOR_MOISTURE = READING_MOISTURE +CONF_SENSOR_CONDUCTIVITY = READING_CONDUCTIVITY +CONF_SENSOR_TEMPERATURE = READING_TEMPERATURE +CONF_SENSOR_BRIGHTNESS = READING_BRIGHTNESS + +SCHEMA_SENSORS = vol.Schema({ + vol.Optional(CONF_SENSOR_BATTERY_LEVEL): cv.entity_id, + vol.Optional(CONF_SENSOR_MOISTURE): cv.entity_id, + vol.Optional(CONF_SENSOR_CONDUCTIVITY): cv.entity_id, + vol.Optional(CONF_SENSOR_TEMPERATURE): cv.entity_id, + vol.Optional(CONF_SENSOR_BRIGHTNESS): cv.entity_id, +}) + +PLANT_SCHEMA = vol.Schema({ + vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS), + vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int, + vol.Optional(CONF_MIN_TEMPERATURE): cv.small_float, + vol.Optional(CONF_MAX_TEMPERATURE): cv.small_float, + vol.Optional(CONF_MIN_MOISTURE): cv.positive_int, + vol.Optional(CONF_MAX_MOISTURE): cv.positive_int, + vol.Optional(CONF_MIN_CONDUCTIVITY): cv.positive_int, + vol.Optional(CONF_MAX_CONDUCTIVITY): cv.positive_int, + vol.Optional(CONF_MIN_BRIGHTNESS): cv.positive_int, + vol.Optional(CONF_MAX_BRIGHTNESS): cv.positive_int, +}) + +DOMAIN = 'plant' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: { + cv.string: PLANT_SCHEMA + }, +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Set up the Plant component.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + + entities = [] + for plant_name, plant_config in config[DOMAIN].items(): + _LOGGER.info("Added plant %s", plant_name) + entity = Plant(plant_name, plant_config) + sensor_entity_ids = list(plant_config[CONF_SENSORS].values()) + _LOGGER.debug("Subscribing to entity_ids %s", sensor_entity_ids) + async_track_state_change(hass, sensor_entity_ids, entity.state_changed) + entities.append(entity) + + yield from component.async_add_entities(entities) + + return True + + +class Plant(Entity): + """Plant monitors the well-being of a plant. + + It also checks the measurements against + configurable min and max values. + """ + + READINGS = { + READING_BATTERY: { + ATTR_UNIT_OF_MEASUREMENT: '%', + 'min': CONF_MIN_BATTERY_LEVEL, + 'icon': 'mdi:battery-outline' + }, + READING_TEMPERATURE: { + ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, + 'min': CONF_MIN_TEMPERATURE, + 'max': CONF_MAX_TEMPERATURE, + 'icon': 'mdi:thermometer' + }, + READING_MOISTURE: { + ATTR_UNIT_OF_MEASUREMENT: '%', + 'min': CONF_MIN_MOISTURE, + 'max': CONF_MAX_MOISTURE, + 'icon': 'mdi:water' + }, + READING_CONDUCTIVITY: { + ATTR_UNIT_OF_MEASUREMENT: 'µS/cm', + 'min': CONF_MIN_CONDUCTIVITY, + 'max': CONF_MAX_CONDUCTIVITY, + 'icon': 'mdi:emoticon-poop' + }, + READING_BRIGHTNESS: { + ATTR_UNIT_OF_MEASUREMENT: 'lux', + 'min': CONF_MIN_BRIGHTNESS, + 'max': CONF_MAX_BRIGHTNESS, + 'icon': 'mdi:white-balance-sunny' + } + } + + def __init__(self, name, config): + """Initialize the Plant component.""" + self._config = config + self._sensormap = dict() + for reading, entity_id in config['sensors'].items(): + self._sensormap[entity_id] = reading + self._state = STATE_UNKNOWN + self._name = name + self._battery = None + self._moisture = None + self._conductivity = None + self._temperature = None + self._brightness = None + self._icon = 'mdi:help-circle' + self._problems = PROBLEM_NONE + + @callback + def state_changed(self, entity_id, _, new_state): + """Update the sensor status. + + This callback is triggered, when the sensor state changes. + """ + value = new_state.state + _LOGGER.debug("Received callback from %s with value %s", + entity_id, value) + if value == STATE_UNKNOWN: + return + + reading = self._sensormap[entity_id] + if reading == READING_MOISTURE: + self._moisture = int(value) + elif reading == READING_BATTERY: + self._battery = int(value) + elif reading == READING_TEMPERATURE: + self._temperature = float(value) + elif reading == READING_CONDUCTIVITY: + self._conductivity = int(value) + elif reading == READING_BRIGHTNESS: + self._brightness = int(value) + else: + raise _LOGGER.error("Unknown reading from sensor %s: %s", + entity_id, value) + self._update_state() + + def _update_state(self): + """Update the state of the class based sensor data.""" + result = [] + for sensor_name in self._sensormap.values(): + params = self.READINGS[sensor_name] + value = getattr(self, '_{}'.format(sensor_name)) + if value is not None: + if 'min' in params and params['min'] in self._config: + min_value = self._config[params['min']] + if value < min_value: + result.append('{} low'.format(sensor_name)) + self._icon = params['icon'] + + if 'max' in params and params['max'] in self._config: + max_value = self._config[params['max']] + if value > max_value: + result.append('{} high'.format(sensor_name)) + self._icon = params['icon'] + + if len(result) == 0: + self._state = 'ok' + self._icon = 'mdi:thumb-up' + self._problems = PROBLEM_NONE + else: + self._state = 'problem' + self._problems = ','.join(result) + _LOGGER.debug("New data processed") + self.hass.async_add_job(self.async_update_ha_state()) + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the entity.""" + return self._state + + @property + def state_attributes(self): + """Return the attributes of the entity. + + Provide the individual measurements from the + sensor in the attributes of the device. + """ + attrib = { + ATTR_ICON: self._icon, + ATTR_PROBLEM: self._problems, + } + + for reading in self._sensormap.values(): + attrib[reading] = getattr(self, '_{}'.format(reading)) + + return attrib diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index 084d6ac7407..38b37cad51e 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -11,13 +11,13 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONF_ZONE, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change from homeassistant.util.distance import convert from homeassistant.util.location import distance -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup_proximity_component(hass, name, config): - """Set up individual proximity component.""" + """Set up the individual proximity component.""" ignored_zones = config.get(CONF_IGNORED_ZONES) proximity_devices = config.get(CONF_DEVICES) tolerance = config.get(CONF_TOLERANCE) @@ -129,7 +129,7 @@ class Proximity(Entity): } def check_proximity_state_change(self, entity, old_state, new_state): - """Function to perform the proximity checking.""" + """Perform the proximity checking.""" entity_name = new_state.name devices_to_calculate = False devices_in_zone = '' diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index 2d497d38273..dd8ceefb169 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -123,7 +123,7 @@ class QSLight(QSToggleEntity, Light): def setup(hass, config): - """Setup the QSUSB component.""" + """Set up the QSUSB component.""" from pyqwikswitch import ( QSUsb, CMD_BUTTONS, QS_NAME, QS_ID, QS_CMD, PQS_VALUE, PQS_TYPE, QSType) @@ -165,13 +165,13 @@ def setup(hass, config): # Load platforms for comp_name in ('switch', 'light'): - if len(QSUSB[comp_name]) > 0: + if QSUSB[comp_name]: load_platform(hass, comp_name, 'qwikswitch', {}, config) def qs_callback(item): """Typically a button press or update signal.""" if qsusb is None: # Shutting down - _LOGGER.info("Done") + _LOGGER.info("Botton press or updating signal done") return # If button pressed, fire a hass event diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 04322646f80..90dc34c4634 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -33,10 +33,12 @@ from . import purge, migration from .const import DATA_INSTANCE from .util import session_scope -DOMAIN = 'recorder' - REQUIREMENTS = ['sqlalchemy==1.1.9'] +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'recorder' + DEFAULT_URL = 'sqlite:///{hass_config_path}' DEFAULT_DB_FILE = 'home-assistant_v2.db' @@ -66,8 +68,6 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) -_LOGGER = logging.getLogger(__name__) - def wait_connection_ready(hass): """ @@ -101,7 +101,7 @@ def run_information(hass, point_in_time: Optional[datetime]=None): @asyncio.coroutine def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Setup the recorder.""" + """Set up the recorder.""" conf = config.get(DOMAIN, {}) purge_days = conf.get(CONF_PURGE_DAYS) @@ -175,7 +175,7 @@ class Recorder(threading.Thread): if not connected: @callback def connection_failed(): - """Connection failed tasks.""" + """Connect failed tasks.""" self.async_db_ready.set_result(False) persistent_notification.async_create( self.hass, diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index ce42ba187c2..6dcb5dbd051 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -1,11 +1,11 @@ """Models for SQLAlchemy.""" - import json from datetime import datetime import logging -from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Index, Integer, - String, Text, distinct) +from sqlalchemy import ( + Boolean, Column, DateTime, ForeignKey, Index, Integer, String, Text, + distinct) from sqlalchemy.ext.declarative import declarative_base import homeassistant.util.dt as dt_util diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index e4ea1af1060..63faf2633b1 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -24,7 +24,7 @@ def session_scope(*, hass=None, session=None): yield session session.commit() except Exception as err: # pylint: disable=broad-except - _LOGGER.error('Error executing query: %s', err) + _LOGGER.error("Error executing query: %s", err) session.rollback() raise finally: @@ -43,7 +43,7 @@ def commit(session, work): session.commit() return True except sqlalchemy.exc.OperationalError as err: - _LOGGER.error('Error executing query: %s', err) + _LOGGER.error("Error executing query: %s", err) session.rollback() time.sleep(QUERY_RETRY_WAIT) return False @@ -63,7 +63,7 @@ def execute(qry): (row.to_native() for row in qry) if row is not None] except SQLAlchemyError as err: - _LOGGER.error('Error executing query: %s', err) + _LOGGER.error("Error executing query: %s", err) if tryno == RETRIES - 1: raise diff --git a/homeassistant/components/remote/demo.py b/homeassistant/components/remote/demo.py index 90c691a3d3c..6976c116be9 100644 --- a/homeassistant/components/remote/demo.py +++ b/homeassistant/components/remote/demo.py @@ -10,7 +10,7 @@ from homeassistant.const import DEVICE_DEFAULT_NAME # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the demo remotes.""" + """Set up the demo remotes.""" add_devices_callback([ DemoRemote('Remote One', False, None), DemoRemote('Remote Two', True, 'mdi:remote'), diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py index 351b85cf902..e0c01023660 100755 --- a/homeassistant/components/remote/harmony.py +++ b/homeassistant/components/remote/harmony.py @@ -81,7 +81,7 @@ def register_services(hass): def _apply_service(service, service_func, *service_func_args): - """Internal func for applying a service.""" + """Handle services to apply.""" entity_ids = service.data.get('entity_id') if entity_ids: diff --git a/homeassistant/components/rest_command.py b/homeassistant/components/rest_command.py index 5bf629ed37f..026f0e9a19b 100644 --- a/homeassistant/components/rest_command.py +++ b/homeassistant/components/rest_command.py @@ -54,7 +54,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Setup the rest_command component.""" + """Set up the REST command component.""" websession = async_get_clientsession(hass) def async_register_rest_command(name, command_config): diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index 6eaf9ad1cf9..3c3f1e00f68 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -14,9 +14,9 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity from homeassistant.const import (ATTR_ENTITY_ID, TEMP_CELSIUS) -REQUIREMENTS = ['pyRFXtrx==0.17.0'] +REQUIREMENTS = ['pyRFXtrx==0.18.0'] -DOMAIN = "rfxtrx" +DOMAIN = 'rfxtrx' DEFAULT_SIGNAL_REPETITIONS = 1 @@ -126,10 +126,10 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the RFXtrx component.""" + """Set up the RFXtrx component.""" # Declare the Handle event def handle_receive(event): - """Callback all subscribers for RFXtrx gateway.""" + """Handle revieved messgaes from RFXtrx gateway.""" # Log RFXCOM event if not event.device.id_string: return @@ -191,7 +191,7 @@ def get_rfx_object(packetid): return obj -def get_devices_from_config(config, device): +def get_devices_from_config(config, device, hass): """Read rfxtrx configuration.""" signal_repetitions = config[CONF_SIGNAL_REPETITIONS] @@ -209,12 +209,13 @@ def get_devices_from_config(config, device): new_device = device(entity_info[ATTR_NAME], event, datas, signal_repetitions) + new_device.hass = hass RFX_DEVICES[device_id] = new_device devices.append(new_device) return devices -def get_new_device(event, config, device): +def get_new_device(event, config, device, hass): """Add entity if not exist and the automatic_add is True.""" device_id = slugify(event.device.id_string.lower()) if device_id in RFX_DEVICES: @@ -235,6 +236,7 @@ def get_new_device(event, config, device): signal_repetitions = config[CONF_SIGNAL_REPETITIONS] new_device = device(pkt_id, event, datas, signal_repetitions) + new_device.hass = hass RFX_DEVICES[device_id] = new_device return new_device diff --git a/homeassistant/components/ring.py b/homeassistant/components/ring.py index 01b9cee9996..450ef6b1978 100644 --- a/homeassistant/components/ring.py +++ b/homeassistant/components/ring.py @@ -13,7 +13,7 @@ import homeassistant.loader as loader from requests.exceptions import HTTPError, ConnectTimeout -REQUIREMENTS = ['ring_doorbell==0.1.3'] +REQUIREMENTS = ['ring_doorbell==0.1.4'] _LOGGER = logging.getLogger(__name__) @@ -35,7 +35,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Set up Ring component.""" + """Set up the Ring component.""" conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py index 0f2f5792cbc..dfc60b5e45e 100644 --- a/homeassistant/components/rpi_gpio.py +++ b/homeassistant/components/rpi_gpio.py @@ -19,7 +19,7 @@ DOMAIN = 'rpi_gpio' # pylint: disable=no-member def setup(hass, config): - """Setup the Raspberry PI GPIO component.""" + """Set up the Raspberry PI GPIO component.""" import RPi.GPIO as GPIO def cleanup_gpio(event): @@ -36,13 +36,13 @@ def setup(hass, config): def setup_output(port): - """Setup a GPIO as output.""" + """Set up 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.""" + """Set up 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) diff --git a/homeassistant/components/rss_feed_template.py b/homeassistant/components/rss_feed_template.py new file mode 100644 index 00000000000..1441a98c0a8 --- /dev/null +++ b/homeassistant/components/rss_feed_template.py @@ -0,0 +1,104 @@ +""" +Exports sensor values via RSS feed. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/rss_feed_template/ +""" + +import asyncio +from html import escape +from aiohttp import web + +import voluptuous as vol + +from homeassistant.components.http import HomeAssistantView +import homeassistant.helpers.config_validation as cv + +CONTENT_TYPE_XML = 'text/xml' +DEPENDENCIES = ['http'] + +DOMAIN = 'rss_feed_template' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + cv.match_all: vol.Schema({ + vol.Optional('requires_api_password', default=True): cv.boolean, + vol.Optional('title'): cv.template, + vol.Required('items'): vol.All( + cv.ensure_list, + [{ + vol.Optional('title'): cv.template, + vol.Optional('description'): cv.template, + }] + ) + }) + }) + }, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the RSS feed template component.""" + for (feeduri, feedconfig) in config[DOMAIN].items(): + url = '/api/rss_template/%s' % feeduri + + requires_auth = feedconfig.get('requires_api_password') + + title = feedconfig.get('title') + if title is not None: + title.hass = hass + + items = feedconfig.get('items') + for item in items: + if 'title' in item: + item['title'].hass = hass + if 'description' in item: + item['description'].hass = hass + + rss_view = RssView(url, requires_auth, title, items) + hass.http.register_view(rss_view) + + return True + + +class RssView(HomeAssistantView): + """Export states and other values as RSS.""" + + requires_auth = True + url = None + name = 'rss_template' + _title = None + _items = None + + def __init__(self, url, requires_auth, title, items): + """Initialize the rss view.""" + self.url = url + self.requires_auth = requires_auth + self._title = title + self._items = items + + @asyncio.coroutine + def get(self, request, entity_id=None): + """Generate the RSS view XML.""" + response = '\n\n' + + response += '\n' + if self._title is not None: + response += (' %s\n' % + escape(self._title.async_render())) + + for item in self._items: + response += ' \n' + if 'title' in item: + response += ' ' + response += escape(item['title'].async_render()) + response += '\n' + if 'description' in item: + response += ' ' + response += escape(item['description'].async_render()) + response += '\n' + response += ' \n' + + response += '\n' + + return web.Response( + body=response, content_type=CONTENT_TYPE_XML, status=200) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 0d407ac3a9a..5b147fbb656 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -68,7 +68,7 @@ def activate(hass, entity_id=None): @asyncio.coroutine def async_setup(hass, config): - """Setup scenes.""" + """Set up the scenes.""" logger = logging.getLogger(__name__) component = EntityComponent(logger, DOMAIN, hass) diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py index 39942eea301..57c56e8b2f6 100644 --- a/homeassistant/components/scene/homeassistant.py +++ b/homeassistant/components/scene/homeassistant.py @@ -37,7 +37,7 @@ SCENECONFIG = namedtuple('SceneConfig', [CONF_NAME, STATES]) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup home assistant scene entries.""" + """Set up home assistant scene entries.""" scene_config = config.get(STATES) async_add_devices(HomeAssistantScene( diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py index c831876bf11..622acbd2583 100644 --- a/homeassistant/components/scene/hunterdouglas_powerview.py +++ b/homeassistant/components/scene/hunterdouglas_powerview.py @@ -20,7 +20,7 @@ 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.""" + """Set up the powerview scenes stored in a Powerview hub.""" from powerview_api import powerview hub_address = config.get(HUB_ADDRESS) @@ -30,7 +30,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _scenes = _pv.get_scenes() _rooms = _pv.get_rooms() except ConnectionError: - _LOGGER.exception("error connecting to powerview " + _LOGGER.exception("Error connecting to powerview " "hub with ip address: %s", hub_address) return False add_devices(PowerViewScene(hass, scene, _rooms, _pv) @@ -49,9 +49,8 @@ class PowerViewScene(Scene): self.scene_data = scene_data self._sync_room_data(room_data) self.entity_id_format = DOMAIN + '.{}' - self.entity_id = generate_entity_id(self.entity_id_format, - str(self.scene_data["id"]), - hass=hass) + self.entity_id = generate_entity_id( + self.entity_id_format, str(self.scene_data["id"]), hass=hass) def _sync_room_data(self, room_data): """Sync the room data.""" diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/scene/lifx_cloud.py index f600510d406..b96a56ca2bd 100644 --- a/homeassistant/components/scene/lifx_cloud.py +++ b/homeassistant/components/scene/lifx_cloud.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = vol.Schema({ # pylint: disable=unused-argument @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the scenes stored in the LIFX Cloud.""" + """Set up the scenes stored in the LIFX Cloud.""" token = config.get(CONF_TOKEN) timeout = config.get(CONF_TIMEOUT) diff --git a/homeassistant/components/scene/wink.py b/homeassistant/components/scene/wink.py index aef7f375f0d..3906e7b5551 100644 --- a/homeassistant/components/scene/wink.py +++ b/homeassistant/components/scene/wink.py @@ -14,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink platform.""" + """Set up the Wink platform.""" import pywink for scene in pywink.get_scenes(): diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index bcab6465dc1..6d2982dd262 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -22,18 +22,20 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script -DOMAIN = "script" -ENTITY_ID_FORMAT = DOMAIN + '.{}' -GROUP_NAME_ALL_SCRIPTS = 'all scripts' +_LOGGER = logging.getLogger(__name__) -CONF_SEQUENCE = "sequence" - -ATTR_VARIABLES = 'variables' +ATTR_CAN_CANCEL = 'can_cancel' ATTR_LAST_ACTION = 'last_action' ATTR_LAST_TRIGGERED = 'last_triggered' -ATTR_CAN_CANCEL = 'can_cancel' +ATTR_VARIABLES = 'variables' -_LOGGER = logging.getLogger(__name__) +CONF_SEQUENCE = 'sequence' + +DOMAIN = 'script' + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +GROUP_NAME_ALL_SCRIPTS = 'all scripts' _SCRIPT_ENTRY_SCHEMA = vol.Schema({ CONF_ALIAS: cv.string, diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py index 549759f5e12..8c5c6570515 100644 --- a/homeassistant/components/scsgate.py +++ b/homeassistant/components/scsgate.py @@ -38,7 +38,7 @@ SCSGATE_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the SCSGate component.""" + """Set up the SCSGate component.""" device = config[DOMAIN][CONF_DEVICE] global SCSGATE @@ -61,7 +61,7 @@ def setup(hass, config): class SCSGate(object): - """The class for dealing with the SCSGate device via scsgate.Reactor.""" + """The class for dealing with the SCSGate device via scsgate.Reactor.""" def __init__(self, device, logger): """Initialize the SCSGate.""" @@ -81,7 +81,7 @@ class SCSGate(object): handle_message=self.handle_message) def handle_message(self, message): - """Method called whenever a message is seen on the bus.""" + """Handle a messages seen on the bus.""" from scsgate.messages import StateMessage, ScenarioTriggeredMessage self._logger.debug("Received message {}".format(message)) @@ -114,7 +114,7 @@ class SCSGate(object): @property def devices(self): - """Dictionary with known devices. + """Return a dictionary with known devices. Key is device ID, value is the device itself. """ @@ -141,7 +141,7 @@ class SCSGate(object): from scsgate.tasks import GetStatusTask with self._devices_to_register_lock: - while len(self._devices_to_register) != 0: + while self._devices_to_register: _, device = self._devices_to_register.popitem() self._devices[device.scs_id] = device self._device_being_registered = device.scs_id diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index a3f361bdffe..92b874cf2c8 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -11,17 +11,20 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'sensor' -SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_FORMAT = DOMAIN + '.{}' +SCAN_INTERVAL = timedelta(seconds=30) + @asyncio.coroutine def async_setup(hass, config): """Track states and offer events for sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL) yield from component.async_setup(config) return True diff --git a/homeassistant/components/sensor/alarmdecoder.py b/homeassistant/components/sensor/alarmdecoder.py index 88246cc0bc2..dba1697f026 100644 --- a/homeassistant/components/sensor/alarmdecoder.py +++ b/homeassistant/components/sensor/alarmdecoder.py @@ -10,19 +10,17 @@ import logging from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity - from homeassistant.components.alarmdecoder import (SIGNAL_PANEL_MESSAGE) - from homeassistant.const import (STATE_UNKNOWN) -DEPENDENCIES = ['alarmdecoder'] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['alarmdecoder'] + @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Perform the setup for AlarmDecoder sensor devices.""" + """Set up for AlarmDecoder sensor devices.""" _LOGGER.debug("AlarmDecoderSensor: async_setup_platform") device = AlarmDecoderSensor(hass) @@ -40,7 +38,7 @@ class AlarmDecoderSensor(Entity): self._icon = 'mdi:alarm-check' self._name = 'Alarm Panel Display' - _LOGGER.debug("AlarmDecoderSensor: Setting up panel") + _LOGGER.debug("Setting up panel") @asyncio.coroutine def async_added_to_hass(self): diff --git a/homeassistant/components/sensor/android_ip_webcam.py b/homeassistant/components/sensor/android_ip_webcam.py index 687649e226a..c9e1238d9a3 100644 --- a/homeassistant/components/sensor/android_ip_webcam.py +++ b/homeassistant/components/sensor/android_ip_webcam.py @@ -15,7 +15,7 @@ DEPENDENCIES = ['android_ip_webcam'] @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the IP Webcam Sensor.""" + """Set up the IP Webcam Sensor.""" if discovery_info is None: return diff --git a/homeassistant/components/sensor/apcupsd.py b/homeassistant/components/sensor/apcupsd.py index 8c2cf22655d..ec4db5e2934 100644 --- a/homeassistant/components/sensor/apcupsd.py +++ b/homeassistant/components/sensor/apcupsd.py @@ -105,7 +105,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup the APCUPSd sensors.""" + """Set up the APCUPSd sensors.""" entities = [] for resource in config[CONF_RESOURCES]: @@ -117,8 +117,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if sensor_type.upper() not in apcupsd.DATA.status: _LOGGER.warning( - 'Sensor type: "%s" does not appear in the APCUPSd status ' - 'output', sensor_type) + "Sensor type: %s does not appear in the APCUPSd status output", + sensor_type) entities.append(APCUPSdSensor(apcupsd.DATA, sensor_type)) diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py index c15ca41d2bf..a8ef179280b 100644 --- a/homeassistant/components/sensor/api_streams.py +++ b/homeassistant/components/sensor/api_streams.py @@ -87,5 +87,5 @@ class APICount(Entity): @property def unit_of_measurement(self): - """Unit of measurement.""" + """Return the unit of measurement.""" return "clients" diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py index 03307a49768..f49d8e76f6c 100644 --- a/homeassistant/components/sensor/arduino.py +++ b/homeassistant/components/sensor/arduino.py @@ -16,7 +16,6 @@ from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) CONF_PINS = 'pins' @@ -36,7 +35,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Arduino platform.""" - # Verify that the Arduino board is present if arduino.BOARD is None: _LOGGER.error("A connection has not been made to the Arduino board") return False diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index d99240cf0d2..6edef785280 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -83,7 +83,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if var_conf is not None: for variable, var_data in var_conf.items(): if variable not in response['variables']: - _LOGGER.error("Variable: '%s' does not exist", variable) + _LOGGER.error("Variable: %s does not exist", variable) continue renderer = make_renderer(var_data.get(CONF_VALUE_TEMPLATE)) diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/sensor/arwn.py index d2e148b8204..a63451771d6 100644 --- a/homeassistant/components/sensor/arwn.py +++ b/homeassistant/components/sensor/arwn.py @@ -135,17 +135,17 @@ class ArwnSensor(Entity): @property def unit_of_measurement(self): - """Unit this state is expressed in.""" + """Return the unit of measurement the state is expressed in.""" return self._unit_of_measurement @property def should_poll(self): - """Should we poll.""" + """Return the polling state.""" return False @property def icon(self): - """Icon of device based on its type.""" + """Return the icon of device based on its type.""" if self._icon: return self._icon else: diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index 48f3c66a4c1..371918a95d4 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -66,8 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): currency = config.get(CONF_CURRENCY) if currency not in exchangerates.get_ticker(): - _LOGGER.warning('Currency "%s" is not available. Using "USD"', - currency) + _LOGGER.warning("Currency %s is not available. Using USD", currency) currency = DEFAULT_CURRENCY data = BitcoinData() diff --git a/homeassistant/components/sensor/blink.py b/homeassistant/components/sensor/blink.py index e069dfa00f7..e434776ffc6 100644 --- a/homeassistant/components/sensor/blink.py +++ b/homeassistant/components/sensor/blink.py @@ -42,7 +42,7 @@ class BlinkSensor(Entity): """A Blink camera sensor.""" def __init__(self, name, sensor_type, index, data): - """A method to initialize sensors from Blink camera.""" + """Initialize sensors from Blink camera.""" self._name = 'blink_' + name + '_' + SENSOR_TYPES[sensor_type][0] self._camera_name = name self._type = sensor_type @@ -53,26 +53,26 @@ class BlinkSensor(Entity): @property def name(self): - """A method to return the name of the camera.""" + """Return the name of the camera.""" return self._name @property def state(self): - """A camera's current state.""" + """Return the camera's current state.""" return self._state @property def unique_id(self): - """A unique camera sensor identifier.""" + """Return the unique camera sensor identifier.""" return "sensor_{}_{}".format(self._name, self.index) @property def unit_of_measurement(self): - """A method to determine the unit of measurement for temperature.""" + """Return the unit of measurement.""" return self._unit_of_measurement def update(self): - """A method to retrieve sensor data from the camera.""" + """Retrieve sensor data from the camera.""" camera = self.data.cameras[self._camera_name] if self._type == 'temperature': self._state = camera.temperature diff --git a/homeassistant/components/sensor/bloomsky.py b/homeassistant/components/sensor/bloomsky.py index 1026e2a92db..62769dc0494 100644 --- a/homeassistant/components/sensor/bloomsky.py +++ b/homeassistant/components/sensor/bloomsky.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available BloomSky weather sensors.""" + """Set up the available BloomSky weather sensors.""" bloomsky = get_component('bloomsky') # Default needed in case of discovery sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES) @@ -68,7 +68,7 @@ class BloomSkySensor(Entity): @property def name(self): - """The name of the BloomSky device and this sensor.""" + """Return the name of the BloomSky device and this sensor.""" return self._name @property @@ -78,7 +78,7 @@ class BloomSkySensor(Entity): @property def state(self): - """The current state, eg. value, of this sensor.""" + """Return the current state, eg. value, of this sensor.""" return self._state @property diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/sensor/bom.py index a83ca49c619..4033211e461 100644 --- a/homeassistant/components/sensor/bom.py +++ b/homeassistant/components/sensor/bom.py @@ -102,14 +102,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if station is not None: if zone_id and wmo_id: _LOGGER.warning( - 'Using config "%s", not "%s" and "%s" for BOM sensor', + "Using config %s, not %s and %s for BOM sensor", CONF_STATION, CONF_ZONE_ID, CONF_WMO_ID) elif zone_id and wmo_id: station = '{}.{}'.format(zone_id, wmo_id) else: - station = closest_station(config.get(CONF_LATITUDE), - config.get(CONF_LONGITUDE), - hass.config.config_dir) + station = closest_station( + config.get(CONF_LATITUDE), config.get(CONF_LONGITUDE), + hass.config.config_dir) if station is None: _LOGGER.error("Could not get BOM weather station from lat/lon") return False @@ -186,7 +186,7 @@ class BOMCurrentData(object): def _build_url(self): url = _RESOURCE.format(self._zone_id, self._zone_id, self._wmo_id) - _LOGGER.info("BOM url %s", url) + _LOGGER.info("BOM URL %s", url) return url @Throttle(MIN_TIME_BETWEEN_UPDATES) @@ -272,7 +272,7 @@ def closest_station(lat, lon, cache_dir): stations = bom_stations(cache_dir) def comparable_dist(wmo_id): - """A fast key function for psudeo-distance from lat/lon.""" + """Create a psudeo-distance from lat/lon.""" station_lat, station_lon = stations[wmo_id] return (lat - station_lat) ** 2 + (lon - station_lon) ** 2 diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/sensor/broadlink.py index 38806959f55..7d5018e054c 100644 --- a/homeassistant/components/sensor/broadlink.py +++ b/homeassistant/components/sensor/broadlink.py @@ -1,4 +1,3 @@ - """ Support for the Broadlink RM2 Pro (only temperature) and A1 devices. @@ -9,12 +8,13 @@ from datetime import timedelta import binascii import logging import socket + import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_HOST, CONF_MAC, - CONF_MONITORED_CONDITIONS, - CONF_NAME, TEMP_CELSIUS, CONF_TIMEOUT) +from homeassistant.const import ( + CONF_HOST, CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS, + CONF_TIMEOUT) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Broadlink device sensors.""" + """Set up the Broadlink device sensors.""" mac = config.get(CONF_MAC).encode().replace(b':', b'') mac_addr = binascii.unhexlify(mac) broadlink_data = BroadlinkData( @@ -71,7 +71,7 @@ class BroadlinkSensor(Entity): def __init__(self, name, broadlink_data, sensor_type): """Initialize the sensor.""" - self._name = "%s %s" % (name, SENSOR_TYPES[sensor_type][0]) + self._name = '{} {}'.format(name, SENSOR_TYPES[sensor_type][0]) self._state = None self._type = sensor_type self._broadlink_data = broadlink_data @@ -119,7 +119,7 @@ class BroadlinkData(object): }) self.update = Throttle(interval)(self._update) if not self._auth(): - _LOGGER.warning("Failed to connect to device.") + _LOGGER.warning("Failed to connect to device") def _update(self, retry=3): try: diff --git a/homeassistant/components/sensor/cert_expiry.py b/homeassistant/components/sensor/cert_expiry.py new file mode 100644 index 00000000000..dfc15510d6f --- /dev/null +++ b/homeassistant/components/sensor/cert_expiry.py @@ -0,0 +1,102 @@ +""" +Counter for the days till a HTTPS (TLS) certificate will expire. + +For more details about this sensor please refer to the documentation at +https://home-assistant.io/components/sensor.cert_expiry/ +""" +import datetime +import logging +import socket +import ssl + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT) +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'SSL Certificate Expiry' +DEFAULT_PORT = 443 + +SCAN_INTERVAL = datetime.timedelta(hours=12) + +TIMEOUT = 10.0 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up certificate expiry sensor.""" + server_name = config.get(CONF_HOST) + server_port = config.get(CONF_PORT) + sensor_name = config.get(CONF_NAME) + + add_devices([SSLCertificate(sensor_name, server_name, server_port)], True) + + +class SSLCertificate(Entity): + """Implementation of the certificate expiry sensor.""" + + def __init__(self, sensor_name, server_name, server_port): + """Initialize the sensor.""" + self.server_name = server_name + self.server_port = server_port + self._name = sensor_name + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + return 'days' + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return 'mdi:certificate' + + def update(self): + """Fetch the certificate information.""" + try: + ctx = ssl.create_default_context() + sock = ctx.wrap_socket( + socket.socket(), server_hostname=self.server_name) + sock.settimeout(TIMEOUT) + sock.connect((self.server_name, self.server_port)) + except socket.gaierror: + _LOGGER.error("Cannot resolve hostname: %s", self.server_name) + return + except socket.timeout: + _LOGGER.error( + "Connection timeout with server: %s", self.server_name) + return + except OSError: + _LOGGER.error("Cannot connect to %s", self.server_name) + return + + try: + cert = sock.getpeercert() + except OSError: + _LOGGER.error("Cannot fetch certificate from %s", self.server_name) + return + + ts_seconds = ssl.cert_time_to_seconds(cert['notAfter']) + timestamp = datetime.datetime.fromtimestamp(ts_seconds) + expiry = timestamp - datetime.datetime.today() + self._state = expiry.days diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/sensor/coinmarketcap.py index eee43328f29..198e3756760 100644 --- a/homeassistant/components/sensor/coinmarketcap.py +++ b/homeassistant/components/sensor/coinmarketcap.py @@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: CoinMarketCapData(currency).update() except HTTPError: - _LOGGER.warning("Currency '%s' is not available. Using 'bitcoin'", + _LOGGER.warning("Currency %s is not available. Using bitcoin", currency) currency = DEFAULT_CURRENCY diff --git a/homeassistant/components/sensor/comed_hourly_pricing.py b/homeassistant/components/sensor/comed_hourly_pricing.py index 747ea0683d8..4f6e0953e54 100644 --- a/homeassistant/components/sensor/comed_hourly_pricing.py +++ b/homeassistant/components/sensor/comed_hourly_pricing.py @@ -20,13 +20,13 @@ _RESOURCE = 'https://hourlypricing.comed.com/api' SCAN_INTERVAL = timedelta(minutes=5) -CONF_MONITORED_FEEDS = 'monitored_feeds' -CONF_SENSOR_TYPE = 'type' -CONF_OFFSET = 'offset' -CONF_NAME = 'name' - -CONF_FIVE_MINUTE = 'five_minute' +CONF_ATTRIBUTION = "Data provided by ComEd Hourly Pricing service" CONF_CURRENT_HOUR_AVERAGE = 'current_hour_average' +CONF_FIVE_MINUTE = 'five_minute' +CONF_MONITORED_FEEDS = 'monitored_feeds' +CONF_NAME = 'name' +CONF_OFFSET = 'offset' +CONF_SENSOR_TYPE = 'type' SENSOR_TYPES = { CONF_FIVE_MINUTE: ['ComEd 5 Minute Price', 'c'], @@ -47,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ComEd Hourly Pricing sensor.""" + """Set up the ComEd Hourly Pricing sensor.""" dev = [] for variable in config[CONF_MONITORED_FEEDS]: dev.append(ComedHourlyPricingSensor( @@ -89,8 +89,7 @@ class ComedHourlyPricingSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = {ATTR_ATTRIBUTION: 'Data provided by ComEd Hourly ' - 'Pricing service'} + attrs = {ATTR_ATTRIBUTION: CONF_ATTRIBUTION} return attrs def update(self): @@ -109,4 +108,4 @@ class ComedHourlyPricingSensor(Entity): else: self._state = STATE_UNKNOWN except (RequestException, ValueError, KeyError): - _LOGGER.warning('Could not update status for %s', self.name) + _LOGGER.warning("Could not update status for %s", self.name) diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index 227b133535d..f2542413abf 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Command Sensor.""" + """Set up the Command Sensor.""" name = config.get(CONF_NAME) command = config.get(CONF_COMMAND) unit = config.get(CONF_UNIT_OF_MEASUREMENT) @@ -97,13 +97,13 @@ class CommandSensorData(object): def update(self): """Get the latest data with a shell command.""" - _LOGGER.info('Running command: %s', self.command) + _LOGGER.info("Running command: %s", self.command) try: - return_value = subprocess.check_output(self.command, shell=True, - timeout=15) + return_value = subprocess.check_output( + self.command, shell=True, timeout=15) self.value = return_value.strip().decode('utf-8') except subprocess.CalledProcessError: - _LOGGER.error('Command failed: %s', self.command) + _LOGGER.error("Command failed: %s", self.command) except subprocess.TimeoutExpired: - _LOGGER.error('Timeout for command: %s', self.command) + _LOGGER.error("Timeout for command: %s", self.command) diff --git a/homeassistant/components/sensor/crimereports.py b/homeassistant/components/sensor/crimereports.py index 8f118fc3c32..b6e5ea33216 100644 --- a/homeassistant/components/sensor/crimereports.py +++ b/homeassistant/components/sensor/crimereports.py @@ -25,11 +25,14 @@ REQUIREMENTS = ['crimereports==1.0.0'] _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(minutes=30) -DOMAIN = 'crimereports' -EVENT_INCIDENT = '{}_incident'.format(DOMAIN) CONF_RADIUS = 'radius' +DOMAIN = 'crimereports' + +EVENT_INCIDENT = '{}_incident'.format(DOMAIN) + +SCAN_INTERVAL = timedelta(minutes=30) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_NAME): cv.string, vol.Required(CONF_RADIUS): vol.Coerce(float), @@ -42,14 +45,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Crime Reports platform.""" + """Set up the Crime Reports platform.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - add_devices([CrimeReportsSensor(hass, config.get(CONF_NAME), - latitude, longitude, - config.get(CONF_RADIUS), - config.get(CONF_INCLUDE), - config.get(CONF_EXCLUDE))], True) + name = config.get(CONF_NAME) + radius = config.get(CONF_RADIUS) + include = config.get(CONF_INCLUDE) + exclude = config.get(CONF_EXCLUDE) + + add_devices([CrimeReportsSensor( + hass, name, latitude, longitude, radius, include, exclude)], True) class CrimeReportsSensor(Entity): @@ -64,8 +69,8 @@ class CrimeReportsSensor(Entity): self._include = include self._exclude = exclude radius_kilometers = convert(radius, LENGTH_METERS, LENGTH_KILOMETERS) - self._crimereports = crimereports.CrimeReports((latitude, longitude), - radius_kilometers) + self._crimereports = crimereports.CrimeReports( + (latitude, longitude), radius_kilometers) self._attributes = None self._state = None self._previous_incidents = set() @@ -103,9 +108,8 @@ class CrimeReportsSensor(Entity): """Update device state.""" import crimereports incident_counts = defaultdict(int) - incidents = self._crimereports.get_incidents(now().date(), - include=self._include, - exclude=self._exclude) + incidents = self._crimereports.get_incidents( + now().date(), include=self._include, exclude=self._exclude) fire_events = len(self._previous_incidents) > 0 if len(incidents) < len(self._previous_incidents): self._previous_incidents = set() diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index aa44611aec2..eaf0c474994 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -130,8 +130,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Dark Sky sensor.""" - # latitude and longitude are inclusive on config + """Set up the Dark Sky sensor.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) @@ -163,8 +162,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors.append(DarkSkySensor(forecast_data, variable, name)) if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: for forecast_day in forecast: - sensors.append(DarkSkySensor(forecast_data, - variable, name, forecast_day)) + sensors.append(DarkSkySensor( + forecast_data, variable, name, forecast_day)) add_devices(sensors, True) @@ -189,8 +188,8 @@ class DarkSkySensor(Entity): if self.forecast_day == 0: return '{} {}'.format(self.client_name, self._name) else: - return '{} {} {}'.format(self.client_name, self._name, - self.forecast_day) + return '{} {} {}'.format( + self.client_name, self._name, self.forecast_day) @property def state(self): @@ -285,7 +284,7 @@ class DarkSkySensor(Entity): def get_state(self, data): """ - Helper function that returns a new state based on the type. + Return a new state based on the type. If the sensor type is unknown, the current state is returned. """ diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index a290a0edd53..ba7c93203df 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -10,7 +10,7 @@ from homeassistant.helpers.entity import Entity # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo sensors.""" + """Set up the Demo sensors.""" add_devices([ DemoSensor('Outside Temperature', 15.6, TEMP_CELSIUS, 12), DemoSensor('Outside Humidity', 54, '%', None), diff --git a/homeassistant/components/sensor/deutsche_bahn.py b/homeassistant/components/sensor/deutsche_bahn.py index 34be6ba078c..0f6e3b267ca 100644 --- a/homeassistant/components/sensor/deutsche_bahn.py +++ b/homeassistant/components/sensor/deutsche_bahn.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Deutsche Bahn Sensor.""" + """Set up the Deutsche Bahn Sensor.""" start = config.get(CONF_START) destination = config.get(CONF_DESTINATION) diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index 1b4b6c63156..a6fc9b10bee 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -18,7 +18,7 @@ from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit # Update this requirement to upstream as soon as it supports Python 3. -REQUIREMENTS = ['http://github.com/adafruit/Adafruit_Python_DHT/archive/' +REQUIREMENTS = ['https://github.com/adafruit/Adafruit_Python_DHT/archive/' 'da8cddf7fb629c1ef4f046ca44f42523c9cf2d11.zip' '#Adafruit_DHT==1.3.0'] @@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the DHT sensor.""" + """Set up the DHT sensor.""" # pylint: disable=import-error import Adafruit_DHT @@ -139,8 +139,8 @@ class DHTClient(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data the DHT sensor.""" - humidity, temperature = self.adafruit_dht.read_retry(self.sensor, - self.pin) + humidity, temperature = self.adafruit_dht.read_retry( + self.sensor, self.pin) if temperature: self.data[SENSOR_TEMPERATURE] = temperature if humidity: diff --git a/homeassistant/components/sensor/dnsip.py b/homeassistant/components/sensor/dnsip.py index 67b2e04d157..7b792d179c5 100644 --- a/homeassistant/components/sensor/dnsip.py +++ b/homeassistant/components/sensor/dnsip.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the DNS IP sensor.""" + """Set up the DNS IP sensor.""" hostname = config.get(CONF_HOSTNAME) ipv6 = config.get(CONF_IPV6) if ipv6: diff --git a/homeassistant/components/sensor/dovado.py b/homeassistant/components/sensor/dovado.py index 6b3ff58fab5..eba6596efc4 100644 --- a/homeassistant/components/sensor/dovado.py +++ b/homeassistant/components/sensor/dovado.py @@ -14,9 +14,9 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util import slugify import homeassistant.helpers.config_validation as cv -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, - CONF_HOST, CONF_PORT, - CONF_SENSORS, DEVICE_DEFAULT_NAME) +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT, CONF_SENSORS, + DEVICE_DEFAULT_NAME) from homeassistant.components.sensor import (DOMAIN, PLATFORM_SCHEMA) _LOGGER = logging.getLogger(__name__) @@ -25,23 +25,23 @@ REQUIREMENTS = ['dovado==0.4.1'] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -SENSOR_UPLOAD = "upload" -SENSOR_DOWNLOAD = "download" -SENSOR_SIGNAL = "signal" -SENSOR_NETWORK = "network" -SENSOR_SMS_UNREAD = "sms" +SENSOR_UPLOAD = 'upload' +SENSOR_DOWNLOAD = 'download' +SENSOR_SIGNAL = 'signal' +SENSOR_NETWORK = 'network' +SENSOR_SMS_UNREAD = 'sms' SENSORS = { - SENSOR_NETWORK: ("signal strength", "Network", None, - "mdi:access-point-network"), - SENSOR_SIGNAL: ("signal strength", "Signal Strength", "%", - "mdi:signal"), - SENSOR_SMS_UNREAD: ("sms unread", "SMS unread", "", - "mdi:message-text-outline"), - SENSOR_UPLOAD: ("traffic modem tx", "Sent", "GB", - "mdi:cloud-upload"), - SENSOR_DOWNLOAD: ("traffic modem rx", "Received", "GB", - "mdi:cloud-download"), + SENSOR_NETWORK: ('signal strength', 'Network', None, + 'mdi:access-point-network'), + SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%', + 'mdi:signal'), + SENSOR_SMS_UNREAD: ('sms unread', 'SMS unread', '', + 'mdi:message-text-outline'), + SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB', + 'mdi:cloud-upload'), + SENSOR_DOWNLOAD: ('traffic modem rx', 'Received', 'GB', + 'mdi:cloud-download'), } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -55,7 +55,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the dovado platform for sensors.""" + """Set up the Dovado platform for sensors.""" return Dovado().setup(hass, config, add_devices) @@ -68,28 +68,24 @@ class Dovado: self._dovado = None def setup(self, hass, config, add_devices): - """Setup the connection.""" + """Set up the connection.""" import dovado self._dovado = dovado.Dovado( - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config.get(CONF_HOST), - config.get(CONF_PORT)) + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + config.get(CONF_HOST), config.get(CONF_PORT)) if not self.update(): return False def send_sms(service): """Send SMS through the router.""" - number = service.data.get("number"), - message = service.data.get("message") - _LOGGER.debug("message for %s: %s", - number, message) + number = service.data.get('number'), + message = service.data.get('message') + _LOGGER.debug("message for %s: %s", number, message) self._dovado.send_sms(number, message) - if self.state.get("sms") == "enabled": - service_name = slugify("{} {}".format(self.name, - "send_sms")) + if self.state.get('sms') == 'enabled': + service_name = slugify("{} {}".format(self.name, 'send_sms')) hass.services.register(DOMAIN, service_name, send_sms) for sensor in SENSORS: @@ -153,8 +149,7 @@ class DovadoSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return "{} {}".format(self._dovado.name, - SENSORS[self._sensor][1]) + return "{} {}".format(self._dovado.name, SENSORS[self._sensor][1]) @property def state(self): @@ -175,4 +170,4 @@ class DovadoSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" return {k: v for k, v in self._dovado.state.items() - if k not in ["date", "time"]} + if k not in ['date', 'time']} diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py index 84669a57000..23324fe7360 100644 --- a/homeassistant/components/sensor/dsmr.py +++ b/homeassistant/components/sensor/dsmr.py @@ -1,4 +1,5 @@ -"""Support for Dutch Smart Meter Requirements. +""" +Support for Dutch Smart Meter Requirements. Also known as: Smartmeter or P1 port. @@ -23,7 +24,6 @@ DSMR version the Entities for this component are create during bootstrap. Another loop (DSMR class) is setup which reads the telegram queue, stores/caches the latest telegram and notifies the Entities that the telegram has been updated. - """ import asyncio from datetime import timedelta @@ -72,8 +72,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): logging.getLogger('dsmr_parser').setLevel(logging.ERROR) from dsmr_parser import obis_references as obis_ref - from dsmr_parser.clients.protocol import (create_dsmr_reader, - create_tcp_dsmr_reader) + from dsmr_parser.clients.protocol import ( + create_dsmr_reader, create_tcp_dsmr_reader) import serial dsmr_version = config[CONF_DSMR_VERSION] @@ -116,18 +116,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # Creates a asyncio.Protocol factory for reading DSMR telegrams from serial # and calls update_entities_telegram to update entities on arrival if config[CONF_HOST]: - reader_factory = partial(create_tcp_dsmr_reader, - config[CONF_HOST], - config[CONF_PORT], - config[CONF_DSMR_VERSION], - update_entities_telegram, - loop=hass.loop) + reader_factory = partial( + create_tcp_dsmr_reader, config[CONF_HOST], config[CONF_PORT], + config[CONF_DSMR_VERSION], update_entities_telegram, + loop=hass.loop) else: - reader_factory = partial(create_dsmr_reader, - config[CONF_PORT], - config[CONF_DSMR_VERSION], - update_entities_telegram, - loop=hass.loop) + reader_factory = partial( + create_dsmr_reader, config[CONF_PORT], config[CONF_DSMR_VERSION], + update_entities_telegram, loop=hass.loop) @asyncio.coroutine def connect_and_reconnect(): @@ -141,7 +137,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): TimeoutError): # log any error while establishing connection and drop to retry # connection wait - _LOGGER.exception('error connecting to DSMR') + _LOGGER.exception("Error connecting to DSMR") transport = None if transport: @@ -174,7 +170,7 @@ class DSMREntity(Entity): """Entity reading values from DSMR telegram.""" def __init__(self, name, obis): - """"Initialize entity.""" + """Initialize entity.""" self._name = name self._obis = obis self.telegram = {} diff --git a/homeassistant/components/sensor/dte_energy_bridge.py b/homeassistant/components/sensor/dte_energy_bridge.py index 4a57bddfb9d..ea2739c3096 100644 --- a/homeassistant/components/sensor/dte_energy_bridge.py +++ b/homeassistant/components/sensor/dte_energy_bridge.py @@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the DTE energy bridge sensor.""" + """Set up the DTE energy bridge sensor.""" name = config.get(CONF_NAME) ip_address = config.get(CONF_IP_ADDRESS) diff --git a/homeassistant/components/sensor/dublin_bus_transport.py b/homeassistant/components/sensor/dublin_bus_transport.py index 10d2c2b39f0..337f8188847 100644 --- a/homeassistant/components/sensor/dublin_bus_transport.py +++ b/homeassistant/components/sensor/dublin_bus_transport.py @@ -1,4 +1,5 @@ -"""Support for Dublin RTPI information from data.dublinked.ie. +""" +Support for Dublin RTPI information from data.dublinked.ie. For more info on the API see : https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61 @@ -22,11 +23,11 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation' -ATTR_STOP_ID = "Stop ID" -ATTR_ROUTE = "Route" -ATTR_DUE_IN = "Due in" -ATTR_DUE_AT = "Due at" -ATTR_NEXT_UP = "Later Bus" +ATTR_STOP_ID = 'Stop ID' +ATTR_ROUTE = 'Route' +ATTR_DUE_IN = 'Due in' +ATTR_DUE_AT = 'Due at' +ATTR_NEXT_UP = 'Later Bus' CONF_ATTRIBUTION = "Data provided by data.dublinked.ie" CONF_STOP_ID = 'stopid' @@ -36,7 +37,7 @@ DEFAULT_NAME = 'Next Bus' ICON = 'mdi:bus' MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -TIME_STR_FORMAT = "%H:%M" +TIME_STR_FORMAT = '%H:%M' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_STOP_ID): cv.string, @@ -57,7 +58,7 @@ def due_in_minutes(timestamp): def setup_platform(hass, config, add_devices, discovery_info=None): - """Get the Dublin public transport sensor.""" + """Set up the Dublin public transport sensor.""" name = config.get(CONF_NAME) stop = config.get(CONF_STOP_ID) route = config.get(CONF_ROUTE) @@ -92,7 +93,7 @@ class DublinPublicTransportSensor(Entity): """Return the state attributes.""" if self._times is not None: next_up = "None" - if len(self._times) > 1: + if self._times: next_up = self._times[1][ATTR_ROUTE] + " in " next_up += self._times[1][ATTR_DUE_IN] @@ -108,7 +109,7 @@ class DublinPublicTransportSensor(Entity): @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return "min" + return 'min' @property def icon(self): @@ -178,7 +179,7 @@ class PublicTransportData(object): due_in_minutes(due_at)} self.info.append(bus_data) - if len(self.info) == 0: + if not self.info: self.info = [{ATTR_DUE_AT: 'n/a', ATTR_ROUTE: self.route, ATTR_DUE_IN: 'n/a'}] diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py index 0f9ea017571..e5f3d00830b 100644 --- a/homeassistant/components/sensor/dweet.py +++ b/homeassistant/components/sensor/dweet.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable, too-many-function-args def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Dweet sensor.""" + """Set up the Dweet sensor.""" import dweepy name = config.get(CONF_NAME) @@ -48,11 +48,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content']) except dweepy.DweepyError: - _LOGGER.error("Device/thing '%s' could not be found", device) + _LOGGER.error("Device/thing %s could not be found", device) return False if value_template.render_with_possible_json_value(content) == '': - _LOGGER.error("'%s' was not found", value_template) + _LOGGER.error("%s was not found", value_template) return False dweet = DweetData(device) @@ -115,5 +115,5 @@ class DweetData(object): try: self.data = dweepy.get_latest_dweet_for(self._device) except dweepy.DweepyError: - _LOGGER.error("Device '%s' could not be found", self._device) + _LOGGER.error("Device %s could not be found", self._device) self.data = None diff --git a/homeassistant/components/sensor/ebox.py b/homeassistant/components/sensor/ebox.py index 8357e9ad8f9..3cfa5ef9ff4 100644 --- a/homeassistant/components/sensor/ebox.py +++ b/homeassistant/components/sensor/ebox.py @@ -1,8 +1,7 @@ """ Support for EBox. -Get data from 'My Usage Page' page: -https://client.ebox.ca/myusage +Get data from 'My Usage Page' page: https://client.ebox.ca/myusage For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.ebox/ @@ -25,12 +24,12 @@ REQUIREMENTS = ['pyebox==0.1.0'] _LOGGER = logging.getLogger(__name__) -GIGABITS = "Gb" # type: str -PRICE = "CAD" # type: str -DAYS = "days" # type: str -PERCENT = "%" # type: str +GIGABITS = 'Gb' # type: str +PRICE = 'CAD' # type: str +DAYS = 'days' # type: str +PERCENT = '%' # type: str -DEFAULT_NAME = "EBox" +DEFAULT_NAME = 'EBox' REQUESTS_TIMEOUT = 15 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) diff --git a/homeassistant/components/sensor/ecobee.py b/homeassistant/components/sensor/ecobee.py index 961fb9aabe3..25a363680c4 100644 --- a/homeassistant/components/sensor/ecobee.py +++ b/homeassistant/components/sensor/ecobee.py @@ -18,7 +18,7 @@ ECOBEE_CONFIG_FILE = 'ecobee.conf' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Ecobee sensors.""" + """Set up the Ecobee sensors.""" if discovery_info is None: return data = ecobee.NETWORK diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index de51ff0d373..7fd5f14e1af 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -1,13 +1,11 @@ -"""Read temperature information from Eddystone beacons. +""" +Read temperature information from Eddystone beacons. Your beacons must be configured to transmit UID (for identification) and TLM (for temperature) frames. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.eddystone_temperature/ - -Original version of this code (for Skybeacons) by anpetrov. -https://github.com/anpetrov/skybeacon """ import logging @@ -24,7 +22,6 @@ REQUIREMENTS = ['beacontools[scan]==1.0.1'] _LOGGER = logging.getLogger(__name__) -# constants CONF_BEACONS = 'beacons' CONF_BT_DEVICE_ID = 'bt_device_id' CONF_INSTANCE = 'instance' @@ -45,8 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Validate configuration, create devices and start monitoring thread.""" - _LOGGER.debug("Setting up...") - bt_device_id = config.get("bt_device_id") beacons = config.get("beacons") @@ -63,17 +58,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): else: devices.append(EddystoneTemp(name, namespace, instance)) - if len(devices) > 0: + if devices: mon = Monitor(hass, devices, bt_device_id) def monitor_stop(_service_or_event): """Stop the monitor thread.""" - _LOGGER.info("Stopping scanner for eddystone beacons") + _LOGGER.info("Stopping scanner for Eddystone beacons") mon.stop() def monitor_start(_service_or_event): """Start the monitor thread.""" - _LOGGER.info("Starting scanner for eddystone beacons") + _LOGGER.info("Starting scanner for Eddystone beacons") mon.start() add_devices(devices) @@ -88,9 +83,8 @@ def get_from_conf(config, config_key, length): """Retrieve value from config and validate length.""" string = config.get(config_key) if len(string) != length: - _LOGGER.error("Error in config parameter \"%s\": Must be exactly %d " - "bytes. Device will not be added.", - config_key, length/2) + _LOGGER.error("Error in config parameter %s: Must be exactly %d " + "bytes. Device will not be added", config_key, length/2) return None else: return string @@ -124,7 +118,7 @@ class EddystoneTemp(Entity): @property def should_poll(self): - """Hass should not poll for state.""" + """Return the polling state.""" return False @@ -135,26 +129,25 @@ class Monitor(object): """Construct interface object.""" self.hass = hass - # list of beacons to monitor + # List of beacons to monitor self.devices = devices - # number of the bt device (hciX) + # Number of the bt device (hciX) self.bt_device_id = bt_device_id def callback(bt_addr, _, packet, additional_info): - """Callback for new packets.""" - self.process_packet(additional_info['namespace'], - additional_info['instance'], - packet.temperature) + """Handle new packets.""" + self.process_packet( + additional_info['namespace'], additional_info['instance'], + packet.temperature) # pylint: disable=import-error - from beacontools import (BeaconScanner, EddystoneFilter, - EddystoneTLMFrame) - # Create a device filter for each device + from beacontools import ( + BeaconScanner, EddystoneFilter, EddystoneTLMFrame) device_filters = [EddystoneFilter(d.namespace, d.instance) for d in devices] - self.scanner = BeaconScanner(callback, bt_device_id, device_filters, - EddystoneTLMFrame) + self.scanner = BeaconScanner( + callback, bt_device_id, device_filters, EddystoneTLMFrame) self.scanning = False def start(self): @@ -163,11 +156,11 @@ class Monitor(object): self.scanner.start() self.scanning = True else: - _LOGGER.debug("Warning: start() called, but scanner is already" - " running") + _LOGGER.debug( + "start() called, but scanner is already running") def process_packet(self, namespace, instance, temperature): - """Assign temperature to hass device.""" + """Assign temperature to device.""" _LOGGER.debug("Received temperature for <%s,%s>: %d", namespace, instance, temperature) @@ -185,5 +178,5 @@ class Monitor(object): _LOGGER.debug("Stopped") self.scanning = False else: - _LOGGER.debug("Warning: stop() called but scanner was not" - " running.") + _LOGGER.debug( + "stop() called but scanner was not running") diff --git a/homeassistant/components/sensor/efergy.py b/homeassistant/components/sensor/efergy.py index 8731158cb6a..da7181972cb 100644 --- a/homeassistant/components/sensor/efergy.py +++ b/homeassistant/components/sensor/efergy.py @@ -30,6 +30,8 @@ CONF_BUDGET = 'budget' CONF_COST = 'cost' CONF_CURRENT_VALUES = 'current_values' +DEFAULT_PERIOD = 'year' + SENSOR_TYPES = { CONF_INSTANT: ['Energy Usage', 'kW'], CONF_AMOUNT: ['Energy Consumed', 'kWh'], @@ -43,7 +45,7 @@ TYPES_SCHEMA = vol.In(SENSOR_TYPES) SENSORS_SCHEMA = vol.Schema({ vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, vol.Optional(CONF_CURRENCY, default=''): cv.string, - vol.Optional(CONF_PERIOD, default='year'): cv.string, + vol.Optional(CONF_PERIOD, default=DEFAULT_PERIOD): cv.string, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -54,7 +56,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Efergy sensor.""" + """Set up the Efergy sensor.""" app_token = config.get(CONF_APPTOKEN) utc_offset = str(config.get(CONF_UTC_OFFSET)) dev = [] @@ -146,4 +148,4 @@ class EfergySensor(Entity): else: self._state = 'Unknown' except (RequestException, ValueError, KeyError): - _LOGGER.warning('Could not update status for %s', self.name) + _LOGGER.warning("Could not update status for %s", self.name) diff --git a/homeassistant/components/sensor/eight_sleep.py b/homeassistant/components/sensor/eight_sleep.py new file mode 100644 index 00000000000..e3b2f92d4e1 --- /dev/null +++ b/homeassistant/components/sensor/eight_sleep.py @@ -0,0 +1,273 @@ +""" +Support for Eight Sleep sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.eight_sleep/ +""" +import logging +import asyncio + +from homeassistant.components.eight_sleep import ( + DATA_EIGHT, EightSleepHeatEntity, EightSleepUserEntity, + CONF_SENSORS, NAME_MAP) + +DEPENDENCIES = ['eight_sleep'] + +ATTR_ROOM_TEMP = 'Room Temperature' +ATTR_AVG_ROOM_TEMP = 'Average Room Temperature' +ATTR_BED_TEMP = 'Bed Temperature' +ATTR_AVG_BED_TEMP = 'Average Bed Temperature' +ATTR_RESP_RATE = 'Respiratory Rate' +ATTR_AVG_RESP_RATE = 'Average Respiratory Rate' +ATTR_HEART_RATE = 'Heart Rate' +ATTR_AVG_HEART_RATE = 'Average Heart Rate' +ATTR_SLEEP_DUR = 'Time Slept' +ATTR_LIGHT_PERC = 'Light Sleep %' +ATTR_DEEP_PERC = 'Deep Sleep %' +ATTR_TNT = 'Tosses & Turns' +ATTR_SLEEP_STAGE = 'Sleep Stage' +ATTR_TARGET_HEAT = 'Target Heating Level' +ATTR_ACTIVE_HEAT = 'Heating Active' +ATTR_DURATION_HEAT = 'Heating Time Remaining' +ATTR_LAST_SEEN = 'Last In Bed' +ATTR_PROCESSING = 'Processing' +ATTR_SESSION_START = 'Session Start' + +_LOGGER = logging.getLogger(__name__) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the eight sleep sensors.""" + if discovery_info is None: + return + + name = 'Eight' + sensors = discovery_info[CONF_SENSORS] + eight = hass.data[DATA_EIGHT] + + if hass.config.units.is_metric: + units = 'si' + else: + units = 'us' + + all_sensors = [] + + for sensor in sensors: + if 'bed_state' in sensor: + all_sensors.append(EightHeatSensor(name, eight, sensor)) + elif 'room_temp' in sensor: + all_sensors.append(EightRoomSensor(name, eight, sensor, units)) + else: + all_sensors.append(EightUserSensor(name, eight, sensor, units)) + + async_add_devices(all_sensors, True) + + +class EightHeatSensor(EightSleepHeatEntity): + """Representation of a eight sleep heat-based sensor.""" + + def __init__(self, name, eight, sensor): + """Initialize the sensor.""" + super().__init__(eight) + + self._sensor = sensor + self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) + self._name = '{} {}'.format(name, self._mapped_name) + self._state = None + + self._side = self._sensor.split('_')[0] + self._userid = self._eight.fetch_userid(self._side) + self._usrobj = self._eight.users[self._userid] + + _LOGGER.debug("Heat Sensor: %s, Side: %s, User: %s", + self._sensor, self._side, self._userid) + + @property + def name(self): + """Return the name of the sensor, if any.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return '%' + + @asyncio.coroutine + def async_update(self): + """Retrieve latest state.""" + _LOGGER.debug("Updating Heat sensor: %s", self._sensor) + self._state = self._usrobj.heating_level + + @property + def device_state_attributes(self): + """Return device state attributes.""" + state_attr = {ATTR_TARGET_HEAT: self._usrobj.target_heating_level} + state_attr[ATTR_ACTIVE_HEAT] = self._usrobj.now_heating + state_attr[ATTR_DURATION_HEAT] = self._usrobj.heating_remaining + state_attr[ATTR_LAST_SEEN] = self._usrobj.last_seen + + return state_attr + + +class EightUserSensor(EightSleepUserEntity): + """Representation of a eight sleep user-based sensor.""" + + def __init__(self, name, eight, sensor, units): + """Initialize the sensor.""" + super().__init__(eight) + + self._sensor = sensor + self._sensor_root = self._sensor.split('_', 1)[1] + self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) + self._name = '{} {}'.format(name, self._mapped_name) + self._state = None + self._attr = None + self._units = units + + self._side = self._sensor.split('_', 1)[0] + self._userid = self._eight.fetch_userid(self._side) + self._usrobj = self._eight.users[self._userid] + + _LOGGER.debug("User Sensor: %s, Side: %s, User: %s", + self._sensor, self._side, self._userid) + + @property + def name(self): + """Return the name of the sensor, if any.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + if 'current_sleep' in self._sensor or 'last_sleep' in self._sensor: + return 'Score' + elif 'bed_temp' in self._sensor: + if self._units == 'si': + return '°C' + else: + return '°F' + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + if 'bed_temp' in self._sensor: + return 'mdi:thermometer' + + @asyncio.coroutine + def async_update(self): + """Retrieve latest state.""" + _LOGGER.debug("Updating User sensor: %s", self._sensor) + if 'current' in self._sensor: + self._state = self._usrobj.current_sleep_score + self._attr = self._usrobj.current_values + elif 'last' in self._sensor: + self._state = self._usrobj.last_sleep_score + self._attr = self._usrobj.last_values + elif 'bed_temp' in self._sensor: + temp = self._usrobj.current_values['bed_temp'] + if self._units == 'si': + self._state = round(temp, 2) + else: + self._state = round((temp*1.8)+32, 2) + elif 'sleep_stage' in self._sensor: + self._state = self._usrobj.current_values['stage'] + + @property + def device_state_attributes(self): + """Return device state attributes.""" + if self._attr is None: + # Skip attributes if sensor type doesn't support + return None + + state_attr = {ATTR_SESSION_START: self._attr['date']} + state_attr[ATTR_TNT] = self._attr['tnt'] + state_attr[ATTR_PROCESSING] = self._attr['processing'] + + sleep_time = sum(self._attr['breakdown'].values()) - \ + self._attr['breakdown']['awake'] + state_attr[ATTR_SLEEP_DUR] = sleep_time + state_attr[ATTR_LIGHT_PERC] = round(( + self._attr['breakdown']['light'] / sleep_time) * 100, 2) + state_attr[ATTR_DEEP_PERC] = round(( + self._attr['breakdown']['deep'] / sleep_time) * 100, 2) + + if self._units == 'si': + room_temp = round(self._attr['room_temp'], 2) + bed_temp = round(self._attr['bed_temp'], 2) + else: + room_temp = round((self._attr['room_temp']*1.8)+32, 2) + bed_temp = round((self._attr['bed_temp']*1.8)+32, 2) + + if 'current' in self._sensor_root: + state_attr[ATTR_RESP_RATE] = round(self._attr['resp_rate'], 2) + state_attr[ATTR_HEART_RATE] = round(self._attr['heart_rate'], 2) + state_attr[ATTR_SLEEP_STAGE] = self._attr['stage'] + state_attr[ATTR_ROOM_TEMP] = room_temp + state_attr[ATTR_BED_TEMP] = bed_temp + elif 'last' in self._sensor_root: + state_attr[ATTR_AVG_RESP_RATE] = round(self._attr['resp_rate'], 2) + state_attr[ATTR_AVG_HEART_RATE] = round( + self._attr['heart_rate'], 2) + state_attr[ATTR_AVG_ROOM_TEMP] = room_temp + state_attr[ATTR_AVG_BED_TEMP] = bed_temp + + return state_attr + + +class EightRoomSensor(EightSleepUserEntity): + """Representation of a eight sleep room sensor.""" + + def __init__(self, name, eight, sensor, units): + """Initialize the sensor.""" + super().__init__(eight) + + self._sensor = sensor + self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) + self._name = '{} {}'.format(name, self._mapped_name) + self._state = None + self._attr = None + self._units = units + + @property + def name(self): + """Return the name of the sensor, if any.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @asyncio.coroutine + def async_update(self): + """Retrieve latest state.""" + _LOGGER.debug("Updating Room sensor: %s", self._sensor) + temp = self._eight.room_temperature() + if self._units == 'si': + self._state = round(temp, 2) + else: + self._state = round((temp*1.8)+32, 2) + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + if self._units == 'si': + return '°C' + else: + return '°F' + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return 'mdi:thermometer' diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index dad15361ba4..21b259863a7 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ELIQ Online sensor.""" + """Set up the ELIQ Online sensor.""" import eliqonline access_token = config.get(CONF_ACCESS_TOKEN) diff --git a/homeassistant/components/sensor/enocean.py b/homeassistant/components/sensor/enocean.py index 5f2f8edf872..6b0207c2488 100644 --- a/homeassistant/components/sensor/enocean.py +++ b/homeassistant/components/sensor/enocean.py @@ -26,7 +26,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup an EnOcean sensor device.""" + """Set up an EnOcean sensor device.""" dev_id = config.get(CONF_ID) devname = config.get(CONF_NAME) @@ -64,4 +64,4 @@ class EnOceanSensor(enocean.EnOceanDevice, Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return "W" + return 'W' diff --git a/homeassistant/components/sensor/envirophat.py b/homeassistant/components/sensor/envirophat.py new file mode 100644 index 00000000000..48370d76c83 --- /dev/null +++ b/homeassistant/components/sensor/envirophat.py @@ -0,0 +1,195 @@ +""" +Support for Enviro pHAT sensors. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/sensor.envirophat +""" +import logging +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (TEMP_CELSIUS, CONF_DISPLAY_OPTIONS, CONF_NAME) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['envirophat==0.0.6', + 'smbus-cffi==0.5.1'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'envirophat' +CONF_USE_LEDS = 'use_leds' + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) + +SENSOR_TYPES = { + 'light': ['light', ' ', 'mdi:weather-sunny'], + 'light_red': ['light_red', ' ', 'mdi:invert-colors'], + 'light_green': ['light_green', ' ', 'mdi:invert-colors'], + 'light_blue': ['light_blue', ' ', 'mdi:invert-colors'], + 'accelerometer_x': ['accelerometer_x', 'G', 'mdi:earth'], + 'accelerometer_y': ['accelerometer_y', 'G', 'mdi:earth'], + 'accelerometer_z': ['accelerometer_z', 'G', 'mdi:earth'], + 'magnetometer_x': ['magnetometer_x', ' ', 'mdi:magnet'], + 'magnetometer_y': ['magnetometer_y', ' ', 'mdi:magnet'], + 'magnetometer_z': ['magnetometer_z', ' ', 'mdi:magnet'], + 'temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer'], + 'pressure': ['pressure', 'hPa', 'mdi:gauge'], + 'voltage_0': ['voltage_0', 'V', 'mdi:flash'], + 'voltage_1': ['voltage_1', 'V', 'mdi:flash'], + 'voltage_2': ['voltage_2', 'V', 'mdi:flash'], + 'voltage_3': ['voltage_3', 'V', 'mdi:flash'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DISPLAY_OPTIONS, default=SENSOR_TYPES): + [vol.In(SENSOR_TYPES)], + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USE_LEDS, default=False): cv.boolean +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Sense HAT sensor platform.""" + data = EnvirophatData(config.get(CONF_USE_LEDS)) + + dev = [] + for variable in config[CONF_DISPLAY_OPTIONS]: + dev.append(EnvirophatSensor(data, variable)) + + add_devices(dev, True) + + +class EnvirophatSensor(Entity): + """Representation of an Enviro pHAT sensor.""" + + def __init__(self, data, sensor_types): + """Initialize the sensor.""" + self.data = data + self._name = SENSOR_TYPES[sensor_types][0] + self._unit_of_measurement = SENSOR_TYPES[sensor_types][1] + self.type = sensor_types + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return SENSOR_TYPES[self.type][2] + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + def update(self): + """Get the latest data and updates the states.""" + self.data.update() + if not self.data.light: + _LOGGER.error("Didn't receive data") + return + + if self.type == 'light': + self._state = self.data.light + if self.type == 'light_red': + self._state = self.data.light_red + if self.type == 'light_green': + self._state = self.data.light_green + if self.type == 'light_blue': + self._state = self.data.light_blue + if self.type == 'accelerometer_x': + self._state = self.data.accelerometer_x + if self.type == 'accelerometer_y': + self._state = self.data.accelerometer_y + if self.type == 'accelerometer_z': + self._state = self.data.accelerometer_z + if self.type == 'magnetometer_x': + self._state = self.data.magnetometer_x + if self.type == 'magnetometer_y': + self._state = self.data.magnetometer_y + if self.type == 'magnetometer_z': + self._state = self.data.magnetometer_z + if self.type == 'temperature': + self._state = self.data.temperature + if self.type == 'pressure': + self._state = self.data.pressure + if self.type == 'voltage_0': + self._state = self.data.voltage_0 + if self.type == 'voltage_1': + self._state = self.data.voltage_1 + if self.type == 'voltage_2': + self._state = self.data.voltage_2 + if self.type == 'voltage_3': + self._state = self.data.voltage_3 + + +class EnvirophatData(object): + """Get the latest data and update.""" + + def __init__(self, use_leds): + """Initialize the data object.""" + self.use_leds = use_leds + # sensors readings + self.light = None + self.light_red = None + self.light_green = None + self.light_blue = None + self.accelerometer_x = None + self.accelerometer_y = None + self.accelerometer_z = None + self.magnetometer_x = None + self.magnetometer_y = None + self.magnetometer_z = None + self.temperature = None + self.pressure = None + self.voltage_0 = None + self.voltage_1 = None + self.voltage_2 = None + self.voltage_3 = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from Enviro pHAT.""" + from envirophat import analog, leds, light, motion, weather + + # Light sensor reading: 16-bit integer + self.light = light.light() + if self.use_leds: + # pylint: disable=no-value-for-parameter + leds.on() + # the three color values scaled agains the overall light, 0-255 + self.light_red, self.light_green, self.light_blue = light.rgb() + if self.use_leds: + # pylint: disable=no-value-for-parameter + leds.off() + + # accelerometer readings in G + self.accelerometer_x, self.accelerometer_y, self.accelerometer_z = \ + motion.accelerometer() + + # raw magnetometer reading + self.magnetometer_x, self.magnetometer_y, self.magnetometer_z = \ + motion.magnetometer() + + # temperature resolution of BMP280 sensor: 0.01°C + self.temperature = round(weather.temperature(), 2) + + # pressure resolution of BMP280 sensor: 0.16 Pa, rounding to 0.1 Pa + # with conversion to 100 Pa = 1 hPa + self.pressure = round(weather.pressure() / 100.0, 3) + + # Voltage sensor, reading between 0-3.3V + self.voltage_0, self.voltage_1, self.voltage_2, self.voltage_3 = \ + analog.read_all() diff --git a/homeassistant/components/sensor/envisalink.py b/homeassistant/components/sensor/envisalink.py index 9803f675913..7f1ee5c0d41 100644 --- a/homeassistant/components/sensor/envisalink.py +++ b/homeassistant/components/sensor/envisalink.py @@ -14,9 +14,10 @@ from homeassistant.components.envisalink import ( SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE) from homeassistant.helpers.entity import Entity -DEPENDENCIES = ['envisalink'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['envisalink'] + @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): @@ -46,7 +47,7 @@ class EnvisalinkSensor(EnvisalinkDevice, Entity): self._icon = 'mdi:alarm' self._partition_number = partition_number - _LOGGER.debug('Setting up sensor for partition: ' + partition_name) + _LOGGER.debug("Setting up sensor for partition: %s", partition_name) super().__init__(partition_name + ' Keypad', info, controller) @asyncio.coroutine diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py index 63ddb0663d7..61f2e000d1d 100644 --- a/homeassistant/components/sensor/fastdotcom.py +++ b/homeassistant/components/sensor/fastdotcom.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Fast.com sensor.""" + """Set up the Fast.com sensor.""" data = SpeedtestData(hass, config) sensor = SpeedtestSensor(data) add_devices([sensor]) @@ -87,7 +87,7 @@ class SpeedtestSensor(Entity): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity is about to be added to hass.""" + """Handle entity which will be added.""" state = yield from async_get_last_state(self.hass, self.entity_id) if not state: return @@ -101,14 +101,13 @@ class SpeedtestData(object): """Initialize the data object.""" self.data = None if not config.get(CONF_MANUAL): - track_time_change(hass, self.update, - second=config.get(CONF_SECOND), - minute=config.get(CONF_MINUTE), - hour=config.get(CONF_HOUR), - day=config.get(CONF_DAY)) + track_time_change( + hass, self.update, second=config.get(CONF_SECOND), + minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR), + day=config.get(CONF_DAY)) def update(self, now): """Get the latest data from fast.com.""" from fastdotcom import fast_com - _LOGGER.info('Executing fast.com speedtest') + _LOGGER.info("Executing fast.com speedtest") self.data = {'download': fast_com()} diff --git a/homeassistant/components/sensor/fedex.py b/homeassistant/components/sensor/fedex.py index 2e1b8e6a6a0..d1626ce2974 100644 --- a/homeassistant/components/sensor/fedex.py +++ b/homeassistant/components/sensor/fedex.py @@ -23,10 +23,13 @@ REQUIREMENTS = ['fedexdeliverymanager==1.0.2'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'fedex' -COOKIE = 'fedexdeliverymanager_cookies.pickle' CONF_UPDATE_INTERVAL = 'update_interval' +COOKIE = 'fedexdeliverymanager_cookies.pickle' + +DOMAIN = 'fedex' + ICON = 'mdi:package-variant-closed' + STATUS_DELIVERED = 'delivered' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -40,19 +43,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Fedex platform.""" + """Set up the Fedex platform.""" import fedexdeliverymanager try: cookie = hass.config.path(COOKIE) - session = fedexdeliverymanager.get_session(config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - cookie_path=cookie) + session = fedexdeliverymanager.get_session( + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + cookie_path=cookie) except fedexdeliverymanager.FedexError: - _LOGGER.exception('Could not connect to Fedex Delivery Manager') + _LOGGER.exception("Could not connect to Fedex Delivery Manager") return False - add_devices([FedexSensor(session, config.get(CONF_NAME), - config.get(CONF_UPDATE_INTERVAL))]) + add_devices([FedexSensor( + session, config.get(CONF_NAME), config.get(CONF_UPDATE_INTERVAL))]) class FedexSensor(Entity): diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/sensor/fido.py index 5deb00db67b..c4f4217616f 100644 --- a/homeassistant/components/sensor/fido.py +++ b/homeassistant/components/sensor/fido.py @@ -21,61 +21,44 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ["pyfido==1.0.1"] +REQUIREMENTS = ['pyfido==1.0.1'] _LOGGER = logging.getLogger(__name__) -KILOBITS = "Kb" # type: str -PRICE = "CAD" # type: str -MESSAGES = "messages" # type: str -MINUTES = "minutes" # type: str +KILOBITS = 'Kb' # type: str +PRICE = 'CAD' # type: str +MESSAGES = 'messages' # type: str +MINUTES = 'minutes' # type: str -DEFAULT_NAME = "Fido" +DEFAULT_NAME = 'Fido' REQUESTS_TIMEOUT = 15 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) SENSOR_TYPES = { - 'fido_dollar': ['Fido dollar', - PRICE, 'mdi:square-inc-cash'], - 'balance': ['Balance', - PRICE, 'mdi:square-inc-cash'], - 'data_used': ['Data used', - KILOBITS, 'mdi:download'], - 'data_limit': ['Data limit', - KILOBITS, 'mdi:download'], - 'data_remaining': ['Data remaining', - KILOBITS, 'mdi:download'], - 'text_used': ['Text used', - MESSAGES, 'mdi:message-text'], - 'text_limit': ['Text limit', - MESSAGES, 'mdi:message-text'], - 'text_remaining': ['Text remaining', - MESSAGES, 'mdi:message-text'], - 'mms_used': ['MMS used', - MESSAGES, 'mdi:message-image'], - 'mms_limit': ['MMS limit', - MESSAGES, 'mdi:message-image'], - 'mms_remaining': ['MMS remaining', - MESSAGES, 'mdi:message-image'], + 'fido_dollar': ['Fido dollar', PRICE, 'mdi:square-inc-cash'], + 'balance': ['Balance', PRICE, 'mdi:square-inc-cash'], + 'data_used': ['Data used', KILOBITS, 'mdi:download'], + 'data_limit': ['Data limit', KILOBITS, 'mdi:download'], + 'data_remaining': ['Data remaining', KILOBITS, 'mdi:download'], + 'text_used': ['Text used', MESSAGES, 'mdi:message-text'], + 'text_limit': ['Text limit', MESSAGES, 'mdi:message-text'], + 'text_remaining': ['Text remaining', MESSAGES, 'mdi:message-text'], + 'mms_used': ['MMS used', MESSAGES, 'mdi:message-image'], + 'mms_limit': ['MMS limit', MESSAGES, 'mdi:message-image'], + 'mms_remaining': ['MMS remaining', MESSAGES, 'mdi:message-image'], 'text_int_used': ['International text used', MESSAGES, 'mdi:message-alert'], 'text_int_limit': ['International text limit', MESSAGES, 'mdi:message-alart'], 'text_int_remaining': ['Internaltional remaining', MESSAGES, 'mdi:message-alert'], - 'talk_used': ['Talk used', - MINUTES, 'mdi:cellphone'], - 'talk_limit': ['Talk limit', - MINUTES, 'mdi:cellphone'], - 'talt_remaining': ['Talk remaining', - MINUTES, 'mdi:cellphone'], - 'other_talk_used': ['Other Talk used', - MINUTES, 'mdi:cellphone'], - 'other_talk_limit': ['Other Talk limit', - MINUTES, 'mdi:cellphone'], - 'other_talk_remaining': ['Other Talk remaining', - MINUTES, 'mdi:cellphone'], + 'talk_used': ['Talk used', MINUTES, 'mdi:cellphone'], + 'talk_limit': ['Talk limit', MINUTES, 'mdi:cellphone'], + 'talt_remaining': ['Talk remaining', MINUTES, 'mdi:cellphone'], + 'other_talk_used': ['Other Talk used', MINUTES, 'mdi:cellphone'], + 'other_talk_limit': ['Other Talk limit', MINUTES, 'mdi:cellphone'], + 'other_talk_remaining': ['Other Talk remaining', MINUTES, 'mdi:cellphone'], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py index 697fecca077..d976da723d6 100644 --- a/homeassistant/components/sensor/fitbit.py +++ b/homeassistant/components/sensor/fitbit.py @@ -156,7 +156,7 @@ def request_app_setup(hass, config, add_devices, config_path, # pylint: disable=unused-argument def fitbit_configuration_callback(callback_data): - """The actions to do when our configuration callback is called.""" + """Handle configuration updates.""" config_path = hass.config.path(FITBIT_CONFIG_FILE) if os.path.isfile(config_path): config_file = config_from_file(config_path) @@ -201,7 +201,7 @@ def request_oauth_completion(hass): # pylint: disable=unused-argument def fitbit_configuration_callback(callback_data): - """The actions to do when our configuration callback is called.""" + """Handle configuration updates.""" start_url = '{}{}'.format(hass.config.api.base_url, FITBIT_AUTH_START) diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/sensor/fixer.py index b30b660516d..87fafe2c27c 100644 --- a/homeassistant/components/sensor/fixer.py +++ b/homeassistant/components/sensor/fixer.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Fixer.io sensor.""" + """Set up the Fixer.io sensor.""" from fixerio import (Fixerio, exceptions) name = config.get(CONF_NAME) @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: Fixerio(base=base, symbols=[target], secure=True).latest() except exceptions.FixerioException: - _LOGGER.error('One of the given currencies is not supported') + _LOGGER.error("One of the given currencies is not supported") return False data = ExchangeData(base, target) @@ -116,9 +116,9 @@ class ExchangeData(object): self.rate = None self.base_currency = base_currency self.target_currency = target_currency - self.exchange = Fixerio(base=self.base_currency, - symbols=[self.target_currency], - secure=True) + self.exchange = Fixerio( + base=self.base_currency, symbols=[self.target_currency], + secure=True) @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/sensor/fritzbox_callmonitor.py index 956ca3fe495..ea2fcd026b3 100644 --- a/homeassistant/components/sensor/fritzbox_callmonitor.py +++ b/homeassistant/components/sensor/fritzbox_callmonitor.py @@ -24,20 +24,22 @@ from homeassistant.util import Throttle REQUIREMENTS = ['fritzconnection==0.6.3'] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Phone' -DEFAULT_HOST = '169.254.1.1' # IP valid for all Fritz!Box routers -DEFAULT_PORT = 1012 -VALUE_DEFAULT = 'idle' -VALUE_RING = 'ringing' -VALUE_CALL = 'dialing' -VALUE_CONNECT = 'talking' -VALUE_DISCONNECT = 'idle' CONF_PHONEBOOK = 'phonebook' CONF_PREFIXES = 'prefixes' +DEFAULT_HOST = '169.254.1.1' # IP valid for all Fritz!Box routers +DEFAULT_NAME = 'Phone' +DEFAULT_PORT = 1012 + INTERVAL_RECONNECT = 60 +VALUE_CALL = 'dialing' +VALUE_CONNECT = 'talking' +VALUE_DEFAULT = 'idle' +VALUE_DISCONNECT = 'idle' +VALUE_RING = 'ringing' + # Return cached results if phonebook was downloaded less then this time ago. MIN_TIME_PHONEBOOK_UPDATE = datetime.timedelta(hours=6) SCAN_INTERVAL = datetime.timedelta(hours=3) @@ -49,13 +51,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PASSWORD, default='admin'): cv.string, vol.Optional(CONF_USERNAME, default=''): cv.string, vol.Optional(CONF_PHONEBOOK, default=0): cv.positive_int, - vol.Optional(CONF_PREFIXES, default=[]): vol.All(cv.ensure_list, - [cv.string]) + vol.Optional(CONF_PREFIXES, default=[]): + vol.All(cv.ensure_list, [cv.string]) }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Fritz!Box call monitor sensor platform.""" + """Set up Fritz!Box call monitor sensor platform.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -65,14 +67,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): prefixes = config.get('prefixes') try: - phonebook = FritzBoxPhonebook(host=host, port=port, - username=username, password=password, - phonebook_id=phonebook_id, - prefixes=prefixes) + phonebook = FritzBoxPhonebook( + host=host, port=port, username=username, password=password, + phonebook_id=phonebook_id, prefixes=prefixes) # pylint: disable=bare-except except: phonebook = None - _LOGGER.warning('Phonebook with ID %s not found on Fritz!Box', + _LOGGER.warning("Phonebook with ID %s not found on Fritz!Box", phonebook_id) sensor = FritzBoxCallSensor(name=name, phonebook=phonebook) @@ -116,7 +117,7 @@ class FritzBoxCallSensor(Entity): @property def should_poll(self): - """Polling needed only to update phonebook, if defined.""" + """Only poll to update phonebook, if defined.""" if self.phonebook is None: return False else: @@ -250,9 +251,8 @@ class FritzBoxPhonebook(object): # pylint: disable=import-error import fritzconnection as fc # Establish a connection to the FRITZ!Box. - self.fph = fc.FritzPhonebook(address=self.host, - user=self.username, - password=self.password) + self.fph = fc.FritzPhonebook( + address=self.host, user=self.username, password=self.password) if self.phonebook_id not in self.fph.list_phonebooks: raise ValueError("Phonebook with this ID not found.") @@ -266,7 +266,7 @@ class FritzBoxPhonebook(object): self.number_dict = {re.sub(r'[^\d\+]', '', nr): name for name, nrs in self.phonebook_dict.items() for nr in nrs} - _LOGGER.info('Fritz!Box phone book successfully updated.') + _LOGGER.info("Fritz!Box phone book successfully updated") def get_name(self, number): """Return a name for a given phone number.""" diff --git a/homeassistant/components/sensor/fritzbox_netmonitor.py b/homeassistant/components/sensor/fritzbox_netmonitor.py index ba0ee8be1b9..003307fbd83 100644 --- a/homeassistant/components/sensor/fritzbox_netmonitor.py +++ b/homeassistant/components/sensor/fritzbox_netmonitor.py @@ -19,19 +19,21 @@ from requests.exceptions import RequestException REQUIREMENTS = ['fritzconnection==0.6.3'] -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) +_LOGGER = logging.getLogger(__name__) CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers. -ATTR_IS_LINKED = "is_linked" -ATTR_IS_CONNECTED = "is_connected" -ATTR_WAN_ACCESS_TYPE = "wan_access_type" -ATTR_EXTERNAL_IP = "external_ip" -ATTR_UPTIME = "uptime" -ATTR_BYTES_SENT = "bytes_sent" -ATTR_BYTES_RECEIVED = "bytes_received" -ATTR_MAX_BYTE_RATE_UP = "max_byte_rate_up" -ATTR_MAX_BYTE_RATE_DOWN = "max_byte_rate_down" +ATTR_BYTES_RECEIVED = 'bytes_received' +ATTR_BYTES_SENT = 'bytes_sent' +ATTR_EXTERNAL_IP = 'external_ip' +ATTR_IS_CONNECTED = 'is_connected' +ATTR_IS_LINKED = 'is_linked' +ATTR_MAX_BYTE_RATE_DOWN = 'max_byte_rate_down' +ATTR_MAX_BYTE_RATE_UP = 'max_byte_rate_up' +ATTR_UPTIME = 'uptime' +ATTR_WAN_ACCESS_TYPE = 'wan_access_type' + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) STATE_ONLINE = 'online' STATE_OFFLINE = 'offline' @@ -42,8 +44,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, }) -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the fritzbox monitor sensors.""" diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index 699781d323b..ef9ea3138bd 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -93,7 +93,7 @@ class GlancesSensor(Entity): @property def name(self): - """The name of the sensor.""" + """Return the name of the sensor.""" if self._name is None: return SENSOR_TYPES[self.type][0] else: diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py index 6c625ce8038..0e495c67817 100644 --- a/homeassistant/components/sensor/google_travel_time.py +++ b/homeassistant/components/sensor/google_travel_time.py @@ -73,15 +73,15 @@ TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone'] def convert_time_to_utc(timestr): """Take a string like 08:00:00 and convert it to a unix timestamp.""" - combined = datetime.combine(dt_util.start_of_local_day(), - dt_util.parse_time(timestr)) + combined = datetime.combine( + dt_util.start_of_local_day(), dt_util.parse_time(timestr)) if combined < datetime.now(): combined = combined + timedelta(days=1) return dt_util.as_timestamp(combined) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the Google travel time platform.""" + """Set up the Google travel time platform.""" def run_setup(event): """Delay the setup until Home Assistant is fully initialized. @@ -225,9 +225,8 @@ class GoogleTravelTimeSensor(Entity): self._origin = self._resolve_zone(self._origin) if self._destination is not None and self._origin is not None: - self._matrix = self._client.distance_matrix(self._origin, - self._destination, - **options_copy) + self._matrix = self._client.distance_matrix( + self._origin, self._destination, **options_copy) def _get_location_from_entity(self, entity_id): """Get the location from the entity state or attributes.""" @@ -246,7 +245,7 @@ class GoogleTravelTimeSensor(Entity): zone_entity = self._hass.states.get("zone.%s" % entity.state) if location.has_location(zone_entity): _LOGGER.debug( - "%s is in %s, getting zone location.", + "%s is in %s, getting zone location", entity_id, zone_entity.entity_id ) return self._get_location_from_attributes(zone_entity) diff --git a/homeassistant/components/sensor/gpsd.py b/homeassistant/components/sensor/gpsd.py index f6f9e75df31..9027802c295 100644 --- a/homeassistant/components/sensor/gpsd.py +++ b/homeassistant/components/sensor/gpsd.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the GPSD component.""" + """Set up the GPSD component.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -57,9 +57,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: sock.connect((host, port)) sock.shutdown(2) - _LOGGER.debug('Connection to GPSD possible') + _LOGGER.debug("Connection to GPSD possible") except socket.error: - _LOGGER.error('Not able to connect to GPSD') + _LOGGER.error("Not able to connect to GPSD") return False add_devices([GpsdSensor(hass, name, host, port)]) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 2726f1f579f..961b8067009 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -160,7 +160,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): os.makedirs(gtfs_dir) if not os.path.exists(os.path.join(gtfs_dir, data)): - _LOGGER.error("The given GTFS data file/folder was not found!") + _LOGGER.error("The given GTFS data file/folder was not found") return False import pygtfs @@ -222,8 +222,8 @@ class GTFSDepartureSensor(Entity): def update(self): """Get the latest data from GTFS and update the states.""" with self.lock: - self._departure = get_next_departure(self._pygtfs, self.origin, - self.destination) + self._departure = get_next_departure( + self._pygtfs, self.origin, self.destination) if not self._departure: self._state = 0 self._attributes = {'Info': 'No more departures today'} diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/sensor/haveibeenpwned.py index f5b6d8cfba0..1c28db9a9df 100644 --- a/homeassistant/components/sensor/haveibeenpwned.py +++ b/homeassistant/components/sensor/haveibeenpwned.py @@ -102,10 +102,9 @@ class HaveIBeenPwnedSensor(Entity): # data after hass startup once we have the data it will update as # normal using update if self._email not in self._data.data: - track_point_in_time(self._hass, - self.update_nothrottle, - dt_util.now() + - MIN_TIME_BETWEEN_FORCED_UPDATES) + track_point_in_time( + self._hass, self.update_nothrottle, + dt_util.now() + MIN_TIME_BETWEEN_FORCED_UPDATES) return if self._email in self._data.data: @@ -153,7 +152,7 @@ class HaveIBeenPwnedData(object): allow_redirects=True, timeout=5) except requests.exceptions.RequestException: - _LOGGER.error("failed fetching HaveIBeenPwned Data for '%s'", + _LOGGER.error("Failed fetching HaveIBeenPwned Data for %s", self._email) return @@ -174,6 +173,6 @@ class HaveIBeenPwnedData(object): self.set_next_email() else: - _LOGGER.error("failed fetching HaveIBeenPwned Data for '%s'" + _LOGGER.error("Failed fetching HaveIBeenPwned Data for %s" "(HTTP Status_code = %d)", self._email, req.status_code) diff --git a/homeassistant/components/sensor/hddtemp.py b/homeassistant/components/sensor/hddtemp.py index c0e3dc32d4d..b7ba91ba94a 100644 --- a/homeassistant/components/sensor/hddtemp.py +++ b/homeassistant/components/sensor/hddtemp.py @@ -38,7 +38,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the HDDTemp sensor.""" + """Set up the HDDTemp sensor.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -118,6 +118,6 @@ class HddTempData(object): host=self.host, port=self.port, timeout=DEFAULT_TIMEOUT) self.data = connection.read_all().decode('ascii') except ConnectionRefusedError: - _LOGGER.error('HDDTemp is not available at %s:%s', self.host, - self.port) + _LOGGER.error( + "HDDTemp is not available at %s:%s", self.host, self.port) self.data = None diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/sensor/history_stats.py index fe073b89fcd..7d9dfaaa48f 100644 --- a/homeassistant/components/sensor/history_stats.py +++ b/homeassistant/components/sensor/history_stats.py @@ -4,7 +4,6 @@ Component to make instant statistics about your history. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.history_stats/ """ - import datetime import logging import math @@ -152,7 +151,7 @@ class HistoryStatsSensor(Entity): @property def should_poll(self): - """Polling required.""" + """Return the polling state.""" return True @property @@ -239,8 +238,8 @@ class HistoryStatsSensor(Entity): start = dt_util.as_local(dt_util.utc_from_timestamp( math.floor(float(start_rendered)))) except ValueError: - _LOGGER.error('PARSING ERROR: start must be a datetime' - ' or a timestamp.') + _LOGGER.error("Parsing error: start must be a datetime" + "or a timestamp") return # Parse end @@ -256,8 +255,8 @@ class HistoryStatsSensor(Entity): end = dt_util.as_local(dt_util.utc_from_timestamp( math.floor(float(end_rendered)))) except ValueError: - _LOGGER.error('PARSING ERROR: end must be a datetime' - ' or a timestamp.') + _LOGGER.error("Parsing error: end must be a datetime " + "or a timestamp") return # Calculate start or end using the duration @@ -305,5 +304,5 @@ class HistoryStatsHelper: # Common during HA startup - so just a warning _LOGGER.warning(ex) return - _LOGGER.error('Error parsing template for [' + field + ']') + _LOGGER.error("Error parsing template for field %s", field) _LOGGER.error(ex) diff --git a/homeassistant/components/sensor/homematic.py b/homeassistant/components/sensor/homematic.py index 2215597f506..44ba7dfa753 100644 --- a/homeassistant/components/sensor/homematic.py +++ b/homeassistant/components/sensor/homematic.py @@ -3,11 +3,7 @@ The homematic sensor platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.homematic/ - -Important: For this platform to work the homematic component has to be -properly configured. """ - import logging from homeassistant.const import STATE_UNKNOWN from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES @@ -17,45 +13,45 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] HM_STATE_HA_CAST = { - "RotaryHandleSensor": {0: "closed", 1: "tilted", 2: "open"}, - "WaterSensor": {0: "dry", 1: "wet", 2: "water"}, - "CO2Sensor": {0: "normal", 1: "added", 2: "strong"}, + 'RotaryHandleSensor': {0: 'closed', 1: 'tilted', 2: 'open'}, + 'WaterSensor': {0: 'dry', 1: 'wet', 2: 'water'}, + 'CO2Sensor': {0: 'normal', 1: 'added', 2: 'strong'}, } HM_UNIT_HA_CAST = { - "HUMIDITY": "%", - "TEMPERATURE": "°C", - "BRIGHTNESS": "#", - "POWER": "W", - "CURRENT": "mA", - "VOLTAGE": "V", - "ENERGY_COUNTER": "Wh", - "GAS_POWER": "m3", - "GAS_ENERGY_COUNTER": "m3", - "LUX": "lux", - "RAIN_COUNTER": "mm", - "WIND_SPEED": "km/h", - "WIND_DIRECTION": "°", - "WIND_DIRECTION_RANGE": "°", - "SUNSHINEDURATION": "#", - "AIR_PRESSURE": "hPa", - "FREQUENCY": "Hz", - "VALUE": "#", + 'HUMIDITY': '%', + 'TEMPERATURE': '°C', + 'BRIGHTNESS': '#', + 'POWER': 'W', + 'CURRENT': 'mA', + 'VOLTAGE': 'V', + 'ENERGY_COUNTER': 'Wh', + 'GAS_POWER': 'm3', + 'GAS_ENERGY_COUNTER': 'm3', + 'LUX': 'lux', + 'RAIN_COUNTER': 'mm', + 'WIND_SPEED': 'km/h', + 'WIND_DIRECTION': '°', + 'WIND_DIRECTION_RANGE': '°', + 'SUNSHINEDURATION': '#', + 'AIR_PRESSURE': 'hPa', + 'FREQUENCY': 'Hz', + 'VALUE': '#', } HM_ICON_HA_CAST = { - "WIND_SPEED": 'mdi:weather-windy', - "HUMIDITY": 'mdi:water-percent', - "TEMPERATURE": 'mdi:thermometer', - "LUX": 'mdi:weather-sunny', - "BRIGHTNESS": 'mdi:invert-colors', - "POWER": 'mdi:flash-red-eye', - "CURRENT": "mdi:flash-red-eye", + 'WIND_SPEED': 'mdi:weather-windy', + 'HUMIDITY': 'mdi:water-percent', + 'TEMPERATURE': 'mdi:thermometer', + 'LUX': 'mdi:weather-sunny', + 'BRIGHTNESS': 'mdi:invert-colors', + 'POWER': 'mdi:flash-red-eye', + 'CURRENT': 'mdi:flash-red-eye', } def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the platform.""" + """Set up the Homematic platform.""" if discovery_info is None: return @@ -96,8 +92,8 @@ class HMSensor(HMDevice): """Generate a data dict (self._data) from hm metadata.""" # Add state to data dict if self._state: - _LOGGER.debug("%s init datadict with main node '%s'", self._name, + _LOGGER.debug("%s init datadict with main node %s", self._name, self._state) self._data.update({self._state: STATE_UNKNOWN}) else: - _LOGGER.critical("Can't correctly init sensor %s.", self._name) + _LOGGER.critical("Can't correctly init sensor %s", self._name) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index 675db6400a0..338a6e7aff5 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -26,7 +26,6 @@ DEFAULT_PORT = 443 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -# Each sensor is defined as follows: 'Descriptive name', 'python-ilo function' SENSOR_TYPES = { 'server_name': ['Server Name', 'get_server_name'], 'server_fqdn': ['Server FQDN', 'get_server_fqdn'], @@ -74,9 +73,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Initialize and add all of the sensors. devices = [] for ilo_type in monitored_variables: - new_device = HpIloSensor(hp_ilo_data=hp_ilo_data, - sensor_type=SENSOR_TYPES.get(ilo_type), - client_name=name) + new_device = HpIloSensor( + hp_ilo_data=hp_ilo_data, sensor_type=SENSOR_TYPES.get(ilo_type), + client_name=name) devices.append(new_device) add_devices(devices) @@ -161,10 +160,9 @@ class HpIloData(object): import hpilo try: - self.data = hpilo.Ilo(hostname=self._host, - login=self._login, - password=self._password, - port=self._port) + self.data = hpilo.Ilo( + hostname=self._host, login=self._login, + password=self._password, port=self._port) except (hpilo.IloError, hpilo.IloCommunicationError, hpilo.IloLoginFailed) as error: raise ValueError("Unable to init HP ILO, %s", error) diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py index 9fcabd62d76..c96909e5bc1 100644 --- a/homeassistant/components/sensor/hydroquebec.py +++ b/homeassistant/components/sensor/hydroquebec.py @@ -25,12 +25,12 @@ REQUIREMENTS = ['pyhydroquebec==1.1.0'] _LOGGER = logging.getLogger(__name__) -KILOWATT_HOUR = "kWh" # type: str -PRICE = "CAD" # type: str -DAYS = "days" # type: str -CONF_CONTRACT = "contract" # type: str +KILOWATT_HOUR = 'kWh' # type: str +PRICE = 'CAD' # type: str +DAYS = 'days' # type: str +CONF_CONTRACT = 'contract' # type: str -DEFAULT_NAME = "HydroQuebec" +DEFAULT_NAME = 'HydroQuebec' REQUESTS_TIMEOUT = 15 MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) @@ -73,10 +73,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -HOST = "https://www.hydroquebec.com" -HOME_URL = "{}/portail/web/clientele/authentification".format(HOST) -PROFILE_URL = ("{}/portail/fr/group/clientele/" - "portrait-de-consommation".format(HOST)) +HOST = 'https://www.hydroquebec.com' +HOME_URL = '{}/portail/web/clientele/authentification'.format(HOST) +PROFILE_URL = ('{}/portail/fr/group/clientele/' + 'portrait-de-consommation'.format(HOST)) MONTHLY_MAP = (('period_total_bill', 'montantFacturePeriode'), ('period_length', 'nbJourLecturePeriode'), ('period_total_days', 'nbJourPrevuPeriode'), @@ -163,9 +163,8 @@ class HydroquebecData(object): def __init__(self, username, password, contract=None): """Initialize the data object.""" from pyhydroquebec import HydroQuebecClient - self.client = HydroQuebecClient(username, - password, - REQUESTS_TIMEOUT) + self.client = HydroQuebecClient( + username, password, REQUESTS_TIMEOUT) self._contract = contract self.data = {} @@ -187,7 +186,5 @@ class HydroquebecData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Return the latest collected data from HydroQuebec.""" - # Fetch data self._fetch_data() - # Update data self.data = self.client.get_data(self._contract)[self._contract] diff --git a/homeassistant/components/sensor/imap.py b/homeassistant/components/sensor/imap.py index 4d7f34ef682..dc772df48c0 100644 --- a/homeassistant/components/sensor/imap.py +++ b/homeassistant/components/sensor/imap.py @@ -31,12 +31,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the IMAP platform.""" - sensor = ImapSensor(config.get(CONF_NAME, None), - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config.get(CONF_SERVER), - config.get(CONF_PORT)) + """Set up the IMAP platform.""" + sensor = ImapSensor( + config.get(CONF_NAME, None), config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), config.get(CONF_SERVER), + config.get(CONF_PORT)) if sensor.connection: add_devices([sensor]) diff --git a/homeassistant/components/sensor/imap_email_content.py b/homeassistant/components/sensor/imap_email_content.py index b5ff92860a0..99613f0f7e8 100644 --- a/homeassistant/components/sensor/imap_email_content.py +++ b/homeassistant/components/sensor/imap_email_content.py @@ -1,5 +1,5 @@ """ -EMail sensor support. +Email sensor support. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.email/ @@ -7,24 +7,26 @@ https://home-assistant.io/components/sensor.email/ import logging import datetime import email - from collections import deque + +import voluptuous as vol + from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_VALUE_TEMPLATE) + CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_VALUE_TEMPLATE, + CONTENT_TYPE_TEXT_PLAIN) import homeassistant.helpers.config_validation as cv -import voluptuous as vol _LOGGER = logging.getLogger(__name__) -CONF_SERVER = "server" -CONF_SENDERS = "senders" +CONF_SERVER = 'server' +CONF_SENDERS = 'senders' -ATTR_FROM = "from" -ATTR_BODY = "body" -ATTR_DATE = "date" -ATTR_SUBJECT = "subject" +ATTR_FROM = 'from' +ATTR_BODY = 'body' +ATTR_DATE = 'date' +ATTR_SUBJECT = 'subject' DEFAULT_PORT = 993 @@ -39,22 +41,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the EMail platform.""" + """Set up the Email sensor platform.""" reader = EmailReader( - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config.get(CONF_SERVER), - config.get(CONF_PORT)) + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + config.get(CONF_SERVER), config.get(CONF_PORT)) value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass sensor = EmailContentSensor( - hass, - reader, - config.get(CONF_NAME, None) or config.get(CONF_USERNAME), - config.get(CONF_SENDERS), - value_template) + hass, reader, config.get(CONF_NAME) or config.get(CONF_USERNAME), + config.get(CONF_SENDERS), value_template) if sensor.connected: add_devices([sensor]) @@ -83,15 +80,13 @@ class EmailReader: self.connection.login(self._user, self._password) return True except imaplib.IMAP4.error: - _LOGGER.error("Failed to login to %s.", self._server) + _LOGGER.error("Failed to login to %s", self._server) return False def _fetch_message(self, message_uid): """Get an email message from a message id.""" _, message_data = self.connection.uid( - 'fetch', - message_uid, - '(RFC822)') + 'fetch', message_uid, '(RFC822)') raw_email = message_data[0][1] email_message = email.message_from_bytes(raw_email) @@ -103,7 +98,7 @@ class EmailReader: try: self.connection.select() - if len(self._unread_ids) == 0: + if not self._unread_ids: search = "SINCE {0:%d-%b-%Y}".format(datetime.date.today()) if self._last_id is not None: search = "UID {}:*".format(self._last_id) @@ -111,7 +106,7 @@ class EmailReader: _, data = self.connection.uid("search", None, search) self._unread_ids = deque(data[0].split()) - while len(self._unread_ids) > 0: + while self._unread_ids: message_uid = self._unread_ids.popleft() if self._last_id is None or int(message_uid) > self._last_id: self._last_id = int(message_uid) @@ -119,29 +114,23 @@ class EmailReader: except imaplib.IMAP4.error: _LOGGER.info( - "Connection to %s lost, attempting to reconnect", - self._server) + "Connection to %s lost, attempting to reconnect", self._server) try: self.connect() except imaplib.IMAP4.error: - _LOGGER.error("Failed to reconnect.") + _LOGGER.error("Failed to reconnect") class EmailContentSensor(Entity): """Representation of an EMail sensor.""" - def __init__(self, - hass, - email_reader, - name, - allowed_senders, + def __init__(self, hass, email_reader, name, allowed_senders, value_template): """Initialize the sensor.""" self.hass = hass self._email_reader = email_reader self._name = name - self._allowed_senders = \ - [sender.upper() for sender in allowed_senders] + self._allowed_senders = [sender.upper() for sender in allowed_senders] self._value_template = value_template self._last_id = None self._message = None @@ -202,7 +191,7 @@ class EmailContentSensor(Entity): message_untyped_text = None for part in email_message.walk(): - if part.get_content_type() == 'text/plain': + if part.get_content_type() == CONTENT_TYPE_TEXT_PLAIN: if message_text is None: message_text = part.get_payload() elif part.get_content_type() == 'text/html': diff --git a/homeassistant/components/sensor/influxdb.py b/homeassistant/components/sensor/influxdb.py index 89c9bbad4b2..b4688c77e1b 100644 --- a/homeassistant/components/sensor/influxdb.py +++ b/homeassistant/components/sensor/influxdb.py @@ -65,7 +65,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the InfluxDB component.""" + """Set up the InfluxDB component.""" influx_conf = {'host': config[CONF_HOST], 'port': config.get(CONF_PORT), 'username': config.get(CONF_USERNAME), @@ -105,13 +105,11 @@ class InfluxSensor(Entity): query.get(CONF_FIELD), query.get(CONF_MEASUREMENT_NAME), query.get(CONF_WHERE)) - influx = InfluxDBClient(host=influx_conf['host'], - port=influx_conf['port'], - username=influx_conf['username'], - password=influx_conf['password'], - database=database, - ssl=influx_conf['ssl'], - verify_ssl=influx_conf['verify_ssl']) + influx = InfluxDBClient( + host=influx_conf['host'], port=influx_conf['port'], + username=influx_conf['username'], password=influx_conf['password'], + database=database, ssl=influx_conf['ssl'], + verify_ssl=influx_conf['verify_ssl']) try: influx.query("select * from /.*/ LIMIT 1;") self.connected = True @@ -135,12 +133,12 @@ class InfluxSensor(Entity): @property def unit_of_measurement(self): - """Unit of measurement of this entity, if any.""" + """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return True def update(self): @@ -168,15 +166,15 @@ class InfluxSensorData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data with a shell command.""" - _LOGGER.info('Running query: %s', self.query) + _LOGGER.info("Running query: %s", self.query) points = list(self.influx.query(self.query).get_points()) - if len(points) == 0: - _LOGGER.warning('Query returned no points, sensor state set' - ' to UNKNOWN : %s', self.query) + if not points: + _LOGGER.warning("Query returned no points, sensor state set " + "to UNKNOWN: %s", self.query) self.value = None else: - if len(points) > 1: - _LOGGER.warning('Query returned multiple points, only first' - ' one shown : %s', self.query) + if points: + _LOGGER.warning("Query returned multiple points, only first " + "one shown: %s", self.query) self.value = points[0].get('value') diff --git a/homeassistant/components/sensor/ios.py b/homeassistant/components/sensor/ios.py index 3173eec4285..c3bcbf60828 100644 --- a/homeassistant/components/sensor/ios.py +++ b/homeassistant/components/sensor/ios.py @@ -7,24 +7,24 @@ https://home-assistant.io/ecosystem/ios/ from homeassistant.components import ios from homeassistant.helpers.entity import Entity -DEPENDENCIES = ["ios"] +DEPENDENCIES = ['ios'] SENSOR_TYPES = { - "level": ["Battery Level", "%"], - "state": ["Battery State", None] + 'level': ['Battery Level', '%'], + 'state': ['Battery State', None] } -DEFAULT_ICON_LEVEL = "mdi:battery" -DEFAULT_ICON_STATE = "mdi:power-plug" +DEFAULT_ICON_LEVEL = 'mdi:battery' +DEFAULT_ICON_STATE = 'mdi:power-plug' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the iOS sensor.""" + """Set up the iOS sensor.""" if discovery_info is None: return dev = list() for device_name, device in ios.devices().items(): - for sensor_type in ("level", "state"): + for sensor_type in ('level', 'state'): dev.append(IOSSensor(sensor_type, device_name, device)) add_devices(dev) @@ -36,7 +36,7 @@ class IOSSensor(Entity): def __init__(self, sensor_type, device_name, device): """Initialize the sensor.""" self._device_name = device_name - self._name = device_name + " " + SENSOR_TYPES[sensor_type][0] + self._name = "{} {}".format(device_name, SENSOR_TYPES[sensor_type][0]) self._device = device self.type = sensor_type self._state = None @@ -63,6 +63,7 @@ class IOSSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement this sensor expresses itself in.""" + return self._unit_of_measurement @property def device_state_attributes(self): diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index d35c76b81dc..2f6d7346283 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -1,15 +1,15 @@ """ -Support for ISY994 binary sensors. +Support for ISY994 sensors. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.isy994/ +https://home-assistant.io/components/sensor.isy994/ """ import logging from typing import Callable # noqa import homeassistant.components.isy994 as isy -from homeassistant.const import (TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_OFF, - STATE_ON) +from homeassistant.const import ( + TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_OFF, STATE_ON) from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -238,17 +238,17 @@ BINARY_UOM = ['2', '78'] # pylint: disable=unused-argument def setup_platform(hass, config: ConfigType, add_devices: Callable[[list], None], discovery_info=None): - """Setup the ISY994 sensor platform.""" + """Set up the ISY994 sensor platform.""" if isy.ISY is None or not isy.ISY.connected: - _LOGGER.error('A connection has not been made to the ISY controller.') + _LOGGER.error("A connection has not been made to the ISY controller") return False devices = [] for node in isy.SENSOR_NODES: - if (len(node.uom) == 0 or node.uom[0] not in BINARY_UOM) and \ + if (not node.uom or node.uom[0] not in BINARY_UOM) and \ STATE_OFF not in node.uom and STATE_ON not in node.uom: - _LOGGER.debug('LOADING %s', node.name) + _LOGGER.debug("Loading %s", node.name) devices.append(ISYSensorDevice(node)) for node in isy.WEATHER_NODES: diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py index fef02710b9e..229f8790291 100644 --- a/homeassistant/components/sensor/knx.py +++ b/homeassistant/components/sensor/knx.py @@ -4,19 +4,18 @@ Sensors of a KNX Device. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/knx/ """ -from homeassistant.const import (TEMP_CELSIUS, TEMPERATURE, CONF_TYPE, - ILLUMINANCE, SPEED_MS, CONF_MINIMUM, - CONF_MAXIMUM) +from homeassistant.const import ( + TEMP_CELSIUS, TEMPERATURE, CONF_TYPE, ILLUMINANCE, SPEED_MS, CONF_MINIMUM, + CONF_MAXIMUM) from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) - -DEPENDENCIES = ["knx"] +DEPENDENCIES = ['knx'] # Speed units -SPEED_METERPERSECOND = "m/s" # type: str +SPEED_METERPERSECOND = 'm/s' # type: str # Illuminance units -ILLUMINANCE_LUX = "lx" # type: str +ILLUMINANCE_LUX = 'lx' # type: str # Predefined Minimum, Maximum Values for Sensors # Temperature as defined in KNX Standard 3.10 - 9.001 DPT_Value_Temp @@ -33,25 +32,24 @@ KNX_SPEED_MS_MAX = 670760 def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup the KNX Sensor platform.""" - # Add KNX Temperature Sensors + """Set up the KNX Sensor platform.""" # KNX Datapoint 9.001 DPT_Value_Temp if config[CONF_TYPE] == TEMPERATURE: minimum_value, maximum_value = \ - update_and_define_min_max(config, KNX_TEMP_MIN, - KNX_TEMP_MAX) + update_and_define_min_max(config, KNX_TEMP_MIN, KNX_TEMP_MAX) add_entities([ - KNXSensorFloatClass(hass, KNXConfig(config), TEMP_CELSIUS, - minimum_value, maximum_value) + KNXSensorFloatClass( + hass, KNXConfig(config), TEMP_CELSIUS, minimum_value, + maximum_value) ]) # Add KNX Speed Sensors(Like Wind Speed) # KNX Datapoint 9.005 DPT_Value_Wsp elif config[CONF_TYPE] == SPEED_MS: minimum_value, maximum_value = \ - update_and_define_min_max(config, KNX_SPEED_MS_MIN, - KNX_SPEED_MS_MAX) + update_and_define_min_max( + config, KNX_SPEED_MS_MIN, KNX_SPEED_MS_MAX) add_entities([ KNXSensorFloatClass(hass, KNXConfig(config), SPEED_METERPERSECOND, @@ -70,9 +68,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ]) -def update_and_define_min_max(config, minimum_default, - maximum_default): - """Function help determinate a min/max value defined in config.""" +def update_and_define_min_max(config, minimum_default, maximum_default): + """Determine a min/max value defined in the configuration.""" minimum_value = minimum_default maximum_value = maximum_default if config.get(CONF_MINIMUM): diff --git a/homeassistant/components/sensor/kwb.py b/homeassistant/components/sensor/kwb.py index 54799ccc6b4..0641917145b 100644 --- a/homeassistant/components/sensor/kwb.py +++ b/homeassistant/components/sensor/kwb.py @@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv - REQUIREMENTS = ['pykwb==0.0.8'] _LOGGER = logging.getLogger(__name__) @@ -50,7 +49,7 @@ PLATFORM_SCHEMA = vol.Schema( def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the KWB component.""" + """Set up the KWB component.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) device = config.get(CONF_DEVICE) diff --git a/homeassistant/components/sensor/linux_battery.py b/homeassistant/components/sensor/linux_battery.py index ddfb12f008b..18e2250ee13 100644 --- a/homeassistant/components/sensor/linux_battery.py +++ b/homeassistant/components/sensor/linux_battery.py @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Linux Battery sensor.""" + """Set up the Linux Battery sensor.""" name = config.get(CONF_NAME) battery_id = config.get(CONF_BATTERY) diff --git a/homeassistant/components/sensor/loopenergy.py b/homeassistant/components/sensor/loopenergy.py index ebd044343b0..a2d6b0c3a0c 100644 --- a/homeassistant/components/sensor/loopenergy.py +++ b/homeassistant/components/sensor/loopenergy.py @@ -59,7 +59,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Loop Energy sensors.""" + """Set up the Loop Energy sensors.""" import pyloopenergy elec_config = config.get(CONF_ELEC) @@ -77,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def stop_loopenergy(event): """Shutdown loopenergy thread on exit.""" - _LOGGER.info("Shutting down loopenergy.") + _LOGGER.info("Shutting down loopenergy") controller.terminate() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_loopenergy) diff --git a/homeassistant/components/sensor/lyft.py b/homeassistant/components/sensor/lyft.py index 3962be0aa2f..c16fae9f5d5 100644 --- a/homeassistant/components/sensor/lyft.py +++ b/homeassistant/components/sensor/lyft.py @@ -79,8 +79,8 @@ class LyftSensor(Entity): self._product_id = product_id self._product = product self._sensortype = sensorType - self._name = '{} {}'.format(self._product['display_name'], - self._sensortype) + self._name = '{} {}'.format( + self._product['display_name'], self._sensortype) if 'lyft' not in self._name.lower(): self._name = 'Lyft{}'.format(self._name) if self._sensortype == 'time': @@ -137,10 +137,8 @@ class LyftSensor(Entity): params['Trip duration (in seconds)'] = estimate.get( 'estimated_duration_seconds') - # Ignore the Prime Time percentage -- the Lyft API always - # returns 0 unless a user is logged in. - # params['Prime Time percentage'] = estimate.get( - # 'primetime_percentage') + params['Prime Time percentage'] = estimate.get( + 'primetime_percentage') if self._product.get("eta") is not None: eta = self._product['eta'] @@ -161,13 +159,14 @@ class LyftSensor(Entity): self._product = self.data.products[self._product_id] except KeyError: return + self._state = None if self._sensortype == 'time': eta = self._product['eta'] if (eta is not None) and (eta.get('is_valid_estimate')): - time_estimate = eta.get('eta_seconds', 0) + time_estimate = eta.get('eta_seconds') + if time_estimate is None: + return self._state = int(time_estimate / 60) - else: - self._state = 0 elif self._sensortype == 'price': estimate = self._product['estimate'] if (estimate is not None) and \ @@ -175,8 +174,6 @@ class LyftSensor(Entity): self._state = (int( (estimate.get('estimated_cost_cents_min', 0) + estimate.get('estimated_cost_cents_max', 0)) / 2) / 100) - else: - self._state = 0 class LyftEstimate(object): diff --git a/homeassistant/components/sensor/metoffice.py b/homeassistant/components/sensor/metoffice.py index 725fca1db44..c1ffb01d212 100644 --- a/homeassistant/components/sensor/metoffice.py +++ b/homeassistant/components/sensor/metoffice.py @@ -4,7 +4,6 @@ Support for UK Met Office weather service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.metoffice/ """ - import logging from datetime import timedelta @@ -25,17 +24,17 @@ REQUIREMENTS = ['datapoint==0.4.3'] CONF_ATTRIBUTION = "Data provided by the Met Office" CONDITION_CLASSES = { - 'cloudy': ["7", "8"], - 'fog': ["5", "6"], - 'hail': ["19", "20", "21"], - 'lightning': ["30"], - 'lightning-rainy': ["28", "29"], - 'partlycloudy': ["2", "3"], - 'pouring': ["13", "14", "15"], - 'rainy': ["9", "10", "11", "12"], - 'snowy': ["22", "23", "24", "25", "26", "27"], - 'snowy-rainy': ["16", "17", "18"], - 'sunny': ["0", "1"], + 'cloudy': ['7', '8'], + 'fog': ['5', '6'], + 'hail': ['19', '20', '21'], + 'lightning': ['30'], + 'lightning-rainy': ['28', '29'], + 'partlycloudy': ['2', '3'], + 'pouring': ['13', '14', '15'], + 'rainy': ['9', '10', '11', '12'], + 'snowy': ['22', '23', '24', '25', '26', '27'], + 'snowy-rainy': ['16', '17', '18'], + 'sunny': ['0', '1'], 'windy': [], 'windy-variant': [], 'exceptional': [], @@ -67,7 +66,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the sensor platform.""" + """Set up the Metoffice sensor platform.""" import datapoint as dp datapoint = dp.connection(api_key=config.get(CONF_API_KEY)) @@ -167,8 +166,8 @@ class MetOfficeCurrentData(object): import datapoint as dp try: - forecast = self._datapoint.get_forecast_for_site(self._site.id, - "3hourly") + forecast = self._datapoint.get_forecast_for_site( + self._site.id, "3hourly") self.data = forecast.now() except (ValueError, dp.exceptions.APIException) as err: _LOGGER.error("Check Met Office %s", err.args) diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index af2c277f2cd..9d78ffd3f1a 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup mFi sensors.""" + """Set up mFi sensors.""" host = config.get(CONF_HOST) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -65,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): client = MFiClient(host, username, password, port=port, use_tls=use_tls, verify=verify_tls) except (FailedToLogin, requests.exceptions.ConnectionError) as ex: - _LOGGER.error('Unable to connect to mFi: %s', str(ex)) + _LOGGER.error("Unable to connect to mFi: %s", str(ex)) return False add_devices(MfiSensor(port, hass) diff --git a/homeassistant/components/sensor/mhz19.py b/homeassistant/components/sensor/mhz19.py index 816b7465f8f..cd559d3bbd2 100644 --- a/homeassistant/components/sensor/mhz19.py +++ b/homeassistant/components/sensor/mhz19.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available CO2 sensors.""" + """Set up the available CO2 sensors.""" from pmsensor import co2sensor try: @@ -118,7 +118,7 @@ class MHZ19Sensor(Entity): class MHZClient(object): - """Get the latest data from the DHT sensor.""" + """Get the latest data from the MH-Z sensor.""" def __init__(self, co2sensor, serial): """Initialize the sensor.""" diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py index 1a13faeee00..ac8646bb3c1 100644 --- a/homeassistant/components/sensor/miflora.py +++ b/homeassistant/components/sensor/miflora.py @@ -58,7 +58,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the MiFlora sensor.""" + """Set up the MiFlora sensor.""" from miflora import miflora_poller cache = config.get(CONF_CACHE) @@ -77,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): unit = SENSOR_TYPES[parameter][1] prefix = config.get(CONF_NAME) - if len(prefix) > 0: + if prefix: name = "{} {}".format(prefix, name) devs.append(MiFloraSensor( @@ -145,7 +145,7 @@ class MiFloraSensor(Entity): self.name) # Remove old data from median list or set sensor value to None # if no data is available anymore - if len(self.data) > 0: + if self.data: self.data = self.data[1:] else: self._state = None diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/sensor/min_max.py index 33ffc769991..59a89fa0e3e 100644 --- a/homeassistant/components/sensor/min_max.py +++ b/homeassistant/components/sensor/min_max.py @@ -123,7 +123,7 @@ class MinMaxSensor(Entity): @callback # pylint: disable=invalid-name def async_min_max_sensor_state_listener(entity, old_state, new_state): - """Called when the sensor changes state.""" + """Handle the sensor state changes.""" if new_state.state is None or new_state.state in STATE_UNKNOWN: self.states[entity] = STATE_UNKNOWN hass.async_add_job(self.async_update_ha_state, True) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index f1449e5df06..3ee59e5ae54 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -17,6 +17,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['modbus'] CONF_COUNT = 'count' diff --git a/homeassistant/components/sensor/modem_callerid.py b/homeassistant/components/sensor/modem_callerid.py index bb9a984c87b..e12ddb445ec 100644 --- a/homeassistant/components/sensor/modem_callerid.py +++ b/homeassistant/components/sensor/modem_callerid.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup modem caller id sensor platform.""" + """Set up modem caller ID sensor platform.""" from basicmodem.basicmodem import BasicModem as bm name = config.get(CONF_NAME) port = config.get(CONF_DEVICE) @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ModemCalleridSensor(Entity): - """Implementation of USB modem callerid sensor.""" + """Implementation of USB modem caller ID sensor.""" def __init__(self, hass, name, port, modem): """Initialize the sensor.""" @@ -100,7 +100,7 @@ class ModemCalleridSensor(Entity): return def _incomingcallcallback(self, newstate): - """Callback from modem, process based on new state.""" + """Handle new states.""" if newstate == self.modem.STATE_RING: if self.state == self.modem.STATE_IDLE: att = {"cid_time": self.modem.get_cidtime, diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index 102b4620410..b668b68005c 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup MoldIndicator sensor.""" + """Set up MoldIndicator sensor.""" name = config.get(CONF_NAME, DEFAULT_NAME) indoor_temp_sensor = config.get(CONF_INDOOR_TEMP) outdoor_temp_sensor = config.get(CONF_OUTDOOR_TEMP) @@ -113,9 +113,8 @@ class MoldIndicator(Entity): elif unit == TEMP_CELSIUS: return temp else: - _LOGGER.error("Temp sensor has unsupported unit: %s" - " (allowed: %s, %s)", - unit, TEMP_CELSIUS, TEMP_FAHRENHEIT) + _LOGGER.error("Temp sensor has unsupported unit: %s (allowed: %s, " + "%s)", unit, TEMP_CELSIUS, TEMP_FAHRENHEIT) return None @@ -151,7 +150,7 @@ class MoldIndicator(Entity): self._calc_moldindicator() def _sensor_changed(self, entity_id, old_state, new_state): - """Called when sensor values change.""" + """Handle sensor state changes.""" if new_state is None: return @@ -167,7 +166,7 @@ class MoldIndicator(Entity): def _calc_dewpoint(self): """Calculate the dewpoint for the indoor air.""" - # use magnus approximation to calculate the dew point + # Use magnus approximation to calculate the dew point alpha = MAGNUS_K2 * self._indoor_temp / (MAGNUS_K3 + self._indoor_temp) beta = MAGNUS_K2 * MAGNUS_K3 / (MAGNUS_K3 + self._indoor_temp) @@ -215,11 +214,11 @@ class MoldIndicator(Entity): else: self._state = '{0:d}'.format(int(crit_humidity)) - _LOGGER.debug('Mold indicator humidity: %s ', self._state) + _LOGGER.debug("Mold indicator humidity: %s", self._state) @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 9fcb9298805..63b015b3dfd 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -75,13 +75,13 @@ class MqttSensor(Entity): self._expiration_trigger = None def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method must be run in the event loop and returns a coroutine. """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" # auto-expire enabled? if self._expire_after is not None and self._expire_after > 0: # Reset old trigger @@ -94,9 +94,7 @@ class MqttSensor(Entity): dt_util.utcnow() + timedelta(seconds=self._expire_after)) self._expiration_trigger = async_track_point_in_utc_time( - self.hass, - self.value_is_expired, - expiration_at) + self.hass, self.value_is_expired, expiration_at) if self._template is not None: payload = self._template.async_render_with_possible_json_value( diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index 427daa1a8a2..3d0dbd68afa 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -58,7 +58,7 @@ MQTT_PAYLOAD = vol.Schema(vol.All(json.loads, vol.Schema({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup MQTT Sensor.""" + """Set up MQTT room Sensor.""" async_add_devices([MQTTRoomSensor( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), @@ -85,7 +85,7 @@ class MQTTRoomSensor(Entity): self._updated = None def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method must be run in the event loop and returns a coroutine. """ @@ -100,12 +100,12 @@ class MQTTRoomSensor(Entity): @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" try: data = MQTT_PAYLOAD(payload) except vol.MultipleInvalid as error: - _LOGGER.debug('skipping update because of malformatted ' - 'data: %s', error) + _LOGGER.debug( + "Skipping update because of malformatted data: %s", error) return device = _parse_update_data(topic, data) diff --git a/homeassistant/components/sensor/mvglive.py b/homeassistant/components/sensor/mvglive.py index c2f8c2be71f..3bb027c6e7e 100644 --- a/homeassistant/components/sensor/mvglive.py +++ b/homeassistant/components/sensor/mvglive.py @@ -29,6 +29,8 @@ CONF_LINES = 'lines' CONF_PRODUCTS = 'products' CONF_TIMEOFFSET = 'timeoffset' +DEFAULT_PRODUCT = ['U-Bahn', 'Tram', 'Bus', 'S-Bahn'] + ICONS = { 'U-Bahn': 'mdi:subway', 'Tram': 'mdi:tram', @@ -47,16 +49,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_DESTINATIONS, default=['']): cv.ensure_list_csv, vol.Optional(CONF_DIRECTIONS, default=['']): cv.ensure_list_csv, vol.Optional(CONF_LINES, default=['']): cv.ensure_list_csv, - vol.Optional(CONF_PRODUCTS, - default=['U-Bahn', 'Tram', - 'Bus', 'S-Bahn']): cv.ensure_list_csv, + vol.Optional(CONF_PRODUCTS, default=DEFAULT_PRODUCT): + cv.ensure_list_csv, vol.Optional(CONF_TIMEOFFSET, default=0): cv.positive_int, vol.Optional(CONF_NAME): cv.string}] }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Get the MVGLive sensor.""" + """Set up the MVGLive sensor.""" sensors = [] for nextdeparture in config.get(CONF_NEXT_DEPARTURE): sensors.append( @@ -147,14 +148,13 @@ class MVGLiveData(object): def update(self): """Update the connection data.""" try: - _departures = self.mvg.getlivedata(station=self._station, - ubahn=self._include_ubahn, - tram=self._include_tram, - bus=self._include_bus, - sbahn=self._include_sbahn) + _departures = self.mvg.getlivedata( + station=self._station, ubahn=self._include_ubahn, + tram=self._include_tram, bus=self._include_bus, + sbahn=self._include_sbahn) except ValueError: self.departures = {} - _LOGGER.warning("Returned data not understood.") + _LOGGER.warning("Returned data not understood") return for _departure in _departures: # find the first departure meeting the criteria diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 9f750e3c10b..d46680c7b66 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -14,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the MySensors platform for sensors.""" + """Set up the MySensors platform for sensors.""" # Only act if loaded via mysensors by discovery event. # Otherwise gateway is not setup. if discovery_info is None: diff --git a/homeassistant/components/sensor/neato.py b/homeassistant/components/sensor/neato.py index 7c33e481069..39d77e736c5 100644 --- a/homeassistant/components/sensor/neato.py +++ b/homeassistant/components/sensor/neato.py @@ -32,12 +32,12 @@ ATTR_CLEAN_SUSP_TIME = 'clean_suspension_time' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Neato sensor platform.""" + """Set up the Neato sensor platform.""" dev = [] for robot in hass.data[NEATO_ROBOTS]: for type_name in SENSOR_TYPES: dev.append(NeatoConnectedSensor(hass, robot, type_name)) - _LOGGER.debug('Adding sensors %s', dev) + _LOGGER.debug("Adding sensors %s", dev) add_devices(dev) @@ -56,7 +56,7 @@ class NeatoConnectedSensor(Entity): except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as ex: self._state = None - _LOGGER.warning('Neato connection error: %s', ex) + _LOGGER.warning("Neato connection error: %s", ex) self._mapdata = hass.data[NEATO_MAP_DATA] self.clean_time_start = None self.clean_time_stop = None @@ -78,7 +78,7 @@ class NeatoConnectedSensor(Entity): requests.exceptions.HTTPError) as ex: self._state = None self._status_state = 'Offline' - _LOGGER.warning('Neato connection error: %s', ex) + _LOGGER.warning("Neato connection error: %s", ex) return if not self._state: return diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index 6305f5265b0..e2567fdf4ca 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -44,7 +44,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Nest Sensor.""" + """Set up the Nest Sensor.""" if discovery_info is None: return diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 41fc4287f5f..a83b218ca8f 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -65,7 +65,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available Netatmo weather sensors.""" + """Set up the available Netatmo weather sensors.""" netatmo = get_component('netatmo') data = NetAtmoData(netatmo.NETATMO_AUTH, config.get(CONF_STATION, None)) @@ -109,9 +109,8 @@ class NetAtmoSensor(Entity): module_id = self.netatmo_data.\ station_data.moduleByName(module=module_name)['_id'] self.module_id = module_id[1] - self._unique_id = "Netatmo Sensor {0} - {1} ({2})".format(self._name, - module_id, - self.type) + self._unique_id = "Netatmo Sensor {0} - {1} ({2})".format( + self._name, module_id, self.type) self.update() @property @@ -307,7 +306,7 @@ class NetAtmoData(object): self.station_data = lnetatmo.WeatherStationData(self.auth) if self.station is not None: - self.data = self.station_data.lastData(station=self.station, - exclude=3600) + self.data = self.station_data.lastData( + station=self.station, exclude=3600) else: self.data = self.station_data.lastData(exclude=3600) diff --git a/homeassistant/components/sensor/netdata.py b/homeassistant/components/sensor/netdata.py index 5a3077350bc..e575cae8529 100644 --- a/homeassistant/components/sensor/netdata.py +++ b/homeassistant/components/sensor/netdata.py @@ -103,7 +103,7 @@ class NetdataSensor(Entity): @property def name(self): - """The name of the sensor.""" + """Return the name of the sensor.""" return self._name @property diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py index 628e739f36f..5e3bf55dc9d 100644 --- a/homeassistant/components/sensor/neurio_energy.py +++ b/homeassistant/components/sensor/neurio_energy.py @@ -43,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Neurio sensor.""" + """Set up the Neurio sensor.""" api_key = config.get(CONF_API_KEY) api_secret = config.get(CONF_API_SECRET) sensor_id = config.get(CONF_SENSOR_ID) @@ -90,7 +90,7 @@ class NeurioData(object): if not self.sensor_id: user_info = self.neurio_client.get_user_information() - _LOGGER.warning('Sensor ID auto-detected: %s', user_info[ + _LOGGER.warning("Sensor ID auto-detected: %s", user_info[ "locations"][0]["sensors"][0]["sensorId"]) self.sensor_id = user_info[ "locations"][0]["sensors"][0]["sensorId"] @@ -111,7 +111,7 @@ class NeurioData(object): sample = self.neurio_client.get_samples_live_last(self.sensor_id) self._active_power = sample['consumptionPower'] except (requests.exceptions.RequestException, ValueError, KeyError): - _LOGGER.warning('Could not update current power usage.') + _LOGGER.warning("Could not update current power usage") return None def get_daily_usage(self): @@ -127,7 +127,7 @@ class NeurioData(object): history = self.neurio_client.get_samples_stats( self.sensor_id, start_time, 'days', end_time) except (requests.exceptions.RequestException, ValueError, KeyError): - _LOGGER.warning('Could not update daily power usage.') + _LOGGER.warning("Could not update daily power usage") return None for result in history: diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/sensor/nut.py index 2608be8ded9..2ebc83724ba 100644 --- a/homeassistant/components/sensor/nut.py +++ b/homeassistant/components/sensor/nut.py @@ -135,7 +135,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Setup the NUT sensors.""" + """Set up the NUT sensors.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -146,7 +146,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = PyNUTData(host, port, alias, username, password) if data.status is None: - _LOGGER.error("NUT Sensor has no data, unable to setup.") + _LOGGER.error("NUT Sensor has no data, unable to setup") return False _LOGGER.debug('NUT Sensors Available: %s', data.status) @@ -160,14 +160,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities.append(NUTSensor(name, data, sensor_type)) else: _LOGGER.warning( - 'Sensor type: "%s" does not appear in the NUT status ' - 'output, cannot add.', sensor_type) + "Sensor type: %s does not appear in the NUT status " + "output, cannot add", sensor_type) try: data.update(no_throttle=True) except data.pynuterror as err: _LOGGER.error("Failure while testing NUT status retrieval. " - "Cannot continue setup., %s", err) + "Cannot continue setup: %s", err) return False add_entities(entities) @@ -180,7 +180,7 @@ class NUTSensor(Entity): """Initialize the sensor.""" self._data = data self.type = sensor_type - self._name = name + ' ' + SENSOR_TYPES[sensor_type][0] + self._name = "{} {}".format(name, SENSOR_TYPES[sensor_type][0]) self._unit = SENSOR_TYPES[sensor_type][1] self.update() @@ -281,8 +281,8 @@ class PyNUTData(object): try: return self._client.list_vars(self._alias) except (self.pynuterror, ConnectionResetError) as err: - _LOGGER.debug("Error getting NUT vars for host %s: %s", - self._host, err) + _LOGGER.debug( + "Error getting NUT vars for host %s: %s", self._host, err) return None @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/sensor/nzbget.py index f007ea034fc..7a95e445ae0 100644 --- a/homeassistant/components/sensor/nzbget.py +++ b/homeassistant/components/sensor/nzbget.py @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the NZBGet sensors.""" + """Set up the NZBGet sensors.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) name = config.get(CONF_NAME) @@ -105,7 +105,7 @@ class NZBGetSensor(Entity): @property def unit_of_measurement(self): - """Unit of measurement of this entity, if any.""" + """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/sensor/octoprint.py index d58f55e0ab7..b029451bd5e 100644 --- a/homeassistant/components/sensor/octoprint.py +++ b/homeassistant/components/sensor/octoprint.py @@ -16,7 +16,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['octoprint'] @@ -24,7 +23,6 @@ DEPENDENCIES = ['octoprint'] DEFAULT_NAME = 'OctoPrint' SENSOR_TYPES = { - # API Endpoint, Group, Key, unit 'Temperatures': ['printer', 'temperature', '*', TEMP_CELSIUS], 'Current State': ['printer', 'state', 'text', None], 'Job Percentage': ['job', 'progress', 'completion', '%'], @@ -39,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the available OctoPrint sensors.""" + """Set up the available OctoPrint sensors.""" octoprint = get_component('octoprint') name = config.get(CONF_NAME) monitored_conditions = config.get(CONF_MONITORED_CONDITIONS) @@ -50,23 +48,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if octo_type == "Temperatures": for tool in octoprint.OCTOPRINT.get_tools(): for temp_type in types: - new_sensor = OctoPrintSensor(octoprint.OCTOPRINT, - temp_type, - temp_type, - name, - SENSOR_TYPES[octo_type][3], - SENSOR_TYPES[octo_type][0], - SENSOR_TYPES[octo_type][1], - tool) + new_sensor = OctoPrintSensor( + octoprint.OCTOPRINT, temp_type, temp_type, name, + SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0], + SENSOR_TYPES[octo_type][1], tool) devices.append(new_sensor) else: - new_sensor = OctoPrintSensor(octoprint.OCTOPRINT, - octo_type, - SENSOR_TYPES[octo_type][2], - name, - SENSOR_TYPES[octo_type][3], - SENSOR_TYPES[octo_type][0], - SENSOR_TYPES[octo_type][1]) + new_sensor = OctoPrintSensor( + octoprint.OCTOPRINT, octo_type, SENSOR_TYPES[octo_type][2], + name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0], + SENSOR_TYPES[octo_type][1]) devices.append(new_sensor) add_devices(devices) @@ -113,16 +104,15 @@ class OctoPrintSensor(Entity): @property def unit_of_measurement(self): - """Unit of measurement of this entity, if any.""" + """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement def update(self): """Update state of sensor.""" try: - self._state = self.api.update(self.sensor_type, - self.api_endpoint, - self.api_group, - self.api_tool) + self._state = self.api.update( + self.sensor_type, self.api_endpoint, self.api_group, + self.api_tool) except requests.exceptions.ConnectionError: # Error calling the api, already logged in api.update() return diff --git a/homeassistant/components/sensor/ohmconnect.py b/homeassistant/components/sensor/ohmconnect.py index 929fa607a4e..9808e6ecef7 100644 --- a/homeassistant/components/sensor/ohmconnect.py +++ b/homeassistant/components/sensor/ohmconnect.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the OhmConnect sensor.""" + """Set up the OhmConnect sensor.""" name = config.get(CONF_NAME) ohmid = config.get(CONF_ID) @@ -52,7 +52,7 @@ class OhmconnectSensor(Entity): @property def name(self): - """The name of the sensor.""" + """Return the name of the sensor.""" return self._name @property diff --git a/homeassistant/components/sensor/onewire.py b/homeassistant/components/sensor/onewire.py index c9028fc28ab..a06573fe97f 100644 --- a/homeassistant/components/sensor/onewire.py +++ b/homeassistant/components/sensor/onewire.py @@ -14,8 +14,11 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS from homeassistant.components.sensor import PLATFORM_SCHEMA +_LOGGER = logging.getLogger(__name__) + CONF_MOUNT_DIR = 'mount_dir' CONF_NAMES = 'names' + DEFAULT_MOUNT_DIR = '/sys/bus/w1/devices/' DEVICE_FAMILIES = ('10', '22', '28', '3B', '42') @@ -24,12 +27,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_MOUNT_DIR, default=DEFAULT_MOUNT_DIR): cv.string, }) -_LOGGER = logging.getLogger(__name__) - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the one wire Sensors.""" + """Set up the one wire Sensors.""" base_dir = config.get(CONF_MOUNT_DIR) sensor_ids = [] device_files = [] @@ -51,9 +52,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): os.path.split(family_file_path)[0], 'temperature')) if device_files == []: - _LOGGER.error('No onewire sensor found. Check if ' - 'dtoverlay=w1-gpio is in your /boot/config.txt. ' - 'Check the mount_dir parameter if it\'s defined.') + _LOGGER.error("No onewire sensor found. Check if dtoverlay=w1-gpio " + "is in your /boot/config.txt. " + "Check the mount_dir parameter if it's defined") return devs = [] @@ -129,11 +130,11 @@ class OneWire(Entity): if len(temp_read) == 1: temp = round(float(temp_read[0]), 1) except ValueError: - _LOGGER.warning('Invalid temperature value read from ' + + _LOGGER.warning("Invalid temperature value read from %s", self._device_file) except FileNotFoundError: - _LOGGER.warning('Cannot read from sensor: ' + - self._device_file) + _LOGGER.warning( + "Cannot read from sensor: %s", self._device_file) if temp < -55 or temp > 125: return diff --git a/homeassistant/components/sensor/openevse.py b/homeassistant/components/sensor/openevse.py index d3ac52f58bd..8751dda83fd 100644 --- a/homeassistant/components/sensor/openevse.py +++ b/homeassistant/components/sensor/openevse.py @@ -4,7 +4,6 @@ Support for monitoring an OpenEVSE Charger. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.openevse/ """ - import logging from requests import RequestException diff --git a/homeassistant/components/sensor/opensky.py b/homeassistant/components/sensor/opensky.py index 17e5f1f351c..43c9177f960 100644 --- a/homeassistant/components/sensor/opensky.py +++ b/homeassistant/components/sensor/opensky.py @@ -20,7 +20,6 @@ from homeassistant.util import distance as util_distance from homeassistant.util import location as util_location import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=12) # opensky public limit is 10 seconds @@ -51,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Open Sky platform.""" + """Set up the Open Sky platform.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) add_devices([OpenSkySensor( @@ -132,10 +131,10 @@ class OpenSkySensor(Entity): @property def unit_of_measurement(self): - """Unit of measurement.""" + """Return the unit of measurement.""" return 'flights' @property def icon(self): - """Icon.""" + """Return the icon.""" return 'mdi:airplane' diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 4c556e61ae2..f3620efdb43 100755 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the OpenWeatherMap sensor.""" + """Set up the OpenWeatherMap sensor.""" if None in (hass.config.latitude, hass.config.longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py index 31335acc466..1d63aacff11 100644 --- a/homeassistant/components/sensor/pi_hole.py +++ b/homeassistant/components/sensor/pi_hole.py @@ -52,7 +52,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Pi-Hole sensor.""" + """Set up the Pi-Hole sensor.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) use_ssl = config.get(CONF_SSL) diff --git a/homeassistant/components/sensor/plex.py b/homeassistant/components/sensor/plex.py index 33da15ac836..eb6b5f49f6a 100644 --- a/homeassistant/components/sensor/plex.py +++ b/homeassistant/components/sensor/plex.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Plex sensor.""" + """Set up the Plex sensor.""" name = config.get(CONF_NAME) plex_user = config.get(CONF_USERNAME) plex_password = config.get(CONF_PASSWORD) diff --git a/homeassistant/components/sensor/pocketcasts.py b/homeassistant/components/sensor/pocketcasts.py index 5ccd082ef89..36e0bb88e0a 100644 --- a/homeassistant/components/sensor/pocketcasts.py +++ b/homeassistant/components/sensor/pocketcasts.py @@ -32,7 +32,7 @@ SCAN_INTERVAL = timedelta(minutes=5) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the pocketcasts platform for sensors.""" + """Set up the pocketcasts platform for sensors.""" import pocketcasts try: api = pocketcasts.Api( @@ -61,9 +61,9 @@ class PocketCastsSensor(Entity): """Update sensor values.""" try: self._state = len(self._api.new_episodes_released()) - _LOGGER.debug('Found %d new episodes', self._state) + _LOGGER.debug("Found %d new episodes", self._state) except OSError as err: - _LOGGER.warning('Failed to contact server: %s', err) + _LOGGER.warning("Failed to contact server: %s", err) @property def name(self): diff --git a/homeassistant/components/sensor/pushbullet.py b/homeassistant/components/sensor/pushbullet.py new file mode 100644 index 00000000000..b81ca1beb38 --- /dev/null +++ b/homeassistant/components/sensor/pushbullet.py @@ -0,0 +1,134 @@ +""" +Pushbullet platform for sensor component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.pushbullet/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import (CONF_API_KEY, CONF_MONITORED_CONDITIONS) +from homeassistant.components.sensor import PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['pushbullet.py==0.10.0'] + +_LOGGER = logging.getLogger(__name__) + +SENSOR_TYPES = { + 'application_name': ['Application name'], + 'body': ['Body'], + 'notification_id': ['Notification ID'], + 'notification_tag': ['Notification tag'], + 'package_name': ['Package name'], + 'receiver_email': ['Receiver email'], + 'sender_email': ['Sender email'], + 'source_device_iden': ['Sender device ID'], + 'title': ['Title'], + 'type': ['Type'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=['title', 'body']): + vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Pushbllet Sensor platform.""" + from pushbullet import PushBullet + from pushbullet import InvalidKeyError + try: + pushbullet = PushBullet(config.get(CONF_API_KEY)) + except InvalidKeyError: + _LOGGER.error("Wrong API key for Pushbullet supplied") + return False + + pbprovider = PushBulletNotificationProvider(pushbullet) + + devices = [] + for sensor_type in config[CONF_MONITORED_CONDITIONS]: + devices.append(PushBulletNotificationSensor(pbprovider, sensor_type)) + add_devices(devices) + + +class PushBulletNotificationSensor(Entity): + """Representation of a Pushbullet Sensor.""" + + def __init__(self, pb, element): + """Initialize the Pushbullet sensor.""" + self.pushbullet = pb + self._element = element + self._state = None + self._state_attributes = None + + def update(self): + """Fetch the latest data from the sensor. + + This will fetch the 'sensor reading' into self._state but also all + attributes into self._state_attributes. + """ + try: + self._state = self.pushbullet.data[self._element] + self._state_attributes = self.pushbullet.data + except (KeyError, TypeError): + pass + + @property + def name(self): + """Return the name of the sensor.""" + return '{} {}'.format('Pushbullet', self._element) + + @property + def state(self): + """Return the current state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return all known attributes of the sensor.""" + return self._state_attributes + + +class PushBulletNotificationProvider(): + """Provider for an account, leading to one or more sensors.""" + + def __init__(self, pb): + """Start to retrieve pushes from the given Pushbullet instance.""" + import threading + self.pushbullet = pb + self._data = None + self.listener = None + self.thread = threading.Thread(target=self.retrieve_pushes) + self.thread.daemon = True + self.thread.start() + + def on_push(self, data): + """Update the current data. + + Currently only monitors pushes but might be extended to monitor + different kinds of Pushbullet events. + """ + if data['type'] == 'push': + self._data = data['push'] + + @property + def data(self): + """Return the current data stored in the provider.""" + return self._data + + def retrieve_pushes(self): + """Retrieve_pushes. + + Spawn a new Listener and links it to self.on_push. + """ + from pushbullet import Listener + self.listener = Listener(account=self.pushbullet, on_push=self.on_push) + _LOGGER.debug("Getting pushes") + try: + self.listener.run_forever() + finally: + self.listener.close() diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/sensor/qnap.py index e57c59e41d6..c72d2d65c6d 100644 --- a/homeassistant/components/sensor/qnap.py +++ b/homeassistant/components/sensor/qnap.py @@ -4,7 +4,6 @@ Support for QNAP NAS Sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.qnap/ """ - import logging from datetime import timedelta @@ -106,7 +105,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the QNAP NAS sensor.""" + """Set up the QNAP NAS sensor.""" api = QNAPStatsAPI(config) api.update() @@ -125,14 +124,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Basic sensors for variable in config[CONF_MONITORED_CONDITIONS]: if variable in _SYSTEM_MON_COND: - sensors.append(QNAPSystemSensor(api, variable, - _SYSTEM_MON_COND[variable])) + sensors.append(QNAPSystemSensor( + api, variable, _SYSTEM_MON_COND[variable])) if variable in _CPU_MON_COND: - sensors.append(QNAPCPUSensor(api, variable, - _CPU_MON_COND[variable])) + sensors.append(QNAPCPUSensor( + api, variable, _CPU_MON_COND[variable])) if variable in _MEMORY_MON_COND: - sensors.append(QNAPMemorySensor(api, variable, - _MEMORY_MON_COND[variable])) + sensors.append(QNAPMemorySensor( + api, variable, _MEMORY_MON_COND[variable])) # Network sensors nics = config[CONF_NICS] @@ -210,7 +209,7 @@ class QNAPStatsAPI(object): self.data["volumes"] = self._api.get_volumes() self.data["bandwidth"] = self._api.get_bandwidth() except: - _LOGGER.exception("Failed to fetch QNAP stats from the NAS.") + _LOGGER.exception("Failed to fetch QNAP stats from the NAS") class QNAPSensor(Entity): @@ -228,19 +227,17 @@ class QNAPSensor(Entity): @property def name(self): """Return the name of the sensor, if any.""" - server_name = self._api.data["system_stats"]["system"]["name"] + server_name = self._api.data['system_stats']['system']['name'] if self.monitor_device is not None: - return "{} {} ({})".format(server_name, - self.var_name, - self.monitor_device) + return "{} {} ({})".format( + server_name, self.var_name, self.monitor_device) else: - return "{} {}".format(server_name, - self.var_name) + return "{} {}".format(server_name, self.var_name) @property def icon(self): - """Icon to use in the frontend, if any.""" + """Return the icon to use in the frontend, if any.""" return self.var_icon @property @@ -259,10 +256,10 @@ class QNAPCPUSensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - if self.var_id == "cpu_temp": - return self._api.data["system_stats"]["cpu"]["temp_c"] - elif self.var_id == "cpu_usage": - return self._api.data["system_stats"]["cpu"]["usage_percent"] + if self.var_id == 'cpu_temp': + return self._api.data['system_stats']['cpu']['temp_c'] + elif self.var_id == 'cpu_usage': + return self._api.data['system_stats']['cpu']['usage_percent'] class QNAPMemorySensor(QNAPSensor): @@ -271,27 +268,27 @@ class QNAPMemorySensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - free = float(self._api.data["system_stats"]["memory"]["free"]) / 1024 - if self.var_id == "memory_free": + free = float(self._api.data['system_stats']['memory']['free']) / 1024 + if self.var_id == 'memory_free': return round_nicely(free) - total = float(self._api.data["system_stats"]["memory"]["total"]) / 1024 + total = float(self._api.data['system_stats']['memory']['total']) / 1024 used = total - free - if self.var_id == "memory_used": + if self.var_id == 'memory_used': return round_nicely(used) - if self.var_id == "memory_percent_used": + if self.var_id == 'memory_percent_used': return round(used / total * 100) @property def device_state_attributes(self): """Return the state attributes.""" if self._api.data: - data = self._api.data["system_stats"]["memory"] - size = round_nicely(float(data["total"]) / 1024) + data = self._api.data['system_stats']['memory'] + size = round_nicely(float(data['total']) / 1024) return { - ATTR_MEMORY_SIZE: "{} GB".format(size), + ATTR_MEMORY_SIZE: '{} GB'.format(size), } @@ -301,30 +298,30 @@ class QNAPNetworkSensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - if self.var_id == "network_link_status": - nic = self._api.data["system_stats"]["nics"][self.monitor_device] - return nic["link_status"] + if self.var_id == 'network_link_status': + nic = self._api.data['system_stats']['nics'][self.monitor_device] + return nic['link_status'] - data = self._api.data["bandwidth"][self.monitor_device] - if self.var_id == "network_tx": - return round_nicely(data["tx"] / 1024 / 1024) + data = self._api.data['bandwidth'][self.monitor_device] + if self.var_id == 'network_tx': + return round_nicely(data['tx'] / 1024 / 1024) - if self.var_id == "network_rx": - return round_nicely(data["rx"] / 1024 / 1024) + if self.var_id == 'network_rx': + return round_nicely(data['rx'] / 1024 / 1024) @property def device_state_attributes(self): """Return the state attributes.""" if self._api.data: - data = self._api.data["system_stats"]["nics"][self.monitor_device] + data = self._api.data['system_stats']['nics'][self.monitor_device] return { - ATTR_IP: data["ip"], - ATTR_MASK: data["mask"], - ATTR_MAC: data["mac"], - ATTR_MAX_SPEED: data["max_speed"], - ATTR_PACKETS_TX: data["tx_packets"], - ATTR_PACKETS_RX: data["rx_packets"], - ATTR_PACKETS_ERR: data["err_packets"] + ATTR_IP: data['ip'], + ATTR_MASK: data['mask'], + ATTR_MAC: data['mac'], + ATTR_MAX_SPEED: data['max_speed'], + ATTR_PACKETS_TX: data['tx_packets'], + ATTR_PACKETS_RX: data['rx_packets'], + ATTR_PACKETS_ERR: data['err_packets'] } @@ -334,28 +331,27 @@ class QNAPSystemSensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - if self.var_id == "status": - return self._api.data["system_health"] + if self.var_id == 'status': + return self._api.data['system_health'] - if self.var_id == "system_temp": - return int(self._api.data["system_stats"]["system"]["temp_c"]) + if self.var_id == 'system_temp': + return int(self._api.data['system_stats']['system']['temp_c']) @property def device_state_attributes(self): """Return the state attributes.""" if self._api.data: - data = self._api.data["system_stats"] - days = int(data["uptime"]["days"]) - hours = int(data["uptime"]["hours"]) - minutes = int(data["uptime"]["minutes"]) + data = self._api.data['system_stats'] + days = int(data['uptime']['days']) + hours = int(data['uptime']['hours']) + minutes = int(data['uptime']['minutes']) return { - ATTR_NAME: data["system"]["name"], - ATTR_MODEL: data["system"]["model"], - ATTR_SERIAL: data["system"]["serial_number"], - ATTR_UPTIME: "{:0>2d}d {:0>2d}h {:0>2d}m".format(days, - hours, - minutes) + ATTR_NAME: data['system']['name'], + ATTR_MODEL: data['system']['model'], + ATTR_SERIAL: data['system']['serial_number'], + ATTR_UPTIME: '{:0>2d}d {:0>2d}h {:0>2d}m'.format( + days, hours, minutes) } @@ -365,18 +361,18 @@ class QNAPDriveSensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - data = self._api.data["smart_drive_health"][self.monitor_device] + data = self._api.data['smart_drive_health'][self.monitor_device] - if self.var_id == "drive_smart_status": - return data["health"] + if self.var_id == 'drive_smart_status': + return data['health'] - if self.var_id == "drive_temp": - return int(data["temp_c"]) + if self.var_id == 'drive_temp': + return int(data['temp_c']) @property def name(self): """Return the name of the sensor, if any.""" - server_name = self._api.data["system_stats"]["system"]["name"] + server_name = self._api.data['system_stats']['system']['name'] return "{} {} (Drive {})".format( server_name, @@ -388,12 +384,12 @@ class QNAPDriveSensor(QNAPSensor): def device_state_attributes(self): """Return the state attributes.""" if self._api.data: - data = self._api.data["smart_drive_health"][self.monitor_device] + data = self._api.data['smart_drive_health'][self.monitor_device] return { - ATTR_DRIVE: data["drive_number"], - ATTR_MODEL: data["model"], - ATTR_SERIAL: data["serial"], - ATTR_TYPE: data["type"], + ATTR_DRIVE: data['drive_number'], + ATTR_MODEL: data['model'], + ATTR_SERIAL: data['serial'], + ATTR_TYPE: data['type'], } @@ -403,27 +399,27 @@ class QNAPVolumeSensor(QNAPSensor): @property def state(self): """Return the state of the sensor.""" - data = self._api.data["volumes"][self.monitor_device] + data = self._api.data['volumes'][self.monitor_device] - free_gb = int(data["free_size"]) / 1024 / 1024 / 1024 - if self.var_id == "volume_size_free": + free_gb = int(data['free_size']) / 1024 / 1024 / 1024 + if self.var_id == 'volume_size_free': return round_nicely(free_gb) - total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 + total_gb = int(data['total_size']) / 1024 / 1024 / 1024 used_gb = total_gb - free_gb - if self.var_id == "volume_size_used": + if self.var_id == 'volume_size_used': return round_nicely(used_gb) - if self.var_id == "volume_percentage_used": + if self.var_id == 'volume_percentage_used': return round(used_gb / total_gb * 100) @property def device_state_attributes(self): """Return the state attributes.""" if self._api.data: - data = self._api.data["volumes"][self.monitor_device] - total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 + data = self._api.data['volumes'][self.monitor_device] + total_gb = int(data['total_size']) / 1024 / 1024 / 1024 return { ATTR_VOLUME_SIZE: "{} GB".format(round_nicely(total_gb)), diff --git a/homeassistant/components/sensor/random.py b/homeassistant/components/sensor/random.py index 0b0761d7076..b9083245218 100644 --- a/homeassistant/components/sensor/random.py +++ b/homeassistant/components/sensor/random.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Random number sensor.""" + """Set up the Random number sensor.""" name = config.get(CONF_NAME) minimum = config.get(CONF_MINIMUM) maximum = config.get(CONF_MAXIMUM) @@ -66,7 +66,7 @@ class RandomSensor(Entity): @property def icon(self): - """Icon to use in the frontend, if any.""" + """Return the icon to use in the frontend, if any.""" return ICON @property diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py index 16b50f8b901..cbb649892f5 100644 --- a/homeassistant/components/sensor/rest.py +++ b/homeassistant/components/sensor/rest.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the RESTful sensor.""" + """Set up the RESTful sensor.""" name = config.get(CONF_NAME) resource = config.get(CONF_RESOURCE) method = config.get(CONF_METHOD) @@ -67,7 +67,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): rest.update() if rest.data is None: - _LOGGER.error('Unable to fetch REST data') + _LOGGER.error("Unable to fetch REST data") return False add_devices([RestSensor(hass, rest, name, unit, value_template)]) diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 5085dc942cc..e01dbc83422 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.rfxtrx/ """ import logging + import voluptuous as vol import homeassistant.components.rfxtrx as rfxtrx @@ -13,8 +14,8 @@ from homeassistant.const import CONF_PLATFORM from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.components.rfxtrx import ( - ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_FIREEVENT, - CONF_DEVICES, ATTR_DATA_TYPE, DATA_TYPES, ATTR_ENTITY_ID) + ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_FIREEVENT, CONF_DEVICES, DATA_TYPES, + ATTR_DATA_TYPE, ATTR_ENTITY_ID) DEPENDENCIES = ['rfxtrx'] @@ -28,7 +29,7 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the RFXtrx platform.""" + """Set up the RFXtrx platform.""" from RFXtrx import SensorEvent sensors = [] for packet_id, entity_info in config[CONF_DEVICES].items(): @@ -40,7 +41,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): sub_sensors = {} data_types = entity_info[ATTR_DATA_TYPE] - if len(data_types) == 0: + if not data_types: data_types = [''] for data_type in DATA_TYPES: if data_type in event.values: @@ -55,7 +56,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): add_devices_callback(sensors) def sensor_update(event): - """Callback for sensor updates from the RFXtrx gateway.""" + """Handle sensor updates from the RFXtrx gateway.""" if not isinstance(event, SensorEvent): return @@ -81,8 +82,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return pkt_id = "".join("{0:02x}".format(x) for x in event.data) - _LOGGER.info("Automatic add rfxtrx.sensor: %s", - pkt_id) + _LOGGER.info("Automatic add rfxtrx.sensor: %s", pkt_id) data_type = '' for _data_type in DATA_TYPES: diff --git a/homeassistant/components/sensor/ring.py b/homeassistant/components/sensor/ring.py index dfe791c4c41..bfe8b2ec1cd 100644 --- a/homeassistant/components/sensor/ring.py +++ b/homeassistant/components/sensor/ring.py @@ -49,15 +49,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for sensor_type in config.get(CONF_MONITORED_CONDITIONS): for device in ring.chimes: if 'chime' in SENSOR_TYPES[sensor_type][1]: - sensors.append(RingSensor(hass, - device, - sensor_type)) + sensors.append(RingSensor(hass, device, sensor_type)) for device in ring.doorbells: if 'doorbell' in SENSOR_TYPES[sensor_type][1]: - sensors.append(RingSensor(hass, - device, - sensor_type)) + sensors.append(RingSensor(hass, device, sensor_type)) add_devices(sensors, True) return True @@ -74,8 +70,8 @@ class RingSensor(Entity): self._extra = None self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[3]) self._kind = SENSOR_TYPES.get(self._sensor_type)[4] - self._name = "{0} {1}".format(self._data.name, - SENSOR_TYPES.get(self._sensor_type)[0]) + self._name = "{0} {1}".format( + self._data.name, SENSOR_TYPES.get(self._sensor_type)[0]) self._state = STATE_UNKNOWN self._tz = str(hass.config.time_zone) @@ -121,7 +117,7 @@ class RingSensor(Entity): def update(self): """Get the latest data and updates the state.""" - _LOGGER.debug("Pulling data from %s sensor.", self._name) + _LOGGER.debug("Pulling data from %s sensor", self._name) self._data.update() @@ -137,5 +133,5 @@ class RingSensor(Entity): if history: self._extra = history[0] created_at = self._extra['created_at'] - self._state = '{0:0>2}:{1:0>2}'.format(created_at.hour, - created_at.minute) + self._state = '{0:0>2}:{1:0>2}'.format( + created_at.hour, created_at.minute) diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index d27f7945e55..9caa0b08374 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -51,7 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SABnzbd sensors.""" + """Set up the SABnzbd sensors.""" from pysabnzbd import SabnzbdApi, SabnzbdApiException host = config.get(CONF_HOST) diff --git a/homeassistant/components/sensor/sensehat.py b/homeassistant/components/sensor/sensehat.py index f421fe79ff9..7a759351b35 100644 --- a/homeassistant/components/sensor/sensehat.py +++ b/homeassistant/components/sensor/sensehat.py @@ -59,7 +59,7 @@ def get_average(temp_base): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Sense HAT sensor platform.""" + """Set up the Sense HAT sensor platform.""" data = SenseHatData(config.get(CONF_IS_HAT_ATTACHED)) dev = [] for variable in config[CONF_DISPLAY_OPTIONS]: diff --git a/homeassistant/components/sensor/skybeacon.py b/homeassistant/components/sensor/skybeacon.py index dd6a117d447..0e8e2c6d2e9 100644 --- a/homeassistant/components/sensor/skybeacon.py +++ b/homeassistant/components/sensor/skybeacon.py @@ -16,7 +16,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, CONF_MAC, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['pygatt==3.0.0'] +REQUIREMENTS = ['pygatt==3.1.1'] _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,7 @@ CONNECT_TIMEOUT = 30 # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the sensor.""" + """Set up the Skybeacon sensor.""" name = config.get(CONF_NAME) mac = config.get(CONF_MAC) _LOGGER.debug("Setting up...") @@ -57,7 +57,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SkybeaconHumid(Entity): - """Representation of a humidity sensor.""" + """Representation of a Skybeacon humidity sensor.""" def __init__(self, name, mon): """Initialize a sensor.""" @@ -89,7 +89,7 @@ class SkybeaconHumid(Entity): class SkybeaconTemp(Entity): - """Representation of a temperature sensor.""" + """Representation of a Skybeacon temperature sensor.""" def __init__(self, name, mon): """Initialize a sensor.""" @@ -146,9 +146,9 @@ class Monitor(threading.Thread): while True: try: _LOGGER.info("Connecting to %s", self.name) - # we need concurrent connect, so lets not reset the device + # We need concurrent connect, so lets not reset the device adapter.start(reset_on_start=False) - # seems only one connection can be initiated at a time + # Seems only one connection can be initiated at a time with CONNECT_LOCK: device = adapter.connect(self.mac, CONNECT_TIMEOUT, @@ -157,7 +157,7 @@ class Monitor(threading.Thread): # HACK: inject handle mapping collected offline # pylint: disable=protected-access device._characteristics[UUID(BLE_TEMP_UUID)] = cached_char - # magic: writing this makes device happy + # Magic: writing this makes device happy device.char_write_handle(0x1b, bytearray([255]), False) device.subscribe(BLE_TEMP_UUID, self._update) _LOGGER.info("Subscribed to %s", self.name) diff --git a/homeassistant/components/sensor/sleepiq.py b/homeassistant/components/sensor/sleepiq.py index ff6fb945e83..cf35a400e47 100644 --- a/homeassistant/components/sensor/sleepiq.py +++ b/homeassistant/components/sensor/sleepiq.py @@ -11,7 +11,7 @@ ICON = 'mdi:hotel' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SleepIQ sensors.""" + """Set up the SleepIQ sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index 27ab20ab67d..2f3a29efbc0 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -1,4 +1,5 @@ -"""SMA Solar Webconnect interface. +""" +SMA Solar Webconnect interface. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sma/ @@ -65,14 +66,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up SMA WebConnect sensor.""" import pysma - # sensor_defs from the library + # Sensor_defs from the library sensor_defs = dict(zip(SENSOR_OPTIONS, [ (pysma.KEY_CURRENT_CONSUMPTION_W, 'W', 1), (pysma.KEY_CURRENT_POWER_W, 'W', 1), (pysma.KEY_TOTAL_CONSUMPTION_KWH, 'kWh', 1000), (pysma.KEY_TOTAL_YIELD_KWH, 'kWh', 1000)])) - # sensor_defs from the custom config + # Sensor_defs from the custom config for name, prop in config[CONF_CUSTOM].items(): if name in sensor_defs: _LOGGER.warning("Custom sensor %s replace built-in sensor", name) diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py index 5035e2464b3..bf3af95d515 100644 --- a/homeassistant/components/sensor/speedtest.py +++ b/homeassistant/components/sensor/speedtest.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_change from homeassistant.helpers.restore_state import async_get_last_state -REQUIREMENTS = ['speedtest-cli==1.0.4'] +REQUIREMENTS = ['speedtest-cli==1.0.6'] _LOGGER = logging.getLogger(__name__) _SPEEDTEST_REGEX = re.compile(r'Ping:\s(\d+\.\d+)\sms[\r\n]+' @@ -125,7 +125,7 @@ class SpeedtestSensor(Entity): @asyncio.coroutine def async_added_to_hass(self): - """Called when entity is about to be added to hass.""" + """Handle all entity which are about to be added.""" state = yield from async_get_last_state(self.hass, self.entity_id) if not state: return @@ -140,17 +140,16 @@ class SpeedtestData(object): self.data = None self._server_id = config.get(CONF_SERVER_ID) if not config.get(CONF_MANUAL): - track_time_change(hass, self.update, - second=config.get(CONF_SECOND), - minute=config.get(CONF_MINUTE), - hour=config.get(CONF_HOUR), - day=config.get(CONF_DAY)) + track_time_change( + hass, self.update, second=config.get(CONF_SECOND), + minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR), + day=config.get(CONF_DAY)) def update(self, now): """Get the latest data from speedtest.net.""" import speedtest - _LOGGER.info('Executing speedtest...') + _LOGGER.info("Executing speedtest...") try: args = [sys.executable, speedtest.__file__, '--simple'] if self._server_id: @@ -161,6 +160,8 @@ class SpeedtestData(object): except CalledProcessError as process_error: _LOGGER.error("Error executing speedtest: %s", process_error) return - self.data = {'ping': round(float(re_output[1]), 2), - 'download': round(float(re_output[2]), 2), - 'upload': round(float(re_output[3]), 2)} + self.data = { + 'ping': round(float(re_output[1]), 2), + 'download': round(float(re_output[2]), 2), + 'upload': round(float(re_output[3]), 2), + } diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py index 342724830e3..67850957388 100644 --- a/homeassistant/components/sensor/statistics.py +++ b/homeassistant/components/sensor/statistics.py @@ -80,7 +80,7 @@ class StatisticsSensor(Entity): @callback # pylint: disable=invalid-name def async_stats_sensor_state_listener(entity, old_state, new_state): - """Called when the sensor changes state.""" + """Handle the sensor state changes.""" self._unit_of_measurement = new_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/sensor/steam_online.py index 679b798a24b..9fd5ab7dd7d 100644 --- a/homeassistant/components/sensor/steam_online.py +++ b/homeassistant/components/sensor/steam_online.py @@ -37,7 +37,7 @@ STATE_PLAY = 'Play' # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Steam platform.""" + """Set up the Steam platform.""" import steam as steamod steamod.api.key.set(config.get(CONF_API_KEY)) add_devices( diff --git a/homeassistant/components/sensor/supervisord.py b/homeassistant/components/sensor/supervisord.py index fae7032ea58..b2d8c32d9a0 100644 --- a/homeassistant/components/sensor/supervisord.py +++ b/homeassistant/components/sensor/supervisord.py @@ -25,12 +25,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Supervisord platform.""" + """Set up the Supervisord platform.""" url = config.get(CONF_URL) try: supervisor_server = xmlrpc.client.ServerProxy(url) except ConnectionRefusedError: - _LOGGER.error('Could not connect to Supervisord') + _LOGGER.error("Could not connect to Supervisord") return False processes = supervisor_server.supervisor.getAllProcessInfo() diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/sensor/swiss_hydrological_data.py index c25d400efcd..0df63956f2e 100644 --- a/homeassistant/components/sensor/swiss_hydrological_data.py +++ b/homeassistant/components/sensor/swiss_hydrological_data.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['xmltodict==0.10.2'] +REQUIREMENTS = ['xmltodict==0.11.0'] _LOGGER = logging.getLogger(__name__) _RESOURCE = 'http://www.hydrodata.ch/xml/SMS.xml' @@ -41,17 +41,16 @@ ATTR_DISCHARGE_MAX = 'discharge_max' ATTR_WATERLEVEL_MAX = 'level_max' ATTR_TEMPERATURE_MAX = 'temperature_max' +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_STATION): vol.Coerce(int), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -# Return cached results if last scan was less then this time ago. -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) - def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Swiss hydrological sensor.""" + """Set up the Swiss hydrological sensor.""" import xmltodict name = config.get(CONF_NAME) @@ -61,10 +60,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): response = requests.get(_RESOURCE, timeout=5) if any(str(station) == location.get('@StrNr') for location in xmltodict.parse(response.text)['AKT_Data']['MesPar']) is False: - _LOGGER.error('The given station does not exist: %s', station) + _LOGGER.error("The given station does not exist: %s", station) return False except requests.exceptions.ConnectionError: - _LOGGER.error('The URL is not accessible') + _LOGGER.error("The URL is not accessible") return False data = HydrologicalData(station) @@ -164,7 +163,7 @@ class HydrologicalData(object): try: response = requests.get(_RESOURCE, timeout=5) except requests.exceptions.ConnectionError: - _LOGGER.error('Unable to retrieve data from %s', _RESOURCE) + _LOGGER.error("Unable to retrieve data from %s", _RESOURCE) try: stations = xmltodict.parse(response.text)['AKT_Data']['MesPar'] diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index a730e5d16cf..3ca8c4cb8e1 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Get the Swiss public transport sensor.""" + """Set up the Swiss public transport sensor.""" name = config.get(CONF_NAME) # journal contains [0] Station ID start, [1] Station ID destination # [2] Station name start, and [3] Station name destination @@ -52,8 +52,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: for location in [config.get(CONF_START), config.get(CONF_DESTINATION)]: # transport.opendata.ch doesn't play nice with requests.Session - result = requests.get(_RESOURCE + 'locations?query=%s' % location, - timeout=10) + result = requests.get( + '{}locations?query={}'.format(_RESOURCE, location), timeout=10) journey.append(result.json()['stations'][0]['name']) except KeyError: _LOGGER.exception( diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/sensor/synologydsm.py index 8ee0d6eae18..b2bcea02c00 100644 --- a/homeassistant/components/sensor/synologydsm.py +++ b/homeassistant/components/sensor/synologydsm.py @@ -4,7 +4,6 @@ Support for Synology NAS Sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.synologydsm/ """ - import logging from datetime import timedelta @@ -86,7 +85,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the Synology NAS Sensor.""" + """Set up the Synology NAS Sensor.""" # pylint: disable=too-many-locals def run_setup(event): """Wait until HASS is fully initialized before creating. @@ -139,7 +138,7 @@ class SynoApi(): # pylint: disable=too-many-arguments, bare-except def __init__(self, host, port, username, password, temp_unit): - """Constructor of the API wrapper class.""" + """Initialize the API wrapper class.""" from SynologyDSM import SynologyDSM self.temp_unit = temp_unit diff --git a/homeassistant/components/sensor/tado.py b/homeassistant/components/sensor/tado.py index 3e5d0101ade..b654a4444c4 100644 --- a/homeassistant/components/sensor/tado.py +++ b/homeassistant/components/sensor/tado.py @@ -1,20 +1,29 @@ -"""tado component to create some sensors for each zone.""" +""" +Tado component to create some sensors for each zone. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.tado/ +""" import logging from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.components.tado import ( - DATA_TADO) +from homeassistant.components.tado import (DATA_TADO) _LOGGER = logging.getLogger(__name__) + +ATTR_DATA_ID = 'data_id' +ATTR_DEVICE = 'device' +ATTR_ID = 'id' +ATTR_NAME = 'name' +ATTR_ZONE = 'zone' + SENSOR_TYPES = ['temperature', 'humidity', 'power', 'link', 'heating', 'tado mode', 'overlay'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the sensor platform.""" - # get the PyTado object from the hub component + """Set up the sensor platform.""" tado = hass.data[DATA_TADO] try: @@ -36,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): tado, me_data, me_data['homes'][0]['name'], me_data['homes'][0]['id'], "tado bridge status")) - if len(sensor_items) > 0: + if sensor_items: add_devices(sensor_items, True) return True else: @@ -48,10 +57,10 @@ def create_zone_sensor(tado, zone, name, zone_id, variable): data_id = 'zone {} {}'.format(name, zone_id) tado.add_sensor(data_id, { - "zone": zone, - "name": name, - "id": zone_id, - "data_id": data_id + ATTR_ZONE: zone, + ATTR_NAME: name, + ATTR_ID: zone_id, + ATTR_DATA_ID: data_id }) return TadoSensor(tado, name, zone_id, variable, data_id) @@ -62,10 +71,10 @@ def create_device_sensor(tado, device, name, device_id, variable): data_id = 'device {} {}'.format(name, device_id) tado.add_sensor(data_id, { - "device": device, - "name": name, - "id": device_id, - "data_id": data_id + ATTR_DEVICE: device, + ATTR_NAME: name, + ATTR_ID: device_id, + ATTR_DATA_ID: data_id }) return TadoSensor(tado, name, device_id, variable, data_id) @@ -75,7 +84,7 @@ class TadoSensor(Entity): """Representation of a tado Sensor.""" def __init__(self, store, zone_name, zone_id, zone_variable, data_id): - """Initialization of TadoSensor class.""" + """Initialize of the Tado Sensor.""" self._store = store self.zone_name = zone_name @@ -133,8 +142,7 @@ class TadoSensor(Entity): data = self._store.get_data(self._data_id) if data is None: - _LOGGER.debug('Recieved no data for zone %s', - self.zone_name) + _LOGGER.debug("Recieved no data for zone %s", self.zone_name) return unit = TEMP_CELSIUS diff --git a/homeassistant/components/sensor/ted5000.py b/homeassistant/components/sensor/ted5000.py index e8a3dcf302b..08681fd37f2 100644 --- a/homeassistant/components/sensor/ted5000.py +++ b/homeassistant/components/sensor/ted5000.py @@ -16,7 +16,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['xmltodict==0.10.2'] +REQUIREMENTS = ['xmltodict==0.11.0'] _LOGGER = logging.getLogger(__name__) @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Ted5000 sensor.""" + """Set up the Ted5000 sensor.""" host = config.get(CONF_HOST) port = config.get(CONF_PORT) name = config.get(CONF_NAME) @@ -68,7 +68,7 @@ class Ted5000Sensor(Entity): @property def name(self): - """The name of the sensor.""" + """Return the name of the sensor.""" return self._name @property diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index 9bebbe6e3dc..68d0bb6535f 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -3,7 +3,6 @@ Support for Tellstick Net/Telstick Live. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.tellduslive/ - """ import logging @@ -36,7 +35,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick sensors.""" + """Set up the Tellstick sensors.""" if discovery_info is None: return add_devices(TelldusLiveSensor(hass, sensor) for sensor in discovery_info) diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index d16bb28f4f4..c9f922207e5 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Tellstick sensors.""" + """Set up the Tellstick sensors.""" import tellcore.telldus as telldus import tellcore.constants as tellcore_constants diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index 4ccd1be7d76..973e07d9cf3 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['temperusb==1.5.1'] +REQUIREMENTS = ['temperusb==1.5.3'] CONF_SCALE = 'scale' CONF_OFFSET = 'offset' @@ -35,7 +35,7 @@ def get_temper_devices(): # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Temper sensors.""" + """Set up the Temper sensors.""" temp_unit = hass.config.units.temperature_unit name = config.get(CONF_NAME) scaling = { @@ -107,6 +107,6 @@ class TemperSensor(Entity): sensor_value = self.temper_device.get_temperature(format_str) self.current_value = round(sensor_value, 1) except IOError: - _LOGGER.error('Failed to get temperature. The device address may' - 'have changed - attempting to reset device') + _LOGGER.error("Failed to get temperature. The device address may" + "have changed. Attempting to reset device") reset_devices() diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 51a7bc82a85..8cf3083d7ed 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -40,7 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the template sensors.""" + """Set up the template sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -100,7 +100,7 @@ class SensorTemplate(Entity): @callback def template_sensor_state_listener(entity, old_state, new_state): - """Called when the target device changes state.""" + """Handle device state changes.""" self.hass.async_add_job(self.async_update_ha_state(True)) @callback diff --git a/homeassistant/components/sensor/thinkingcleaner.py b/homeassistant/components/sensor/thinkingcleaner.py index 35462466f2a..83cf799e3cd 100644 --- a/homeassistant/components/sensor/thinkingcleaner.py +++ b/homeassistant/components/sensor/thinkingcleaner.py @@ -12,9 +12,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/TheRealLink/pythinkingcleaner' - '/archive/v0.0.2.zip' - '#pythinkingcleaner==0.0.2'] +REQUIREMENTS = ['pythinkingcleaner==0.0.3'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py index 9182145dc95..97e6bfd4b3a 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/sensor/time_date.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Time and Date sensor.""" + """Set up the Time and Date sensor.""" if hass.config.time_zone is None: _LOGGER.error("Timezone is not set in Home Assistant configuration") return False diff --git a/homeassistant/components/sensor/torque.py b/homeassistant/components/sensor/torque.py index 219275aee52..acc7958ea7f 100644 --- a/homeassistant/components/sensor/torque.py +++ b/homeassistant/components/sensor/torque.py @@ -48,7 +48,7 @@ def convert_pid(value): # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Torque platform.""" + """Set up the Torque platform.""" vehicle = config.get(CONF_NAME) email = config.get(CONF_EMAIL) sensors = {} diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index 3c9aad05626..add9cb1aca6 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Transmission sensors.""" + """Set up the Transmission sensors.""" import transmissionrpc from transmissionrpc.error import TransmissionError diff --git a/homeassistant/components/sensor/twitch.py b/homeassistant/components/sensor/twitch.py index 249d18ce6cb..6d075049b74 100644 --- a/homeassistant/components/sensor/twitch.py +++ b/homeassistant/components/sensor/twitch.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Twitch platform.""" + """Set up the Twitch platform.""" channels = config.get(CONF_CHANNELS, []) add_devices([TwitchSensor(channel) for channel in channels]) diff --git a/homeassistant/components/sensor/ups.py b/homeassistant/components/sensor/ups.py index 415ff1f8745..905cdab566e 100644 --- a/homeassistant/components/sensor/ups.py +++ b/homeassistant/components/sensor/ups.py @@ -40,15 +40,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the UPS platform.""" + """Set up the UPS platform.""" import upsmychoice try: cookie = hass.config.path(COOKIE) - session = upsmychoice.get_session(config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - cookie_path=cookie) + session = upsmychoice.get_session( + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + cookie_path=cookie) except upsmychoice.UPSError: - _LOGGER.exception('Could not connect to UPS My Choice') + _LOGGER.exception("Could not connect to UPS My Choice") return False add_devices([UPSSensor(session, config.get(CONF_NAME), diff --git a/homeassistant/components/sensor/usps.py b/homeassistant/components/sensor/usps.py index c0fc3d9cfe4..ec1b2f5575e 100644 --- a/homeassistant/components/sensor/usps.py +++ b/homeassistant/components/sensor/usps.py @@ -39,15 +39,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the USPS platform.""" + """Set up the USPS platform.""" import myusps try: cookie = hass.config.path(COOKIE) - session = myusps.get_session(config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - cookie_path=cookie) + session = myusps.get_session( + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + cookie_path=cookie) except myusps.USPSError: - _LOGGER.exception('Could not connect to My USPS') + _LOGGER.exception("Could not connect to My USPS") return False add_devices([USPSSensor(session, config.get(CONF_NAME), diff --git a/homeassistant/components/sensor/vasttrafik.py b/homeassistant/components/sensor/vasttrafik.py index 9f62c8620de..c78d352e626 100644 --- a/homeassistant/components/sensor/vasttrafik.py +++ b/homeassistant/components/sensor/vasttrafik.py @@ -42,11 +42,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the departure sensor.""" + """Set up the departure sensor.""" import vasttrafik planner = vasttrafik.JournyPlanner( - config.get(CONF_KEY), - config.get(CONF_SECRET)) + config.get(CONF_KEY), config.get(CONF_SECRET)) sensors = [] for departure in config.get(CONF_DEPARTURES): sensors.append( @@ -104,7 +103,7 @@ class VasttrafikDepartureSensor(Entity): """Return the next departure time.""" if not self._departureboard: _LOGGER.warning( - 'No departures from "%s" heading "%s"', + "No departures from %s heading %s", self._departure['name'], self._heading['name'] if self._heading else 'ANY') return @@ -121,5 +120,5 @@ class VasttrafikDepartureSensor(Entity): direction=self._heading['id'] if self._heading else None, date=datetime.now()+self._delay) except self._vasttrafik.Error: - _LOGGER.warning('Unable to read departure board, updating token') + _LOGGER.warning("Unable to read departure board, updating token") self._planner.update_token() diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index e0fa16d7907..5cb528219a5 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.vera/ """ import logging +from datetime import timedelta from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT) @@ -18,9 +19,11 @@ DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=5) + def setup_platform(hass, config, add_devices, discovery_info=None): - """Perform the setup for Vera controller devices.""" + """Set up the Vera controller devices.""" add_devices( VeraSensor(device, VERA_CONTROLLER) for device in VERA_DEVICES['sensor']) @@ -33,6 +36,7 @@ class VeraSensor(VeraDevice, Entity): """Initialize the sensor.""" self.current_value = None self._temperature_units = None + self.last_changed_time = None VeraDevice.__init__(self, vera_device, controller) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @@ -70,6 +74,14 @@ class VeraSensor(VeraDevice, Entity): self.current_value = self.vera_device.light elif self.vera_device.category == "Humidity Sensor": self.current_value = self.vera_device.humidity + elif self.vera_device.category == "Scene Controller": + value = self.vera_device.get_last_scene_id(True) + time = self.vera_device.get_last_scene_time(True) + if time == self.last_changed_time: + self.current_value = None + else: + self.current_value = value + self.last_changed_time = time elif self.vera_device.category == "Power meter": power = convert(self.vera_device.power, float, 0) self.current_value = int(round(power, 0)) diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 932da40bc9f..4b22512fd4d 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Verisure platform.""" + """Set up the Verisure platform.""" sensors = [] if int(hub.config.get(CONF_THERMOMETERS, 1)): diff --git a/homeassistant/components/sensor/volvooncall.py b/homeassistant/components/sensor/volvooncall.py index 9554d8c5b02..703315c478c 100644 --- a/homeassistant/components/sensor/volvooncall.py +++ b/homeassistant/components/sensor/volvooncall.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Volvo sensors.""" + """Set up the Volvo sensors.""" if discovery_info is None: return add_devices([VolvoSensor(hass, *discovery_info)]) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 93e747cd16f..d50f6b0897c 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -112,11 +112,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the WUnderground sensor.""" - rest = WUndergroundData(hass, - config.get(CONF_API_KEY), - config.get(CONF_PWS_ID), - config.get(CONF_LANG)) + """Set up the WUnderground sensor.""" + rest = WUndergroundData( + hass, config.get(CONF_API_KEY), config.get(CONF_PWS_ID), + config.get(CONF_LANG)) sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(WUndergroundSensor(rest, variable)) diff --git a/homeassistant/components/sensor/xbox_live.py b/homeassistant/components/sensor/xbox_live.py index 010812b58de..3b59f06be31 100644 --- a/homeassistant/components/sensor/xbox_live.py +++ b/homeassistant/components/sensor/xbox_live.py @@ -8,9 +8,9 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_API_KEY, STATE_UNKNOWN) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity REQUIREMENTS = ['xboxapi==0.1.1'] @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if new_device.success_init: devices.append(new_device) - if len(devices) > 0: + if devices: add_devices(devices) else: return False diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 4306ad68553..16951f21c5d 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -25,8 +25,7 @@ from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_utc_time_change) from homeassistant.util import dt as dt_util - -REQUIREMENTS = ['xmltodict==0.10.2'] +REQUIREMENTS = ['xmltodict==0.11.0'] _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Yr.no sensor.""" + """Set up the Yr.no sensor.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0) @@ -82,9 +81,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): weather = YrData(hass, coordinates, dev) # Update weather on the hour, spread seconds - async_track_utc_time_change(hass, weather.async_update, - minute=randrange(1, 10), - second=randrange(0, 59)) + async_track_utc_time_change( + hass, weather.async_update, minute=randrange(1, 10), + second=randrange(0, 59)) yield from weather.async_update() @@ -166,8 +165,8 @@ class YrData(object): try: websession = async_get_clientsession(self.hass) with async_timeout.timeout(10, loop=self.hass.loop): - resp = yield from websession.get(self._url, - params=self._urlparams) + resp = yield from websession.get( + self._url, params=self._urlparams) if resp.status != 200: try_again('{} returned {}'.format(resp.url, resp.status)) return diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index b45da4121bb..3144e812870 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -52,7 +52,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Yahoo! weather sensor.""" + """Setnup the Yahoo! weather sensor.""" from yahooweather import get_woeid, UNIT_C, UNIT_F unit = hass.config.units.temperature_unit @@ -60,15 +60,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): forecast = config.get(CONF_FORECAST) name = config.get(CONF_NAME) - # convert unit yunit = UNIT_C if unit == TEMP_CELSIUS else UNIT_F - # for print HA style temp SENSOR_TYPES["temperature"][1] = unit SENSOR_TYPES["temp_min"][1] = unit SENSOR_TYPES["temp_max"][1] = unit - # if not exists a customer woeid / calc from HA + # If not exists a customer woeid / calc from HA if woeid is None: woeid = get_woeid(hass.config.latitude, hass.config.longitude) # receive a error? @@ -76,15 +74,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.critical("Can't retrieve WOEID from yahoo!") return False - # create api object yahoo_api = YahooWeatherData(woeid, yunit) - # if update is false, it will never work... if not yahoo_api.update(): _LOGGER.critical("Can't retrieve weather data from yahoo!") return False - # check if forecast support by API if forecast >= len(yahoo_api.yahoo.Forecast): _LOGGER.error("Yahoo! only support %d days forcast!", len(yahoo_api.yahoo.Forecast)) @@ -152,30 +147,30 @@ class YahooWeatherSensor(Entity): return # default code for weather image - self._code = self._data.yahoo.Now["code"] + self._code = self._data.yahoo.Now['code'] # read data - if self._type == "weather_current": - self._state = self._data.yahoo.Now["text"] - elif self._type == "weather": - self._code = self._data.yahoo.Forecast[self._forecast]["code"] - self._state = self._data.yahoo.Forecast[self._forecast]["text"] - elif self._type == "temperature": - self._state = self._data.yahoo.Now["temp"] - elif self._type == "temp_min": - self._code = self._data.yahoo.Forecast[self._forecast]["code"] - self._state = self._data.yahoo.Forecast[self._forecast]["low"] - elif self._type == "temp_max": - self._code = self._data.yahoo.Forecast[self._forecast]["code"] - self._state = self._data.yahoo.Forecast[self._forecast]["high"] - elif self._type == "wind_speed": - self._state = self._data.yahoo.Wind["speed"] - elif self._type == "humidity": - self._state = self._data.yahoo.Atmosphere["humidity"] - elif self._type == "pressure": - self._state = self._data.yahoo.Atmosphere["pressure"] - elif self._type == "visibility": - self._state = self._data.yahoo.Atmosphere["visibility"] + if self._type == 'weather_current': + self._state = self._data.yahoo.Now['text'] + elif self._type == 'weather': + self._code = self._data.yahoo.Forecast[self._forecast]['code'] + self._state = self._data.yahoo.Forecast[self._forecast]['text'] + elif self._type == 'temperature': + self._state = self._data.yahoo.Now['temp'] + elif self._type == 'temp_min': + self._code = self._data.yahoo.Forecast[self._forecast]['code'] + self._state = self._data.yahoo.Forecast[self._forecast]['low'] + elif self._type == 'temp_max': + self._code = self._data.yahoo.Forecast[self._forecast]['code'] + self._state = self._data.yahoo.Forecast[self._forecast]['high'] + elif self._type == 'wind_speed': + self._state = self._data.yahoo.Wind['speed'] + elif self._type == 'humidity': + self._state = self._data.yahoo.Atmosphere['humidity'] + elif self._type == 'pressure': + self._state = self._data.yahoo.Atmosphere['pressure'] + elif self._type == 'visibility': + self._state = self._data.yahoo.Atmosphere['visibility'] class YahooWeatherData(object): diff --git a/homeassistant/components/sensor/zabbix.py b/homeassistant/components/sensor/zabbix.py index 6c3d0a3d653..a47d466c07e 100644 --- a/homeassistant/components/sensor/zabbix.py +++ b/homeassistant/components/sensor/zabbix.py @@ -5,28 +5,28 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.zabbix/ """ import logging + import voluptuous as vol -from homeassistant.helpers.entity import Entity import homeassistant.components.zabbix as zabbix -from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv - +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zabbix'] -_CONF_TRIGGERS = "triggers" -_CONF_HOSTIDS = "hostids" -_CONF_INDIVIDUAL = "individual" -_CONF_NAME = "name" +_CONF_TRIGGERS = 'triggers' +_CONF_HOSTIDS = 'hostids' +_CONF_INDIVIDUAL = 'individual' _ZABBIX_ID_LIST_SCHEMA = vol.Schema([int]) _ZABBIX_TRIGGER_SCHEMA = vol.Schema({ vol.Optional(_CONF_HOSTIDS, default=[]): _ZABBIX_ID_LIST_SCHEMA, vol.Optional(_CONF_INDIVIDUAL, default=False): cv.boolean(True), - vol.Optional(_CONF_NAME, default=None): cv.string, + vol.Optional(CONF_NAME, default=None): cv.string, }) # SCAN_INTERVAL = 30 @@ -42,18 +42,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): zapi = hass.data[zabbix.DOMAIN] if not zapi: - _LOGGER.error("zapi is None. Zabbix component hasn't been loaded?") + _LOGGER.error("zapi is None. Zabbix component hasn't been loaded?") return False - _LOGGER.info("Connected to Zabbix API Version %s", - zapi.api_version()) + _LOGGER.info("Connected to Zabbix API Version %s", zapi.api_version()) trigger_conf = config.get(_CONF_TRIGGERS) - # The following code seems overly complex. Need to think about this... + # The following code seems overly complex. Need to think about this... if trigger_conf: hostids = trigger_conf.get(_CONF_HOSTIDS) individual = trigger_conf.get(_CONF_INDIVIDUAL) - name = trigger_conf.get(_CONF_NAME) + name = trigger_conf.get(CONF_NAME) if individual: # Individual sensor per host @@ -63,10 +62,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False for hostid in hostids: - _LOGGER.debug("Creating Zabbix Sensor: " + str(hostid)) - sensor = ZabbixSingleHostTriggerCountSensor(zapi, - [hostid], - name) + _LOGGER.debug("Creating Zabbix Sensor: %s", str(hostid)) + sensor = ZabbixSingleHostTriggerCountSensor( + zapi, [hostid], name) sensors.append(sensor) else: if not hostids: @@ -75,10 +73,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensor = ZabbixTriggerCountSensor(zapi, name) else: # Single sensor that sums total issues for all hosts - _LOGGER.debug("Creating Zabbix Sensor group: " + str(hostids)) - sensor = ZabbixMultipleHostTriggerCountSensor(zapi, - hostids, - name) + _LOGGER.debug("Creating Zabbix Sensor group: %s", str(hostids)) + sensor = ZabbixMultipleHostTriggerCountSensor( + zapi, hostids, name) sensors.append(sensor) else: # Single sensor that provides the total count of triggers. @@ -93,7 +90,7 @@ class ZabbixTriggerCountSensor(Entity): """Get the active trigger count for all Zabbix monitored hosts.""" def __init__(self, zApi, name="Zabbix"): - """Initiate Zabbix sensor.""" + """Initialize Zabbix sensor.""" self._name = name self._zapi = zApi self._state = None @@ -115,14 +112,12 @@ class ZabbixTriggerCountSensor(Entity): return 'issues' def _call_zabbix_api(self): - return self._zapi.trigger.get(output="extend", - only_true=1, - monitored=1, - filter={"value": 1}) + return self._zapi.trigger.get( + output="extend", only_true=1, monitored=1, filter={"value": 1}) def update(self): """Update the sensor.""" - _LOGGER.debug("Updating ZabbixTriggerCountSensor: " + str(self._name)) + _LOGGER.debug("Updating ZabbixTriggerCountSensor: %s", str(self._name)) triggers = self._call_zabbix_api() self._state = len(triggers) @@ -136,39 +131,35 @@ class ZabbixSingleHostTriggerCountSensor(ZabbixTriggerCountSensor): """Get the active trigger count for a single Zabbix monitored host.""" def __init__(self, zApi, hostid, name=None): - """Initiate Zabbix sensor.""" + """Initialize Zabbix sensor.""" super().__init__(zApi, name) self._hostid = hostid if not name: - self._name = self._zapi.host.get(hostids=self._hostid, - output="extend")[0]["name"] + self._name = self._zapi.host.get( + hostids=self._hostid, output="extend")[0]["name"] self._attributes["Host ID"] = self._hostid def _call_zabbix_api(self): - return self._zapi.trigger.get(hostids=self._hostid, - output="extend", - only_true=1, - monitored=1, - filter={"value": 1}) + return self._zapi.trigger.get( + hostids=self._hostid, output="extend", only_true=1, monitored=1, + filter={"value": 1}) class ZabbixMultipleHostTriggerCountSensor(ZabbixTriggerCountSensor): """Get the active trigger count for specified Zabbix monitored hosts.""" def __init__(self, zApi, hostids, name=None): - """Initiate Zabbix sensor.""" + """Initialize Zabbix sensor.""" super().__init__(zApi, name) self._hostids = hostids if not name: - host_names = self._zapi.host.get(hostids=self._hostids, - output="extend") + host_names = self._zapi.host.get( + hostids=self._hostids, output="extend") self._name = " ".join(name["name"] for name in host_names) self._attributes["Host IDs"] = self._hostids def _call_zabbix_api(self): - return self._zapi.trigger.get(hostids=self._hostids, - output="extend", - only_true=1, - monitored=1, - filter={"value": 1}) + return self._zapi.trigger.get( + hostids=self._hostids, output="extend", only_true=1, + monitored=1, filter={"value": 1}) diff --git a/homeassistant/components/sensor/zamg.py b/homeassistant/components/sensor/zamg.py index 3d5f6146a39..1391572ce52 100644 --- a/homeassistant/components/sensor/zamg.py +++ b/homeassistant/components/sensor/zamg.py @@ -157,6 +157,7 @@ class ZamgData(object): response = requests.get( cls.API_URL, headers=cls.API_HEADERS, timeout=15) response.raise_for_status() + response.encoding = 'UTF8' return csv.DictReader(response.text.splitlines(), delimiter=';', quotechar='"') except Exception: # pylint:disable=broad-except @@ -185,7 +186,7 @@ class ZamgData(object): .format(self._station_id)) def get_data(self, variable): - """Generic accessor for data.""" + """Get the data.""" return self.data.get(variable) @@ -231,7 +232,7 @@ def closest_station(lat, lon, cache_dir): stations = zamg_stations(cache_dir) def comparable_dist(zamg_id): - """A fast key function for psudeo-distance from lat/lon.""" + """Calculater psudeo-distance from lat/lon.""" station_lat, station_lon = stations[zamg_id] return (lat - station_lat) ** 2 + (lon - station_lon) ** 2 diff --git a/homeassistant/components/sensor/zha.py b/homeassistant/components/sensor/zha.py new file mode 100644 index 00000000000..78627674c55 --- /dev/null +++ b/homeassistant/components/sensor/zha.py @@ -0,0 +1,96 @@ +""" +Sensors on Zigbee Home Automation networks. + +For more details on this platform, please refer to the documentation +at https://home-assistant.io/components/sensor.zha/ +""" +import asyncio +import logging + +from homeassistant.components.sensor import DOMAIN +from homeassistant.components import zha +from homeassistant.const import TEMP_CELSIUS +from homeassistant.util.temperature import convert as convert_temperature + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zha'] + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up Zigbee Home Automation sensors.""" + discovery_info = zha.get_discovery_info(hass, discovery_info) + if discovery_info is None: + return + + sensor = yield from make_sensor(discovery_info) + async_add_devices([sensor]) + + +@asyncio.coroutine +def make_sensor(discovery_info): + """Create ZHA sensors factory.""" + from bellows.zigbee import zcl + if isinstance(discovery_info['clusters'][0], + zcl.clusters.measurement.TemperatureMeasurement): + sensor = TemperatureSensor(**discovery_info) + else: + sensor = Sensor(**discovery_info) + + clusters = discovery_info['clusters'] + attr = sensor.value_attribute + if discovery_info['new_join']: + cluster = clusters[0] + yield from cluster.bind() + yield from cluster.configure_reporting( + attr, 300, 600, sensor.min_reportable_change, + ) + + return sensor + + +class Sensor(zha.Entity): + """Base ZHA sensor.""" + + _domain = DOMAIN + value_attribute = 0 + min_reportable_change = 1 + + def __init__(self, **kwargs): + """Initialize ZHA sensor.""" + super().__init__(**kwargs) + + @property + def state(self) -> str: + """Return the state of the entity.""" + if isinstance(self._state, float): + return str(round(self._state, 2)) + return self._state + + def attribute_updated(self, attribute, value): + """Handle attribute update from device.""" + _LOGGER.debug("Attribute updated: %s %s %s", self, attribute, value) + if attribute == self.value_attribute: + self._state = value + self.schedule_update_ha_state() + + +class TemperatureSensor(Sensor): + """ZHA temperature sensor.""" + + min_reportable_change = 50 # 0.5'C + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entityy.""" + return self.hass.config.units.temperature_unit + + @property + def state(self): + """Return the state of the entity.""" + if self._state == 'unknown': + return 'unknown' + celsius = round(float(self._state) / 100, 1) + return convert_temperature( + celsius, TEMP_CELSIUS, self.unit_of_measurement) diff --git a/homeassistant/components/sensor/zigbee.py b/homeassistant/components/sensor/zigbee.py index f3e8d5480a8..a1d549cb382 100644 --- a/homeassistant/components/sensor/zigbee.py +++ b/homeassistant/components/sensor/zigbee.py @@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ZigBee platform. + """Set up the ZigBee platform. Uses the 'type' config value to work out which type of ZigBee sensor we're dealing with and instantiates the relevant classes to handle it. @@ -67,7 +67,7 @@ class ZigBeeTemperatureSensor(Entity): @property def unit_of_measurement(self): - """Unit the value is expressed in.""" + """Return the unit of measurement the value is expressed in.""" return TEMP_CELSIUS def update(self, *args): diff --git a/homeassistant/components/sensor/zoneminder.py b/homeassistant/components/sensor/zoneminder.py index 388c12641c5..b31b942f486 100644 --- a/homeassistant/components/sensor/zoneminder.py +++ b/homeassistant/components/sensor/zoneminder.py @@ -51,7 +51,7 @@ class ZMSensorMonitors(Entity): """Get the status of each ZoneMinder monitor.""" def __init__(self, monitor_id, monitor_name): - """Initiate monitor sensor.""" + """Initialize monitor sensor.""" self._monitor_id = monitor_id self._monitor_name = monitor_name self._state = None @@ -81,7 +81,7 @@ class ZMSensorEvents(Entity): """Get the number of events for each monitor.""" def __init__(self, monitor_id, monitor_name, include_archived): - """Initiate event sensor.""" + """Initialize event sensor.""" self._monitor_id = monitor_id self._monitor_name = monitor_name self._include_archived = include_archived diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 92ea3966b0f..e9ea0e7512d 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) def get_device(node, values, **kwargs): - """Create zwave entity device.""" + """Create Z-Wave entity device.""" # Generic Device mappings if node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL): return ZWaveMultilevelSensor(values) @@ -38,7 +38,7 @@ class ZWaveSensor(zwave.ZWaveDeviceEntity): self.update_properties() def update_properties(self): - """Callback on data changes for node values.""" + """Handle the data changes for node values.""" self._state = self.values.primary.data self._units = self.values.primary.units diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 6cff6d5f4f4..0807eb617ee 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -388,3 +388,17 @@ hassio: addon: description: Name of addon. example: 'smb_config' + +eight_sleep: + heat_set: + description: Set heating level for eight sleep. + fields: + entity_id: + description: Entity id of the bed state to adjust. + example: 'sensor.eight_left_bed_state' + target: + description: Target heating level from 0-100. + example: 35 + duration: + description: Duration to heat at the target level in seconds. + example: 3600 diff --git a/homeassistant/components/shell_command.py b/homeassistant/components/shell_command.py index e4807251932..6aabdc8ddf7 100644 --- a/homeassistant/components/shell_command.py +++ b/homeassistant/components/shell_command.py @@ -26,7 +26,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the shell_command component.""" + """Set up the shell_command component.""" conf = config.get(DOMAIN, {}) cache = {} @@ -51,16 +51,16 @@ def setup(hass, config): try: rendered_args = args_compiled.render(call.data) except TemplateError as ex: - _LOGGER.exception('Error rendering command template: %s', ex) + _LOGGER.exception("Error rendering command template: %s", ex) return else: rendered_args = None if rendered_args == args: - # no template used. default behavior + # No template used. default behavior shell = True else: - # template used. Break into list and use shell=False for security + # Template used. Break into list and use shell=False for security cmd = [prog] + shlex.split(rendered_args) shell = False @@ -69,7 +69,7 @@ def setup(hass, config): stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.SubprocessError: - _LOGGER.exception('Error running command: %s', cmd) + _LOGGER.exception("Error running command: %s", cmd) for name in conf.keys(): hass.services.register(DOMAIN, name, service_handler) diff --git a/homeassistant/components/sleepiq.py b/homeassistant/components/sleepiq.py index 610f4e79bb2..d9d81d3fee0 100644 --- a/homeassistant/components/sleepiq.py +++ b/homeassistant/components/sleepiq.py @@ -4,7 +4,6 @@ Support for SleepIQ from SleepNumber. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sleepiq/ """ - import logging from datetime import timedelta @@ -47,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup SleepIQ. + """Set up the SleepIQ component. Will automatically load sensor components to support devices discovered on the account. @@ -76,7 +75,7 @@ def setup(hass, config): class SleepIQData(object): - """Gets the latest data from SleepIQ.""" + """Get the latest data from SleepIQ.""" def __init__(self, client): """Initialize the data object.""" @@ -112,9 +111,8 @@ class SleepIQSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return 'SleepNumber {} {} {}'.format(self.bed.name, - self.side.sleeper.first_name, - self._name) + return 'SleepNumber {} {} {}'.format( + self.bed.name, self.side.sleeper.first_name, self._name) def update(self): """Get the latest data from SleepIQ and updates the states.""" diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk.py index 2ae2842bceb..e9a2ee13ae2 100644 --- a/homeassistant/components/splunk.py +++ b/homeassistant/components/splunk.py @@ -34,7 +34,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Splunk component.""" + """Set up the Splunk component.""" conf = config[DOMAIN] host = conf.get(CONF_HOST) port = conf.get(CONF_PORT) @@ -73,12 +73,14 @@ def setup(hass, config): ] try: - payload = {"host": event_collector, - "event": json_body} + payload = { + "host": event_collector, + "event": json_body, + } requests.post(event_collector, data=json.dumps(payload), - headers=headers) + headers=headers, timeout=10) except requests.exceptions.RequestException as error: - _LOGGER.exception('Error saving event to Splunk: %s', error) + _LOGGER.exception("Error saving event to Splunk: %s", error) hass.bus.listen(EVENT_STATE_CHANGED, splunk_event_listener) diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd.py index d85bc1e030c..452faf8933b 100644 --- a/homeassistant/components/statsd.py +++ b/homeassistant/components/statsd.py @@ -39,7 +39,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the StatsD component.""" + """Set up the StatsD component.""" import statsd conf = config[DOMAIN] diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index f5fb0115a43..b2af30d8438 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -356,7 +356,7 @@ class Sun(Entity): self.location.longitude) def point_in_time_listener(self, now): - """Called when the state of the sun has changed.""" + """Run when the state of the sun has changed.""" self.update_as_of(now) self.schedule_update_ha_state() diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index e792f384e1c..786319f4200 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -31,12 +31,12 @@ ENTITY_ID_ALL_SWITCHES = group.ENTITY_ID_FORMAT.format('all_switches') ENTITY_ID_FORMAT = DOMAIN + '.{}' ATTR_TODAY_ENERGY_KWH = "today_energy_kwh" -ATTR_CURRRENT_POWER_W = "current_power_w" +ATTR_CURRENT_POWER_W = "current_power_w" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) PROP_TO_ATTR = { - 'current_power_w': ATTR_CURRRENT_POWER_W, + 'current_power_w': ATTR_CURRENT_POWER_W, 'today_energy_kwh': ATTR_TODAY_ENERGY_KWH, } diff --git a/homeassistant/components/switch/anel_pwrctrl.py b/homeassistant/components/switch/anel_pwrctrl.py index ff3eaf387ab..bfa6e2af976 100644 --- a/homeassistant/components/switch/anel_pwrctrl.py +++ b/homeassistant/components/switch/anel_pwrctrl.py @@ -21,8 +21,8 @@ REQUIREMENTS = ['https://github.com/mweinelt/anel-pwrctrl/archive/' _LOGGER = logging.getLogger(__name__) -CONF_PORT_RECV = "port_recv" -CONF_PORT_SEND = "port_send" +CONF_PORT_RECV = 'port_recv' +CONF_PORT_SEND = 'port_send' MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup PwrCtrl devices/switches.""" + """Set up PwrCtrl devices/switches.""" host = config.get(CONF_HOST, None) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -76,7 +76,7 @@ class PwrCtrlSwitch(SwitchDevice): @property def should_poll(self): - """Polling is needed.""" + """Return the polling state.""" return True @property diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index e97745cc0c6..91bcf6c4aa5 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -10,33 +10,33 @@ import asyncio import binascii import logging import socket + import voluptuous as vol import homeassistant.loader as loader from homeassistant.util.dt import utcnow from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_FRIENDLY_NAME, CONF_SWITCHES, - CONF_COMMAND_OFF, CONF_COMMAND_ON, - CONF_TIMEOUT, CONF_HOST, CONF_MAC, - CONF_TYPE) +from homeassistant.const import ( + CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_COMMAND_OFF, CONF_COMMAND_ON, + CONF_TIMEOUT, CONF_HOST, CONF_MAC, CONF_TYPE) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['broadlink==0.3'] _LOGGER = logging.getLogger(__name__) -DOMAIN = "broadlink" +DOMAIN = 'broadlink' DEFAULT_NAME = 'Broadlink switch' DEFAULT_TIMEOUT = 10 DEFAULT_RETRY = 3 -SERVICE_LEARN = "learn_command" -SERVICE_SEND = "send_packet" +SERVICE_LEARN = 'learn_command' +SERVICE_SEND = 'send_packet' -RM_TYPES = ["rm", "rm2", "rm_mini", "rm_pro_phicomm", "rm2_home_plus", - "rm2_home_plus_gdt", "rm2_pro_plus", "rm2_pro_plus2", - "rm2_pro_plus_bl", "rm_mini_shate"] -SP1_TYPES = ["sp1"] -SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"] +RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus', + 'rm2_home_plus_gdt', 'rm2_pro_plus', 'rm2_pro_plus2', + 'rm2_pro_plus_bl', 'rm_mini_shate'] +SP1_TYPES = ['sp1'] +SP2_TYPES = ['sp2', 'honeywell_sp2', 'sp3', 'spmini2', 'spminiplus'] SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES @@ -58,7 +58,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Broadlink switches.""" + """Set up Broadlink switches.""" import broadlink devices = config.get(CONF_SWITCHES, {}) ip_addr = config.get(CONF_HOST) @@ -75,33 +75,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): auth = yield from hass.loop.run_in_executor(None, broadlink_device.auth) except socket.timeout: - _LOGGER.error("Failed to connect to device, timeout.") + _LOGGER.error("Failed to connect to device, timeout") return if not auth: - _LOGGER.error("Failed to connect to device.") + _LOGGER.error("Failed to connect to device") return - yield from hass.loop.run_in_executor(None, - broadlink_device.enter_learning) + yield from hass.loop.run_in_executor( + None, broadlink_device.enter_learning) _LOGGER.info("Press the key you want HASS to learn") start_time = utcnow() while (utcnow() - start_time) < timedelta(seconds=20): - packet = yield from hass.loop.run_in_executor(None, - broadlink_device. - check_data) + packet = yield from hass.loop.run_in_executor( + None, broadlink_device.check_data) if packet: - log_msg = 'Recieved packet is: {}'.\ + log_msg = "Recieved packet is: {}".\ format(b64encode(packet).decode('utf8')) _LOGGER.info(log_msg) - persistent_notification.async_create(hass, log_msg, - title='Broadlink switch') + persistent_notification.async_create( + hass, log_msg, title='Broadlink switch') return yield from asyncio.sleep(1, loop=hass.loop) - _LOGGER.error('Did not received any signal.') - persistent_notification.async_create(hass, - "Did not received any signal", - title='Broadlink switch') + _LOGGER.error("Did not received any signal") + persistent_notification.async_create( + hass, "Did not received any signal", title='Broadlink switch') @asyncio.coroutine def _send_packet(call): @@ -119,7 +117,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): None, broadlink_device.auth) except socket.timeout: if retry == DEFAULT_RETRY-1: - _LOGGER.error("Failed to send packet to device.") + _LOGGER.error("Failed to send packet to device") if switch_type in RM_TYPES: broadlink_device = broadlink.rm((ip_addr, 80), mac_addr) @@ -148,7 +146,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: broadlink_device.auth() except socket.timeout: - _LOGGER.error("Failed to connect to device.") + _LOGGER.error("Failed to connect to device") add_devices(switches) @@ -176,7 +174,7 @@ class BroadlinkRMSwitch(SwitchDevice): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property @@ -199,7 +197,7 @@ class BroadlinkRMSwitch(SwitchDevice): def _sendpacket(self, packet, retry=2): """Send packet to device.""" if packet is None: - _LOGGER.debug("Empty packet.") + _LOGGER.debug("Empty packet") return True try: self._device.send_data(packet) @@ -259,7 +257,7 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return True def update(self): diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 54277d3ab39..83b8ae796bb 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -10,7 +10,7 @@ from homeassistant.const import DEVICE_DEFAULT_NAME # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the demo switches.""" + """Set up the demo switches.""" add_devices_callback([ DemoSwitch('Decorative Lights', True, None, True), DemoSwitch('AC', False, 'mdi:air-conditioner', False) diff --git a/homeassistant/components/switch/digital_ocean.py b/homeassistant/components/switch/digital_ocean.py index 066a356ba7b..c873439dd58 100644 --- a/homeassistant/components/switch/digital_ocean.py +++ b/homeassistant/components/switch/digital_ocean.py @@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Digital Ocean droplet switch.""" + """Set up the Digital Ocean droplet switch.""" digital_ocean = get_component('digital_ocean') droplets = config.get(CONF_DROPLETS) diff --git a/homeassistant/components/switch/digitalloggers.py b/homeassistant/components/switch/digitalloggers.py index a9d30e52ee6..dabd8ea1d2d 100755 --- a/homeassistant/components/switch/digitalloggers.py +++ b/homeassistant/components/switch/digitalloggers.py @@ -1,15 +1,8 @@ """ Support for Digital Loggers DIN III Relays. -Support for Digital Loggers DIN III Relays and possibly other items -through Dwight Hubbard's, python-dlipower. - -For more details about python-dlipower, please see -https://github.com/dwighthubbard/python-dlipower - -Custom ports are NOT supported due to a limitation of the dlipower -library, not the digital loggers switch - +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.digitalloggers/ """ import logging from datetime import timedelta @@ -22,9 +15,11 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle - REQUIREMENTS = ['dlipower==0.7.165'] +_LOGGER = logging.getLogger(__name__) + + CONF_CYCLETIME = 'cycletime' DEFAULT_NAME = 'DINRelay' @@ -35,8 +30,6 @@ DEFAULT_CYCLETIME = 2 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -_LOGGER = logging.getLogger(__name__) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -46,7 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.All(vol.Coerce(int), vol.Range(min=1, max=600)), vol.Optional(CONF_CYCLETIME, default=DEFAULT_CYCLETIME): vol.All(vol.Coerce(int), vol.Range(min=1, max=600)), - }) @@ -103,7 +95,7 @@ class DINRelay(SwitchDevice): @property def should_poll(self): - """Polling is needed.""" + """Return the polling state.""" return True def turn_on(self, **kwargs): diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/switch/dlink.py index 11aff81a0d5..d5036f9cb06 100644 --- a/homeassistant/components/switch/dlink.py +++ b/homeassistant/components/switch/dlink.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup a D-Link Smart Plug.""" + """Set up a D-Link Smart Plug.""" from pyW215.pyW215 import SmartPlug host = config.get(CONF_HOST) diff --git a/homeassistant/components/switch/enocean.py b/homeassistant/components/switch/enocean.py index ead5d789bbd..abe197485d4 100644 --- a/homeassistant/components/switch/enocean.py +++ b/homeassistant/components/switch/enocean.py @@ -26,7 +26,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the EnOcean switch platform.""" + """Set up the EnOcean switch platform.""" dev_id = config.get(CONF_ID) devname = config.get(CONF_NAME) diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index 354e3b409db..2052ffc4c15 100644 --- a/homeassistant/components/switch/flux.py +++ b/homeassistant/components/switch/flux.py @@ -17,7 +17,7 @@ from homeassistant.const import CONF_NAME, CONF_PLATFORM from homeassistant.helpers.event import track_time_change from homeassistant.util.color import ( color_temperature_to_rgb, color_RGB_to_xy, - color_temperature_kelvin_to_mired, HASS_COLOR_MIN, HASS_COLOR_MAX) + color_temperature_kelvin_to_mired) from homeassistant.util.dt import now as dt_now import homeassistant.helpers.config_validation as cv @@ -81,7 +81,7 @@ def set_lights_temp(hass, lights, mired, brightness): # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Flux switches.""" + """Set up the Flux switches.""" name = config.get(CONF_NAME) lights = config.get(CONF_LIGHTS) start_time = config.get(CONF_START_TIME) @@ -113,7 +113,6 @@ class FluxSwitch(SwitchDevice): """Initialize the Flux switch.""" self._name = name self.hass = hass - self._state = state self._lights = lights self._start_time = start_time self._stop_time = stop_time @@ -133,15 +132,19 @@ class FluxSwitch(SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return self._state + return self.unsub_tracker is not None def turn_on(self, **kwargs): """Turn on flux.""" - if not self._state: # make initial update - self.flux_update() - self._state = True - self.unsub_tracker = track_time_change(self.hass, self.flux_update, - second=[0, 30]) + if self.is_on: + return + + # Make initial update + self.flux_update() + + self.unsub_tracker = track_time_change( + self.hass, self.flux_update, second=[0, 30]) + self.schedule_update_ha_state() def turn_off(self, **kwargs): @@ -150,20 +153,18 @@ class FluxSwitch(SwitchDevice): self.unsub_tracker() self.unsub_tracker = None - self._state = False self.schedule_update_ha_state() def flux_update(self, now=None): """Update all the lights using flux.""" if now is None: now = dt_now() - sunset = next_setting(self.hass, SUN).replace(day=now.day, - month=now.month, - year=now.year) + sunset = next_setting(self.hass, SUN).replace( + day=now.day, month=now.month, year=now.year) start_time = self.find_start_time(now) - stop_time = now.replace(hour=self._stop_time.hour, - minute=self._stop_time.minute, - second=0) + stop_time = now.replace( + hour=self._stop_time.hour, minute=self._stop_time.minute, + second=0) if start_time < now < sunset: # Daytime @@ -201,27 +202,25 @@ class FluxSwitch(SwitchDevice): if self._mode == MODE_XY: set_lights_xy(self.hass, self._lights, x_val, y_val, brightness) - _LOGGER.info("Lights updated to x:%s y:%s brightness:%s, %s%%" - " of %s cycle complete at %s", x_val, y_val, + _LOGGER.info("Lights updated to x:%s y:%s brightness:%s, %s%% " + "of %s cycle complete at %s", x_val, y_val, brightness, round( percentage_complete * 100), time_state, now) else: # Convert to mired and clamp to allowed values mired = color_temperature_kelvin_to_mired(temp) - mired = max(HASS_COLOR_MIN, min(mired, HASS_COLOR_MAX)) set_lights_temp(self.hass, self._lights, mired, brightness) - _LOGGER.info("Lights updated to mired:%s brightness:%s, %s%%" - " of %s cycle complete at %s", mired, brightness, + _LOGGER.info("Lights updated to mired:%s brightness:%s, %s%% " + "of %s cycle complete at %s", mired, brightness, round(percentage_complete * 100), time_state, now) def find_start_time(self, now): """Return sunrise or start_time if given.""" if self._start_time: - sunrise = now.replace(hour=self._start_time.hour, - minute=self._start_time.minute, - second=0) + sunrise = now.replace( + hour=self._start_time.hour, minute=self._start_time.minute, + second=0) else: - sunrise = next_rising(self.hass, SUN).replace(day=now.day, - month=now.month, - year=now.year) + sunrise = next_rising(self.hass, SUN).replace( + day=now.day, month=now.month, year=now.year) return sunrise diff --git a/homeassistant/components/switch/hdmi_cec.py b/homeassistant/components/switch/hdmi_cec.py index 0e17aeab8e4..a100b582e64 100644 --- a/homeassistant/components/switch/hdmi_cec.py +++ b/homeassistant/components/switch/hdmi_cec.py @@ -67,5 +67,5 @@ class CecSwitchDevice(CecDevice, SwitchDevice): @property def state(self) -> str: - """Cached state of device.""" + """Return the cached state of device.""" return self._state diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index 220011b2fb0..74d3a2429eb 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Hikvision camera.""" + """Set up Hikvision camera.""" import hikvision.api from hikvision.error import HikvisionError, MissingParamError diff --git a/homeassistant/components/switch/homematic.py b/homeassistant/components/switch/homematic.py index bbacfdb1db9..a95f414bb1b 100644 --- a/homeassistant/components/switch/homematic.py +++ b/homeassistant/components/switch/homematic.py @@ -15,7 +15,7 @@ DEPENDENCIES = ['homematic'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Homematic switch platform.""" + """Set up the Homematic switch platform.""" if discovery_info is None: return diff --git a/homeassistant/components/switch/insteon_local.py b/homeassistant/components/switch/insteon_local.py index 2017ad96a47..e6e34f6de27 100644 --- a/homeassistant/components/switch/insteon_local.py +++ b/homeassistant/components/switch/insteon_local.py @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): conf_switches = config_from_file(hass.config.path( INSTEON_LOCAL_SWITCH_CONF)) - if len(conf_switches): + if conf_switches: for device_id in conf_switches: setup_switch( device_id, conf_switches[device_id], insteonhub, hass, @@ -48,8 +48,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass, add_devices) -def request_configuration(device_id, insteonhub, model, hass, - add_devices_callback): +def request_configuration( + device_id, insteonhub, model, hass, add_devices_callback): """Request configuration steps from the user.""" configurator = get_component('configurator') @@ -61,7 +61,7 @@ def request_configuration(device_id, insteonhub, model, hass, return def insteon_switch_config_callback(data): - """The actions to do when our configuration callback is called.""" + """Handle configuration changes.""" setup_switch(device_id, data.get('name'), insteonhub, hass, add_devices_callback) @@ -81,7 +81,7 @@ def setup_switch(device_id, name, insteonhub, hass, add_devices_callback): request_id = _CONFIGURING.pop(device_id) configurator = get_component('configurator') configurator.request_done(request_id) - _LOGGER.info("Device configuration done!") + _LOGGER.info("Device configuration done") conf_switch = config_from_file(hass.config.path(INSTEON_LOCAL_SWITCH_CONF)) if device_id not in conf_switch: diff --git a/homeassistant/components/switch/kankun.py b/homeassistant/components/switch/kankun.py index f3008d3b086..88a07b68cd9 100644 --- a/homeassistant/components/switch/kankun.py +++ b/homeassistant/components/switch/kankun.py @@ -58,7 +58,7 @@ class KankunSwitch(SwitchDevice): """Representation of a Kankun Wifi switch.""" def __init__(self, hass, name, host, port, path, user, passwd): - """Initialise the device.""" + """Initialize the device.""" self._hass = hass self._name = name self._state = False @@ -92,17 +92,17 @@ class KankunSwitch(SwitchDevice): @property def should_poll(self): - """Switch should always be polled.""" + """Return the polling state.""" return True @property def name(self): - """The name of the switch.""" + """Return the name of the switch.""" return self._name @property def is_on(self): - """True if device is on.""" + """Return true if device is on.""" return self._state def update(self): diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/switch/knx.py index 5141e26cdf3..d07df08ed5c 100644 --- a/homeassistant/components/switch/knx.py +++ b/homeassistant/components/switch/knx.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the KNX switch platform.""" + """Set up the KNX switch platform.""" add_devices([KNXSwitch(hass, KNXConfig(config))]) diff --git a/homeassistant/components/switch/lutron_caseta.py b/homeassistant/components/switch/lutron_caseta.py index a8fca67e91a..585dc043315 100644 --- a/homeassistant/components/switch/lutron_caseta.py +++ b/homeassistant/components/switch/lutron_caseta.py @@ -1,11 +1,15 @@ -"""Support for Lutron Caseta switches.""" +""" +Support for Lutron Caseta switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sitch.lutron_caseta/ +""" import logging from homeassistant.components.lutron_caseta import ( LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) from homeassistant.components.switch import SwitchDevice - _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] @@ -13,7 +17,7 @@ DEPENDENCIES = ['lutron_caseta'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Lutron switch.""" + """Set up Lutron switch.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] switch_devices = bridge.get_devices_by_type("WallSwitch") @@ -43,6 +47,6 @@ class LutronCasetaLight(LutronCasetaDevice, SwitchDevice): return self._state["current_state"] > 0 def update(self): - """Called when forcing a refresh of the device.""" + """Update when forcing a refresh of the device.""" self._state = self._smartbridge.get_device_by_id(self._device_id) _LOGGER.debug(self._state) diff --git a/homeassistant/components/switch/mfi.py b/homeassistant/components/switch/mfi.py index da74d189c6f..6f5dd655ba4 100644 --- a/homeassistant/components/switch/mfi.py +++ b/homeassistant/components/switch/mfi.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup mFi sensors.""" + """Set up mFi sensors.""" host = config.get(CONF_HOST) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -56,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): client = MFiClient(host, username, password, port=port, use_tls=use_tls, verify=verify_tls) except (FailedToLogin, requests.exceptions.ConnectionError) as ex: - _LOGGER.error('Unable to connect to mFi: %s', str(ex)) + _LOGGER.error("Unable to connect to mFi: %s", str(ex)) return False add_devices(MfiSwitch(port) @@ -75,7 +75,7 @@ class MfiSwitch(SwitchDevice): @property def should_poll(self): - """Polling is needed.""" + """Return the polling state.""" return True @property diff --git a/homeassistant/components/switch/mochad.py b/homeassistant/components/switch/mochad.py index b7ebcabeb86..21df1898fd2 100644 --- a/homeassistant/components/switch/mochad.py +++ b/homeassistant/components/switch/mochad.py @@ -31,7 +31,7 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup X10 switches over a mochad controller.""" + """Set up X10 switches over a mochad controller.""" devs = config.get(CONF_DEVICES) add_devices([MochadSwitch( hass, mochad.CONTROLLER.ctrl, dev) for dev in devs]) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 391df2ad67e..e9c282e4c45 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -78,13 +78,13 @@ class MqttSwitch(SwitchDevice): @asyncio.coroutine def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method is a coroutine. """ @callback def message_received(topic, payload, qos): - """A new MQTT message has been received.""" + """Handle new MQTT messages.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) @@ -104,7 +104,7 @@ class MqttSwitch(SwitchDevice): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 968166d4d65..c72ea1e4cfe 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -28,7 +28,7 @@ SEND_IR_CODE_SERVICE_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for switches.""" + """Set up the mysensors platform for switches.""" # Only act if loaded via mysensors by discovery event. # Otherwise gateway is not setup. if discovery_info is None: @@ -158,7 +158,7 @@ class MySensorsIRSwitch(MySensorsSwitch): """IR switch child class to MySensorsSwitch.""" def __init__(self, *args): - """Setup instance attributes.""" + """Set up instance attributes.""" MySensorsSwitch.__init__(self, *args) self._ir_code = None diff --git a/homeassistant/components/switch/neato.py b/homeassistant/components/switch/neato.py index b6cf6549cae..e61a46f78a6 100644 --- a/homeassistant/components/switch/neato.py +++ b/homeassistant/components/switch/neato.py @@ -24,12 +24,12 @@ SWITCH_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Neato switches.""" + """Set up the Neato switches.""" dev = [] for robot in hass.data[NEATO_ROBOTS]: for type_name in SWITCH_TYPES: dev.append(NeatoConnectedSwitch(hass, robot, type_name)) - _LOGGER.debug('Adding switches %s', dev) + _LOGGER.debug("Adding switches %s", dev) add_devices(dev) @@ -41,25 +41,26 @@ class NeatoConnectedSwitch(ToggleEntity): self.type = switch_type self.robot = robot self.neato = hass.data[NEATO_LOGIN] - self._robot_name = self.robot.name + ' ' + SWITCH_TYPES[self.type][0] + self._robot_name = '{} {}'.format( + self.robot.name, SWITCH_TYPES[self.type][0]) try: self._state = self.robot.state except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as ex: - _LOGGER.warning('Neato connection error: %s', ex) + _LOGGER.warning("Neato connection error: %s", ex) self._state = None self._schedule_state = None self._clean_state = None def update(self): """Update the states of Neato switches.""" - _LOGGER.debug('Running switch update') + _LOGGER.debug("Running switch update") self.neato.update_robots() try: self._state = self.robot.state except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as ex: - _LOGGER.warning('Neato connection error: %s', ex) + _LOGGER.warning("Neato connection error: %s", ex) self._state = None return _LOGGER.debug('self._state=%s', self._state) @@ -71,14 +72,14 @@ class NeatoConnectedSwitch(ToggleEntity): self._clean_state = STATE_ON else: self._clean_state = STATE_OFF - _LOGGER.debug('schedule_state=%s', self._schedule_state) + _LOGGER.debug("Schedule state: %s", self._schedule_state) if self.type == SWITCH_TYPE_SCHEDULE: - _LOGGER.debug('self._state=%s', self._state) + _LOGGER.debug("State: %s", self._state) if self.robot.schedule_enabled: self._schedule_state = STATE_ON else: self._schedule_state = STATE_OFF - _LOGGER.debug('schedule_state=%s', self._schedule_state) + _LOGGER.debug("Shedule state: %s", self._schedule_state) @property def name(self): diff --git a/homeassistant/components/switch/netio.py b/homeassistant/components/switch/netio.py index ee3d9588a7e..3351f39eeea 100644 --- a/homeassistant/components/switch/netio.py +++ b/homeassistant/components/switch/netio.py @@ -50,7 +50,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Configure the Netio platform.""" + """Set up the Netio platform.""" from pynetio import Netio host = config.get(CONF_HOST) @@ -58,14 +58,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config.get(CONF_PASSWORD) port = config.get(CONF_PORT) - if len(DEVICES) == 0: + if not DEVICES: hass.http.register_view(NetioApiView) dev = Netio(host, port, username, password) DEVICES[host] = Device(dev, []) - # Throttle the update for all NetioSwitches of one Netio + # Throttle the update for all Netio switches of one Netio dev.update = util.Throttle(MIN_TIME_BETWEEN_SCANS)(dev.update) for key in config[CONF_OUTLETS]: @@ -123,22 +123,22 @@ class NetioApiView(HomeAssistantView): class NetioSwitch(SwitchDevice): - """Provide a netio linked switch.""" + """Provide a Netio linked switch.""" def __init__(self, netio, outlet, name): - """Defined to handle throttle.""" + """Initialize the Netio switch.""" self._name = name self.outlet = outlet self.netio = netio @property def name(self): - """Netio device's name.""" + """Return the device's name.""" return self._name @property def available(self): - """Return True if entity is available.""" + """Return true if entity is available.""" return not hasattr(self, 'telnet') def turn_on(self): @@ -158,11 +158,11 @@ class NetioSwitch(SwitchDevice): @property def is_on(self): - """Return switch's status.""" + """Return the switch's status.""" return self.netio.states[self.outlet - 1] def update(self): - """Called by Home Assistant.""" + """Update the state.""" self.netio.update() @property @@ -180,7 +180,7 @@ class NetioSwitch(SwitchDevice): @property def cumulated_consumption_kwh(self): - """Total enerygy consumption since start_date.""" + """Return the total enerygy consumption since start_date.""" return self.netio.cumulated_consumptions[self.outlet - 1] @property diff --git a/homeassistant/components/switch/orvibo.py b/homeassistant/components/switch/orvibo.py index 0ce1426dd1f..e039a29809d 100644 --- a/homeassistant/components/switch/orvibo.py +++ b/homeassistant/components/switch/orvibo.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup S20 switches.""" + """Set up S20 switches.""" from orvibo.s20 import discover, S20, S20Exception switch_data = {} @@ -72,7 +72,7 @@ class S20Switch(SwitchDevice): @property def should_poll(self): - """Polling is needed.""" + """Return the polling state.""" return True @property diff --git a/homeassistant/components/switch/pulseaudio_loopback.py b/homeassistant/components/switch/pulseaudio_loopback.py index 46f9173437c..69b932ecf71 100644 --- a/homeassistant/components/switch/pulseaudio_loopback.py +++ b/homeassistant/components/switch/pulseaudio_loopback.py @@ -82,7 +82,7 @@ class PAServer(): _current_module_state = "" def __init__(self, host, port, buff_sz, tcp_timeout): - """Simple constructor for reading in our configuration.""" + """Initialize PulseAudio server.""" self._pa_host = host self._pa_port = int(port) self._buffer_size = int(buff_sz) @@ -106,7 +106,7 @@ class PAServer(): return return_data def _get_full_response(self, sock): - """Helper method to get the full response back from pulseaudio.""" + """Get the full response back from pulseaudio.""" result = "" rcv_buffer = sock.recv(self._buffer_size) result += rcv_buffer.decode('utf-8') @@ -160,7 +160,7 @@ class PALoopbackSwitch(SwitchDevice): @property def is_on(self): - """Tell the core logic if device is on.""" + """Return true if device is on.""" return self._module_idx > 0 def turn_on(self, **kwargs): diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/switch/rest.py index 179b3b24068..419f4028def 100644 --- a/homeassistant/components/switch/rest.py +++ b/homeassistant/components/switch/rest.py @@ -62,7 +62,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): req = yield from websession.get(resource) if req.status >= 400: - _LOGGER.error('Got non-ok response from resource: %s', req.status) + _LOGGER.error("Got non-ok response from resource: %s", req.status) return False except (TypeError, ValueError): @@ -95,7 +95,7 @@ class RestSwitch(SwitchDevice): @property def name(self): - """The name of the switch.""" + """Return the name of the switch.""" return self._name @property diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 959bab5fe40..36044f5f168 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -17,27 +17,27 @@ PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the RFXtrx platform.""" + """Set up the RFXtrx platform.""" import RFXtrx as rfxtrxmod # Add switch from config file - switches = rfxtrx.get_devices_from_config(config, RfxtrxSwitch) + switches = rfxtrx.get_devices_from_config(config, RfxtrxSwitch, hass) add_devices_callback(switches) def switch_update(event): - """Callback for sensor updates from the RFXtrx gateway.""" + """Handle sensor updates from the RFXtrx gateway.""" if not isinstance(event.device, rfxtrxmod.LightingDevice) or \ event.device.known_to_be_dimmable or \ event.device.known_to_be_rollershutter: return - new_device = rfxtrx.get_new_device(event, config, RfxtrxSwitch) + new_device = rfxtrx.get_new_device(event, config, RfxtrxSwitch, hass) if new_device: add_devices_callback([new_device]) rfxtrx.apply_received_command(event) - # Subscribe to main rfxtrx events + # Subscribe to main RFXtrx events if switch_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(switch_update) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index cc761250be4..18d05db2f28 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Raspberry PI GPIO devices.""" + """Set up the Raspberry PI GPIO devices.""" invert_logic = config.get(CONF_INVERT_LOGIC) switches = [] diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index 965011d12ea..7c22e0d5a88 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the SCSGate switches.""" + """Set up the SCSGate switches.""" logger = logging.getLogger(__name__) _setup_traditional_switches( diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/switch/tellduslive.py index 5f3901d79b8..eec63ebaa5c 100644 --- a/homeassistant/components/switch/tellduslive.py +++ b/homeassistant/components/switch/tellduslive.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick switches.""" + """Set up Tellstick switches.""" if discovery_info is None: return add_devices(TelldusLiveSwitch(hass, switch) for switch in discovery_info) diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index 094db06c49f..c631eedc050 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -17,7 +17,7 @@ PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick switches.""" + """Set up Tellstick switches.""" if (discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None): return diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 4ea2d82388d..b81c702bc02 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Template switch.""" + """Set up the Template switch.""" switches = [] for device, device_config in config[CONF_SWITCHES].items(): @@ -59,13 +59,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): switches.append( SwitchTemplate( - hass, - device, - friendly_name, - state_template, - on_action, - off_action, - entity_ids) + hass, device, friendly_name, state_template, on_action, + off_action, entity_ids) ) if not switches: _LOGGER.error("No switches added") @@ -82,8 +77,8 @@ class SwitchTemplate(SwitchDevice): on_action, off_action, entity_ids): """Initialize the Template switch.""" self.hass = hass - self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, - hass=hass) + self.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._on_script = Script(hass, on_action) @@ -100,7 +95,7 @@ class SwitchTemplate(SwitchDevice): @callback def template_switch_state_listener(entity, old_state, new_state): - """Called when the target device changes state.""" + """Handle target device state changes.""" self.hass.async_add_job(self.async_update_ha_state(True)) @callback @@ -126,7 +121,7 @@ class SwitchTemplate(SwitchDevice): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/switch/thinkingcleaner.py b/homeassistant/components/switch/thinkingcleaner.py index f577b29d2d5..37c2f52e228 100644 --- a/homeassistant/components/switch/thinkingcleaner.py +++ b/homeassistant/components/switch/thinkingcleaner.py @@ -15,9 +15,7 @@ from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/TheRealLink/pythinkingcleaner' - '/archive/v0.0.2.zip' - '#pythinkingcleaner==0.0.2'] +REQUIREMENTS = ['pythinkingcleaner==0.0.3'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -33,7 +31,7 @@ SWITCH_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ThinkingCleaner platform.""" + """Set up the ThinkingCleaner platform.""" from pythinkingcleaner import Discovery discovery = Discovery() diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py index cc00a3691ee..1b8ef585557 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the TPLink switch platform.""" + """Set up the TPLink switch platform.""" from pyHS100 import SmartPlug host = config.get(CONF_HOST) name = config.get(CONF_NAME) diff --git a/homeassistant/components/switch/transmission.py b/homeassistant/components/switch/transmission.py index 6b8f89838d5..656a6227358 100644 --- a/homeassistant/components/switch/transmission.py +++ b/homeassistant/components/switch/transmission.py @@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Transmission switch.""" + """Set up the Transmission switch.""" import transmissionrpc from transmissionrpc.error import TransmissionError diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index 6f41c98e7b8..1e92612b9a9 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Find and return Vera switches.""" + """Set up the Vera switches.""" add_devices( VeraSwitch(device, VERA_CONTROLLER) for device in VERA_DEVICES['switch']) @@ -46,7 +46,7 @@ class VeraSwitch(VeraDevice, SwitchDevice): @property def current_power_w(self): - """Current power usage in W.""" + """Return the current power usage in W.""" power = self.vera_device.power if power: return convert(power, float, 0.0) @@ -57,5 +57,5 @@ class VeraSwitch(VeraDevice, SwitchDevice): return self._state def update(self): - """Called by the vera device callback to update state.""" + """Update device state.""" self._state = self.vera_device.is_switched_on() diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index d7974335811..3aeb092f35b 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -14,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Verisure switch platform.""" + """Set up the Verisure switch platform.""" if not int(hub.config.get(CONF_SMARTPLUGS, 1)): return False diff --git a/homeassistant/components/switch/volvooncall.py b/homeassistant/components/switch/volvooncall.py index 46cfd667951..9e20ddb5e7e 100644 --- a/homeassistant/components/switch/volvooncall.py +++ b/homeassistant/components/switch/volvooncall.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick switches.""" + """Set up Tellstick switches.""" if discovery_info is None: return add_devices([VolvoSwitch(hass, *discovery_info)]) diff --git a/homeassistant/components/switch/wake_on_lan.py b/homeassistant/components/switch/wake_on_lan.py index 4413da191d3..8430a5b8810 100644 --- a/homeassistant/components/switch/wake_on_lan.py +++ b/homeassistant/components/switch/wake_on_lan.py @@ -66,7 +66,7 @@ class WOLSwitch(SwitchDevice): @property def should_poll(self): - """Poll for status regularly.""" + """Return the polling state.""" return True @property @@ -76,14 +76,14 @@ class WOLSwitch(SwitchDevice): @property def name(self): - """The name of the switch.""" + """Return the name of the switch.""" return self._name def turn_on(self): """Turn the device on.""" if self._broadcast_address: - self._wol.send_magic_packet(self._mac_address, - ip_address=self._broadcast_address) + self._wol.send_magic_packet( + self._mac_address, ip_address=self._broadcast_address) else: self._wol.send_magic_packet(self._mac_address) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 700d9d25b5a..1c6ad76ceba 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -17,13 +17,13 @@ DEPENDENCIES = ['wemo'] _LOGGER = logging.getLogger(__name__) -ATTR_SENSOR_STATE = "sensor_state" -ATTR_SWITCH_MODE = "switch_mode" +ATTR_SENSOR_STATE = 'sensor_state' +ATTR_SWITCH_MODE = 'switch_mode' ATTR_CURRENT_STATE_DETAIL = 'state_detail' -ATTR_COFFEMAKER_MODE = "coffeemaker_mode" +ATTR_COFFEMAKER_MODE = 'coffeemaker_mode' -MAKER_SWITCH_MOMENTARY = "momentary" -MAKER_SWITCH_TOGGLE = "toggle" +MAKER_SWITCH_MOMENTARY = 'momentary' +MAKER_SWITCH_TOGGLE = 'toggle' WEMO_ON = 1 WEMO_OFF = 0 @@ -32,7 +32,7 @@ WEMO_STANDBY = 8 # pylint: disable=unused-argument, too-many-function-args def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup discovered WeMo switches.""" + """Set up discovered WeMo switches.""" import pywemo.discovery as discovery if discovery_info is not None: @@ -62,10 +62,8 @@ class WemoSwitch(SwitchDevice): wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) def _update_callback(self, _device, _type, _params): - """Called by the Wemo device callback to update state.""" - _LOGGER.info( - 'Subscription update for %s', - _device) + """Update the state by the Wemo device.""" + _LOGGER.info("Subscription update for %s", _device) updated = self.wemo.subscription_update(_type, _params) self._update(force_update=(not updated)) @@ -133,14 +131,12 @@ class WemoSwitch(SwitchDevice): def as_uptime(_seconds): """Format seconds into uptime string in the format: 00d 00h 00m 00s.""" uptime = datetime(1, 1, 1) + timedelta(seconds=_seconds) - return "{:0>2d}d {:0>2d}h {:0>2d}m {:0>2d}s".format(uptime.day-1, - uptime.hour, - uptime.minute, - uptime.second) + return "{:0>2d}d {:0>2d}h {:0>2d}m {:0>2d}s".format( + uptime.day-1, uptime.hour, uptime.minute, uptime.second) @property def current_power_w(self): - """Current power usage in W.""" + """Return the current power usage in W.""" if self.insight_params: return convert( self.insight_params['currentpower'], float, 0.0 @@ -148,7 +144,7 @@ class WemoSwitch(SwitchDevice): @property def today_energy_kwh(self): - """Today total energy usage in kWh.""" + """Return the today total energy usage in kWh.""" if self.insight_params: miliwatts = convert(self.insight_params['todaymw'], float, 0.0) return round(miliwatts / (1000.0 * 1000.0 * 60), 2) @@ -176,7 +172,7 @@ class WemoSwitch(SwitchDevice): @property def available(self): - """True if switch is available.""" + """Return true if switch is available.""" if self._model_name == 'Insight' and self.insight_params is None: return False if self._model_name == 'Maker' and self.maker_params is None: @@ -187,7 +183,7 @@ class WemoSwitch(SwitchDevice): @property def icon(self): - """Icon of device based on its type.""" + """Return the icon of device based on its type.""" if self._model_name == 'CoffeeMaker': return 'mdi:coffee' else: @@ -210,6 +206,7 @@ class WemoSwitch(SwitchDevice): self._update(force_update=True) def _update(self, force_update=True): + """Update the device state.""" try: self._state = self.wemo.get_state(force_update) if self._model_name == 'Insight': @@ -221,5 +218,5 @@ class WemoSwitch(SwitchDevice): elif self._model_name == 'CoffeeMaker': self.coffeemaker_mode = self.wemo.mode except AttributeError as err: - _LOGGER.warning('Could not update status for %s (%s)', + _LOGGER.warning("Could not update status for %s (%s)", self.name, err) diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 5d5b477be99..6783f2201c1 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -12,7 +12,7 @@ DEPENDENCIES = ['wink'] def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wink platform.""" + """Set up the Wink platform.""" import pywink for switch in pywink.get_switches(): diff --git a/homeassistant/components/switch/zha.py b/homeassistant/components/switch/zha.py new file mode 100644 index 00000000000..c98db2e894e --- /dev/null +++ b/homeassistant/components/switch/zha.py @@ -0,0 +1,49 @@ +""" +Switches on Zigbee Home Automation networks. + +For more details on this platform, please refer to the documentation +at https://home-assistant.io/components/switch.zha/ +""" +import asyncio +import logging + +from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components import zha + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zha'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Zigbee Home Automation switches.""" + discovery_info = zha.get_discovery_info(hass, discovery_info) + if discovery_info is None: + return + + add_devices([Switch(**discovery_info)]) + + +class Switch(zha.Entity, SwitchDevice): + """ZHA switch.""" + + _domain = DOMAIN + + @property + def is_on(self) -> bool: + """Return if the switch is on based on the statemachine.""" + if self._state == 'unknown': + return False + return bool(self._state) + + @asyncio.coroutine + def async_turn_on(self, **kwargs): + """Turn the entity on.""" + yield from self._endpoint.on_off.on() + self._state = 1 + + @asyncio.coroutine + def async_turn_off(self, **kwargs): + """Turn the entity off.""" + yield from self._endpoint.on_off.off() + self._state = 0 diff --git a/homeassistant/components/switch/zigbee.py b/homeassistant/components/switch/zigbee.py index 7a58b0867c1..a0db5685a90 100644 --- a/homeassistant/components/switch/zigbee.py +++ b/homeassistant/components/switch/zigbee.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ZigBee switch platform.""" + """Set up the ZigBee switch platform.""" add_devices([ZigBeeSwitch(hass, ZigBeeDigitalOutConfig(config))]) diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index ba64785013f..3b82d87d7e7 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -33,7 +33,7 @@ class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice): self._state = self.values.primary.data def update_properties(self): - """Callback on data changes for node values.""" + """Handle data changes for node values.""" self._state = self.values.primary.data if self.refresh_on_update and \ time.perf_counter() - self.last_update > 30: diff --git a/homeassistant/components/tado.py b/homeassistant/components/tado.py index b7758c95c0e..a465119dc2d 100644 --- a/homeassistant/components/tado.py +++ b/homeassistant/components/tado.py @@ -107,21 +107,21 @@ class TadoDataStore: return data def get_zones(self): - """Wrapper for getZones().""" + """Wrap for getZones().""" return self.tado.getZones() def get_capabilities(self, tado_id): - """Wrapper for getCapabilities(..).""" + """Wrap for getCapabilities(..).""" return self.tado.getCapabilities(tado_id) def get_me(self): - """Wrapper for getMet().""" + """Wrap for getMet().""" return self.tado.getMe() def reset_zone_overlay(self, zone_id): - """Wrapper for resetZoneOverlay(..).""" + """Wrap for resetZoneOverlay(..).""" return self.tado.resetZoneOverlay(zone_id) def set_zone_overlay(self, zone_id, mode, temperature=None, duration=None): - """Wrapper for setZoneOverlay(..).""" + """Wrap for setZoneOverlay(..).""" return self.tado.setZoneOverlay(zone_id, mode, temperature, duration) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 1cd77491c51..200c4227f4d 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -1,12 +1,9 @@ -""" -Component to receive telegram messages. - -Either by polling or webhook. -""" - +"""Component to receive telegram messages.""" import asyncio import logging + import voluptuous as vol + import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_PLATFORM, CONF_API_KEY from homeassistant.exceptions import HomeAssistantError @@ -32,17 +29,17 @@ CONF_ALLOWED_CHAT_IDS = 'allowed_chat_ids' PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): cv.string, vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_ALLOWED_CHAT_IDS): vol.All(cv.ensure_list, - [cv.positive_int]) + vol.Required(CONF_ALLOWED_CHAT_IDS): + vol.All(cv.ensure_list, [cv.positive_int]) }, extra=vol.ALLOW_EXTRA) @asyncio.coroutine def async_setup(hass, config): - """Setup the telegram bot component.""" + """Set up the telegram bot component.""" @asyncio.coroutine def async_setup_platform(p_type, p_config=None, discovery_info=None): - """Setup a telegram bot platform.""" + """Set up a telegram bot platform.""" platform = yield from async_prepare_setup_platform( hass, config, DOMAIN, p_type) @@ -62,11 +59,11 @@ def async_setup(hass, config): None, platform.setup_platform, hass, p_config, discovery_info) else: - raise HomeAssistantError("Invalid telegram bot platform.") + raise HomeAssistantError("Invalid Telegram bot platform") if notify_service is None: _LOGGER.error( - "Failed to initialize telegram bot %s", p_type) + "Failed to initialize Telegram bot %s", p_type) return except Exception: # pylint: disable=broad-except @@ -81,7 +78,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_platform_discovered(platform, info): - """Callback to load a platform.""" + """Handle the loading of a platform.""" yield from async_setup_platform(platform, discovery_info=info) discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) @@ -105,15 +102,14 @@ class BaseTelegramBotEntity: 'from' not in data or 'text' not in data or data['from'].get('id') not in self.allowed_chat_ids): - # Message is not correct. - _LOGGER.error("Incoming message does not have required data.") + _LOGGER.error("Incoming message does not have required data") return False event = EVENT_TELEGRAM_COMMAND event_data = { ATTR_USER_ID: data['from']['id'], - ATTR_FROM_FIRST: data['from']['first_name'], - ATTR_FROM_LAST: data['from']['last_name']} + ATTR_FROM_FIRST: data['from'].get('first_name', 'N/A'), + ATTR_FROM_LAST: data['from'].get('last_name', 'N/A')} if data['text'][0] == '/': pieces = data['text'].split(' ') diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index d46c3f539d2..8ae0a07a480 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -1,5 +1,9 @@ -"""Telegram bot polling implementation.""" +""" +Telegram bot polling implementation. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/telegram_bot.polling/ +""" import asyncio from asyncio.futures import CancelledError import logging @@ -23,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the polling platform.""" + """Set up the Telegram polling platform.""" import telegram bot = telegram.Bot(config[CONF_API_KEY]) pol = TelegramPoll(bot, hass, config[CONF_ALLOWED_CHAT_IDS]) @@ -101,7 +105,7 @@ class TelegramPoll(BaseTelegramBotEntity): @asyncio.coroutine def handle(self): - """" Receiving and processing incoming messages.""" + """Receiving and processing incoming messages.""" _updates = yield from self.get_updates(self.update_id) for update in _updates['result']: self.update_id = update['update_id'] + 1 @@ -109,7 +113,7 @@ class TelegramPoll(BaseTelegramBotEntity): @asyncio.coroutine def check_incoming(self): - """"Loop which continuously checks for incoming telegram messages.""" + """Loop which continuously checks for incoming telegram messages.""" try: while True: # Each handle call sends a long polling post request @@ -118,4 +122,4 @@ class TelegramPoll(BaseTelegramBotEntity): # timeout will for this reason not really stress the processor. yield from self.handle() except CancelledError: - _LOGGER.debug("Stopping telegram polling bot") + _LOGGER.debug("Stopping Telegram polling bot") diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 3444c58809a..d647fab490b 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -1,9 +1,8 @@ """ Allows utilizing telegram webhooks. -See https://core.telegram.org/bots/webhooks for details - about webhooks. - +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/telegram_bot.webhooks/ """ import asyncio import logging @@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the polling platform.""" + """Set up the Telegram webhooks platform.""" import telegram bot = telegram.Bot(config[CONF_API_KEY]) @@ -54,9 +53,9 @@ def setup_platform(hass, config, async_add_devices, discovery_info=None): hass.config.api.base_url, TELEGRAM_HANDLER_URL) if current_status and current_status['url'] != handler_url: if bot.setWebhook(handler_url): - _LOGGER.info("set new telegram webhook %s", handler_url) + _LOGGER.info("Set new telegram webhook %s", handler_url) else: - _LOGGER.error("set telegram webhook failed %s", handler_url) + _LOGGER.error("Set telegram webhook failed %s", handler_url) return False hass.bus.listen_once( @@ -68,7 +67,7 @@ def setup_platform(hass, config, async_add_devices, discovery_info=None): class BotPushReceiver(HomeAssistantView, BaseTelegramBotEntity): - """Handle pushes from telegram.""" + """Handle pushes from Telegram.""" requires_auth = False url = TELEGRAM_HANDLER_URL diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index eb2957d7b4a..a9f3cea81e7 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -46,14 +46,13 @@ ATTR_LAST_UPDATED = 'time_last_updated' def setup(hass, config): - """Setup the Telldus Live component.""" + """Set up the Telldus Live component.""" client = TelldusLiveClient(hass, config) if not client.validate_session(): _LOGGER.error( - 'Authentication Error: ' - 'Please make sure you have configured your keys ' - 'that can be aquired from https://api.telldus.com/keys/index') + "Authentication Error: Please make sure you have configured your " + "keys that can be aquired from https://api.telldus.com/keys/index") return False hass.data[DOMAIN] = client @@ -94,7 +93,7 @@ class TelldusLiveClient(object): def update(self, now): """Periodically poll the servers for current state.""" - _LOGGER.debug('Updating') + _LOGGER.debug("Updating") try: self._sync() finally: @@ -104,7 +103,7 @@ class TelldusLiveClient(object): def _sync(self): """Update local list of devices.""" if not self._client.update(): - _LOGGER.warning('Failed request') + _LOGGER.warning("Failed request") def identify_device(device): """Find out what type of HA component to create.""" @@ -116,8 +115,8 @@ class TelldusLiveClient(object): elif device.methods & TURNON: return 'switch' else: - _LOGGER.warning('Unidentified device type (methods: %d)', - device.methods) + _LOGGER.warning( + "Unidentified device type (methods: %d)", device.methods) return 'switch' def discover(device_id, component): @@ -158,10 +157,10 @@ class TelldusLiveEntity(Entity): self._client = hass.data[DOMAIN] self._client.entities.append(self) self._name = self.device.name - _LOGGER.debug('Created device %s', self) + _LOGGER.debug("Created device %s", self) def changed(self): - """A property of the device might have changed.""" + """Return the property of the device might have changed.""" if self.device.name: self._name = self.device.name self.schedule_update_ha_state() @@ -183,7 +182,7 @@ class TelldusLiveEntity(Entity): @property def should_poll(self): - """Polling is not needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py index e6031a91ab4..5d0ec78dfa7 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -13,17 +13,16 @@ from homeassistant.helpers import discovery from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity -DOMAIN = 'tellstick' - REQUIREMENTS = ['tellcore-py==1.1.2'] _LOGGER = logging.getLogger(__name__) -ATTR_SIGNAL_REPETITIONS = 'signal_repetitions' -DEFAULT_SIGNAL_REPETITIONS = 1 - -ATTR_DISCOVER_DEVICES = 'devices' ATTR_DISCOVER_CONFIG = 'config' +ATTR_DISCOVER_DEVICES = 'devices' +ATTR_SIGNAL_REPETITIONS = 'signal_repetitions' + +DEFAULT_SIGNAL_REPETITIONS = 1 +DOMAIN = 'tellstick' # Use a global tellstick domain lock to avoid getting Tellcore errors when # calling concurrently. @@ -42,8 +41,8 @@ CONFIG_SCHEMA = vol.Schema({ def _discover(hass, config, component_name, found_tellcore_devices): - """Setup and send the discovery event.""" - if not len(found_tellcore_devices): + """Set up and send the discovery event.""" + if not found_tellcore_devices: return _LOGGER.info("Discovered %d new %s devices", len(found_tellcore_devices), @@ -57,7 +56,7 @@ def _discover(hass, config, component_name, found_tellcore_devices): def setup(hass, config): - """Setup the Tellstick component.""" + """Set up the Tellstick component.""" from tellcore.constants import TELLSTICK_DIM from tellcore.telldus import AsyncioCallbackDispatcher from tellcore.telldus import TelldusCore @@ -66,7 +65,7 @@ def setup(hass, config): tellcore_lib = TelldusCore( callback_dispatcher=AsyncioCallbackDispatcher(hass.loop)) except OSError: - _LOGGER.exception('Could not initialize Tellstick') + _LOGGER.exception("Could not initialize Tellstick") return False # Get all devices, switches and lights alike @@ -152,7 +151,7 @@ class TellstickDevice(Entity): """ def __init__(self, tellcore_id, tellcore_registry, signal_repetitions): - """Initalize the Tellstick device.""" + """Init the Tellstick device.""" self._signal_repetitions = signal_repetitions self._state = None self._requested_state = None @@ -218,7 +217,7 @@ class TellstickDevice(Entity): _LOGGER.error(err) def _change_device_state(self, new_state, data): - """The logic for actually turning on or off the device.""" + """Turn on or off the device.""" with TELLSTICK_LOCK: # Set the requested state and number of repeats before calling # _send_repeated_command the first time. Subsequent calls will be @@ -245,8 +244,8 @@ class TellstickDevice(Entity): def _update_model_from_command(self, tellcore_command, tellcore_data): """Update the model, from a sent tellcore command and data.""" - from tellcore.constants import (TELLSTICK_TURNON, TELLSTICK_TURNOFF, - TELLSTICK_DIM) + from tellcore.constants import ( + TELLSTICK_TURNON, TELLSTICK_TURNOFF, TELLSTICK_DIM) if tellcore_command not in [TELLSTICK_TURNON, TELLSTICK_TURNOFF, TELLSTICK_DIM]: @@ -269,8 +268,8 @@ class TellstickDevice(Entity): def _update_from_tellcore(self): """Read the current state of the device from the tellcore library.""" from tellcore.library import TelldusError - from tellcore.constants import (TELLSTICK_TURNON, TELLSTICK_TURNOFF, - TELLSTICK_DIM) + from tellcore.constants import ( + TELLSTICK_TURNON, TELLSTICK_TURNOFF, TELLSTICK_DIM) with TELLSTICK_LOCK: try: diff --git a/homeassistant/components/tradfri.py b/homeassistant/components/tradfri.py index 3fd51a68db0..e1ef0f5fabd 100644 --- a/homeassistant/components/tradfri.py +++ b/homeassistant/components/tradfri.py @@ -45,7 +45,7 @@ def request_configuration(hass, config, host): @asyncio.coroutine def configuration_callback(callback_data): - """Called when config is submitted.""" + """Handle the submitted configuration.""" res = yield from _setup_gateway(hass, config, host, callback_data.get('key')) if not res: @@ -73,14 +73,14 @@ def request_configuration(hass, config, host): @asyncio.coroutine def async_setup(hass, config): - """Setup Tradfri.""" + """Set up the Tradfri component.""" conf = config.get(DOMAIN, {}) host = conf.get(CONF_HOST) key = conf.get(CONF_API_KEY) @asyncio.coroutine def gateway_discovered(service, info): - """Called when a gateway is discovered.""" + """Run when a gateway is discovered.""" keys = yield from hass.async_add_job(_read_config, hass) host = info['host'] diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index c175290f451..c91ab47a324 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -29,9 +29,10 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform import homeassistant.helpers.config_validation as cv +REQUIREMENTS = ["mutagen==1.37.0"] + DOMAIN = 'tts' DEPENDENCIES = ['http'] -REQUIREMENTS = ["mutagen==1.36.2"] _LOGGER = logging.getLogger(__name__) @@ -79,11 +80,11 @@ SCHEMA_SERVICE_CLEAR_CACHE = vol.Schema({}) @asyncio.coroutine def async_setup(hass, config): - """Setup TTS.""" + """Set up TTS.""" tts = SpeechManager(hass) try: - conf = config[DOMAIN][0] if len(config.get(DOMAIN, [])) > 0 else {} + conf = config[DOMAIN][0] if config.get(DOMAIN, []) else {} use_cache = conf.get(CONF_CACHE, DEFAULT_CACHE) cache_dir = conf.get(CONF_CACHE_DIR, DEFAULT_CACHE_DIR) time_memory = conf.get(CONF_TIME_MEMORY, DEFAULT_TIME_MEMORY) @@ -101,7 +102,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_setup_platform(p_type, p_config, disc_info=None): - """Setup a tts platform.""" + """Set up a TTS platform.""" platform = yield from async_prepare_setup_platform( hass, config, DOMAIN, p_type) if platform is None: @@ -116,12 +117,12 @@ def async_setup(hass, config): None, platform.get_engine, hass, p_config) if provider is None: - _LOGGER.error('Error setting up platform %s', p_type) + _LOGGER.error("Error setting up platform %s", p_type) return tts.async_register_engine(p_type, provider, p_config) except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error setting up platform %s', p_type) + _LOGGER.exception("Error setting up platform %s", p_type) return @asyncio.coroutine @@ -209,8 +210,7 @@ class SpeechManager(object): self.cache_dir = yield from self.hass.loop.run_in_executor( None, init_tts_cache_dir, cache_dir) except OSError as err: - raise HomeAssistantError( - "Can't init cache dir {}".format(err)) + raise HomeAssistantError("Can't init cache dir {}".format(err)) def get_cache_files(): """Return a dict of given engine files.""" @@ -231,8 +231,7 @@ class SpeechManager(object): cache_files = yield from self.hass.loop.run_in_executor( None, get_cache_files) except OSError as err: - raise HomeAssistantError( - "Can't read cache dir {}".format(err)) + raise HomeAssistantError("Can't read cache dir {}".format(err)) if cache_files: self.file_cache.update(cache_files) @@ -273,14 +272,14 @@ class SpeechManager(object): msg_hash = hashlib.sha1(bytes(message, 'utf-8')).hexdigest() use_cache = cache if cache is not None else self.use_cache - # languages + # Languages language = language or provider.default_language if language is None or \ language not in provider.supported_languages: raise HomeAssistantError("Not supported language {0}".format( language)) - # options + # Options if provider.default_options and options: options = provider.default_options.copy().update(options) options = options or provider.default_options @@ -297,14 +296,14 @@ class SpeechManager(object): key = KEY_PATTERN.format( msg_hash, language, options_key, engine).lower() - # is speech allready in memory + # Is speech already in memory if key in self.mem_cache: filename = self.mem_cache[key][MEM_CACHE_FILENAME] - # is file store in file cache + # Is file store in file cache elif use_cache and key in self.file_cache: filename = self.file_cache[key] self.hass.async_add_job(self.async_file_to_mem(key)) - # load speech from provider into memory + # Load speech from provider into memory else: filename = yield from self.async_get_tts_audio( engine, key, message, use_cache, language, options) @@ -327,13 +326,13 @@ class SpeechManager(object): raise HomeAssistantError( "No TTS from {} for '{}'".format(engine, message)) - # create file infos + # Create file infos filename = ("{}.{}".format(key, extension)).lower() data = self.write_tags( filename, data, provider, message, language, options) - # save to memory + # Save to memory self._async_store_to_memcache(key, filename, data) if cache: @@ -455,29 +454,29 @@ class SpeechManager(object): class Provider(object): - """Represent a single provider.""" + """Represent a single TTS provider.""" hass = None name = None @property def default_language(self): - """Default language.""" + """Return the default language.""" return None @property def supported_languages(self): - """List of supported languages.""" + """Return a list of supported languages.""" return None @property def supported_options(self): - """List of supported options like voice, emotionen.""" + """Return a list of supported options like voice, emotionen.""" return None @property def default_options(self): - """Dict include default options.""" + """Return a dict include default options.""" return None def get_tts_audio(self, message, language, options=None): @@ -497,11 +496,11 @@ class Provider(object): class TextToSpeechView(HomeAssistantView): - """TTS view to serve an speech audio.""" + """TTS view to serve a speech audio.""" requires_auth = False - url = "/api/tts_proxy/{filename}" - name = "api:tts:speech" + url = '/api/tts_proxy/{filename}' + name = 'api:tts:speech' def __init__(self, tts): """Initialize a tts view.""" diff --git a/homeassistant/components/tts/amazon_polly.py b/homeassistant/components/tts/amazon_polly.py index 7dab49482ed..9be882a4cb3 100644 --- a/homeassistant/components/tts/amazon_polly.py +++ b/homeassistant/components/tts/amazon_polly.py @@ -11,57 +11,57 @@ from homeassistant.components.tts import Provider, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["boto3==1.4.3"] +REQUIREMENTS = ['boto3==1.4.3'] -CONF_REGION = "region_name" -CONF_ACCESS_KEY_ID = "aws_access_key_id" -CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" -CONF_PROFILE_NAME = "profile_name" -ATTR_CREDENTIALS = "credentials" +CONF_REGION = 'region_name' +CONF_ACCESS_KEY_ID = 'aws_access_key_id' +CONF_SECRET_ACCESS_KEY = 'aws_secret_access_key' +CONF_PROFILE_NAME = 'profile_name' +ATTR_CREDENTIALS = 'credentials' -DEFAULT_REGION = "us-east-1" -SUPPORTED_REGIONS = ["us-east-1", "us-east-2", "us-west-2", "eu-west-1"] +DEFAULT_REGION = 'us-east-1' +SUPPORTED_REGIONS = ['us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1'] -CONF_VOICE = "voice" -CONF_OUTPUT_FORMAT = "output_format" -CONF_SAMPLE_RATE = "sample_rate" -CONF_TEXT_TYPE = "text_type" +CONF_VOICE = 'voice' +CONF_OUTPUT_FORMAT = 'output_format' +CONF_SAMPLE_RATE = 'sample_rate' +CONF_TEXT_TYPE = 'text_type' -SUPPORTED_VOICES = ["Geraint", "Gwyneth", "Mads", "Naja", "Hans", "Marlene", - "Nicole", "Russell", "Amy", "Brian", "Emma", "Raveena", - "Ivy", "Joanna", "Joey", "Justin", "Kendra", "Kimberly", - "Salli", "Conchita", "Enrique", "Miguel", "Penelope", - "Chantal", "Celine", "Mathieu", "Dora", "Karl", "Carla", - "Giorgio", "Mizuki", "Liv", "Lotte", "Ruben", "Ewa", - "Jacek", "Jan", "Maja", "Ricardo", "Vitoria", "Cristiano", - "Ines", "Carmen", "Maxim", "Tatyana", "Astrid", "Filiz"] +SUPPORTED_VOICES = ['Geraint', 'Gwyneth', 'Mads', 'Naja', 'Hans', 'Marlene', + 'Nicole', 'Russell', 'Amy', 'Brian', 'Emma', 'Raveena', + 'Ivy', 'Joanna', 'Joey', 'Justin', 'Kendra', 'Kimberly', + 'Salli', 'Conchita', 'Enrique', 'Miguel', 'Penelope', + 'Chantal', 'Celine', 'Mathieu', 'Dora', 'Karl', 'Carla', + 'Giorgio', 'Mizuki', 'Liv', 'Lotte', 'Ruben', 'Ewa', + 'Jacek', 'Jan', 'Maja', 'Ricardo', 'Vitoria', 'Cristiano', + 'Ines', 'Carmen', 'Maxim', 'Tatyana', 'Astrid', 'Filiz'] -SUPPORTED_OUTPUT_FORMATS = ["mp3", "ogg_vorbis", "pcm"] +SUPPORTED_OUTPUT_FORMATS = ['mp3', 'ogg_vorbis', 'pcm'] -SUPPORTED_SAMPLE_RATES = ["8000", "16000", "22050"] +SUPPORTED_SAMPLE_RATES = ['8000', '16000', '22050'] SUPPORTED_SAMPLE_RATES_MAP = { - "mp3": ["8000", "16000", "22050"], - "ogg_vorbis": ["8000", "16000", "22050"], - "pcm": ["8000", "16000"] + 'mp3': ['8000', '16000', '22050'], + 'ogg_vorbis': ['8000', '16000', '22050'], + 'pcm': ['8000', '16000'] } -SUPPORTED_TEXT_TYPES = ["text", "ssml"] +SUPPORTED_TEXT_TYPES = ['text', 'ssml'] CONTENT_TYPE_EXTENSIONS = { - "audio/mpeg": "mp3", - "audio/ogg": "ogg", - "audio/pcm": "pcm" + 'audio/mpeg': 'mp3', + 'audio/ogg': 'ogg', + 'audio/pcm': 'pcm' } -DEFAULT_VOICE = "Joanna" -DEFAULT_OUTPUT_FORMAT = "mp3" -DEFAULT_TEXT_TYPE = "text" +DEFAULT_VOICE = 'Joanna' +DEFAULT_OUTPUT_FORMAT = 'mp3' +DEFAULT_TEXT_TYPE = 'text' DEFAULT_SAMPLE_RATES = { - "mp3": "22050", - "ogg_vorbis": "22050", - "pcm": "16000" + 'mp3': '22050', + 'ogg_vorbis': '22050', + 'pcm': '16000' } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -81,7 +81,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_engine(hass, config): - """Setup Amazon Polly speech component.""" + """Set up Amazon Polly speech component.""" # pylint: disable=import-error output_format = config.get(CONF_OUTPUT_FORMAT) sample_rate = config.get(CONF_SAMPLE_RATE, @@ -110,7 +110,7 @@ def get_engine(hass, config): del config[CONF_ACCESS_KEY_ID] del config[CONF_SECRET_ACCESS_KEY] - polly_client = boto3.client("polly", **aws_config) + polly_client = boto3.client('polly', **aws_config) supported_languages = [] @@ -118,10 +118,10 @@ def get_engine(hass, config): all_voices_req = polly_client.describe_voices() - for voice in all_voices_req.get("Voices"): - all_voices[voice.get("Id")] = voice - if voice.get("LanguageCode") not in supported_languages: - supported_languages.append(voice.get("LanguageCode")) + for voice in all_voices_req.get('Voices'): + all_voices[voice.get('Id')] = voice + if voice.get('LanguageCode') not in supported_languages: + supported_languages.append(voice.get('LanguageCode')) return AmazonPollyProvider(polly_client, config, supported_languages, all_voices) @@ -142,29 +142,29 @@ class AmazonPollyProvider(Provider): @property def supported_languages(self): - """List of supported languages.""" + """Return a list of supported languages.""" return self.supported_langs @property def default_language(self): - """Default language.""" - return self.all_voices.get(self.default_voice).get("LanguageCode") + """Return the default language.""" + return self.all_voices.get(self.default_voice).get('LanguageCode') @property def default_options(self): - """Dict include default options.""" + """Return dict include default options.""" return {CONF_VOICE: self.default_voice} @property def supported_options(self): - """List of supported options.""" + """Return a list of supported options.""" return [CONF_VOICE] def get_tts_audio(self, message, language=None, options=None): """Request TTS file from Polly.""" voice_id = options.get(CONF_VOICE, self.default_voice) voice_in_dict = self.all_voices.get(voice_id) - if language is not voice_in_dict.get("LanguageCode"): + if language is not voice_in_dict.get('LanguageCode'): _LOGGER.error("%s does not support the %s language", voice_id, language) return (None, None) @@ -177,5 +177,5 @@ class AmazonPollyProvider(Provider): VoiceId=voice_id ) - return (CONTENT_TYPE_EXTENSIONS[resp.get("ContentType")], - resp.get("AudioStream").read()) + return (CONTENT_TYPE_EXTENSIONS[resp.get('ContentType')], + resp.get('AudioStream').read()) diff --git a/homeassistant/components/tts/demo.py b/homeassistant/components/tts/demo.py index d9d1eccec8d..ba854fc2f5e 100644 --- a/homeassistant/components/tts/demo.py +++ b/homeassistant/components/tts/demo.py @@ -22,12 +22,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_engine(hass, config): - """Setup Demo speech component.""" + """Set up Demo speech component.""" return DemoProvider(config[CONF_LANG]) class DemoProvider(Provider): - """Demo speech api provider.""" + """Demo speech API provider.""" def __init__(self, lang): """Initialize demo provider.""" @@ -36,26 +36,26 @@ class DemoProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._lang @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES @property def supported_options(self): - """List of supported options like voice, emotionen.""" + """Return list of supported options like voice, emotionen.""" return ['voice', 'age'] def get_tts_audio(self, message, language, options=None): """Load TTS from demo.""" - filename = os.path.join(os.path.dirname(__file__), "demo.mp3") + filename = os.path.join(os.path.dirname(__file__), 'demo.mp3') try: with open(filename, 'rb') as voice: data = voice.read() except OSError: return (None, None) - return ("mp3", data) + return ('mp3', data) diff --git a/homeassistant/components/tts/google.py b/homeassistant/components/tts/google.py index e88022a8ba9..24e96a239b2 100644 --- a/homeassistant/components/tts/google.py +++ b/homeassistant/components/tts/google.py @@ -28,12 +28,11 @@ SUPPORT_LANGUAGES = [ 'hr', 'cs', 'da', 'nl', 'en', 'en-au', 'en-uk', 'en-us', 'eo', 'fi', 'fr', 'de', 'el', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'la', 'lv', 'mk', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 'sr', 'sk', 'es', 'es-es', - 'es-us', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy', + 'es-us', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy', 'uk', ] DEFAULT_LANG = 'en' - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), }) @@ -62,12 +61,12 @@ class GoogleProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._lang @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES @asyncio.coroutine diff --git a/homeassistant/components/tts/marytts.py b/homeassistant/components/tts/marytts.py index ffb6950d79b..d7db09856a6 100644 --- a/homeassistant/components/tts/marytts.py +++ b/homeassistant/components/tts/marytts.py @@ -51,7 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_get_engine(hass, config): - """Setup MaryTTS speech component.""" + """Set up MaryTTS speech component.""" return MaryTTSProvider(hass, config) @@ -70,12 +70,12 @@ class MaryTTSProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._language @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES @asyncio.coroutine @@ -105,13 +105,13 @@ class MaryTTSProvider(Provider): request = yield from websession.get(url, params=url_param) if request.status != 200: - _LOGGER.error("Error %d on load url %s.", + _LOGGER.error("Error %d on load url %s", request.status, request.url) return (None, None) data = yield from request.read() except (asyncio.TimeoutError, aiohttp.ClientError): - _LOGGER.error("Timeout for MaryTTS API.") + _LOGGER.error("Timeout for MaryTTS API") return (None, None) return (self._codec, data) diff --git a/homeassistant/components/tts/picotts.py b/homeassistant/components/tts/picotts.py index a22196cfbe0..59c698303ad 100644 --- a/homeassistant/components/tts/picotts.py +++ b/homeassistant/components/tts/picotts.py @@ -42,12 +42,12 @@ class PicoProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._lang @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES def get_tts_audio(self, message, language, options=None): diff --git a/homeassistant/components/tts/voicerss.py b/homeassistant/components/tts/voicerss.py index e8622d3b9a1..38f6e2290b5 100644 --- a/homeassistant/components/tts/voicerss.py +++ b/homeassistant/components/tts/voicerss.py @@ -16,7 +16,6 @@ from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) VOICERSS_API_URL = "https://api.voicerss.org/" @@ -106,12 +105,12 @@ class VoiceRSSProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._lang @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES @asyncio.coroutine diff --git a/homeassistant/components/tts/yandextts.py b/homeassistant/components/tts/yandextts.py index fb95faf1ecf..05daad55412 100644 --- a/homeassistant/components/tts/yandextts.py +++ b/homeassistant/components/tts/yandextts.py @@ -66,7 +66,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_get_engine(hass, config): - """Setup VoiceRSS speech component.""" + """Set up VoiceRSS speech component.""" return YandexSpeechKitProvider(hass, config) @@ -86,12 +86,12 @@ class YandexSpeechKitProvider(Provider): @property def default_language(self): - """Default language.""" + """Return the default language.""" return self._language @property def supported_languages(self): - """List of supported languages.""" + """Return list of supported languages.""" return SUPPORT_LANGUAGES @asyncio.coroutine @@ -112,17 +112,17 @@ class YandexSpeechKitProvider(Provider): 'speed': self._speed } - request = yield from websession.get(YANDEX_API_URL, - params=url_param) + request = yield from websession.get( + YANDEX_API_URL, params=url_param) if request.status != 200: - _LOGGER.error("Error %d on load url %s.", + _LOGGER.error("Error %d on load URL %s", request.status, request.url) return (None, None) data = yield from request.read() except (asyncio.TimeoutError, aiohttp.ClientError): - _LOGGER.error("Timeout for yandex speech kit api.") + _LOGGER.error("Timeout for yandex speech kit API") return (None, None) return (self._codec, data) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index c6cd1a69d0c..137757fdbd4 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -4,22 +4,25 @@ Support to check for available updates. For more details about this component, please refer to the documentation at https://home-assistant.io/components/updater/ """ +import asyncio import json import logging import os import platform import uuid -from datetime import datetime, timedelta +from datetime import timedelta # pylint: disable=no-name-in-module, import-error from distutils.version import StrictVersion -import requests +import aiohttp +import async_timeout import voluptuous as vol +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -from homeassistant.const import __version__ as CURRENT_VERSION -from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, __version__ as CURRENT_VERSION) from homeassistant.helpers import event REQUIREMENTS = ['distro==1.0.4'] @@ -67,59 +70,63 @@ def _load_uuid(hass, filename=UPDATER_UUID_FILE): return _create_uuid(hass, filename) -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Set up the updater component.""" if 'dev' in CURRENT_VERSION: # This component only makes sense in release versions _LOGGER.warning("Running on 'dev', only analytics will be submitted") config = config.get(DOMAIN, {}) - huuid = _load_uuid(hass) if config.get(CONF_REPORTING) else None + if config.get(CONF_REPORTING): + huuid = yield from hass.async_add_job(_load_uuid, hass) + else: + huuid = None + + @asyncio.coroutine + def check_new_version(now): + """Check if a new version is available and report if one is.""" + result = yield from get_newest_version(hass, huuid) + + if result is None: + return + + newest, releasenotes = result + + if newest is None or 'dev' in CURRENT_VERSION: + return + + if StrictVersion(newest) > StrictVersion(CURRENT_VERSION): + _LOGGER.info("The latest available version is %s", newest) + hass.states.async_set( + ENTITY_ID, newest, {ATTR_FRIENDLY_NAME: 'Update Available', + ATTR_RELEASE_NOTES: releasenotes} + ) + elif StrictVersion(newest) == StrictVersion(CURRENT_VERSION): + _LOGGER.info( + "You are on the latest version (%s) of Home Assistant", newest) # Update daily, start 1 hour after startup - _dt = datetime.now() + timedelta(hours=1) - event.track_time_change( - hass, lambda _: check_newest_version(hass, huuid), + _dt = dt_util.utcnow() + timedelta(hours=1) + event.async_track_utc_time_change( + hass, check_new_version, hour=_dt.hour, minute=_dt.minute, second=_dt.second) return True -def check_newest_version(hass, huuid): - """Check if a new version is available and report if one is.""" - result = get_newest_version(huuid) - - if result is None: - return - - newest, releasenotes = result - - if newest is None or 'dev' in CURRENT_VERSION: - return - - if StrictVersion(newest) > StrictVersion(CURRENT_VERSION): - _LOGGER.info("The latest available version is %s", newest) - hass.states.set( - ENTITY_ID, newest, {ATTR_FRIENDLY_NAME: 'Update Available', - ATTR_RELEASE_NOTES: releasenotes} - ) - elif StrictVersion(newest) == StrictVersion(CURRENT_VERSION): - _LOGGER.info("You are on the latest version (%s) of Home Assistant", - newest) - - -def get_newest_version(huuid): - """Get the newest Home Assistant version.""" +@asyncio.coroutine +def get_system_info(hass): + """Return info about the system.""" info_object = { 'arch': platform.machine(), - 'dev': ('dev' in CURRENT_VERSION), + 'dev': 'dev' in CURRENT_VERSION, 'docker': False, 'os_name': platform.system(), 'python_version': platform.python_version(), 'timezone': dt_util.DEFAULT_TIME_ZONE.zone, - 'uuid': huuid, 'version': CURRENT_VERSION, - 'virtualenv': (os.environ.get('VIRTUAL_ENV') is not None), + 'virtualenv': os.environ.get('VIRTUAL_ENV') is not None, } if platform.system() == 'Windows': @@ -130,32 +137,44 @@ def get_newest_version(huuid): info_object['os_version'] = platform.release() elif platform.system() == 'Linux': import distro - linux_dist = distro.linux_distribution(full_distribution_name=False) + linux_dist = yield from hass.async_add_job( + distro.linux_distribution, False) info_object['distribution'] = linux_dist[0] info_object['os_version'] = linux_dist[1] info_object['docker'] = os.path.isfile('/.dockerenv') - if not huuid: + return info_object + + +@asyncio.coroutine +def get_newest_version(hass, huuid): + """Get the newest Home Assistant version.""" + if huuid: + info_object = yield from get_system_info(hass) + info_object['huuid'] = huuid + else: info_object = {} - res = None + session = async_get_clientsession(hass) try: - req = requests.post(UPDATER_URL, json=info_object, timeout=5) - res = req.json() - res = RESPONSE_SCHEMA(res) - + with async_timeout.timeout(5, loop=hass.loop): + req = yield from session.post(UPDATER_URL, json=info_object) _LOGGER.info(("Submitted analytics to Home Assistant servers. " "Information submitted includes %s"), info_object) - return (res['version'], res['release-notes']) - except requests.RequestException: + except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Could not contact Home Assistant Update to check " "for updates") return None + try: + res = yield from req.json() except ValueError: - _LOGGER.error("Received invalid response from Home Assistant Update") + _LOGGER.error("Received invalid JSON from Home Assistant Update") return None + try: + res = RESPONSE_SCHEMA(res) + return (res['version'], res['release-notes']) except vol.Invalid: _LOGGER.error('Got unexpected response: %s', res) return None diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py index 9d7494147e0..8eb381a0b85 100644 --- a/homeassistant/components/vera.py +++ b/homeassistant/components/vera.py @@ -20,7 +20,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyvera==0.2.26'] +REQUIREMENTS = ['pyvera==0.2.30'] _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,7 @@ VERA_COMPONENTS = [ # pylint: disable=unused-argument, too-many-function-args def setup(hass, base_config): - """Common setup for Vera devices.""" + """Set up for Vera devices.""" global VERA_CONTROLLER import pyvera as veraApi @@ -102,7 +102,7 @@ def setup(hass, base_config): # pylint: disable=too-many-return-statements def map_vera_device(vera_device, remap): - """Map vera classes to HA types.""" + """Map vera classes to Home Assistant types.""" import pyvera as veraApi if isinstance(vera_device, veraApi.VeraDimmer): return 'light' @@ -118,6 +118,8 @@ def map_vera_device(vera_device, remap): return 'climate' if isinstance(vera_device, veraApi.VeraCurtain): return 'cover' + if isinstance(vera_device, veraApi.VeraSceneController): + return 'sensor' if isinstance(vera_device, veraApi.VeraSwitch): if vera_device.device_id in remap: return 'light' @@ -143,6 +145,7 @@ class VeraDevice(Entity): self.update() def _update_callback(self, _device): + """Update the state.""" self.update() self.schedule_update_ha_state() @@ -153,8 +156,8 @@ class VeraDevice(Entity): @property def should_poll(self): - """No polling needed.""" - return False + """Get polling requirement from vera device.""" + return self.vera_device.should_poll @property def device_state_attributes(self): diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index f2b091aa0f1..72837b07019 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -23,6 +23,7 @@ REQUIREMENTS = ['vsure==0.11.1'] _LOGGER = logging.getLogger(__name__) ATTR_DEVICE_SERIAL = 'device_serial' + CONF_ALARM = 'alarm' CONF_CODE_DIGITS = 'code_digits' CONF_HYDROMETERS = 'hygrometers' @@ -31,7 +32,9 @@ CONF_MOUSE = 'mouse' CONF_SMARTPLUGS = 'smartplugs' CONF_THERMOMETERS = 'thermometers' CONF_SMARTCAM = 'smartcam' + DOMAIN = 'verisure' + SERVICE_CAPTURE_SMARTCAM = 'capture_smartcam' HUB = None @@ -57,7 +60,7 @@ CAPTURE_IMAGE_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Verisure component.""" + """Set up the Verisure component.""" import verisure global HUB HUB = VerisureHub(config[DOMAIN], verisure) @@ -75,7 +78,7 @@ def setup(hass, config): """Capture a new picture from a smartcam.""" device_id = service.data.get(ATTR_DEVICE_SERIAL) HUB.smartcam_capture(device_id) - _LOGGER.debug('Capturing new image from %s', ATTR_DEVICE_SERIAL) + _LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL) hass.services.register(DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, @@ -119,7 +122,7 @@ class VerisureHub(object): try: self.my_pages.login() except self._verisure.Error as ex: - _LOGGER.error('Could not log in to verisure mypages, %s', ex) + _LOGGER.error("Could not log in to verisure mypages, %s", ex) return False return True @@ -168,9 +171,9 @@ class VerisureHub(object): @Throttle(timedelta(seconds=30)) def update_smartcam_imagelist(self): """Update the imagelist for the camera.""" - _LOGGER.debug('Running update imagelist') + _LOGGER.debug("Running update imagelist") self.smartcam_dict = self.my_pages.smartcam.get_imagelist() - _LOGGER.debug('New dict: %s', self.smartcam_dict) + _LOGGER.debug("New dict: %s", self.smartcam_dict) @Throttle(timedelta(seconds=30)) def smartcam_capture(self, device_id): @@ -191,7 +194,7 @@ class VerisureHub(object): except AttributeError: status[overview.deviceLabel] = overview except self._verisure.Error as ex: - _LOGGER.info('Caught connection error %s, tries to reconnect', ex) + _LOGGER.info("Caught connection error %s, tries to reconnect", ex) self.reconnect() def reconnect(self): diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall.py index 04420bec567..da419ff0ab3 100644 --- a/homeassistant/components/volvooncall.py +++ b/homeassistant/components/volvooncall.py @@ -56,7 +56,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the VOC component.""" + """Set up the Volvo On Call component.""" from volvooncall import Connection connection = Connection( config[DOMAIN].get(CONF_USERNAME), @@ -79,14 +79,11 @@ def setup(hass, config): for attr, (component, *_) in RESOURCES.items(): if (getattr(vehicle, attr + '_supported', True) and attr in config[DOMAIN].get(CONF_RESOURCES, [attr])): - discovery.load_platform(hass, - component, - DOMAIN, - (vehicle.vin, attr), - config) + discovery.load_platform( + hass, component, DOMAIN, (vehicle.vin, attr), config) def update_vehicle(vehicle): - """Updated information on vehicle received.""" + """Revieve updated information on vehicle.""" state.vehicles[vehicle.vin] = vehicle if vehicle.vin not in state.entities: discover_vehicle(vehicle) @@ -101,7 +98,7 @@ def setup(hass, config): """Update status from the online service.""" try: if not connection.update(): - _LOGGER.warning('Could not query server') + _LOGGER.warning("Could not query server") return False for vehicle in connection.vehicles: @@ -111,7 +108,7 @@ def setup(hass, config): finally: track_point_in_utc_time(hass, update, utcnow() + interval) - _LOGGER.info('Logging in to service') + _LOGGER.info("Logging in to service") return update(utcnow()) @@ -154,7 +151,7 @@ class VolvoEntity(Entity): @property def should_poll(self): - """Polling is not needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index d67af26f560..9b0daf10efb 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -36,7 +36,7 @@ ATTR_FORECAST_TIME = 'datetime' @asyncio.coroutine def async_setup(hass, config): - """Setup the weather component.""" + """Set up the weather component.""" component = EntityComponent(_LOGGER, DOMAIN, hass) yield from component.async_setup(config) diff --git a/homeassistant/components/weather/demo.py b/homeassistant/components/weather/demo.py index 00470e86e1b..0a404447346 100644 --- a/homeassistant/components/weather/demo.py +++ b/homeassistant/components/weather/demo.py @@ -29,7 +29,7 @@ CONDITION_CLASSES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Demo weather.""" + """Set up the Demo weather.""" add_devices([ DemoWeather('South', 'Sunshine', 21, 92, 1099, 0.5, TEMP_CELSIUS, [22, 19, 15, 12, 14, 18, 21]), diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py index aa3213c3832..088ca359cc1 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/weather/openweathermap.py @@ -53,7 +53,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the OpenWeatherMap weather platform.""" + """Set up the OpenWeatherMap weather platform.""" import pyowm longitude = config.get(CONF_LONGITUDE, round(hass.config.longitude, 5)) diff --git a/homeassistant/components/weblink.py b/homeassistant/components/weblink.py index 7fe121d64c9..f55fe1f0bb5 100644 --- a/homeassistant/components/weblink.py +++ b/homeassistant/components/weblink.py @@ -34,7 +34,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup weblink component.""" + """Set up the weblink component.""" links = config.get(DOMAIN) for link in links.get(CONF_ENTITIES): diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py index a3557a301c5..466236573c8 100644 --- a/homeassistant/components/websocket_api.py +++ b/homeassistant/components/websocket_api.py @@ -1,4 +1,9 @@ -"""Websocket based API for Home Assistant.""" +""" +Websocket based API for Home Assistant. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/developers/websocket_api/ +""" import asyncio from functools import partial import json @@ -22,7 +27,7 @@ from homeassistant.components.http.ban import process_wrong_login DOMAIN = 'websocket_api' -URL = "/api/websocket" +URL = '/api/websocket' DEPENDENCIES = 'http', ERR_ID_REUSE = 1 @@ -209,16 +214,19 @@ class ActiveConnection: def debug(self, message1, message2=''): """Print a debug message.""" - _LOGGER.debug('WS %s: %s %s', id(self.wsock), message1, message2) + _LOGGER.debug("WS %s: %s %s", id(self.wsock), message1, message2) def log_error(self, message1, message2=''): """Print an error message.""" - _LOGGER.error('WS %s: %s %s', id(self.wsock), message1, message2) + _LOGGER.error("WS %s: %s %s", id(self.wsock), message1, message2) def send_message(self, message): - """Helper method to send messages.""" - self.debug('Sending', message) - self.wsock.send_json(message, dumps=JSON_DUMP) + """Send messages. + + Returns a coroutine object. + """ + self.debug("Sending", message) + return self.wsock.send_json(message, dumps=JSON_DUMP) @asyncio.coroutine def handle(self): @@ -234,10 +242,10 @@ class ActiveConnection: """Cancel this connection.""" socket_task.cancel() - unsub_stop = self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, - cancel_connection) + unsub_stop = self.hass.bus.async_listen( + EVENT_HOMEASSISTANT_STOP, cancel_connection) - self.debug('Connected') + self.debug("Connected") msg = None authenticated = False @@ -247,7 +255,7 @@ class ActiveConnection: authenticated = True else: - self.send_message(auth_required_message()) + yield from self.send_message(auth_required_message()) msg = yield from wsock.receive_json() msg = AUTH_MESSAGE_SCHEMA(msg) @@ -255,38 +263,39 @@ class ActiveConnection: authenticated = True else: - self.debug('Invalid password') - self.send_message(auth_invalid_message('Invalid password')) + self.debug("Invalid password") + yield from self.send_message( + auth_invalid_message('Invalid password')) if not authenticated: yield from process_wrong_login(self.request) return wsock - self.send_message(auth_ok_message()) + yield from self.send_message(auth_ok_message()) msg = yield from wsock.receive_json() last_id = 0 while msg: - self.debug('Received', msg) + self.debug("Received", msg) msg = BASE_COMMAND_MESSAGE_SCHEMA(msg) cur_id = msg['id'] if cur_id <= last_id: - self.send_message(error_message( + yield from self.send_message(error_message( cur_id, ERR_ID_REUSE, 'Identifier values have to increase.')) else: handler_name = 'handle_{}'.format(msg['type']) - getattr(self, handler_name)(msg) + yield from getattr(self, handler_name)(msg) last_id = cur_id msg = yield from wsock.receive_json() except vol.Invalid as err: - error_msg = 'Message incorrectly formatted: ' + error_msg = "Message incorrectly formatted: " if msg: error_msg += humanize_error(msg, err) else: @@ -295,7 +304,7 @@ class ActiveConnection: self.log_error(error_msg) if not authenticated: - self.send_message(auth_invalid_message(error_msg)) + yield from self.send_message(auth_invalid_message(error_msg)) else: if isinstance(msg, dict): @@ -303,27 +312,27 @@ class ActiveConnection: else: iden = None - self.send_message(error_message(iden, ERR_INVALID_FORMAT, - error_msg)) + yield from self.send_message(error_message( + iden, ERR_INVALID_FORMAT, error_msg)) except TypeError as err: if wsock.closed: - self.debug('Connection closed by client') + self.debug("Connection closed by client") else: - self.log_error('Unexpected TypeError', msg) + self.log_error("Unexpected TypeError", msg) except ValueError as err: - msg = 'Received invalid JSON' + msg = "Received invalid JSON" value = getattr(err, 'doc', None) # Py3.5+ only if value: msg += ': {}'.format(value) self.log_error(msg) except asyncio.CancelledError: - self.debug('Connection cancelled by server') + self.debug("Connection cancelled by server") except Exception: # pylint: disable=broad-except - error = 'Unexpected error inside websocket API. ' + error = "Unexpected error inside websocket API. " if msg is not None: error += str(msg) _LOGGER.exception(error) @@ -335,22 +344,23 @@ class ActiveConnection: unsub() yield from wsock.close() - self.debug('Closed connection') + self.debug("Closed connection") return wsock + @asyncio.coroutine def handle_subscribe_events(self, msg): """Handle subscribe events command.""" msg = SUBSCRIBE_EVENTS_MESSAGE_SCHEMA(msg) - @callback + @asyncio.coroutine def forward_events(event): - """Helper to forward events to websocket.""" + """Forward events to websocket.""" if event.event_type == EVENT_TIME_CHANGED: return try: - self.send_message(event_message(msg['id'], event)) + yield from self.send_message(event_message(msg['id'], event)) except RuntimeError: # Socket has been closed. pass @@ -358,33 +368,40 @@ class ActiveConnection: self.event_listeners[msg['id']] = self.hass.bus.async_listen( msg['event_type'], forward_events) - self.send_message(result_message(msg['id'])) + return self.send_message(result_message(msg['id'])) def handle_unsubscribe_events(self, msg): - """Handle unsubscribe events command.""" + """Handle unsubscribe events command. + + Returns a coroutine object. + """ msg = UNSUBSCRIBE_EVENTS_MESSAGE_SCHEMA(msg) subscription = msg['subscription'] - if subscription not in self.event_listeners: - self.send_message(error_message( + if subscription in self.event_listeners: + self.event_listeners.pop(subscription)() + return self.send_message(result_message(msg['id'])) + else: + return self.send_message(error_message( msg['id'], ERR_NOT_FOUND, 'Subscription not found.')) - else: - self.event_listeners.pop(subscription)() - self.send_message(result_message(msg['id'])) + @asyncio.coroutine def handle_call_service(self, msg): - """Handle call service command.""" + """Handle call service command. + + This is a coroutine. + """ msg = CALL_SERVICE_MESSAGE_SCHEMA(msg) @asyncio.coroutine def call_service_helper(msg): - """Helper to call a service and fire complete message.""" + """Call a service and fire complete message.""" yield from self.hass.services.async_call( msg['domain'], msg['service'], msg['service_data'], True) try: - self.send_message(result_message(msg['id'])) + yield from self.send_message(result_message(msg['id'])) except RuntimeError: # Socket has been closed. pass @@ -392,33 +409,48 @@ class ActiveConnection: self.hass.async_add_job(call_service_helper(msg)) def handle_get_states(self, msg): - """Handle get states command.""" + """Handle get states command. + + Returns a coroutine object. + """ msg = GET_STATES_MESSAGE_SCHEMA(msg) - self.send_message(result_message(msg['id'], - self.hass.states.async_all())) + return self.send_message(result_message( + msg['id'], self.hass.states.async_all())) def handle_get_services(self, msg): - """Handle get services command.""" + """Handle get services command. + + Returns a coroutine object. + """ msg = GET_SERVICES_MESSAGE_SCHEMA(msg) - self.send_message(result_message(msg['id'], - self.hass.services.async_services())) + return self.send_message(result_message( + msg['id'], self.hass.services.async_services())) def handle_get_config(self, msg): - """Handle get config command.""" + """Handle get config command. + + Returns a coroutine object. + """ msg = GET_CONFIG_MESSAGE_SCHEMA(msg) - self.send_message(result_message(msg['id'], - self.hass.config.as_dict())) + return self.send_message(result_message( + msg['id'], self.hass.config.as_dict())) def handle_get_panels(self, msg): - """Handle get panels command.""" + """Handle get panels command. + + Returns a coroutine object. + """ msg = GET_PANELS_MESSAGE_SCHEMA(msg) - self.send_message(result_message( + return self.send_message(result_message( msg['id'], self.hass.data[frontend.DATA_PANELS])) def handle_ping(self, msg): - """Handle ping command.""" - self.send_message(pong_message(msg['id'])) + """Handle ping command. + + Returns a coroutine object. + """ + return self.send_message(pong_message(msg['id'])) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 98eefbc42d8..3d7226e3c8b 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -45,7 +45,7 @@ CONFIG_SCHEMA = vol.Schema({ # pylint: disable=unused-argument, too-many-function-args def setup(hass, config): - """Common setup for WeMo devices.""" + """Set up for WeMo devices.""" import pywemo global SUBSCRIPTION_REGISTRY diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 8cf32f64d61..c22e32b51d4 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -5,17 +5,21 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/wink/ """ import logging +import time +import json +from datetime import timedelta import voluptuous as vol from homeassistant.helpers import discovery +from homeassistant.helpers.event import track_time_interval from homeassistant.const import ( CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-wink==1.2.3', 'pubnubsub-handler==1.0.2'] +REQUIREMENTS = ['python-wink==1.2.4', 'pubnubsub-handler==1.0.2'] _LOGGER = logging.getLogger(__name__) @@ -33,6 +37,9 @@ CONF_DEFINED_BOTH_MSG = 'Remove access token to use oath2.' CONF_MISSING_OATH_MSG = 'Missing oath2 credentials.' CONF_TOKEN_URL = "https://winkbearertoken.appspot.com/token" +SERVICE_ADD_NEW_DEVICES = 'add_new_devices' +SERVICE_REFRESH_STATES = 'refresh_state_from_wink' + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Inclusive(CONF_EMAIL, CONF_APPSPOT, @@ -65,6 +72,10 @@ def setup(hass, config): import requests from pubnubsubhandler import PubNubSubscriptionHandler + hass.data[DOMAIN] = {} + hass.data[DOMAIN]['entities'] = [] + hass.data[DOMAIN]['unique_ids'] = [] + user_agent = config[DOMAIN].get(CONF_USER_AGENT) if user_agent: @@ -73,18 +84,10 @@ def setup(hass, config): access_token = config[DOMAIN].get(CONF_ACCESS_TOKEN) client_id = config[DOMAIN].get('client_id') - if access_token: - pywink.set_bearer_token(access_token) - elif client_id: - email = config[DOMAIN][CONF_EMAIL] - password = config[DOMAIN][CONF_PASSWORD] - client_id = config[DOMAIN]['client_id'] - client_secret = config[DOMAIN]['client_secret'] - pywink.set_wink_credentials(email, password, client_id, - client_secret) - else: - email = config[DOMAIN][CONF_EMAIL] - password = config[DOMAIN][CONF_PASSWORD] + def _get_wink_token_from_web(): + email = hass.data[DOMAIN]["oath"]["email"] + password = hass.data[DOMAIN]["oath"]["password"] + payload = {'username': email, 'password': password} token_response = requests.post(CONF_TOKEN_URL, data=payload) try: @@ -94,12 +97,49 @@ def setup(hass, config): return False pywink.set_bearer_token(token) - hass.data[DOMAIN] = {} - hass.data[DOMAIN]['entities'] = [] - hass.data[DOMAIN]['unique_ids'] = [] + if access_token: + pywink.set_bearer_token(access_token) + elif client_id: + email = config[DOMAIN][CONF_EMAIL] + password = config[DOMAIN][CONF_PASSWORD] + client_id = config[DOMAIN]['client_id'] + client_secret = config[DOMAIN]['client_secret'] + pywink.set_wink_credentials(email, password, client_id, client_secret) + hass.data[DOMAIN]['oath'] = {"email": email, + "password": password, + "client_id": client_id, + "client_secret": client_secret} + else: + email = config[DOMAIN][CONF_EMAIL] + password = config[DOMAIN][CONF_PASSWORD] + hass.data[DOMAIN]['oath'] = {"email": email, "password": password} + _get_wink_token_from_web() + hass.data[DOMAIN]['pubnub'] = PubNubSubscriptionHandler( - pywink.get_subscription_key(), - pywink.wink_api_fetch) + pywink.get_subscription_key()) + + def keep_alive_call(event_time): + """Call the Wink API endpoints to keep PubNub working.""" + _LOGGER.info("Getting a new Wink token.") + if hass.data[DOMAIN]["oath"].get("client_id") is not None: + _email = hass.data[DOMAIN]["oath"]["email"] + _password = hass.data[DOMAIN]["oath"]["password"] + _client_id = hass.data[DOMAIN]["oath"]["client_id"] + _client_secret = hass.data[DOMAIN]["oath"]["client_secret"] + pywink.set_wink_credentials(_email, _password, _client_id, + _client_secret) + else: + _LOGGER.info("Getting a new Wink token.") + _get_wink_token_from_web() + time.sleep(1) + _LOGGER.info("Polling the Wink API to keep PubNub updates flowing.") + _LOGGER.debug(str(json.dumps(pywink.wink_api_fetch()))) + time.sleep(1) + _LOGGER.debug(str(json.dumps(pywink.get_user()))) + + # Call the Wink API every hour to keep PubNub updates flowing + if access_token is None: + track_time_interval(hass, keep_alive_call, timedelta(minutes=120)) def start_subscription(event): """Start the pubnub subscription.""" @@ -115,15 +155,17 @@ def setup(hass, config): """Force all devices to poll the Wink API.""" _LOGGER.info("Refreshing Wink states from API") for entity in hass.data[DOMAIN]['entities']: + # Throttle the calls to Wink API + time.sleep(1) entity.schedule_update_ha_state(True) - hass.services.register(DOMAIN, 'Refresh state from Wink', force_update) + hass.services.register(DOMAIN, SERVICE_REFRESH_STATES, force_update) def pull_new_devices(call): """Pull new devices added to users Wink account since startup.""" - _LOGGER.info("Getting new devices from Wink API.") + _LOGGER.info("Getting new devices from Wink API") for component in WINK_COMPONENTS: discovery.load_platform(hass, component, DOMAIN, {}, config) - hass.services.register(DOMAIN, 'Add new devices', pull_new_devices) + hass.services.register(DOMAIN, SERVICE_ADD_NEW_DEVICES, pull_new_devices) # Load components for the devices in Wink that we support for component in WINK_COMPONENTS: @@ -166,7 +208,7 @@ class WinkDevice(Entity): @property def available(self): - """True if connection == True.""" + """Return true if connection == True.""" return self.wink.available() def update(self): diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py new file mode 100644 index 00000000000..8c84fe166f0 --- /dev/null +++ b/homeassistant/components/zha/__init__.py @@ -0,0 +1,299 @@ +""" +Support for ZigBee Home Automation devices. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import asyncio +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant import const as ha_const +from homeassistant.helpers import discovery, entity +from homeassistant.util import slugify + +REQUIREMENTS = ['bellows==0.2.7'] + +DOMAIN = 'zha' + +CONF_USB_PATH = 'usb_path' +CONF_DATABASE = 'database_path' +CONF_DEVICE_CONFIG = 'device_config' +DATA_DEVICE_CONFIG = 'zha_device_config' + +DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({ + vol.Optional(ha_const.CONF_TYPE): cv.string, +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + CONF_USB_PATH: cv.string, + CONF_DATABASE: cv.string, + vol.Optional(CONF_DEVICE_CONFIG, default={}): + vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}), + }) +}, extra=vol.ALLOW_EXTRA) + +ATTR_DURATION = 'duration' + +SERVICE_PERMIT = 'permit' +SERVICE_DESCRIPTIONS = { + SERVICE_PERMIT: { + "description": "Allow nodes to join the Zigbee network", + "fields": { + "duration": { + "description": "Time to permit joins, in seconds", + "example": "60", + }, + }, + }, +} +SERVICE_SCHEMAS = { + SERVICE_PERMIT: vol.Schema({ + vol.Optional(ATTR_DURATION, default=60): + vol.All(vol.Coerce(int), vol.Range(1, 254)), + }), +} + + +# ZigBee definitions +CENTICELSIUS = 'C-100' +# Key in hass.data dict containing discovery info +DISCOVERY_KEY = 'zha_discovery_info' + +# Internal definitions +APPLICATION_CONTROLLER = None +_LOGGER = logging.getLogger(__name__) + + +@asyncio.coroutine +def async_setup(hass, config): + """Set up ZHA. + + Will automatically load components to support devices found on the network. + """ + global APPLICATION_CONTROLLER + + import bellows.ezsp + from bellows.zigbee.application import ControllerApplication + + ezsp_ = bellows.ezsp.EZSP() + usb_path = config[DOMAIN].get(CONF_USB_PATH) + yield from ezsp_.connect(usb_path) + + database = config[DOMAIN].get(CONF_DATABASE) + APPLICATION_CONTROLLER = ControllerApplication(ezsp_, database) + listener = ApplicationListener(hass, config) + APPLICATION_CONTROLLER.add_listener(listener) + yield from APPLICATION_CONTROLLER.startup(auto_form=True) + + for device in APPLICATION_CONTROLLER.devices.values(): + hass.async_add_job(listener.async_device_initialized(device, False)) + + @asyncio.coroutine + def permit(service): + """Allow devices to join this network.""" + duration = service.data.get(ATTR_DURATION) + _LOGGER.info("Permitting joins for %ss", duration) + yield from APPLICATION_CONTROLLER.permit(duration) + + hass.services.async_register(DOMAIN, SERVICE_PERMIT, permit, + SERVICE_DESCRIPTIONS[SERVICE_PERMIT], + SERVICE_SCHEMAS[SERVICE_PERMIT]) + + return True + + +class ApplicationListener: + """All handlers for events that happen on the ZigBee application.""" + + def __init__(self, hass, config): + """Initialize the listener.""" + self._hass = hass + self._config = config + hass.data[DISCOVERY_KEY] = hass.data.get(DISCOVERY_KEY, {}) + + def device_joined(self, device): + """Handle device joined. + + At this point, no information about the device is known other than its + address + """ + # Wait for device_initialized, instead + pass + + def device_initialized(self, device): + """Handle device joined and basic information discovered.""" + self._hass.async_add_job(self.async_device_initialized(device, True)) + + @asyncio.coroutine + def async_device_initialized(self, device, join): + """Handle device joined and basic information discovered (async).""" + import bellows.zigbee.profiles + import homeassistant.components.zha.const as zha_const + zha_const.populate_data() + + for endpoint_id, endpoint in device.endpoints.items(): + if endpoint_id == 0: # ZDO + continue + + discovered_info = yield from _discover_endpoint_info(endpoint) + + component = None + used_clusters = [] + device_key = '%s-%s' % (str(device.ieee), endpoint_id) + node_config = self._config[DOMAIN][CONF_DEVICE_CONFIG].get( + device_key, {}) + + if endpoint.profile_id in bellows.zigbee.profiles.PROFILES: + profile = bellows.zigbee.profiles.PROFILES[endpoint.profile_id] + if zha_const.DEVICE_CLASS.get(endpoint.profile_id, + {}).get(endpoint.device_type, + None): + used_clusters = profile.CLUSTERS[endpoint.device_type] + profile_info = zha_const.DEVICE_CLASS[endpoint.profile_id] + component = profile_info[endpoint.device_type] + + if ha_const.CONF_TYPE in node_config: + component = node_config[ha_const.CONF_TYPE] + used_clusters = zha_const.COMPONENT_CLUSTERS[component] + + if component: + clusters = [endpoint.clusters[c] for c in used_clusters if c in + endpoint.clusters] + discovery_info = { + 'endpoint': endpoint, + 'clusters': clusters, + 'new_join': join, + } + discovery_info.update(discovered_info) + self._hass.data[DISCOVERY_KEY][device_key] = discovery_info + + yield from discovery.async_load_platform( + self._hass, + component, + DOMAIN, + {'discovery_key': device_key}, + self._config, + ) + + for cluster_id, cluster in endpoint.clusters.items(): + cluster_type = type(cluster) + if cluster_id in used_clusters: + continue + if cluster_type not in zha_const.SINGLE_CLUSTER_DEVICE_CLASS: + continue + + component = zha_const.SINGLE_CLUSTER_DEVICE_CLASS[cluster_type] + discovery_info = { + 'endpoint': endpoint, + 'clusters': [cluster], + 'new_join': join, + } + discovery_info.update(discovered_info) + cluster_key = '%s-%s' % (device_key, cluster_id) + self._hass.data[DISCOVERY_KEY][cluster_key] = discovery_info + + yield from discovery.async_load_platform( + self._hass, + component, + DOMAIN, + {'discovery_key': cluster_key}, + self._config, + ) + + +class Entity(entity.Entity): + """A base class for ZHA entities.""" + + _domain = None # Must be overriden by subclasses + + def __init__(self, endpoint, clusters, manufacturer, model, **kwargs): + """Init ZHA entity.""" + self._device_state_attributes = {} + ieeetail = ''.join([ + '%02x' % (o, ) for o in endpoint.device.ieee[-4:] + ]) + if manufacturer and model is not None: + self.entity_id = '%s.%s_%s_%s_%s' % ( + self._domain, + slugify(manufacturer), + slugify(model), + ieeetail, + endpoint.endpoint_id, + ) + self._device_state_attributes['friendly_name'] = '%s %s' % ( + manufacturer, + model, + ) + else: + self.entity_id = "%s.zha_%s_%s" % ( + self._domain, + ieeetail, + endpoint.endpoint_id, + ) + for cluster in clusters: + cluster.add_listener(self) + self._endpoint = endpoint + self._clusters = {c.cluster_id: c for c in clusters} + self._state = ha_const.STATE_UNKNOWN + + def attribute_updated(self, attribute, value): + """Handle an attribute updated on this cluster.""" + pass + + def zdo_command(self, aps_frame, tsn, command_id, args): + """Handle a ZDO command received on this cluster.""" + pass + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return self._device_state_attributes + + +@asyncio.coroutine +def _discover_endpoint_info(endpoint): + """Find some basic information about an endpoint.""" + extra_info = { + 'manufacturer': None, + 'model': None, + } + if 0 not in endpoint.clusters: + return extra_info + + result, _ = yield from endpoint.clusters[0].read_attributes( + ['manufacturer', 'model'], + allow_cache=True, + ) + extra_info.update(result) + + for key, value in extra_info.items(): + if isinstance(value, bytes): + try: + extra_info[key] = value.decode('ascii') + except UnicodeDecodeError: + # Unsure what the best behaviour here is. Unset the key? + pass + + return extra_info + + +def get_discovery_info(hass, discovery_info): + """Get the full discovery info for a device. + + Some of the info that needs to be passed to platforms is not JSON + serializable, so it cannot be put in the discovery_info dictionary. This + component places that info we need to pass to the platform in hass.data, + and this function is a helper for platforms to retrieve the complete + discovery info. + """ + if discovery_info is None: + return + + discovery_key = discovery_info.get('discovery_key', None) + all_discovery_info = hass.data.get(DISCOVERY_KEY, {}) + discovery_info = all_discovery_info.get(discovery_key, None) + return discovery_info diff --git a/homeassistant/components/zha/const.py b/homeassistant/components/zha/const.py new file mode 100644 index 00000000000..ed06f18c1f5 --- /dev/null +++ b/homeassistant/components/zha/const.py @@ -0,0 +1,51 @@ +"""All constants related to the ZHA component.""" + +DEVICE_CLASS = {} +SINGLE_CLUSTER_DEVICE_CLASS = {} +COMPONENT_CLUSTERS = {} + + +def populate_data(): + """Populate data using constants from bellows. + + These cannot be module level, as importing bellows must be done in a + in a function. + """ + from bellows.zigbee import zcl + from bellows.zigbee.profiles import PROFILES, zha, zll + + DEVICE_CLASS[zha.PROFILE_ID] = { + zha.DeviceType.ON_OFF_SWITCH: 'switch', + zha.DeviceType.SMART_PLUG: 'switch', + + zha.DeviceType.ON_OFF_LIGHT: 'light', + zha.DeviceType.DIMMABLE_LIGHT: 'light', + zha.DeviceType.COLOR_DIMMABLE_LIGHT: 'light', + zha.DeviceType.ON_OFF_LIGHT_SWITCH: 'light', + zha.DeviceType.DIMMER_SWITCH: 'light', + zha.DeviceType.COLOR_DIMMER_SWITCH: 'light', + } + DEVICE_CLASS[zll.PROFILE_ID] = { + zll.DeviceType.ON_OFF_LIGHT: 'light', + zll.DeviceType.ON_OFF_PLUGIN_UNIT: 'switch', + zll.DeviceType.DIMMABLE_LIGHT: 'light', + zll.DeviceType.DIMMABLE_PLUGIN_UNIT: 'light', + zll.DeviceType.COLOR_LIGHT: 'light', + zll.DeviceType.EXTENDED_COLOR_LIGHT: 'light', + zll.DeviceType.COLOR_TEMPERATURE_LIGHT: 'light', + } + + SINGLE_CLUSTER_DEVICE_CLASS.update({ + zcl.clusters.general.OnOff: 'switch', + zcl.clusters.measurement.TemperatureMeasurement: 'sensor', + zcl.clusters.security.IasZone: 'binary_sensor', + }) + + # A map of hass components to all Zigbee clusters it could use + for profile_id, classes in DEVICE_CLASS.items(): + profile = PROFILES[profile_id] + for device_type, component in classes.items(): + if component not in COMPONENT_CLUSTERS: + COMPONENT_CLUSTERS[component] = set() + clusters = profile.CLUSTERS[device_type] + COMPONENT_CLUSTERS[component].update(clusters) diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py index 817e7e432db..95b0971373d 100644 --- a/homeassistant/components/zigbee.py +++ b/homeassistant/components/zigbee.py @@ -61,7 +61,7 @@ PLATFORM_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the connection to the ZigBee device.""" + """Set up the connection to the ZigBee device.""" global DEVICE global GPIO_DIGITAL_OUTPUT_LOW global GPIO_DIGITAL_OUTPUT_HIGH @@ -98,7 +98,7 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_serial_port) def _frame_received(frame): - """Called when a ZigBee frame is received. + """Run when a ZigBee frame is received. Pickles the frame, then encodes it into base64 since it contains non JSON serializable binary. @@ -134,12 +134,12 @@ class ZigBeeConfig(object): @property def name(self): - """The name given to the entity.""" + """Return the name given to the entity.""" return self._config["name"] @property def address(self): - """The address of the device. + """Return the address of the device. If an address has been provided, unhexlify it, otherwise return None as we're talking to our local ZigBee device. @@ -151,16 +151,16 @@ class ZigBeeConfig(object): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return self._should_poll class ZigBeePinConfig(ZigBeeConfig): - """Handle the fetching of configuration from the config file.""" + """Handle the fetching of configuration from the configuration file.""" @property def pin(self): - """The GPIO pin number.""" + """Return the GPIO pin number.""" return self._config["pin"] @@ -195,7 +195,7 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): @property def bool2state(self): - """A dictionary mapping the internal value to the ZigBee value. + """Return a dictionary mapping the internal value to the ZigBee value. For the translation of on/off as being pin high or low. """ @@ -203,7 +203,7 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): @property def state2bool(self): - """A dictionary mapping the ZigBee value to the internal value. + """Return a dictionary mapping the ZigBee value to the internal value. For the translation of pin high/low as being on or off. """ @@ -245,7 +245,7 @@ class ZigBeeDigitalOutConfig(ZigBeePinConfig): @property def bool2state(self): - """A dictionary mapping booleans to GPIOSetting objects. + """Return a dictionary mapping booleans to GPIOSetting objects. For the translation of on/off as being pin high or low. """ @@ -253,7 +253,7 @@ class ZigBeeDigitalOutConfig(ZigBeePinConfig): @property def state2bool(self): - """A dictionary mapping GPIOSetting objects to booleans. + """Return a dictionary mapping GPIOSetting objects to booleans. For the translation of pin high/low as being on or off. """ @@ -265,7 +265,7 @@ class ZigBeeAnalogInConfig(ZigBeePinConfig): @property def max_voltage(self): - """The voltage at which the ADC will report its highest value.""" + """Return the voltage for ADC to report its highest value.""" return float(self._config.get("max_volts", DEFAULT_ADC_MAX_VOLTS)) @@ -306,7 +306,7 @@ class ZigBeeDigitalIn(Entity): @property def config(self): - """The entity's configuration.""" + """Return the entity's configuration.""" return self._config @property @@ -428,17 +428,17 @@ class ZigBeeAnalogIn(Entity): @property def name(self): - """The name of the input.""" + """Return the name of the input.""" return self._config.name @property def config(self): - """The entity's configuration.""" + """Return the entity's configuration.""" return self._config @property def should_poll(self): - """The state of the polling, if needed.""" + """Return the polling state, if needed.""" return self._config.should_poll @property diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index 235fe11934a..e1a8b8e721b 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -104,7 +104,7 @@ def in_zone(zone, latitude, longitude, radius=0): @asyncio.coroutine def async_setup(hass, config): - """Setup zone.""" + """Set up the zone.""" entities = set() tasks = [] for _, entry in config_per_platform(config, DOMAIN): @@ -112,8 +112,8 @@ def async_setup(hass, config): zone = Zone(hass, name, entry[CONF_LATITUDE], entry[CONF_LONGITUDE], entry.get(CONF_RADIUS), entry.get(CONF_ICON), entry.get(CONF_PASSIVE)) - zone.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, - entities) + zone.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, name, entities) tasks.append(zone.async_update_ha_state()) entities.add(zone.entity_id) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 0a32a664dc3..d6077763464 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -125,7 +125,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_DEVICE_CONFIG, default={}): vol.Schema({cv.entity_id: DEVICE_CONFIG_SCHEMA_ENTRY}), vol.Optional(CONF_DEVICE_CONFIG_GLOB, default={}): - cv.ordered_dict(DEVICE_CONFIG_SCHEMA_ENTRY, cv.string), + vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}), vol.Optional(CONF_DEVICE_CONFIG_DOMAIN, default={}): vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}), vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean, @@ -199,7 +199,7 @@ def get_config_value(node, value_index, tries=5): @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Generic Z-Wave platform setup.""" + """Set up the Z-Wave platform (generic part).""" if discovery_info is None or ZWAVE_NETWORK not in hass.data: return False @@ -214,7 +214,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # pylint: disable=R0914 def setup(hass, config): - """Setup Z-Wave. + """Set up Z-Wave. Will automatically load components to support devices found on the network. """ @@ -278,7 +278,7 @@ def setup(hass, config): discovered_values = [] def value_added(node, value): - """Called when a value is added to a node on the network.""" + """Handle new added value to a node on the network.""" # Check if this value should be tracked by an existing entity for values in discovered_values: values.check_value(value) @@ -298,18 +298,18 @@ def setup(hass, config): component = EntityComponent(_LOGGER, DOMAIN, hass) def node_added(node): - """Called when a node is added on the network.""" + """Handle a new node on the network.""" entity = ZWaveNodeEntity(node, network) node_config = device_config.get(entity.entity_id) if node_config.get(CONF_IGNORED): _LOGGER.info( - "Ignoring node entity %s due to device settings.", + "Ignoring node entity %s due to device settings", entity.entity_id) return component.add_entities([entity]) def scene_activated(node, scene_id): - """Called when a scene is activated on any node in the network.""" + """Handle an activated scene on any node in the network.""" hass.bus.fire(const.EVENT_SCENE_ACTIVATED, { ATTR_ENTITY_ID: _node_object_id(node), const.ATTR_OBJECT_ID: _node_object_id(node), @@ -317,23 +317,23 @@ def setup(hass, config): }) def node_event_activated(node, value): - """Called when a nodeevent is activated on any node in the network.""" + """Handle a nodeevent on any node in the network.""" hass.bus.fire(const.EVENT_NODE_EVENT, { const.ATTR_OBJECT_ID: _node_object_id(node), const.ATTR_BASIC_LEVEL: value }) def network_ready(): - """Called when all awake nodes have been queried.""" - _LOGGER.info("Zwave network is ready for use. All awake nodes" - " have been queried. Sleeping nodes will be" - " queried when they awake.") + """Handle the query of all awake nodes.""" + _LOGGER.info("Zwave network is ready for use. All awake nodes " + "have been queried. Sleeping nodes will be " + "queried when they awake.") hass.bus.fire(const.EVENT_NETWORK_READY) def network_complete(): - """Called when all nodes on network have been queried.""" - _LOGGER.info("Zwave network is complete. All nodes on the network" - " have been queried") + """Handle the querying of all nodes on network.""" + _LOGGER.info("Z-Wave network is complete. All nodes on the network " + "have been queried") hass.bus.fire(const.EVENT_NETWORK_COMPLETE) dispatcher.connect( @@ -351,42 +351,42 @@ def setup(hass, config): def add_node(service): """Switch into inclusion mode.""" - _LOGGER.info("Zwave add_node have been initialized.") + _LOGGER.info("Z-Wave add_node have been initialized") network.controller.add_node() def add_node_secure(service): """Switch into secure inclusion mode.""" - _LOGGER.info("Zwave add_node_secure have been initialized.") + _LOGGER.info("Z-Wave add_node_secure have been initialized") network.controller.add_node(True) def remove_node(service): """Switch into exclusion mode.""" - _LOGGER.info("Zwave remove_node have been initialized.") + _LOGGER.info("Z-Wwave remove_node have been initialized") network.controller.remove_node() def cancel_command(service): """Cancel a running controller command.""" - _LOGGER.info("Cancel running ZWave command.") + _LOGGER.info("Cancel running Z-Wave command") network.controller.cancel_command() def heal_network(service): """Heal the network.""" - _LOGGER.info("ZWave heal running.") + _LOGGER.info("Z-Wave heal running") network.heal() def soft_reset(service): """Soft reset the controller.""" - _LOGGER.info("Zwave soft_reset have been initialized.") + _LOGGER.info("Z-Wave soft_reset have been initialized") network.controller.soft_reset() def test_network(service): """Test the network by sending commands to all the nodes.""" - _LOGGER.info("Zwave test_network have been initialized.") + _LOGGER.info("Z-Wave test_network have been initialized") network.test() def stop_network(_service_or_event): """Stop Z-Wave network.""" - _LOGGER.info("Stopping ZWave network.") + _LOGGER.info("Stopping Z-Wave network") network.stop() if hass.state == CoreState.running: hass.bus.fire(const.EVENT_NETWORK_STOP) @@ -398,18 +398,18 @@ def setup(hass, config): name = service.data.get(const.ATTR_NAME) node.name = name _LOGGER.info( - "Renamed ZWave node %d to %s", node_id, name) + "Renamed Z-Wave node %d to %s", node_id, name) def remove_failed_node(service): """Remove failed node.""" node_id = service.data.get(const.ATTR_NODE_ID) - _LOGGER.info('Trying to remove zwave node %d', node_id) + _LOGGER.info("Trying to remove zwave node %d", node_id) network.controller.remove_failed_node(node_id) def replace_failed_node(service): """Replace failed node.""" node_id = service.data.get(const.ATTR_NODE_ID) - _LOGGER.info('Trying to replace zwave node %d', node_id) + _LOGGER.info("Trying to replace zwave node %d", node_id) network.controller.replace_failed_node(node_id) def set_config_parameter(service): @@ -424,21 +424,21 @@ def setup(hass, config): node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION) .values()): if value.index == param and value.type == const.TYPE_LIST: - _LOGGER.debug('Values for parameter %s: %s', param, + _LOGGER.debug("Values for parameter %s: %s", param, value.data_items) i = len(value.data_items) - 1 if i == 0: node.set_config_param(param, selection, size) else: if selection > i: - _LOGGER.info('Config parameter selection does not exist!' - ' Please check zwcfg_[home_id].xml in' - ' your homeassistant config directory. ' - ' Available selections are 0 to %s', i) + _LOGGER.info("Config parameter selection does not exist! " + "Please check zwcfg_[home_id].xml in " + "your homeassistant config directory. " + "Available selections are 0 to %s", i) return node.set_config_param(param, selection, size) - _LOGGER.info('Setting config parameter %s on Node %s ' - 'with selection %s and size=%s', param, node_id, + _LOGGER.info("Setting config parameter %s on Node %s " + "with selection %s and size=%s", param, node_id, selection, size) def print_config_parameter(service): @@ -446,7 +446,7 @@ def setup(hass, config): node_id = service.data.get(const.ATTR_NODE_ID) node = network.nodes[node_id] param = service.data.get(const.ATTR_CONFIG_PARAMETER) - _LOGGER.info("Config parameter %s on Node %s : %s", + _LOGGER.info("Config parameter %s on Node %s: %s", param, node_id, get_config_value(node, param)) def print_node(service): @@ -503,7 +503,7 @@ def setup(hass, config): def start_zwave(_service_or_event): """Startup Z-Wave network.""" - _LOGGER.info("Starting ZWave network.") + _LOGGER.info("Starting Z-Wave network...") network.start() hass.bus.fire(const.EVENT_NETWORK_START) @@ -515,7 +515,7 @@ def setup(hass, config): "network state: %d %s", hass.data[ZWAVE_NETWORK].state, network.state_str) if network.state >= network.STATE_AWAKED: - _LOGGER.info("zwave ready after %d seconds", i) + _LOGGER.info("Z-Wave ready after %d seconds", i) break time.sleep(1) else: @@ -532,7 +532,7 @@ def setup(hass, config): network.set_poll_interval(polling_interval, False) poll_interval = network.get_poll_interval() - _LOGGER.info("zwave polling interval set to %d ms", poll_interval) + _LOGGER.info("Z-Wave polling interval set to %d ms", poll_interval) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_network) @@ -611,7 +611,7 @@ def setup(hass, config): # Setup autoheal if autoheal: - _LOGGER.info("ZWave network autoheal is enabled.") + _LOGGER.info("Z-Wave network autoheal is enabled") track_time_change(hass, heal_network, hour=0, minute=0, second=0) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave) @@ -722,7 +722,7 @@ class ZWaveDeviceEntityValues(): if node_config.get(CONF_IGNORED): _LOGGER.info( - "Ignoring entity %s due to device settings.", name) + "Ignoring entity %s due to device settings", name) # No entity will be created for this value self._workaround_ignore = True return @@ -780,16 +780,16 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): self.network_value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) def network_value_changed(self, value): - """Called when a value has changed on the network.""" + """Handle a value change on the network.""" if value.value_id in [v.value_id for v in self.values if v]: return self.value_changed() def value_added(self): - """Called when a new value is added to this entity.""" + """Handle a new value of this entity.""" pass def value_changed(self): - """Called when a value for this entity's node has changed.""" + """Handle a changed value for this entity's node.""" self._update_attributes() self.update_properties() self.maybe_schedule_update() @@ -813,7 +813,7 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): self.power_consumption = None def update_properties(self): - """Callback on data changes for node values.""" + """Update on data changes for node values.""" pass @property diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index e43ee735ac7..19527e59792 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -96,7 +96,7 @@ class ZWaveNodeEntity(ZWaveBaseEntity): self.network_node_changed, ZWaveNetwork.SIGNAL_NOTIFICATION) def network_node_changed(self, node=None, args=None): - """Called when node has changed on the network.""" + """Handle a changed node on the network.""" if node and node.node_id != self.node_id: return if args is not None and 'nodeId' in args and \ @@ -106,8 +106,8 @@ class ZWaveNodeEntity(ZWaveBaseEntity): def get_node_statistics(self): """Retrieve statistics from the node.""" - return self._network.manager.getNodeStatistics(self._network.home_id, - self.node_id) + return self._network.manager.getNodeStatistics( + self._network.home_id, self.node_id) def node_changed(self): """Update node properties.""" diff --git a/homeassistant/config.py b/homeassistant/config.py index 1508493a6d1..39a6d3304ac 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -68,6 +68,10 @@ http: # base_url: example.duckdns.org:8123 # Checks for available updates +# Note: This component will send some information about your system to +# the developers to assist with development of Home Assistant. +# For more information, please see: +# https://home-assistant.io/blog/2016/10/25/explaining-the-updater/ updater: # Discover some devices automatically @@ -108,7 +112,7 @@ CUSTOMIZE_CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_CUSTOMIZE_DOMAIN, default={}): vol.Schema({cv.string: dict}), vol.Optional(CONF_CUSTOMIZE_GLOB, default={}): - cv.ordered_dict(OrderedDict, cv.string), + vol.Schema({cv.string: OrderedDict}), }) CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({ @@ -321,7 +325,7 @@ def async_process_ha_core_config(hass, config): hac = hass.config def set_time_zone(time_zone_str): - """Helper method to set time zone.""" + """Help to set the time zone.""" if time_zone_str is None: return @@ -450,10 +454,9 @@ def _identify_config_schema(module): except (AttributeError, KeyError): return (None, None) t_schema = str(schema) - if (t_schema.startswith(' 0: + if pending: yield from asyncio.wait(pending, loop=self.loop) else: yield from asyncio.sleep(0, loop=self.loop) @@ -296,7 +296,7 @@ class EventOrigin(enum.Enum): class Event(object): - """Represents an event within the Bus.""" + """Representation of an event within the bus.""" __slots__ = ['event_type', 'data', 'origin', 'time_fired'] @@ -341,7 +341,7 @@ class Event(object): class EventBus(object): - """Allows firing of and listening for events.""" + """Allow the firing of and listening for events.""" def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" @@ -350,7 +350,7 @@ class EventBus(object): @callback def async_listeners(self): - """Dict with events and the number of listeners. + """Return dictionary with events and the number of listeners. This method must be run in the event loop. """ @@ -359,7 +359,7 @@ class EventBus(object): @property def listeners(self): - """Dict with events and the number of listeners.""" + """Return dictionary with events and the number of listeners.""" return run_callback_threadsafe( self._hass.loop, self.async_listeners ).result() @@ -736,7 +736,7 @@ class StateMachine(object): class Service(object): - """Represents a callable service.""" + """Representation of a callable service.""" __slots__ = ['func', 'description', 'fields', 'schema', 'is_callback', 'is_coroutinefunction'] @@ -759,7 +759,7 @@ class Service(object): class ServiceCall(object): - """Represents a call to a service.""" + """Representation of a call to a service.""" __slots__ = ['domain', 'service', 'data', 'call_id'] @@ -780,7 +780,7 @@ class ServiceCall(object): class ServiceRegistry(object): - """Offers services over the eventbus.""" + """Offer the services over the eventbus.""" def __init__(self, hass): """Initialize a service registry.""" @@ -799,14 +799,14 @@ class ServiceRegistry(object): @property def services(self): - """Dict with per domain a list of available services.""" + """Return dictionary with per domain a list of available services.""" return run_callback_threadsafe( self._hass.loop, self.async_services, ).result() @callback def async_services(self): - """Dict with per domain a list of available services. + """Return dictionary with per domain a list of available services. This method must be run in the event loop. """ @@ -952,7 +952,7 @@ class ServiceRegistry(object): @callback def service_executed(event): - """Callback method that is called when service is executed.""" + """Handle an executed service.""" if event.data[ATTR_SERVICE_CALL_ID] == call_id: fut.set_result(True) @@ -970,7 +970,7 @@ class ServiceRegistry(object): @asyncio.coroutine def _event_to_service_call(self, event): - """Callback for SERVICE_CALLED events from the event bus.""" + """Handle the SERVICE_CALLED events from the EventBus.""" service_data = event.data.get(ATTR_SERVICE_DATA) or {} domain = event.data.get(ATTR_DOMAIN).lower() service = event.data.get(ATTR_SERVICE).lower() @@ -1056,7 +1056,7 @@ class Config(object): location.distance(self.latitude, self.longitude, lat, lon), 'm') def path(self, *path): - """Generate path to the file within the config dir. + """Generate path to the file within the configuration directory. Async friendly. """ @@ -1065,7 +1065,7 @@ class Config(object): return os.path.join(self.config_dir, *path) def as_dict(self): - """Create a dict representation of this dict. + """Create a dictionary representation of this dict. Async friendly. """ diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index f45fd3c3841..4981e13beeb 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,4 +1,4 @@ -"""Exceptions used by Home Assistant.""" +"""The exceptions used by Home Assistant.""" class HomeAssistantError(Exception): @@ -23,6 +23,6 @@ class TemplateError(HomeAssistantError): """Error during template rendering.""" def __init__(self, exception): - """Initalize the error.""" + """Init the error.""" super().__init__('{}: {}'.format(exception.__class__.__name__, exception)) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 47a7627b5ce..91ec5051552 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -17,7 +17,7 @@ ConfigType = Dict[str, Any] # pylint: disable=invalid-sequence-index def config_per_platform(config: ConfigType, domain: str) -> Iterable[Tuple[Any, Any]]: - """Generator to break a component config into different platforms. + """Break a component config into different platforms. For example, will find 'switch', 'switch 2', 'switch 3', .. etc Async friendly. diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 24af8a26351..bbfb19f7806 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) def _threaded_factory(async_factory): - """Helper method to create threaded versions of async factories.""" + """Create threaded versions of async factories.""" @ft.wraps(async_factory) def factory(config, config_validation=True): """Threaded factory.""" @@ -90,7 +90,7 @@ def async_and_from_config(config: ConfigType, config_validation: bool=True): if not check(hass, variables): return False except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning('Error during and-condition: %s', ex) + _LOGGER.warning("Error during and-condition: %s", ex) return False return True @@ -121,7 +121,7 @@ def async_or_from_config(config: ConfigType, config_validation: bool=True): if check(hass, variables): return True except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning('Error during or-condition: %s', ex) + _LOGGER.warning("Error during or-condition: %s", ex) return False @@ -285,7 +285,7 @@ def async_template(hass, value_template, variables=None): try: value = value_template.async_render(variables) except TemplateError as ex: - _LOGGER.error('Error during template condition: %s', ex) + _LOGGER.error("Error during template condition: %s", ex) return False return value.lower() == 'true' diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 4bcde01c132..3378116163f 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,5 +1,4 @@ """Helpers for config validation using voluptuous.""" -from collections import OrderedDict from datetime import timedelta, datetime as datetime_sys import os import re @@ -44,7 +43,7 @@ T = TypeVar('T') # Adapted from: # https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666 def has_at_least_one_key(*keys: str) -> Callable: - """Validator that at least one key exists.""" + """Validate that at least one key exists.""" def validate(obj: Dict) -> Dict: """Test keys exist in dict.""" if not isinstance(obj, dict): @@ -193,7 +192,7 @@ time_period = vol.Any(time_period_str, time_period_seconds, timedelta, def match_all(value): - """Validator that matches all values.""" + """Validate that matches all values.""" return value @@ -242,7 +241,7 @@ def slugify(value): if value is None: raise vol.Invalid('Slug should not be None') slg = util_slugify(str(value)) - if len(slg) > 0: + if slg: return slg raise vol.Invalid('Unable to slugify {}'.format(value)) @@ -373,29 +372,6 @@ def x10_address(value): return str(value).lower() -def ordered_dict(value_validator, key_validator=match_all): - """Validate an ordered dict validator that maintains ordering. - - value_validator will be applied to each value of the dictionary. - key_validator (optional) will be applied to each key of the dictionary. - """ - item_validator = vol.Schema({key_validator: value_validator}) - - def validator(value): - """Validate ordered dict.""" - config = OrderedDict() - - if not isinstance(value, dict): - raise vol.Invalid('Value {} is not a dictionary'.format(value)) - for key, val in value.items(): - v_res = item_validator({key: val}) - config.update(v_res) - - return config - - return validator - - def ensure_list_csv(value: Any) -> Sequence: """Ensure that input is a list or make one from comma-separated string.""" if isinstance(value, str): diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index a2396b9d30f..88de3a48aa0 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -1,5 +1,4 @@ """Deprecation helpers for Home Assistant.""" - import inspect import logging @@ -13,9 +12,9 @@ def deprecated_substitute(substitute_name): warning will be issued alerting the user of the impending change. """ def decorator(func): - """Decorator function.""" + """Decorate function as deprecated.""" def func_wrapper(self): - """Wrapper for original function.""" + """Wrap for the original function.""" if hasattr(self, substitute_name): # If this platform is still using the old property, issue # a logger warning once with instructions on how to fix it. diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 67fa71ece29..7d3d7d0e823 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -19,7 +19,7 @@ ATTR_PLATFORM = 'platform' def listen(hass, service, callback): - """Setup listener for discovery of specific service. + """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ @@ -29,7 +29,7 @@ def listen(hass, service, callback): @core.callback def async_listen(hass, service, callback): - """Setup listener for discovery of specific service. + """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index c4c5ea59d6b..d5fbadec883 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -1,4 +1,4 @@ -"""Helpers for hass dispatcher & internal component / platform.""" +"""Helpers for Home Assistant dispatcher & internal component/platform.""" import logging from homeassistant.core import callback diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 6a625ee9e3e..f687d1e808f 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -206,13 +206,13 @@ class Entity(object): # update entity data if force_refresh: if self._update_warn: - _LOGGER.warning('Update for %s is already in progress', + _LOGGER.warning("Update for %s is already in progress", self.entity_id) return self._update_warn = self.hass.loop.call_later( SLOW_UPDATE_WARNING, _LOGGER.warning, - 'Update of %s is taking over %s seconds.', self.entity_id, + "Update of %s is taking over %s seconds", self.entity_id, SLOW_UPDATE_WARNING ) @@ -224,7 +224,7 @@ class Entity(object): yield from self.hass.loop.run_in_executor( None, self.update) except Exception: # pylint: disable=broad-except - _LOGGER.exception('Update for %s fails', self.entity_id) + _LOGGER.exception("Update for %s fails", self.entity_id) return finally: self._update_warn.cancel() @@ -264,9 +264,9 @@ class Entity(object): if not self._slow_reported and end - start > 0.4: self._slow_reported = True - _LOGGER.warning('Updating state for %s took %.3f seconds. ' - 'Please report platform to the developers at ' - 'https://goo.gl/Nvioub', self.entity_id, + _LOGGER.warning("Updating state for %s took %.3f seconds. " + "Please report platform to the developers at " + "https://goo.gl/Nvioub", self.entity_id, end - start) # Overwrite properties that have been set in the config file. @@ -316,7 +316,7 @@ class Entity(object): self.hass.states.async_remove(self.entity_id) def _attr_setter(self, name, typ, attr, attrs): - """Helper method to populate attributes based on properties.""" + """Populate attributes based on properties.""" if attr in attrs: return diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 9e059528619..6e69f772d1e 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -76,7 +76,7 @@ class EntityComponent(object): # Refer to: homeassistant.components.discovery.load_platform() @callback def component_platform_discovered(platform, info): - """Callback to load a platform.""" + """Handle the loading of a platform.""" self.hass.async_add_job( self._async_setup_platform(platform, {}, info)) @@ -114,7 +114,7 @@ class EntityComponent(object): @asyncio.coroutine def _async_setup_platform(self, platform_type, platform_config, discovery_info=None): - """Setup a platform for this component. + """Set up a platform for this component. This method must be run in the event loop. """ @@ -140,7 +140,7 @@ class EntityComponent(object): self.logger.info("Setting up %s.%s", self.domain, platform_type) warn_task = self.hass.loop.call_later( SLOW_SETUP_WARNING, self.logger.warning, - 'Setup of platform %s is taking over %s seconds.', platform_type, + "Setup of platform %s is taking over %s seconds.", platform_type, SLOW_SETUP_WARNING) try: @@ -161,7 +161,7 @@ class EntityComponent(object): '{}.{}'.format(self.domain, platform_type)) except Exception: # pylint: disable=broad-except self.logger.exception( - 'Error while setting up platform %s', platform_type) + "Error while setting up platform %s", platform_type) finally: warn_task.cancel() @@ -297,7 +297,7 @@ class EntityPlatform(object): """Keep track of entities for a single platform and stay in loop.""" def __init__(self, component, platform, scan_interval, entity_namespace): - """Initalize the entity platform.""" + """Initialize the entity platform.""" self.component = component self.platform = platform self.scan_interval = scan_interval @@ -430,7 +430,7 @@ class EntityPlatform(object): yield from update_coro except Exception: # pylint: disable=broad-except self.component.logger.exception( - 'Error while update entity from %s in %s', + "Error while update entity from %s in %s", self.platform, self.component.domain) if tasks: diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 12e031bfc3e..0cdcca42eca 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -59,7 +59,7 @@ def async_track_state_change(hass, entity_ids, action, from_state=None, @callback def state_change_listener(event): - """The listener that listens for specific state changes.""" + """Handle specific state changes.""" if entity_ids != MATCH_ALL and \ event.data.get('entity_id') not in entity_ids: return @@ -175,7 +175,7 @@ def async_track_time_interval(hass, action, interval): @callback def interval_listener(now): - """Called when when the interval has elapsed.""" + """Handle elaspsed intervals.""" nonlocal remove remove = async_track_point_in_utc_time( hass, interval_listener, next_interval()) @@ -212,7 +212,7 @@ def async_track_sunrise(hass, action, offset=None): @callback def sunrise_automation_listener(now): - """Called when it's time for action.""" + """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunrise_automation_listener, next_rise()) @@ -249,7 +249,7 @@ def async_track_sunset(hass, action, offset=None): @callback def sunset_automation_listener(now): - """Called when it's time for action.""" + """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, next_set()) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index c022d5ae8f3..08e7a91397a 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -50,7 +50,7 @@ def _load_restore_cache(hass: HomeAssistant): @asyncio.coroutine def async_get_last_state(hass, entity_id: str): - """Helper to restore state.""" + """Restore state.""" if DATA_RESTORE_CACHE in hass.data: return hass.data[DATA_RESTORE_CACHE].get(entity_id) @@ -84,7 +84,7 @@ def async_get_last_state(hass, entity_id: str): @asyncio.coroutine def async_restore_state(entity, extract_info): - """Helper to call entity.async_restore_state with cached info.""" + """Call entity.async_restore_state with cached info.""" if entity.hass.state not in (CoreState.starting, CoreState.not_running): _LOGGER.debug("Not restoring state for %s: Hass is not starting: %s", entity.entity_id, entity.hass.state) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 03a01ece768..b44905a3141 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -19,14 +19,14 @@ from homeassistant.util.async import ( _LOGGER = logging.getLogger(__name__) -CONF_ALIAS = "alias" -CONF_SERVICE = "service" -CONF_SERVICE_DATA = "data" -CONF_SEQUENCE = "sequence" -CONF_EVENT = "event" -CONF_EVENT_DATA = "event_data" -CONF_DELAY = "delay" -CONF_WAIT_TEMPLATE = "wait_template" +CONF_ALIAS = 'alias' +CONF_SERVICE = 'service' +CONF_SERVICE_DATA = 'data' +CONF_SEQUENCE = 'sequence' +CONF_EVENT = 'event' +CONF_EVENT_DATA = 'event_data' +CONF_DELAY = 'delay' +CONF_WAIT_TEMPLATE = 'wait_template' def call_from_config(hass: HomeAssistant, config: ConfigType, @@ -88,7 +88,7 @@ class Script(): @callback def async_script_delay(now): - """Called after delay is done.""" + """Handle delay.""" # pylint: disable=cell-var-from-loop self._async_listener.remove(unsub) self.hass.async_add_job(self.async_run(variables)) @@ -124,7 +124,7 @@ class Script(): @callback def async_script_wait(entity_id, from_s, to_s): - """Called after template condition is true.""" + """Handle script after template condition is true.""" self._async_remove_listener() self.hass.async_add_job(self.async_run(variables)) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index ce0d4f6c8a3..af6aa0f2195 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -81,7 +81,7 @@ def async_call_from_config(hass, config, blocking=False, variables=None, def extract_entity_ids(hass, service_call, expand_group=True): - """Helper method to extract a list of entity ids from a service call. + """Extract a list of entity ids from a service call. Will convert group entity ids to the entity ids it represents. diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index dbc32d1c37e..7715e49880d 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -133,7 +133,7 @@ def async_reproduce_state(hass, states, blocking=False): for state in states: if hass.states.get(state.entity_id) is None: - _LOGGER.warning('reproduce_state: Unable to find entity %s', + _LOGGER.warning("reproduce_state: Unable to find entity %s", state.entity_id) continue diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4eabf1d071b..dafa77da972 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,4 +1,4 @@ -"""Template helper methods for rendering strings with HA data.""" +"""Template helper methods for rendering strings with Home Assistant data.""" from datetime import datetime import json import logging @@ -54,7 +54,7 @@ class Template(object): """Class to hold a template and manage caching and rendering.""" def __init__(self, template, hass=None): - """Instantiate a Template.""" + """Instantiate a template.""" if not isinstance(template, str): raise TypeError('Expected template to be a string') @@ -131,7 +131,7 @@ class Template(object): try: return self._compiled.render(variables).strip() except jinja2.TemplateError as ex: - _LOGGER.error('Error parsing value: %s (value: %s, template: %s)', + _LOGGER.error("Error parsing value: %s (value: %s, template: %s)", ex, value, self.template) return value if error_value is _SENTINEL else error_value @@ -238,11 +238,11 @@ class LocationMethods(object): point_state = self._resolve_state(args[0]) if point_state is None: - _LOGGER.warning('Closest:Unable to find state %s', args[0]) + _LOGGER.warning("Closest:Unable to find state %s", args[0]) return None elif not loc_helper.has_location(point_state): _LOGGER.warning( - 'Closest:State does not contain valid location: %s', + "Closest:State does not contain valid location: %s", point_state) return None @@ -257,7 +257,7 @@ class LocationMethods(object): if latitude is None or longitude is None: _LOGGER.warning( - 'Closest:Received invalid coordinates: %s, %s', + "Closest:Received invalid coordinates: %s, %s", args[0], args[1]) return None @@ -297,7 +297,7 @@ class LocationMethods(object): if latitude is None or longitude is None: _LOGGER.warning( - 'Distance:State does not contains a location: %s', + "Distance:State does not contains a location: %s", value) return None @@ -305,7 +305,7 @@ class LocationMethods(object): # We expect this and next value to be lat&lng if not to_process: _LOGGER.warning( - 'Distance:Expected latitude and longitude, got %s', + "Distance:Expected latitude and longitude, got %s", value) return None @@ -314,8 +314,8 @@ class LocationMethods(object): longitude = convert(value_2, float) if latitude is None or longitude is None: - _LOGGER.warning('Distance:Unable to process latitude and ' - 'longitude: %s, %s', value, value_2) + _LOGGER.warning("Distance:Unable to process latitude and " + "longitude: %s, %s", value, value_2) return None locations.append((latitude, longitude)) @@ -336,7 +336,7 @@ class LocationMethods(object): def forgiving_round(value, precision=0): - """Rounding filter that accepts strings.""" + """Round accepted strings.""" try: value = round(float(value), precision) return int(value) if precision == 0 else value diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 24774ac29da..d0feab414da 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -1,4 +1,4 @@ -"""Typing Helpers for Home-Assistant.""" +"""Typing Helpers for Home Assistant.""" from typing import Dict, Any, Tuple import homeassistant.core diff --git a/homeassistant/loader.py b/homeassistant/loader.py index a24f89c0e3f..586988a3436 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -202,15 +202,15 @@ def _load_order_component(comp_name: str, load_order: OrderedSet, # If we are already loading it, we have a circular dependency. if dependency in loading: - _LOGGER.error('Circular dependency detected: %s -> %s', + _LOGGER.error("Circular dependency detected: %s -> %s", comp_name, dependency) return OrderedSet() dep_load_order = _load_order_component(dependency, load_order, loading) # length == 0 means error loading dependency or children - if len(dep_load_order) == 0: - _LOGGER.error('Error loading %s dependency: %s', + if not dep_load_order: + _LOGGER.error("Error loading %s dependency: %s", comp_name, dependency) return OrderedSet() diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f2ea6e743c4..57fedd7278d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -3,7 +3,7 @@ pyyaml>=3.11,<4 pytz>=2017.02 pip>=7.1.0 jinja2>=2.9.5 -voluptuous==0.9.3 +voluptuous==0.10.5 typing>=3,<4 aiohttp==2.0.7 async_timeout==1.2.0 diff --git a/homeassistant/remote.py b/homeassistant/remote.py index c0e3d9d6459..b65b3f3de22 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -25,15 +25,15 @@ from homeassistant.const import ( HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) from homeassistant.exceptions import HomeAssistantError -METHOD_GET = "get" -METHOD_POST = "post" -METHOD_DELETE = "delete" - _LOGGER = logging.getLogger(__name__) +METHOD_GET = 'get' +METHOD_POST = 'post' +METHOD_DELETE = 'delete' + class APIStatus(enum.Enum): - """Represent API status.""" + """Representation of an API status.""" # pylint: disable=no-init, invalid-name OK = "ok" @@ -51,7 +51,7 @@ class API(object): def __init__(self, host: str, api_password: Optional[str]=None, port: Optional[int]=SERVER_PORT, use_ssl: bool=False) -> None: - """Initalize the API.""" + """Init the API.""" self.host = host self.port = port self.api_password = api_password diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index f8f4a3e9a6d..1e06f96b3e4 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -50,7 +50,7 @@ def color(the_color, *args, reset=None): """Color helper.""" from colorlog.escape_codes import escape_codes, parse_colors try: - if len(args) == 0: + if not args: assert reset is None, "You cannot reset if nothing being printed" return parse_colors(the_color) return parse_colors(the_color) + ' '.join(args) + \ @@ -106,7 +106,7 @@ def run(script_args: List) -> int: the_color = '' if yfn in res['yaml_files'] else 'red' print(color(the_color, '-', yfn)) - if len(res['except']) > 0: + if res['except']: print(color('bold_white', 'Failed config')) for domain, config in res['except'].items(): domain_info.append(domain) diff --git a/homeassistant/scripts/db_migrator.py b/homeassistant/scripts/db_migrator.py index ee3ee253b65..bf4dddc94fe 100644 --- a/homeassistant/scripts/db_migrator.py +++ b/homeassistant/scripts/db_migrator.py @@ -47,7 +47,7 @@ def print_progress(iteration: int, total: int, prefix: str='', suffix: str='', def run(script_args: List) -> int: - """The actual script body.""" + """Run the actual script.""" # pylint: disable=invalid-name from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker diff --git a/homeassistant/scripts/influxdb_import.py b/homeassistant/scripts/influxdb_import.py new file mode 100644 index 00000000000..c21ac4adad9 --- /dev/null +++ b/homeassistant/scripts/influxdb_import.py @@ -0,0 +1,220 @@ +"""Script to import recorded data into influxdb.""" +import argparse +import json +import os + +from typing import List + +import homeassistant.config as config_util + + +def run(script_args: List) -> int: + """Run the actual script.""" + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + from influxdb import InfluxDBClient + from homeassistant.components.recorder import models + from homeassistant.helpers import state as state_helper + from homeassistant.core import State + + parser = argparse.ArgumentParser( + description="import data to influxDB.") + parser.add_argument( + '-c', '--config', + metavar='path_to_config_dir', + default=config_util.get_default_config_dir(), + help="Directory that contains the Home Assistant configuration") + parser.add_argument( + '--uri', + type=str, + help="Connect to URI and import (if other than default sqlite) " + "eg: mysql://localhost/homeassistant") + parser.add_argument( + '-d', '--dbname', + metavar='dbname', + required=True, + help="InfluxDB database name") + parser.add_argument( + '-H', '--host', + metavar='host', + default='127.0.0.1', + help="InfluxDB host address") + parser.add_argument( + '-P', '--port', + metavar='port', + default=8086, + help="InfluxDB host port") + parser.add_argument( + '-u', '--username', + metavar='username', + default='root', + help="InfluxDB username") + parser.add_argument( + '-p', '--password', + metavar='password', + default='root', + help="InfluxDB password") + parser.add_argument( + '-s', '--step', + metavar='step', + default=1000, + help="How many points to import at the same time") + parser.add_argument( + '-t', '--tags', + metavar='tags', + default="", + help="Comma separated list of tags (key:value) for all points") + parser.add_argument( + '-D', '--default-measurement', + metavar='default_measurement', + default="", + help="Store all your points in the same measurement") + parser.add_argument( + '-o', '--override-measurement', + metavar='override_measurement', + default="", + help="Store all your points in the same measurement") + parser.add_argument( + '-e', '--exclude_entities', + metavar='exclude_entities', + default="", + help="Comma separated list of excluded entities") + parser.add_argument( + '-E', '--exclude_domains', + metavar='exclude_domains', + default="", + help="Comma separated list of excluded domains") + parser.add_argument( + "-S", "--simulate", + default=False, + action="store_true", + help=("Do not write points but simulate preprocessing and print " + "statistics")) + parser.add_argument( + '--script', + choices=['influxdb_import']) + + args = parser.parse_args() + simulate = args.simulate + + client = None + if not simulate: + client = InfluxDBClient(args.host, args.port, + args.username, args.password) + client.switch_database(args.dbname) + + config_dir = os.path.join(os.getcwd(), args.config) # type: str + + # Test if configuration directory exists + if not os.path.isdir(config_dir): + if config_dir != config_util.get_default_config_dir(): + print(('Fatal Error: Specified configuration directory does ' + 'not exist {} ').format(config_dir)) + return 1 + + src_db = '{}/home-assistant_v2.db'.format(config_dir) + + if not os.path.exists(src_db) and not args.uri: + print("Fatal Error: Database '{}' does not exist " + "and no uri given".format(src_db)) + return 1 + + uri = args.uri or "sqlite:///{}".format(src_db) + engine = create_engine(uri, echo=False) + session_factory = sessionmaker(bind=engine) + session = session_factory() + step = int(args.step) + + tags = {} + if args.tags: + tags.update(dict(elem.split(":") for elem in args.tags.split(","))) + excl_entities = args.exclude_entities.split(",") + excl_domains = args.exclude_domains.split(",") + override_measurement = args.override_measurement + default_measurement = args.default_measurement + + query = session.query(models.Events).filter( + models.Events.event_type == "state_changed").order_by( + models.Events.time_fired) + + points = [] + count = 0 + from collections import defaultdict + entities = defaultdict(int) + + for event in query: + event_data = json.loads(event.event_data) + state = State.from_dict(event_data.get("new_state")) + + if not state or ( + excl_entities and state.entity_id in excl_entities) or ( + excl_domains and state.domain in excl_domains): + session.expunge(event) + continue + + try: + _state = float(state_helper.state_as_number(state)) + _state_key = "value" + except ValueError: + _state = state.state + _state_key = "state" + + if override_measurement: + measurement = override_measurement + else: + measurement = state.attributes.get('unit_of_measurement') + if measurement in (None, ''): + if default_measurement: + measurement = default_measurement + else: + measurement = state.entity_id + + point = { + 'measurement': measurement, + 'tags': { + 'domain': state.domain, + 'entity_id': state.object_id, + }, + 'time': event.time_fired, + 'fields': { + _state_key: _state, + } + } + + for key, value in state.attributes.items(): + if key != 'unit_of_measurement': + # If the key is already in fields + if key in point['fields']: + key = key + "_" + # Prevent column data errors in influxDB. + # For each value we try to cast it as float + # But if we can not do it we store the value + # as string add "_str" postfix to the field key + try: + point['fields'][key] = float(value) + except (ValueError, TypeError): + new_key = "{}_str".format(key) + point['fields'][new_key] = str(value) + + entities[state.entity_id] += 1 + point['tags'].update(tags) + points.append(point) + session.expunge(event) + if len(points) >= step: + if not simulate: + print("Write {} points to the database".format(len(points))) + client.write_points(points) + count += len(points) + points = [] + + if points: + if not simulate: + print("Write {} points to the database".format(len(points))) + client.write_points(points) + count += len(points) + + print("\nStatistics:") + print("\n".join(["{:6}: {}".format(v, k) for k, v + in sorted(entities.items(), key=lambda x: x[1])])) + print("\nImport finished {} points written".format(count)) + return 0 diff --git a/homeassistant/scripts/influxdb_migrator.py b/homeassistant/scripts/influxdb_migrator.py index 6f643c592de..6f130d18757 100644 --- a/homeassistant/scripts/influxdb_migrator.py +++ b/homeassistant/scripts/influxdb_migrator.py @@ -32,7 +32,7 @@ def print_progress(iteration: int, total: int, prefix: str='', suffix: str='', def run(script_args: List) -> int: - """The actual script body.""" + """Run the actual script.""" from influxdb import InfluxDBClient parser = argparse.ArgumentParser( diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py index a37275e715f..275a33627a9 100644 --- a/homeassistant/scripts/macos/__init__.py +++ b/homeassistant/scripts/macos/__init__.py @@ -4,7 +4,7 @@ import time def install_osx(): - """Setup to run via launchd on OS X.""" + """Set up to run via launchd on OS X.""" with os.popen('which hass') as inp: hass_path = inp.read().strip() diff --git a/homeassistant/setup.py b/homeassistant/setup.py index f10e3f21124..96d27e3494b 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -1,4 +1,4 @@ -"""Provides methods to bootstrap a home assistant instance.""" +"""All methods needed to bootstrap a Home Assistant instance.""" import asyncio import logging import logging.handlers @@ -28,7 +28,7 @@ SLOW_SETUP_WARNING = 10 def setup_component(hass: core.HomeAssistant, domain: str, config: Optional[Dict]=None) -> bool: - """Setup a component and all its dependencies.""" + """Set up a component and all its dependencies.""" return run_coroutine_threadsafe( async_setup_component(hass, domain, config), loop=hass.loop).result() @@ -36,7 +36,7 @@ def setup_component(hass: core.HomeAssistant, domain: str, @asyncio.coroutine def async_setup_component(hass: core.HomeAssistant, domain: str, config: Optional[Dict]=None) -> bool: - """Setup a component and all its dependencies. + """Set up a component and all its dependencies. This method is a coroutine. """ @@ -78,15 +78,15 @@ def _async_process_requirements(hass: core.HomeAssistant, name: str, """Install packages.""" return pkg_util.install_package( mod, target=hass.config.path('deps'), - constraints=os.path.join(os.path.dirname(__file__), - CONSTRAINT_FILE)) + constraints=os.path.join( + os.path.dirname(__file__), CONSTRAINT_FILE)) with (yield from pip_lock): for req in requirements: ret = yield from hass.loop.run_in_executor(None, pip_install, req) if not ret: - _LOGGER.error('Not initializing %s because could not install ' - 'dependency %s', name, req) + _LOGGER.error("Not initializing %s because could not install " + "dependency %s", name, req) async_notify_setup_error(hass, name) return False @@ -100,8 +100,8 @@ def _async_process_dependencies(hass, config, name, dependencies): if dep in loader.DEPENDENCY_BLACKLIST] if blacklisted: - _LOGGER.error('Unable to setup dependencies of %s: ' - 'found blacklisted dependencies: %s', + _LOGGER.error("Unable to setup dependencies of %s: " + "found blacklisted dependencies: %s", name, ', '.join(blacklisted)) return False @@ -117,8 +117,8 @@ def _async_process_dependencies(hass, config, name, dependencies): in enumerate(results) if not res] if failed: - _LOGGER.error('Unable to setup dependencies of %s. ' - 'Setup failed for dependencies: %s', + _LOGGER.error("Unable to setup dependencies of %s. " + "Setup failed for dependencies: %s", name, ', '.join(failed)) return False @@ -128,23 +128,19 @@ def _async_process_dependencies(hass, config, name, dependencies): @asyncio.coroutine def _async_setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: - """Setup a component for Home Assistant. + """Set up a component for Home Assistant. This method is a coroutine. - - hass: Home Assistant instance. - domain: Domain of component to setup. - config: The Home Assistant configuration. """ def log_error(msg, link=True): """Log helper.""" - _LOGGER.error('Setup failed for %s: %s', domain, msg) + _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) component = loader.get_component(domain) if not component: - log_error('Component not found.', False) + log_error("Component not found.", False) return False # Validate no circular dependencies @@ -152,21 +148,21 @@ def _async_setup_component(hass: core.HomeAssistant, # OrderedSet is empty if component or dependencies could not be resolved if not components: - log_error('Unable to resolve component or dependencies.') + log_error("Unable to resolve component or dependencies.") return False processed_config = \ conf_util.async_process_component_config(hass, config, domain) if processed_config is None: - log_error('Invalid config.') + log_error("Invalid config.") return False if not hass.config.skip_pip and hasattr(component, 'REQUIREMENTS'): req_success = yield from _async_process_requirements( hass, domain, component.REQUIREMENTS) if not req_success: - log_error('Could not install all requirements.') + log_error("Could not install all requirements.") return False if hasattr(component, 'DEPENDENCIES'): @@ -174,7 +170,7 @@ def _async_setup_component(hass: core.HomeAssistant, hass, config, domain, component.DEPENDENCIES) if not dep_success: - log_error('Could not setup all dependencies.') + log_error("Could not setup all dependencies.") return False async_comp = hasattr(component, 'async_setup') @@ -182,7 +178,7 @@ def _async_setup_component(hass: core.HomeAssistant, _LOGGER.info("Setting up %s", domain) warn_task = hass.loop.call_later( SLOW_SETUP_WARNING, _LOGGER.warning, - 'Setup of %s is taking over %s seconds.', domain, SLOW_SETUP_WARNING) + "Setup of %s is taking over %s seconds.", domain, SLOW_SETUP_WARNING) try: if async_comp: @@ -191,24 +187,24 @@ def _async_setup_component(hass: core.HomeAssistant, result = yield from hass.loop.run_in_executor( None, component.setup, hass, processed_config) except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error during setup of component %s', domain) + _LOGGER.exception("Error during setup of component %s", domain) async_notify_setup_error(hass, domain, True) return False finally: warn_task.cancel() if result is False: - log_error('Component failed to initialize.') + log_error("Component failed to initialize.") return False elif result is not True: - log_error('Component did not return boolean if setup was successful. ' - 'Disabling component.') + log_error("Component did not return boolean if setup was successful. " + "Disabling component.") loader.set_component(domain, None) return False hass.config.components.add(component.DOMAIN) - # cleanup + # Cleanup if domain in hass.data[DATA_SETUP]: hass.data[DATA_SETUP].pop(domain) @@ -231,7 +227,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, def log_error(msg): """Log helper.""" - _LOGGER.error('Unable to prepare setup for platform %s: %s', + _LOGGER.error("Unable to prepare setup for platform %s: %s", platform_path, msg) async_notify_setup_error(hass, platform_path) @@ -239,7 +235,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, # Not found if platform is None: - log_error('Platform not found.') + log_error("Platform not found.") return None # Already loaded @@ -252,7 +248,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, hass, config, platform_path, platform.DEPENDENCIES) if not dep_success: - log_error('Could not setup all dependencies.') + log_error("Could not setup all dependencies.") return None if not hass.config.skip_pip and hasattr(platform, 'REQUIREMENTS'): @@ -260,7 +256,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, hass, platform_path, platform.REQUIREMENTS) if not req_success: - log_error('Could not install all requirements.') + log_error("Could not install all requirements.") return None return platform diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 1186892b512..616b9100815 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -185,7 +185,7 @@ class OrderedSet(MutableSet): next_item[1] = prev_item def __iter__(self): - """Iteration of the set.""" + """Iterate of the set.""" end = self.end curr = end[2] while curr is not end: @@ -273,7 +273,7 @@ class Throttle(object): @wraps(method) def wrapper(*args, **kwargs): - """Wrapper that allows wrapped to be called only once per min_time. + """Wrap that allows wrapped to be called only once per min_time. If we cannot acquire the lock, it is running so return None. """ diff --git a/homeassistant/util/async.py b/homeassistant/util/async.py index 58aaa4b0338..ea8e5e3c874 100644 --- a/homeassistant/util/async.py +++ b/homeassistant/util/async.py @@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) def _set_result_unless_cancelled(fut, result): - """Helper setting the result only if the future was not cancelled.""" + """Set the result only if the Future was not cancelled.""" if fut.cancelled(): return fut.set_result(result) @@ -41,7 +41,7 @@ def _set_concurrent_future_state(concurr, source): def _copy_future_state(source, dest): - """Internal helper to copy state from another Future. + """Copy state from another Future. The other Future may be a concurrent.futures.Future. """ @@ -112,7 +112,7 @@ def run_coroutine_threadsafe(coro, loop): future = concurrent.futures.Future() def callback(): - """Callback to call the coroutine.""" + """Handle the call to the coroutine.""" try: # pylint: disable=deprecated-method _chain_future(ensure_future(coro, loop=loop), future) @@ -142,7 +142,7 @@ def fire_coroutine_threadsafe(coro, loop): raise TypeError('A coroutine object is required: %s' % coro) def callback(): - """Callback to fire coroutine.""" + """Handle the firing of a coroutine.""" # pylint: disable=deprecated-method ensure_future(coro, loop=loop) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 57d88c5328d..396b8a63601 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -7,9 +7,6 @@ from typing import Tuple _LOGGER = logging.getLogger(__name__) -HASS_COLOR_MAX = 500 # mireds (inverted) -HASS_COLOR_MIN = 154 - # Official CSS3 colors from w3.org: # https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4 # names do not have spaces in them so that we can compare against @@ -268,10 +265,10 @@ def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[int, int, int]: # pylint: disable=invalid-sequence-index -def color_xy_brightness_to_hsv(vX: float, vY: float, - ibrightness: int) -> Tuple[int, int, int]: - """Convert an xy brightness color to its hsv representation.""" - return color_RGB_to_hsv(*color_xy_brightness_to_RGB(vX, vY, ibrightness)) +def color_xy_to_hs(vX: float, vY: float) -> Tuple[int, int]: + """Convert an xy color to its hs representation.""" + h, s, _ = color_RGB_to_hsv(*color_xy_brightness_to_RGB(vX, vY, 255)) + return (h, s) # pylint: disable=invalid-sequence-index diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 095e906efe1..16d5c750172 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -73,7 +73,7 @@ class AsyncHandler(object): self.loop.call_soon_threadsafe(self._queue.put_nowait, record) def __repr__(self): - """String name of this.""" + """Return the string names.""" return str(self.handler) def _process(self): diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 9a4fa038cfe..ed533a3872f 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,9 +1,9 @@ """Helpers to install PyPi packages.""" import logging import os -import subprocess import sys import threading +from subprocess import Popen, PIPE from urllib.parse import urlparse from typing import Optional @@ -36,12 +36,15 @@ def install_package(package: str, upgrade: bool=True, if constraints is not None: args += ['--constraint', constraints] - try: - return subprocess.call(args) == 0 - except subprocess.SubprocessError: - _LOGGER.exception('Unable to install package %s', package) + process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) + _, stderr = process.communicate() + if process.returncode != 0: + _LOGGER.error('Unable to install package %s: %s', + package, stderr.decode('utf-8').lstrip().strip()) return False + return True + def check_package_exists(package: str, lib_dir: str) -> bool: """Check if a package is installed globally or in lib_dir. diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index c773e564011..adaa8afcb57 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -1,30 +1,26 @@ """Temperature util functions.""" from homeassistant.const import ( - TEMP_CELSIUS, - TEMP_FAHRENHEIT, - UNIT_NOT_RECOGNIZED_TEMPLATE, - TEMPERATURE -) + TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_NOT_RECOGNIZED_TEMPLATE, TEMPERATURE) def fahrenheit_to_celsius(fahrenheit: float) -> float: - """Convert a Fahrenheit temperature to Celsius.""" + """Convert a temperature in Fahrenheit to Celsius.""" return (fahrenheit - 32.0) / 1.8 def celsius_to_fahrenheit(celsius: float) -> float: - """Convert a Celsius temperature to Fahrenheit.""" + """Convert a temperature in Celsius to Fahrenheit.""" return celsius * 1.8 + 32.0 def convert(temperature: float, from_unit: str, to_unit: str) -> float: """Convert a temperature from one unit to another.""" if from_unit not in (TEMP_CELSIUS, TEMP_FAHRENHEIT): - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, - TEMPERATURE)) + raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format( + from_unit, TEMPERATURE)) if to_unit not in (TEMP_CELSIUS, TEMP_FAHRENHEIT): - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, - TEMPERATURE)) + raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format( + to_unit, TEMPERATURE)) if from_unit == to_unit: return temperature diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 15366f1b670..7827f484fdf 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -20,18 +20,20 @@ _SECRET_YAML = 'secrets.yaml' __SECRET_CACHE = {} # type: Dict +class NodeListClass(list): + """Wrapper class to be able to add attributes on a list.""" + + pass + + +class NodeStrClass(str): + """Wrapper class to be able to add attributes on a string.""" + + pass + + def _add_reference(obj, loader, node): """Add file reference information to an object.""" - class NodeListClass(list): - """Wrapper class to be able to add attributes on a list.""" - - pass - - class NodeStrClass(str): - """Wrapper class to be able to add attributes on a string.""" - - pass - if isinstance(obj, list): obj = NodeListClass(obj) if isinstance(obj, str): @@ -65,12 +67,12 @@ def load_yaml(fname: str) -> Union[List, Dict]: _LOGGER.error(exc) raise HomeAssistantError(exc) except UnicodeDecodeError as exc: - _LOGGER.error('Unable to read file %s: %s', fname, exc) + _LOGGER.error("Unable to read file %s: %s", fname, exc) raise HomeAssistantError(exc) def dump(_dict: dict) -> str: - """Dump yaml to a string and remove null.""" + """Dump YAML to a string and remove null.""" return yaml.safe_dump(_dict, default_flow_style=False) \ .replace(': null\n', ':\n') @@ -237,8 +239,8 @@ def _secret_yaml(loader: SafeLineLoader, secrets = _load_secret_yaml(secret_path) if node.value in secrets: - _LOGGER.debug('Secret %s retrieved from secrets.yaml in ' - 'folder %s', node.value, secret_path) + _LOGGER.debug("Secret %s retrieved from secrets.yaml in " + "folder %s", node.value, secret_path) return secrets[node.value] if secret_path == os.path.dirname(sys.path[0]): @@ -252,10 +254,10 @@ def _secret_yaml(loader: SafeLineLoader, # do some keyring stuff pwd = keyring.get_password(_SECRET_NAMESPACE, node.value) if pwd: - _LOGGER.debug('Secret %s retrieved from keyring.', node.value) + _LOGGER.debug("Secret %s retrieved from keyring", node.value) return pwd - _LOGGER.error('Secret %s not defined.', node.value) + _LOGGER.error("Secret %s not defined", node.value) raise HomeAssistantError(node.value) @@ -305,4 +307,9 @@ def represent_odict(dump, tag, mapping, flow_style=None): yaml.SafeDumper.add_representer( OrderedDict, lambda dumper, value: - represent_odict(dumper, u'tag:yaml.org,2002:map', value)) + represent_odict(dumper, 'tag:yaml.org,2002:map', value)) + +yaml.SafeDumper.add_representer( + NodeListClass, + lambda dumper, value: + dumper.represent_sequence('tag:yaml.org,2002:seq', value)) diff --git a/requirements_all.txt b/requirements_all.txt index 54fa194d4fc..3dd416f3b67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -4,14 +4,14 @@ pyyaml>=3.11,<4 pytz>=2017.02 pip>=7.1.0 jinja2>=2.9.5 -voluptuous==0.9.3 +voluptuous==0.10.5 typing>=3,<4 aiohttp==2.0.7 async_timeout==1.2.0 chardet==3.0.2 # homeassistant.components.nuimo_controller ---only-binary=all http://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 +--only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 # homeassistant.components.bbb_gpio # Adafruit_BBIO==1.0.0 @@ -38,17 +38,17 @@ SoCo==0.12 TwitterAPI==2.4.5 # homeassistant.components.device_tracker.automatic -aioautomatic==0.2.1 +aioautomatic==0.3.1 # homeassistant.components.sensor.dnsip aiodns==1.1.1 # homeassistant.components.emulated_hue # homeassistant.components.http -aiohttp_cors==0.5.2 +aiohttp_cors==0.5.3 # homeassistant.components.light.lifx -aiolifx==0.4.4 +aiolifx==0.4.6 # homeassistant.components.alarmdecoder alarmdecoder==0.12.1.0 @@ -71,7 +71,7 @@ apns2==0.1.1 astral==1.4 # homeassistant.components.light.avion -# avion==0.5 +# avion==0.6 # homeassistant.components.sensor.linux_battery batinfo==0.4.2 @@ -83,12 +83,18 @@ batinfo==0.4.2 # homeassistant.components.sensor.scrape beautifulsoup4==4.5.3 +# homeassistant.components.zha +bellows==0.2.7 + # homeassistant.components.blink blinkpy==0.5.2 # homeassistant.components.light.blinksticklight blinkstick==1.1.8 +# homeassistant.components.light.blinkt +blinkt==0.1.0 + # homeassistant.components.sensor.bitcoin blockchain==1.3.3 @@ -123,10 +129,10 @@ crimereports==1.0.0 datapoint==0.4.3 # homeassistant.components.light.decora -# decora==0.3 +# decora==0.4 # homeassistant.components.media_player.denonavr -denonavr==0.3.1 +denonavr==0.4.0 # homeassistant.components.media_player.directv directpy==0.1 @@ -159,12 +165,19 @@ eliqonline==1.0.13 # homeassistant.components.enocean enocean==0.31 +# homeassistant.components.sensor.envirophat +envirophat==0.0.6 + # homeassistant.components.keyboard_remote # evdev==0.6.1 # homeassistant.components.climate.honeywell evohomeclient==0.2.5 +# homeassistant.components.image_processing.dlib_face_detect +# homeassistant.components.image_processing.dlib_face_identify +# face_recognition==0.1.14 + # homeassistant.components.sensor.fastdotcom fastdotcom==0.0.1 @@ -181,7 +194,7 @@ fitbit==0.2.3 fixerio==0.1.1 # homeassistant.components.light.flux_led -flux_led==0.18 +flux_led==0.19 # homeassistant.components.notify.free_mobile freesms==0.1.1 @@ -239,15 +252,11 @@ hikvision==0.4 # homeassistant.components.binary_sensor.workday holidays==0.8.1 -# homeassistant.components.sensor.dht -# http://github.com/adafruit/Adafruit_Python_DHT/archive/da8cddf7fb629c1ef4f046ca44f42523c9cf2d11.zip#Adafruit_DHT==1.3.0 - # homeassistant.components.switch.dlink https://github.com/LinuxChristian/pyW215/archive/v0.4.zip#pyW215==0.4 -# homeassistant.components.sensor.thinkingcleaner -# homeassistant.components.switch.thinkingcleaner -https://github.com/TheRealLink/pythinkingcleaner/archive/v0.0.2.zip#pythinkingcleaner==0.0.2 +# homeassistant.components.sensor.dht +# https://github.com/adafruit/Adafruit_Python_DHT/archive/da8cddf7fb629c1ef4f046ca44f42523c9cf2d11.zip#Adafruit_DHT==1.3.0 # homeassistant.components.media_player.braviatv https://github.com/aparraga/braviarc/archive/0.3.7.zip#braviarc==0.3.7 @@ -291,16 +300,6 @@ https://github.com/molobrakos/python-pocketcasts/archive/9f61ff00c77c7c98ffa0af9 # homeassistant.components.switch.anel_pwrctrl https://github.com/mweinelt/anel-pwrctrl/archive/ed26e8830e28a2bfa4260a9002db23ce3e7e63d7.zip#anel_pwrctrl==0.0.1 -# homeassistant.components.ecobee -https://github.com/nkgilley/python-ecobee-api/archive/a4496b293956b2eac285305136a62ac78bef510d.zip#python-ecobee==0.0.7 - -# homeassistant.components.joaoapps_join -# homeassistant.components.notify.joaoapps_join -https://github.com/nkgilley/python-join-api/archive/3e1e849f1af0b4080f551b62270c6d244d5fbcbd.zip#python-join-api==0.0.1 - -# homeassistant.components.notify.mailgun -https://github.com/pschmitt/pymailgun/archive/1.3.zip#pymailgun==1.3 - # homeassistant.components.switch.edimax https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1 @@ -314,7 +313,7 @@ https://github.com/sander76/powerviewApi/archive/246e782d60d5c0addcc98d7899a0186 https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4 # homeassistant.components.light.osramlightify -https://github.com/tfriedel/python-lightify/archive/d6eadcf311e6e21746182d1480e97b350dda2b3e.zip#lightify==1.0.4 +https://github.com/tfriedel/python-lightify/archive/1bb1db0e7bd5b14304d7bb267e2398cd5160df46.zip#lightify==1.0.5 # homeassistant.components.lutron https://github.com/thecynic/pylutron/archive/v0.1.0.zip#pylutron==0.1.0 @@ -360,6 +359,9 @@ knxip==0.3.3 # homeassistant.components.device_tracker.owntracks libnacl==1.5.0 +# homeassistant.components.device_tracker.mikrotik +librouteros==1.0.2 + # homeassistant.components.media_player.soundtouch libsoundtouch==0.3.0 @@ -392,7 +394,7 @@ mficlient==0.3.0 miflora==0.1.16 # homeassistant.components.tts -mutagen==1.36.2 +mutagen==1.37.0 # homeassistant.components.sensor.usps myusps==1.0.5 @@ -403,12 +405,18 @@ netdisco==1.0.0rc3 # homeassistant.components.sensor.neurio_energy neurio==0.3.1 +# homeassistant.components.opencv +numpy==1.12.0 + # homeassistant.components.google oauth2client==4.0.0 # homeassistant.components.climate.oem oemthermostat==1.1 +# homeassistant.components.opencv +# opencv-python==3.2.0.6 + # homeassistant.components.sensor.openevse openevsewifi==0.4 @@ -460,6 +468,7 @@ psutil==5.2.2 pubnubsub-handler==1.0.2 # homeassistant.components.notify.pushbullet +# homeassistant.components.sensor.pushbullet pushbullet.py==0.10.0 # homeassistant.components.notify.pushetta @@ -481,7 +490,7 @@ pyCEC==0.4.13 pyHS100==0.2.4.2 # homeassistant.components.rfxtrx -pyRFXtrx==0.17.0 +pyRFXtrx==0.18.0 # homeassistant.components.alarm_control_panel.alarmdotcom pyalarmdotcom==0.3.0 @@ -520,6 +529,9 @@ pydroid-ipcam==0.8 # homeassistant.components.sensor.ebox pyebox==0.1.0 +# homeassistant.components.eight_sleep +pyeight==0.0.4 + # homeassistant.components.notify.html5 pyelliptic==1.5.7 @@ -536,7 +548,7 @@ pyfido==1.0.1 pyfttt==0.3 # homeassistant.components.sensor.skybeacon -pygatt==3.0.0 +pygatt==3.1.1 # homeassistant.components.remote.harmony pyharmony==1.0.12 @@ -545,7 +557,7 @@ pyharmony==1.0.12 pyhik==0.1.2 # homeassistant.components.homematic -pyhomematic==0.1.24 +pyhomematic==0.1.25 # homeassistant.components.sensor.hydroquebec pyhydroquebec==1.1.0 @@ -567,7 +579,7 @@ pylast==1.8.0 # homeassistant.components.media_player.webostv # homeassistant.components.notify.webostv -pylgtv==0.1.6 +pylgtv==0.1.7 # homeassistant.components.litejet pylitejet==0.1 @@ -575,6 +587,9 @@ pylitejet==0.1 # homeassistant.components.sensor.loopenergy pyloopenergy==0.0.17 +# homeassistant.components.notify.mailgun +pymailgunner==1.4 + # homeassistant.components.mochad pymochad==0.1.1 @@ -601,6 +616,9 @@ pyowm==2.6.1 # homeassistant.components.qwikswitch pyqwikswitch==0.4 +# homeassistant.components.climate.sensibo +pysensibo==1.0.1 + # homeassistant.components.switch.acer_projector pyserial==3.1.1 @@ -611,12 +629,19 @@ pysma==0.1.3 # homeassistant.components.sensor.snmp pysnmp==4.3.5 +# homeassistant.components.sensor.thinkingcleaner +# homeassistant.components.switch.thinkingcleaner +pythinkingcleaner==0.0.3 + # homeassistant.components.media_player.clementine python-clementine-remote==1.0.1 # homeassistant.components.digital_ocean python-digitalocean==1.11 +# homeassistant.components.ecobee +python-ecobee-api==0.0.7 + # homeassistant.components.climate.eq3btsmart # python-eq3bt==0.1.5 @@ -626,6 +651,10 @@ python-forecastio==1.3.5 # homeassistant.components.sensor.hp_ilo python-hpilo==3.9 +# homeassistant.components.joaoapps_join +# homeassistant.components.notify.joaoapps_join +python-join-api==0.0.2 + # homeassistant.components.lirc # python-lirc==1.2.3 @@ -660,7 +689,7 @@ python-twitch==1.3.0 python-vlc==1.1.2 # homeassistant.components.wink -python-wink==1.2.3 +python-wink==1.2.4 # homeassistant.components.device_tracker.trackr pytrackr==0.0.5 @@ -675,7 +704,7 @@ pyunifi==2.0 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.2.26 +pyvera==0.2.30 # homeassistant.components.notify.html5 pywebpush==0.6.1 @@ -696,7 +725,7 @@ radiotherm==1.2 rflink==0.0.31 # homeassistant.components.ring -ring_doorbell==0.1.3 +ring_doorbell==0.1.4 # homeassistant.components.switch.rpi_rf # rpi-rf==0.9.6 @@ -716,6 +745,7 @@ scsgate==0.1.0 # homeassistant.components.notify.sendgrid sendgrid==4.0.0 +# homeassistant.components.light.sensehat # homeassistant.components.sensor.sensehat sense-hat==2.2.0 @@ -734,6 +764,9 @@ sleekxmpp==1.3.2 # homeassistant.components.sleepiq sleepyq==0.6 +# homeassistant.components.sensor.envirophat +smbus-cffi==0.5.1 + # homeassistant.components.media_player.snapcast snapcast==1.2.2 @@ -741,7 +774,7 @@ snapcast==1.2.2 somecomfort==0.4.1 # homeassistant.components.sensor.speedtest -speedtest-cli==1.0.4 +speedtest-cli==1.0.6 # homeassistant.components.recorder # homeassistant.scripts.db_migrator @@ -761,7 +794,7 @@ tellcore-py==1.1.2 tellduslive==0.3.4 # homeassistant.components.sensor.temper -temperusb==1.5.1 +temperusb==1.5.3 # homeassistant.components.thingspeak thingspeak==0.4.1 @@ -818,7 +851,7 @@ xboxapi==0.1.1 # homeassistant.components.sensor.swiss_hydrological_data # homeassistant.components.sensor.ted5000 # homeassistant.components.sensor.yr -xmltodict==0.10.2 +xmltodict==0.11.0 # homeassistant.components.sensor.yahoo_finance yahoo-finance==1.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 94635a58525..4d08ff349a0 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -15,6 +15,7 @@ COMMENT_REQUIREMENTS = ( 'pybluez', 'beacontools', 'bluepy', + 'opencv-python', 'python-lirc', 'gattlib', 'pyuserinput', @@ -22,7 +23,8 @@ COMMENT_REQUIREMENTS = ( 'pycups', 'python-eq3bt', 'avion', - 'decora' + 'decora', + 'face_recognition' ) IGNORE_PACKAGES = ( diff --git a/setup.py b/setup.py index 05f117652d1..65d02c3e8c6 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ REQUIRES = [ 'pytz>=2017.02', 'pip>=7.1.0', 'jinja2>=2.9.5', - 'voluptuous==0.9.3', + 'voluptuous==0.10.5', 'typing>=3,<4', 'aiohttp==2.0.7', 'async_timeout==1.2.0', diff --git a/tests/common.py b/tests/common.py index a6627344879..1585cb33e23 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,9 +1,10 @@ """Test the helper method for writing tests.""" import asyncio +import functools as ft import os import sys from datetime import timedelta -from unittest.mock import patch, MagicMock +from unittest.mock import patch, MagicMock, Mock from io import StringIO import logging import threading @@ -36,6 +37,21 @@ _LOGGER = logging.getLogger(__name__) INST_COUNT = 0 +def threadsafe_callback_factory(func): + """Create threadsafe functions out of callbacks. + + Callback needs to have `hass` as first argument. + """ + @ft.wraps(func) + def threadsafe(*args, **kwargs): + """Call func threadsafe.""" + hass = args[0] + run_callback_threadsafe( + hass.loop, ft.partial(func, *args, **kwargs)).result() + + return threadsafe + + def get_test_config_dir(*add_path): """Return a path to a test config dir.""" return os.path.join(os.path.dirname(__file__), 'testing_config', *add_path) @@ -62,7 +78,7 @@ def get_test_home_assistant(): orig_stop = hass.stop def start_hass(*mocks): - """Helper to start hass.""" + """Start hass.""" run_coroutine_threadsafe(hass.async_start(), loop=hass.loop).result() def stop_hass(): @@ -93,8 +109,8 @@ def async_test_home_assistant(loop): def async_add_job(target, *args): """Add a magic mock.""" - if isinstance(target, MagicMock): - return + if isinstance(target, Mock): + return mock_coro(target()) return orig_async_add_job(target, *args) hass.async_add_job = async_add_job @@ -151,12 +167,12 @@ def get_test_instance_port(): def mock_service(hass, domain, service): - """Setup a fake service & return a list that logs calls to this service.""" + """Set up a fake service & return a calls log list to this service.""" calls = [] @asyncio.coroutine def mock_service_log(call): # pylint: disable=unnecessary-lambda - """"Mocked service call.""" + """Mock service call.""" calls.append(call) if hass.loop.__dict__.get("_thread_ident", 0) == threading.get_ident(): @@ -177,15 +193,16 @@ def async_fire_mqtt_message(hass, topic, payload, qos=0): payload, qos) -def fire_mqtt_message(hass, topic, payload, qos=0): - """Fire the MQTT message.""" - run_callback_threadsafe( - hass.loop, async_fire_mqtt_message, hass, topic, payload, qos).result() +fire_mqtt_message = threadsafe_callback_factory(async_fire_mqtt_message) -def fire_time_changed(hass, time): +@ha.callback +def async_fire_time_changed(hass, time): """Fire a time changes event.""" - hass.bus.fire(EVENT_TIME_CHANGED, {'now': time}) + hass.bus.async_fire(EVENT_TIME_CHANGED, {'now': time}) + + +fire_time_changed = threadsafe_callback_factory(async_fire_time_changed) def fire_service_discovered(hass, service, info): @@ -211,7 +228,7 @@ def ensure_sun_set(hass): def load_fixture(filename): - """Helper to load a fixture.""" + """Load a fixture.""" path = os.path.join(os.path.dirname(__file__), 'fixtures', filename) with open(path) as fptr: return fptr.read() @@ -271,6 +288,7 @@ def mock_mqtt_component(hass): return mock_mqtt +@ha.callback def mock_component(hass, component): """Mock a component is setup.""" if component in hass.config.components: @@ -302,7 +320,7 @@ class MockModule(object): self.async_setup = async_setup def setup(self, hass, config): - """Setup the component. + """Set up the component. We always define this mock because MagicMock setups will be seen by the executor as a coroutine, raising an exception. @@ -326,7 +344,7 @@ class MockPlatform(object): self.PLATFORM_SCHEMA = platform_schema def setup_platform(self, hass, config, add_devices, discovery_info=None): - """Setup the platform.""" + """Set up the platform.""" if self._setup_platform is not None: self._setup_platform(hass, config, add_devices, discovery_info) @@ -391,7 +409,7 @@ def patch_yaml_files(files_dict, endswith=True): """Mock open() in the yaml module, used by load_yaml.""" # Return the mocked file on full match if fname in files_dict: - _LOGGER.debug('patch_yaml_files match %s', fname) + _LOGGER.debug("patch_yaml_files match %s", fname) res = StringIO(files_dict[fname]) setattr(res, 'name', fname) return res @@ -399,34 +417,29 @@ def patch_yaml_files(files_dict, endswith=True): # Match using endswith for ends in matchlist: if fname.endswith(ends): - _LOGGER.debug('patch_yaml_files end match %s: %s', ends, fname) + _LOGGER.debug("patch_yaml_files end match %s: %s", ends, fname) res = StringIO(files_dict[ends]) setattr(res, 'name', fname) return res # Fallback for hass.components (i.e. services.yaml) if 'homeassistant/components' in fname: - _LOGGER.debug('patch_yaml_files using real file: %s', fname) + _LOGGER.debug("patch_yaml_files using real file: %s", fname) return open(fname, encoding='utf-8') # Not found - raise FileNotFoundError('File not found: {}'.format(fname)) + raise FileNotFoundError("File not found: {}".format(fname)) return patch.object(yaml, 'open', mock_open_f, create=True) def mock_coro(return_value=None): - """Helper method to return a coro that returns a value.""" - @asyncio.coroutine - def coro(): - """Fake coroutine.""" - return return_value - - return coro() + """Return a coro that returns a value.""" + return mock_coro_func(return_value)() def mock_coro_func(return_value=None): - """Helper method to return a coro that returns a value.""" + """Return a method to create a coro function that returns a value.""" @asyncio.coroutine def coro(*args, **kwargs): """Fake coroutine.""" @@ -456,7 +469,7 @@ def assert_setup_component(count, domain=None): res = async_process_component_config( hass, config_input, domain) config[domain] = None if res is None else res.get(domain) - _LOGGER.debug('Configuration for %s, Validated: %s, Original %s', + _LOGGER.debug("Configuration for %s, Validated: %s, Original %s", domain, config[domain], config_input.get(domain)) return res diff --git a/tests/components/binary_sensor/test_workday.py b/tests/components/binary_sensor/test_workday.py index 814606613f5..c828a3b0cd6 100644 --- a/tests/components/binary_sensor/test_workday.py +++ b/tests/components/binary_sensor/test_workday.py @@ -38,12 +38,27 @@ class TestWorkdaySetup(object): }, } + self.config_state = { + 'binary_sensor': { + 'platform': 'workday', + 'country': 'US', + 'province': 'CA' + }, + } + + self.config_nostate = { + 'binary_sensor': { + 'platform': 'workday', + 'country': 'US', + }, + } + self.config_includeholiday = { 'binary_sensor': { 'platform': 'workday', 'country': 'DE', 'province': 'BW', - 'workdays': ['holiday', 'mon', 'tue', 'wed', 'thu', 'fri'], + 'workdays': ['holiday'], 'excludes': ['sat', 'sun'] }, } @@ -122,6 +137,34 @@ class TestWorkdaySetup(object): entity = self.hass.states.get('binary_sensor.workday_sensor') assert entity.state == 'on' + # Freeze time to a public holiday in state CA + @freeze_time("Mar 31st, 2017") + def test_public_holiday_state(self): + """Test if public holidays are reported correctly.""" + with assert_setup_component(1, 'binary_sensor'): + setup_component(self.hass, 'binary_sensor', self.config_state) + + assert self.hass.states.get('binary_sensor.workday_sensor') is not None + + self.hass.start() + + entity = self.hass.states.get('binary_sensor.workday_sensor') + assert entity.state == 'off' + + # Freeze time to a public holiday in state CA + @freeze_time("Mar 31st, 2017") + def test_public_holiday_nostate(self): + """Test if public holidays are reported correctly.""" + with assert_setup_component(1, 'binary_sensor'): + setup_component(self.hass, 'binary_sensor', self.config_nostate) + + assert self.hass.states.get('binary_sensor.workday_sensor') is not None + + self.hass.start() + + entity = self.hass.states.get('binary_sensor.workday_sensor') + assert entity.state == 'on' + def test_setup_component_invalidprovince(self): """Setup workday component.""" with assert_setup_component(1, 'binary_sensor'): diff --git a/tests/components/climate/test_zwave.py b/tests/components/climate/test_zwave.py index ed9e0cf9daa..fbd6ea7f798 100644 --- a/tests/components/climate/test_zwave.py +++ b/tests/components/climate/test_zwave.py @@ -16,8 +16,8 @@ def device(hass, mock_openzwave): values = MockEntityValues( primary=MockValue(data=1, node=node), temperature=MockValue(data=5, node=node, units=None), - mode=MockValue(data=b'test1', data_items=[0, 1, 2], node=node), - fan_mode=MockValue(data=b'test2', data_items=[3, 4, 5], node=node), + mode=MockValue(data='test1', data_items=[0, 1, 2], node=node), + fan_mode=MockValue(data='test2', data_items=[3, 4, 5], node=node), operating_state=MockValue(data=6, node=node), fan_state=MockValue(data=7, node=node), ) @@ -34,12 +34,12 @@ def device_zxt_120(hass, mock_openzwave): values = MockEntityValues( primary=MockValue(data=1, node=node), temperature=MockValue(data=5, node=node, units=None), - mode=MockValue(data=b'test1', data_items=[0, 1, 2], node=node), - fan_mode=MockValue(data=b'test2', data_items=[3, 4, 5], node=node), + mode=MockValue(data='test1', data_items=[0, 1, 2], node=node), + fan_mode=MockValue(data='test2', data_items=[3, 4, 5], node=node), operating_state=MockValue(data=6, node=node), fan_state=MockValue(data=7, node=node), zxt_120_swing_mode=MockValue( - data=b'test3', data_items=[6, 7, 8], node=node), + data='test3', data_items=[6, 7, 8], node=node), ) device = zwave.get_device(hass, node=node, values=values, node_config={}) @@ -54,16 +54,16 @@ def test_zxt_120_swing_mode(device_zxt_120): assert device._zxt_120 == 1 # Test set mode - assert device.values.zxt_120_swing_mode.data == b'test3' + assert device.values.zxt_120_swing_mode.data == 'test3' device.set_swing_mode('test_swing_set') - assert device.values.zxt_120_swing_mode.data == b'test_swing_set' + assert device.values.zxt_120_swing_mode.data == 'test_swing_set' # Test mode changed value_changed(device.values.zxt_120_swing_mode) - assert device.current_swing_mode == b'test_swing_set' - device.values.zxt_120_swing_mode.data = b'test_swing_updated' + assert device.current_swing_mode == 'test_swing_set' + device.values.zxt_120_swing_mode.data = 'test_swing_updated' value_changed(device.values.zxt_120_swing_mode) - assert device.current_swing_mode == b'test_swing_updated' + assert device.current_swing_mode == 'test_swing_updated' def test_temperature_unit(device): @@ -104,16 +104,16 @@ def test_target_value_set(device): def test_operation_value_set(device): """Test values changed for climate device.""" - assert device.values.mode.data == b'test1' + assert device.values.mode.data == 'test1' device.set_operation_mode('test_set') - assert device.values.mode.data == b'test_set' + assert device.values.mode.data == 'test_set' def test_fan_mode_value_set(device): """Test values changed for climate device.""" - assert device.values.fan_mode.data == b'test2' + assert device.values.fan_mode.data == 'test2' device.set_fan_mode('test_fan_set') - assert device.values.fan_mode.data == b'test_fan_set' + assert device.values.fan_mode.data == 'test_fan_set' def test_target_value_changed(device): @@ -134,18 +134,18 @@ def test_temperature_value_changed(device): def test_operation_value_changed(device): """Test values changed for climate device.""" - assert device.current_operation == b'test1' - device.values.mode.data = b'test_updated' + assert device.current_operation == 'test1' + device.values.mode.data = 'test_updated' value_changed(device.values.mode) - assert device.current_operation == b'test_updated' + assert device.current_operation == 'test_updated' def test_fan_mode_value_changed(device): """Test values changed for climate device.""" - assert device.current_fan_mode == b'test2' - device.values.fan_mode.data = b'test_updated_fan' + assert device.current_fan_mode == 'test2' + device.values.fan_mode.data = 'test_updated_fan' value_changed(device.values.fan_mode) - assert device.current_fan_mode == b'test_updated_fan' + assert device.current_fan_mode == 'test_updated_fan' def test_operating_state_value_changed(device): diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index 5cd79fdb74c..b2dcf8e175d 100644 --- a/tests/components/cover/test_mqtt.py +++ b/tests/components/cover/test_mqtt.py @@ -213,6 +213,7 @@ class TestCoverMQTT(unittest.TestCase): state_attributes_dict = self.hass.states.get( 'cover.test').attributes self.assertFalse('current_position' in state_attributes_dict) + self.assertFalse('current_tilt_position' in state_attributes_dict) fire_mqtt_message(self.hass, 'state-topic', '0') self.hass.block_till_done() @@ -237,3 +238,215 @@ class TestCoverMQTT(unittest.TestCase): current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] self.assertEqual(50, current_cover_position) + + def test_tilt_defaults(self): + """Test the defaults.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command', + 'tilt_status_topic': 'tilt-status' + } + })) + + state_attributes_dict = self.hass.states.get( + 'cover.test').attributes + self.assertTrue('current_tilt_position' in state_attributes_dict) + + current_cover_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(STATE_UNKNOWN, current_cover_position) + + def test_tilt_via_invocation_defaults(self): + """Test tilt defaults on close/open.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic' + } + })) + + cover.open_cover_tilt(self.hass, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 100, 0, False), + self.mock_publish.mock_calls[-2][1]) + + cover.close_cover_tilt(self.hass, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 0, 0, False), + self.mock_publish.mock_calls[-2][1]) + + def test_tilt_given_value(self): + """Test tilting to a given value.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic', + 'tilt_opened_value': 400, + 'tilt_closed_value': 125 + } + })) + + cover.open_cover_tilt(self.hass, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 400, 0, False), + self.mock_publish.mock_calls[-2][1]) + + cover.close_cover_tilt(self.hass, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 125, 0, False), + self.mock_publish.mock_calls[-2][1]) + + def test_tilt_via_topic(self): + """Test tilt by updating status via MQTT.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic', + 'tilt_opened_value': 400, + 'tilt_closed_value': 125 + } + })) + + fire_mqtt_message(self.hass, 'tilt-status-topic', '0') + self.hass.block_till_done() + + current_cover_tilt_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(0, current_cover_tilt_position) + + fire_mqtt_message(self.hass, 'tilt-status-topic', '50') + self.hass.block_till_done() + + current_cover_tilt_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(50, current_cover_tilt_position) + + def test_tilt_via_topic_altered_range(self): + """Test tilt status via MQTT with altered tilt range.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic', + 'tilt_opened_value': 400, + 'tilt_closed_value': 125, + 'tilt_min': 0, + 'tilt_max': 50 + } + })) + + fire_mqtt_message(self.hass, 'tilt-status-topic', '0') + self.hass.block_till_done() + + current_cover_tilt_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(0, current_cover_tilt_position) + + fire_mqtt_message(self.hass, 'tilt-status-topic', '50') + self.hass.block_till_done() + + current_cover_tilt_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(100, current_cover_tilt_position) + + fire_mqtt_message(self.hass, 'tilt-status-topic', '25') + self.hass.block_till_done() + + current_cover_tilt_position = self.hass.states.get( + 'cover.test').attributes['current_tilt_position'] + self.assertEqual(50, current_cover_tilt_position) + + def test_tilt_position(self): + """Test tilt via method invocation.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic', + 'tilt_opened_value': 400, + 'tilt_closed_value': 125 + } + })) + + cover.set_cover_tilt_position(self.hass, 50, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 50, 0, False), + self.mock_publish.mock_calls[-2][1]) + + def test_tilt_position_altered_range(self): + """Test tilt via method invocation with altered range.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP', + 'tilt_command_topic': 'tilt-command-topic', + 'tilt_status_topic': 'tilt-status-topic', + 'tilt_opened_value': 400, + 'tilt_closed_value': 125, + 'tilt_min': 0, + 'tilt_max': 50 + } + })) + + cover.set_cover_tilt_position(self.hass, 50, 'cover.test') + self.hass.block_till_done() + + self.assertEqual(('tilt-command-topic', 25, 0, False), + self.mock_publish.mock_calls[-2][1]) diff --git a/tests/components/device_tracker/test_automatic.py b/tests/components/device_tracker/test_automatic.py index dd03fd1da57..f823f3c3262 100644 --- a/tests/components/device_tracker/test_automatic.py +++ b/tests/components/device_tracker/test_automatic.py @@ -44,6 +44,7 @@ def test_valid_credentials(mock_create_session, hass): vehicle.id = 'mock_id' vehicle.display_name = 'mock_display_name' vehicle.fuel_level_percent = 45.6 + vehicle.latest_location = None trip.end_location.lat = 45.567 trip.end_location.lon = 34.345 diff --git a/tests/components/device_tracker/test_ddwrt.py b/tests/components/device_tracker/test_ddwrt.py index 4d4f22f2181..d55bc4e2ae1 100644 --- a/tests/components/device_tracker/test_ddwrt.py +++ b/tests/components/device_tracker/test_ddwrt.py @@ -87,7 +87,7 @@ class TestDdwrt(unittest.TestCase): }}) self.assertTrue( - 'Invalid response from ddwrt' in + 'Invalid response from DD-WRT' in str(mock_error.call_args_list[-1])) @mock.patch('homeassistant.components.device_tracker._LOGGER.error') diff --git a/tests/components/fan/__init__.py b/tests/components/fan/__init__.py index 463e96a4319..54ed1fcc505 100644 --- a/tests/components/fan/__init__.py +++ b/tests/components/fan/__init__.py @@ -17,7 +17,7 @@ class TestFanEntity(unittest.TestCase): """Test coverage for base fan entity class.""" def setUp(self): - """Setup test data.""" + """Set up test data.""" self.fan = BaseFan() def tearDown(self): diff --git a/tests/components/light/test_demo.py b/tests/components/light/test_demo.py index f51b5a45b20..9e318ea9192 100644 --- a/tests/components/light/test_demo.py +++ b/tests/components/light/test_demo.py @@ -53,6 +53,8 @@ class TestDemoLight(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(ENTITY_LIGHT) self.assertEqual(400, state.attributes.get(light.ATTR_COLOR_TEMP)) + self.assertEqual(154, state.attributes.get(light.ATTR_MIN_MIREDS)) + self.assertEqual(500, state.attributes.get(light.ATTR_MAX_MIREDS)) self.assertEqual('none', state.attributes.get(light.ATTR_EFFECT)) def test_turn_off(self): diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 62be4aca267..d2cc874a541 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -30,6 +30,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): self._source = None self._tracks = 12 self._media_image_url = None + self._shuffle = False self.service_calls = { 'turn_on': mock_service( @@ -67,6 +68,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): 'clear_playlist': mock_service( hass, media_player.DOMAIN, media_player.SERVICE_CLEAR_PLAYLIST), + 'shuffle_set': mock_service( + hass, media_player.DOMAIN, + media_player.SERVICE_SHUFFLE_SET), } @property @@ -99,6 +103,11 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): """Image url of current playing media.""" return self._media_image_url + @property + def shuffle(self): + """Return true if the media player is shuffling.""" + return self._shuffle + def turn_on(self): """Mock turn_on function.""" self._state = STATE_UNKNOWN @@ -131,6 +140,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): """Clear players playlist.""" self._tracks = 0 + def set_shuffle(self, shuffle): + """Clear players playlist.""" + self._shuffle = shuffle + class TestMediaPlayer(unittest.TestCase): """Test the media_player module.""" @@ -164,6 +177,9 @@ class TestMediaPlayer(unittest.TestCase): self.mock_source_id = input_select.ENTITY_ID_FORMAT.format('source') self.hass.states.set(self.mock_source_id, 'dvd') + self.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format('shuffle') + self.hass.states.set(self.mock_shuffle_switch_id, STATE_OFF) + self.config_children_only = { 'name': 'test', 'platform': 'universal', 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), @@ -178,7 +194,8 @@ class TestMediaPlayer(unittest.TestCase): 'volume_level': self.mock_volume_id, 'source': self.mock_source_id, 'source_list': self.mock_source_list_id, - 'state': self.mock_state_switch_id + 'state': self.mock_state_switch_id, + 'shuffle': self.mock_shuffle_switch_id } } @@ -541,6 +558,7 @@ class TestMediaPlayer(unittest.TestCase): config['commands']['volume_mute'] = 'test' config['commands']['volume_set'] = 'test' config['commands']['select_source'] = 'test' + config['commands']['shuffle_set'] = 'test' ump = universal.UniversalMediaPlayer(self.hass, **config) ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) @@ -553,7 +571,7 @@ class TestMediaPlayer(unittest.TestCase): check_flags = universal.SUPPORT_TURN_ON | universal.SUPPORT_TURN_OFF \ | universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE \ - | universal.SUPPORT_SELECT_SOURCE + | universal.SUPPORT_SELECT_SOURCE | universal.SUPPORT_SHUFFLE_SET self.assertEqual(check_flags, ump.supported_features) @@ -674,6 +692,11 @@ class TestMediaPlayer(unittest.TestCase): self.assertEqual( 1, len(self.mock_mp_2.service_calls['clear_playlist'])) + run_coroutine_threadsafe( + ump.async_set_shuffle(True), + self.hass.loop).result() + self.assertEqual(1, len(self.mock_mp_2.service_calls['shuffle_set'])) + def test_service_call_to_command(self): """Test service call to command.""" config = self.config_children_only diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 0017674e82f..0ef512edcd6 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -209,6 +209,46 @@ class TestMQTT(unittest.TestCase): self.hass.block_till_done() self.assertEqual(0, len(self.calls)) + def test_subscribe_topic_level_wildcard_and_wildcard_root_topic(self): + """Test the subscription of wildcard topics.""" + mqtt.subscribe(self.hass, '+/test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'hi/test-topic', 'test-payload') + + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('hi/test-topic', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_level_wildcard_and_wildcard_subtree_topic(self): + """Test the subscription of wildcard topics.""" + mqtt.subscribe(self.hass, '+/test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'hi/test-topic/here-iam', 'test-payload') + + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('hi/test-topic/here-iam', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_level_wildcard_and_wildcard_level_no_match(self): + """Test the subscription of wildcard topics.""" + mqtt.subscribe(self.hass, '+/test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'hi/here-iam/test-topic', 'test-payload') + + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_subscribe_topic_level_wildcard_and_wildcard_no_match(self): + """Test the subscription of wildcard topics.""" + mqtt.subscribe(self.hass, '+/test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'hi/another-test-topic', 'test-payload') + + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + def test_subscribe_binary_topic(self): """Test the subscription to a binary topic.""" mqtt.subscribe(self.hass, 'test-topic', self.record_calls, diff --git a/tests/components/notify/test_smtp.py b/tests/components/notify/test_smtp.py index 6e8f85bcf86..016c6a5d1f4 100644 --- a/tests/components/notify/test_smtp.py +++ b/tests/components/notify/test_smtp.py @@ -22,7 +22,8 @@ class TestNotifySmtp(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.mailer = MockSMTP('localhost', 25, 5, 'test@test.com', 1, - 'testuser', 'testpass', 'testrecip@test.com', 0) + 'testuser', 'testpass', + ['recip1@example.com', 'testrecip@test.com'], 0) def tearDown(self): # pylint: disable=invalid-name """"Stop down everything that was started.""" @@ -36,7 +37,7 @@ class TestNotifySmtp(unittest.TestCase): 'MIME-Version: 1.0\n' 'Content-Transfer-Encoding: 7bit\n' 'Subject: Home Assistant\n' - 'To: testrecip@test.com\n' + 'To: recip1@example.com,testrecip@test.com\n' 'From: test@test.com\n' 'X-Mailer: HomeAssistant\n' 'Date: [^\n]+\n' diff --git a/tests/components/test_discovery.py b/tests/components/test_discovery.py index 7073c420341..d5be9c483ad 100644 --- a/tests/components/test_discovery.py +++ b/tests/components/test_discovery.py @@ -8,7 +8,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import discovery from homeassistant.util.dt import utcnow -from tests.common import mock_coro, fire_time_changed +from tests.common import mock_coro, async_fire_time_changed # One might consider to "mock" services, but it's easy enough to just use # what is already available. @@ -47,7 +47,7 @@ def mock_discovery(hass, discoveries, config=BASE_CONFIG): return_value=mock_coro()) as mock_discover, \ patch('homeassistant.components.discovery.async_load_platform', return_value=mock_coro()) as mock_platform: - fire_time_changed(hass, utcnow()) + async_fire_time_changed(hass, utcnow()) # Work around an issue where our loop.call_soon not get caught yield from hass.async_block_till_done() yield from hass.async_block_till_done() diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 53c8697b44a..658e78b4523 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -5,7 +5,6 @@ from unittest.mock import patch, Mock, MagicMock import pytest -import homeassistant.components.hassio as ho from homeassistant.setup import async_setup_component from tests.common import mock_coro, mock_http_component_app @@ -48,32 +47,13 @@ def test_fail_setup_cannot_connect(hass): @asyncio.coroutine -def test_invalid_path(hassio_client): - """Test requesting invalid path.""" - with patch.dict(ho.HASSIO_REST_COMMANDS, {}, clear=True): - resp = yield from hassio_client.post('/api/hassio/beer') - - assert resp.status == 404 - - -@asyncio.coroutine -def test_invalid_method(hassio_client): - """Test requesting path with invalid method.""" - with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer': ['POST']}): - resp = yield from hassio_client.get('/api/hassio/beer') - - assert resp.status == 405 - - -@asyncio.coroutine -def test_forward_normal_path(hassio_client): +def test_forward_request(hassio_client): """Test fetching normal path.""" response = MagicMock() response.read.return_value = mock_coro('data') - with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer': ['POST']}), \ - patch('homeassistant.components.hassio.HassIO.command_proxy', - Mock(return_value=mock_coro(response))), \ + with patch('homeassistant.components.hassio.HassIO.command_proxy', + Mock(return_value=mock_coro(response))), \ patch('homeassistant.components.hassio._create_response') as mresp: mresp.return_value = 'response' resp = yield from hassio_client.post('/api/hassio/beer') @@ -89,14 +69,13 @@ def test_forward_normal_path(hassio_client): @asyncio.coroutine -def test_forward_normal_log_path(hassio_client): +def test_forward_log_request(hassio_client): """Test fetching normal log path.""" response = MagicMock() response.read.return_value = mock_coro('data') - with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer/logs': ['GET']}), \ - patch('homeassistant.components.hassio.HassIO.command_proxy', - Mock(return_value=mock_coro(response))), \ + with patch('homeassistant.components.hassio.HassIO.command_proxy', + Mock(return_value=mock_coro(response))), \ patch('homeassistant.components.hassio.' '_create_response_log') as mresp: mresp.return_value = 'response' @@ -112,69 +91,6 @@ def test_forward_normal_log_path(hassio_client): assert mresp.mock_calls[0][1] == (response, 'data') -@asyncio.coroutine -def test_forward_addon_path(hassio_client): - """Test fetching addon path.""" - response = MagicMock() - response.read.return_value = mock_coro('data') - - with patch.dict(ho.ADDON_REST_COMMANDS, {'install': ['POST']}), \ - patch('homeassistant.components.hassio.' - 'HassIO.command_proxy') as proxy_command, \ - patch('homeassistant.components.hassio._create_response') as mresp: - proxy_command.return_value = mock_coro(response) - mresp.return_value = 'response' - resp = yield from hassio_client.post('/api/hassio/addons/beer/install') - - # Check we got right response - assert resp.status == 200 - body = yield from resp.text() - assert body == 'response' - - assert proxy_command.mock_calls[0][1][0] == 'addons/beer/install' - - # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') - - -@asyncio.coroutine -def test_forward_addon_log_path(hassio_client): - """Test fetching addon log path.""" - response = MagicMock() - response.read.return_value = mock_coro('data') - - with patch.dict(ho.ADDON_REST_COMMANDS, {'logs': ['GET']}), \ - patch('homeassistant.components.hassio.' - 'HassIO.command_proxy') as proxy_command, \ - patch('homeassistant.components.hassio.' - '_create_response_log') as mresp: - proxy_command.return_value = mock_coro(response) - mresp.return_value = 'response' - resp = yield from hassio_client.get('/api/hassio/addons/beer/logs') - - # Check we got right response - assert resp.status == 200 - body = yield from resp.text() - assert body == 'response' - - assert proxy_command.mock_calls[0][1][0] == 'addons/beer/logs' - - # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') - - -@asyncio.coroutine -def test_bad_request_when_wrong_addon_url(hassio_client): - """Test we cannot mess with addon url.""" - resp = yield from hassio_client.get('/api/hassio/addons/../../info') - assert resp.status == 404 - - resp = yield from hassio_client.get('/api/hassio/addons/info') - assert resp.status == 404 - - @asyncio.coroutine def test_bad_gateway_when_cannot_find_supervisor(hassio_client): """Test we get a bad gateway error if we can't find supervisor.""" diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index c1ad2672365..ab1f8916c37 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -96,7 +96,10 @@ class TestInfluxDB(unittest.TestCase): 'host': 'host', 'username': 'user', 'password': 'pass', - 'blacklist': ['fake.blacklisted'] + 'exclude': { + 'entities': ['fake.blacklisted'], + 'domains': ['another_fake'] + } } } assert setup_component(self.hass, influxdb.DOMAIN, config) @@ -273,6 +276,129 @@ class TestInfluxDB(unittest.TestCase): self.assertFalse(mock_client.return_value.write_points.called) mock_client.return_value.write_points.reset_mock() + def test_event_listener_blacklist_domain(self, mock_client): + """Test the event listener against a blacklist.""" + self._setup() + + for domain in ('ok', 'another_fake'): + state = mock.MagicMock( + state=1, domain=domain, + entity_id='{}.something'.format(domain), + object_id='something', attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) + body = [{ + 'measurement': '{}.something'.format(domain), + 'tags': { + 'domain': domain, + 'entity_id': 'something', + }, + 'time': 12345, + 'fields': { + 'value': 1, + }, + }] + self.handler_method(event) + if domain == 'ok': + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) + else: + self.assertFalse(mock_client.return_value.write_points.called) + mock_client.return_value.write_points.reset_mock() + + def test_event_listener_whitelist(self, mock_client): + """Test the event listener against a whitelist.""" + config = { + 'influxdb': { + 'host': 'host', + 'username': 'user', + 'password': 'pass', + 'include': { + 'entities': ['fake.included'], + } + } + } + assert setup_component(self.hass, influxdb.DOMAIN, config) + self.handler_method = self.hass.bus.listen.call_args_list[0][0][1] + + for entity_id in ('included', 'default'): + state = mock.MagicMock( + state=1, domain='fake', entity_id='fake.{}'.format(entity_id), + object_id=entity_id, attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) + body = [{ + 'measurement': 'fake.{}'.format(entity_id), + 'tags': { + 'domain': 'fake', + 'entity_id': entity_id, + }, + 'time': 12345, + 'fields': { + 'value': 1, + }, + }] + self.handler_method(event) + if entity_id == 'included': + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) + else: + self.assertFalse(mock_client.return_value.write_points.called) + mock_client.return_value.write_points.reset_mock() + + def test_event_listener_whitelist_domain(self, mock_client): + """Test the event listener against a whitelist.""" + config = { + 'influxdb': { + 'host': 'host', + 'username': 'user', + 'password': 'pass', + 'include': { + 'domains': ['fake'], + } + } + } + assert setup_component(self.hass, influxdb.DOMAIN, config) + self.handler_method = self.hass.bus.listen.call_args_list[0][0][1] + + for domain in ('fake', 'another_fake'): + state = mock.MagicMock( + state=1, domain=domain, + entity_id='{}.something'.format(domain), + object_id='something', attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) + body = [{ + 'measurement': '{}.something'.format(domain), + 'tags': { + 'domain': domain, + 'entity_id': 'something', + }, + 'time': 12345, + 'fields': { + 'value': 1, + }, + }] + self.handler_method(event) + if domain == 'fake': + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) + else: + self.assertFalse(mock_client.return_value.write_points.called) + mock_client.return_value.write_points.reset_mock() + def test_event_listener_invalid_type(self, mock_client): """Test the event listener when an attirbute has an invalid type.""" self._setup() @@ -343,7 +469,9 @@ class TestInfluxDB(unittest.TestCase): 'username': 'user', 'password': 'pass', 'default_measurement': 'state', - 'blacklist': ['fake.blacklisted'] + 'exclude': { + 'entities': ['fake.blacklisted'] + } } } assert setup_component(self.hass, influxdb.DOMAIN, config) diff --git a/tests/components/test_plant.py b/tests/components/test_plant.py new file mode 100644 index 00000000000..57339f28941 --- /dev/null +++ b/tests/components/test_plant.py @@ -0,0 +1,73 @@ +"""Unit tests for platform/plant.py.""" + +import unittest + +from tests.common import get_test_home_assistant +import homeassistant.components.plant as plant + + +class TestPlant(unittest.TestCase): + """test the processing of data.""" + + GOOD_DATA = { + 'moisture': 50, + 'battery': 90, + 'temperature': 23.4, + 'conductivity': 777, + 'brightness': 987, + } + + GOOD_CONFIG = { + 'sensors': { + 'moisture': 'sensor.mqtt_plant_moisture', + 'battery': 'sensor.mqtt_plant_battery', + 'temperature': 'sensor.mqtt_plant_temperature', + 'conductivity': 'sensor.mqtt_plant_conductivity', + 'brightness': 'sensor.mqtt_plant_brightness', + }, + 'min_moisture': 20, + 'max_moisture': 60, + 'min_battery': 17, + 'min_conductivity': 500, + 'min_temperature': 15, + } + + class _MockState(object): + + def __init__(self, state=None): + self.state = state + + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_valid_data(self): + """Test processing valid data.""" + self.sensor = plant.Plant('my plant', self.GOOD_CONFIG) + self.sensor.hass = self.hass + for reading, value in self.GOOD_DATA.items(): + self.sensor.state_changed( + self.GOOD_CONFIG['sensors'][reading], None, + TestPlant._MockState(value)) + self.assertEqual(self.sensor.state, 'ok') + attrib = self.sensor.state_attributes + for reading, value in self.GOOD_DATA.items(): + # battery level has a different name in + # the JSON format than in hass + self.assertEqual(attrib[reading], value) + + def test_low_battery(self): + """Test processing with low battery data and limit set.""" + self.sensor = plant.Plant(self.hass, self.GOOD_CONFIG) + self.sensor.hass = self.hass + self.assertEqual(self.sensor.state_attributes['problem'], 'none') + self.sensor.state_changed('sensor.mqtt_plant_battery', + TestPlant._MockState(45), + TestPlant._MockState(10)) + self.assertEqual(self.sensor.state, 'problem') + self.assertEqual(self.sensor.state_attributes['problem'], + 'battery low') diff --git a/tests/components/test_rss_feed_template.py b/tests/components/test_rss_feed_template.py new file mode 100644 index 00000000000..60eb2530ea1 --- /dev/null +++ b/tests/components/test_rss_feed_template.py @@ -0,0 +1,49 @@ +"""The tests for the rss_feed_api component.""" +import asyncio +from xml.etree import ElementTree + +import pytest + +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def mock_http_client(loop, hass, test_client): + """Setup test fixture.""" + config = { + 'rss_feed_template': { + 'testfeed': { + 'title': 'feed title is {{states.test.test1.state}}', + 'items': [{ + 'title': 'item title is {{states.test.test2.state}}', + 'description': 'desc {{states.test.test3.state}}'}]}}} + + loop.run_until_complete(async_setup_component(hass, + 'rss_feed_template', + config)) + return loop.run_until_complete(test_client(hass.http.app)) + + +@asyncio.coroutine +def test_get_noexistant_feed(mock_http_client): + """Test if we can retrieve the correct rss feed.""" + resp = yield from mock_http_client.get('/api/rss_template/otherfeed') + assert resp.status == 404 + + +@asyncio.coroutine +def test_get_rss_feed(mock_http_client, hass): + """Test if we can retrieve the correct rss feed.""" + hass.states.async_set('test.test1', 'a_state_1') + hass.states.async_set('test.test2', 'a_state_2') + hass.states.async_set('test.test3', 'a_state_3') + + resp = yield from mock_http_client.get('/api/rss_template/testfeed') + assert resp.status == 200 + + text = yield from resp.text() + + xml = ElementTree.fromstring(text) + assert xml[0].text == 'feed title is a_state_1' + assert xml[1][0].text == 'item title is a_state_2' + assert xml[1][1].text == 'desc a_state_3' diff --git a/tests/components/test_splunk.py b/tests/components/test_splunk.py index 661f53b533a..4d5d4cc5d45 100644 --- a/tests/components/test_splunk.py +++ b/tests/components/test_splunk.py @@ -106,7 +106,8 @@ class TestSplunk(unittest.TestCase): self.mock_post.call_args, mock.call( payload['host'], data=payload, - headers={'Authorization': 'Splunk secret'} + headers={'Authorization': 'Splunk secret'}, + timeout=10 ) ) self.mock_post.reset_mock() diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index da9775e17e6..8cc1c78cdcb 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -1,132 +1,158 @@ """The tests for the Updater component.""" -from datetime import datetime, timedelta -import unittest -from unittest.mock import patch -import os +import asyncio +from datetime import timedelta +from unittest.mock import patch, Mock -import requests -import requests_mock -import voluptuous as vol +from freezegun import freeze_time +import pytest -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.components import updater - -from tests.common import ( - assert_setup_component, fire_time_changed, get_test_home_assistant) +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed, mock_coro NEW_VERSION = '10000.0' - -# We need to use a 'real' looking version number to load the updater component -MOCK_CURRENT_VERSION = '10.0' +MOCK_VERSION = '10.0' +MOCK_DEV_VERSION = '10.0.dev0' +MOCK_HUUID = 'abcdefg' +MOCK_RESPONSE = { + 'version': '0.15', + 'release-notes': 'https://home-assistant.io' +} -class TestUpdater(unittest.TestCase): - """Test the Updater component.""" +@pytest.fixture +def mock_get_newest_version(): + """Fixture to mock get_newest_version.""" + with patch('homeassistant.components.updater.get_newest_version') as mock: + yield mock - hass = None - def setup_method(self, _): - """Setup things to be run when tests are started.""" - self.hass = get_test_home_assistant() +@pytest.fixture +def mock_get_uuid(): + """Fixture to mock get_uuid.""" + with patch('homeassistant.components.updater._load_uuid') as mock: + yield mock - def teardown_method(self, _): - """Stop everything that was started.""" - self.hass.stop() - @patch('homeassistant.components.updater.get_newest_version') - def test_new_version_shows_entity_on_start( # pylint: disable=invalid-name - self, mock_get_newest_version): - """Test if new entity is created if new version is available.""" - mock_get_newest_version.return_value = (NEW_VERSION, '') - updater.CURRENT_VERSION = MOCK_CURRENT_VERSION +@asyncio.coroutine +@freeze_time("Mar 15th, 2017") +def test_new_version_shows_entity_after_hour(hass, mock_get_uuid, + mock_get_newest_version): + """Test if new entity is created if new version is available.""" + mock_get_uuid.return_value = MOCK_HUUID + mock_get_newest_version.return_value = mock_coro((NEW_VERSION, '')) - with assert_setup_component(1) as config: - setup_component(self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) - _dt = datetime.now() + timedelta(hours=1) - assert config['updater'] == {'reporting': True} + res = yield from async_setup_component( + hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert res, 'Updater failed to setup' - for secs in [-1, 0, 1]: - fire_time_changed(self.hass, _dt + timedelta(seconds=secs)) - self.hass.block_till_done() + with patch('homeassistant.components.updater.CURRENT_VERSION', + MOCK_VERSION): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1)) + yield from hass.async_block_till_done() - self.assertTrue(self.hass.states.is_state( - updater.ENTITY_ID, NEW_VERSION)) + assert hass.states.is_state(updater.ENTITY_ID, NEW_VERSION) - @patch('homeassistant.components.updater.get_newest_version') - def test_no_entity_on_same_version( # pylint: disable=invalid-name - self, mock_get_newest_version): - """Test if no entity is created if same version.""" - mock_get_newest_version.return_value = (MOCK_CURRENT_VERSION, '') - updater.CURRENT_VERSION = MOCK_CURRENT_VERSION - with assert_setup_component(1) as config: - assert setup_component( - self.hass, updater.DOMAIN, {updater.DOMAIN: {}}) - _dt = datetime.now() + timedelta(hours=1) - assert config['updater'] == {'reporting': True} +@asyncio.coroutine +@freeze_time("Mar 15th, 2017") +def test_same_version_not_show_entity(hass, mock_get_uuid, + mock_get_newest_version): + """Test if new entity is created if new version is available.""" + mock_get_uuid.return_value = MOCK_HUUID + mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, '')) - self.assertIsNone(self.hass.states.get(updater.ENTITY_ID)) + res = yield from async_setup_component( + hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert res, 'Updater failed to setup' - mock_get_newest_version.return_value = (NEW_VERSION, '') + with patch('homeassistant.components.updater.CURRENT_VERSION', + MOCK_VERSION): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1)) + yield from hass.async_block_till_done() - for secs in [-1, 0, 1]: - fire_time_changed(self.hass, _dt + timedelta(seconds=secs)) - self.hass.block_till_done() + assert hass.states.get(updater.ENTITY_ID) is None - self.assertTrue(self.hass.states.is_state( - updater.ENTITY_ID, NEW_VERSION)) - @patch('homeassistant.components.updater.requests.post') - def test_errors_while_fetching_new_version( # pylint: disable=invalid-name - self, mock_get): - """Test for errors while fetching the new version.""" - mock_get.side_effect = requests.RequestException - uuid = '0000' - self.assertIsNone(updater.get_newest_version(uuid)) +@asyncio.coroutine +@freeze_time("Mar 15th, 2017") +def test_disable_reporting(hass, mock_get_uuid, mock_get_newest_version): + """Test if new entity is created if new version is available.""" + mock_get_uuid.return_value = MOCK_HUUID + mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, '')) - mock_get.side_effect = ValueError - self.assertIsNone(updater.get_newest_version(uuid)) + res = yield from async_setup_component( + hass, updater.DOMAIN, {updater.DOMAIN: { + 'reporting': False + }}) + assert res, 'Updater failed to setup' - mock_get.side_effect = vol.Invalid('Expected dictionary') - self.assertIsNone(updater.get_newest_version(uuid)) + with patch('homeassistant.components.updater.CURRENT_VERSION', + MOCK_VERSION): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1)) + yield from hass.async_block_till_done() - def test_uuid_function(self): - """Test if the uuid function works.""" - path = self.hass.config.path(updater.UPDATER_UUID_FILE) - try: - # pylint: disable=protected-access - uuid = updater._load_uuid(self.hass) - assert os.path.isfile(path) - uuid2 = updater._load_uuid(self.hass) - assert uuid == uuid2 - os.remove(path) - uuid2 = updater._load_uuid(self.hass) - assert uuid != uuid2 - finally: - os.remove(path) + assert hass.states.get(updater.ENTITY_ID) is None + call = mock_get_newest_version.mock_calls[0][1] + assert call[0] is hass + assert call[1] is None - @requests_mock.Mocker() - def test_reporting_false_works(self, m): - """Test we do not send any data.""" - m.post(updater.UPDATER_URL, - json={'version': '0.15', - 'release-notes': 'https://home-assistant.io'}) - response = updater.get_newest_version(None) +@asyncio.coroutine +def test_get_newest_version_no_analytics_when_no_huuid(hass, aioclient_mock): + """Test we do not gather analytics when no huuid is passed in.""" + aioclient_mock.post(updater.UPDATER_URL, json=MOCK_RESPONSE) - assert response == ('0.15', 'https://home-assistant.io') + with patch('homeassistant.components.updater.get_system_info', + side_effect=Exception): + res = yield from updater.get_newest_version(hass, None) + assert res == (MOCK_RESPONSE['version'], + MOCK_RESPONSE['release-notes']) - history = m.request_history - assert len(history) == 1 - assert history[0].json() == {} +@asyncio.coroutine +def test_get_newest_version_analytics_when_huuid(hass, aioclient_mock): + """Test we do not gather analytics when no huuid is passed in.""" + aioclient_mock.post(updater.UPDATER_URL, json=MOCK_RESPONSE) - @patch('homeassistant.components.updater.get_newest_version') - def test_error_during_fetch_works( - self, mock_get_newest_version): - """Test if no entity is created if same version.""" - mock_get_newest_version.return_value = None + with patch('homeassistant.components.updater.get_system_info', + Mock(return_value=mock_coro({'fake': 'bla'}))): + res = yield from updater.get_newest_version(hass, MOCK_HUUID) + assert res == (MOCK_RESPONSE['version'], + MOCK_RESPONSE['release-notes']) - updater.check_newest_version(self.hass, None) - self.assertIsNone(self.hass.states.get(updater.ENTITY_ID)) +@asyncio.coroutine +def test_error_fetching_new_version_timeout(hass): + """Test we do not gather analytics when no huuid is passed in.""" + with patch('homeassistant.components.updater.get_system_info', + Mock(return_value=mock_coro({'fake': 'bla'}))), \ + patch('async_timeout.timeout', side_effect=asyncio.TimeoutError): + res = yield from updater.get_newest_version(hass, MOCK_HUUID) + assert res is None + + +@asyncio.coroutine +def test_error_fetching_new_version_bad_json(hass, aioclient_mock): + """Test we do not gather analytics when no huuid is passed in.""" + aioclient_mock.post(updater.UPDATER_URL, text='not json') + + with patch('homeassistant.components.updater.get_system_info', + Mock(return_value=mock_coro({'fake': 'bla'}))): + res = yield from updater.get_newest_version(hass, MOCK_HUUID) + assert res is None + + +@asyncio.coroutine +def test_error_fetching_new_version_invalid_response(hass, aioclient_mock): + """Test we do not gather analytics when no huuid is passed in.""" + aioclient_mock.post(updater.UPDATER_URL, json={ + 'version': '0.15' + # 'release-notes' is missing + }) + + with patch('homeassistant.components.updater.get_system_info', + Mock(return_value=mock_coro({'fake': 'bla'}))): + res = yield from updater.get_newest_version(hass, MOCK_HUUID) + assert res is None diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 7255447cd49..ac652e29833 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,5 +1,4 @@ """Test config validators.""" -from collections import OrderedDict from datetime import timedelta, datetime, date import enum import os @@ -448,63 +447,6 @@ def test_has_at_least_one_key(): schema(value) -def test_ordered_dict_only_dict(): - """Test ordered_dict validator.""" - schema = vol.Schema(cv.ordered_dict(cv.match_all, cv.match_all)) - - for value in (None, [], 100, 'hello'): - with pytest.raises(vol.MultipleInvalid): - schema(value) - - -def test_ordered_dict_order(): - """Test ordered_dict validator.""" - schema = vol.Schema(cv.ordered_dict(int, cv.string)) - - val = OrderedDict() - val['first'] = 1 - val['second'] = 2 - - validated = schema(val) - - assert isinstance(validated, OrderedDict) - assert ['first', 'second'] == list(validated.keys()) - - -def test_ordered_dict_key_validator(): - """Test ordered_dict key validator.""" - schema = vol.Schema(cv.ordered_dict(cv.match_all, cv.string)) - - with pytest.raises(vol.Invalid): - schema({None: 1}) - - schema({'hello': 'world'}) - - schema = vol.Schema(cv.ordered_dict(cv.match_all, int)) - - with pytest.raises(vol.Invalid): - schema({'hello': 1}) - - schema({1: 'works'}) - - -def test_ordered_dict_value_validator(): # pylint: disable=invalid-name - """Test ordered_dict validator.""" - schema = vol.Schema(cv.ordered_dict(cv.string)) - - with pytest.raises(vol.Invalid): - schema({'hello': None}) - - schema({'hello': 'world'}) - - schema = vol.Schema(cv.ordered_dict(int)) - - with pytest.raises(vol.Invalid): - schema({'hello': 'world'}) - - schema({'hello': 5}) - - def test_enum(): """Test enum validator.""" class TestEnum(enum.Enum): diff --git a/tests/test_config.py b/tests/test_config.py index 1d1208f8859..c555c879300 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -509,7 +509,7 @@ def test_merge_once_only(merge_log_err): 'mqtt': {}, 'api': {} } config_util.merge_packages_config(config, packages) - assert merge_log_err.call_count == 2 + assert merge_log_err.call_count == 1 assert len(config) == 3 @@ -521,7 +521,7 @@ def test_merge_id_schema(hass): 'script': 'dict', 'input_boolean': 'dict', 'shell_command': 'dict', - 'qwikswitch': '', + 'qwikswitch': 'dict', } for name, expected_type in types.items(): module = config_util.get_component(name) diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 39e926ab7e7..0af5321c65f 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -63,7 +63,7 @@ class AiohttpClientMocker: @property def call_count(self): - """Number of requests made.""" + """Return the number of requests made.""" return len(self.mock_calls) def clear_requests(self): diff --git a/tests/testing_config/custom_components/image_processing/test.py b/tests/testing_config/custom_components/image_processing/test.py index 0c538bc6781..29d362699f5 100644 --- a/tests/testing_config/custom_components/image_processing/test.py +++ b/tests/testing_config/custom_components/image_processing/test.py @@ -4,7 +4,7 @@ from homeassistant.components.image_processing import ImageProcessingEntity def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the test image_processing platform.""" + """Set up the test image_processing platform.""" add_devices([TestImageProcessing('camera.demo_camera', "Test")]) diff --git a/tests/testing_config/custom_components/light/test.py b/tests/testing_config/custom_components/light/test.py index 07a856d42bc..fafe88eecbe 100644 --- a/tests/testing_config/custom_components/light/test.py +++ b/tests/testing_config/custom_components/light/test.py @@ -11,7 +11,7 @@ DEVICES = [] def init(empty=False): - """Initalize the platform with devices.""" + """Initialize the platform with devices.""" global DEVICES DEVICES = [] if empty else [ diff --git a/tests/testing_config/custom_components/switch/test.py b/tests/testing_config/custom_components/switch/test.py index ca027e9e906..2819f2f2951 100644 --- a/tests/testing_config/custom_components/switch/test.py +++ b/tests/testing_config/custom_components/switch/test.py @@ -11,7 +11,7 @@ DEVICES = [] def init(empty=False): - """Initalize the platform with devices.""" + """Initialize the platform with devices.""" global DEVICES DEVICES = [] if empty else [ diff --git a/tests/util/test_color.py b/tests/util/test_color.py index bf2f4e5832f..43b5904a895 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -56,22 +56,22 @@ class TestColorUtil(unittest.TestCase): self.assertEqual((0, 255, 255), color_util.color_RGB_to_hsv(255, 0, 0)) - def test_color_xy_brightness_to_hsv(self): - """Test color_RGB_to_xy.""" - self.assertEqual(color_util.color_RGB_to_hsv(0, 0, 0), - color_util.color_xy_brightness_to_hsv(1, 1, 0)) + def test_color_xy_to_hs(self): + """Test color_xy_to_hs.""" + self.assertEqual((8609, 255), + color_util.color_xy_to_hs(1, 1)) - self.assertEqual(color_util.color_RGB_to_hsv(255, 243, 222), - color_util.color_xy_brightness_to_hsv(.35, .35, 255)) + self.assertEqual((6950, 32), + color_util.color_xy_to_hs(.35, .35)) - self.assertEqual(color_util.color_RGB_to_hsv(255, 0, 60), - color_util.color_xy_brightness_to_hsv(1, 0, 255)) + self.assertEqual((62965, 255), + color_util.color_xy_to_hs(1, 0)) - self.assertEqual(color_util.color_RGB_to_hsv(0, 255, 0), - color_util.color_xy_brightness_to_hsv(0, 1, 255)) + self.assertEqual((21845, 255), + color_util.color_xy_to_hs(0, 1)) - self.assertEqual(color_util.color_RGB_to_hsv(0, 63, 255), - color_util.color_xy_brightness_to_hsv(0, 0, 255)) + self.assertEqual((40992, 255), + color_util.color_xy_to_hs(0, 0)) def test_rgb_hex_to_rgb_list(self): """Test rgb_hex_to_rgb_list.""" diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 20fb8ca9a2f..e0682d79f57 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -1,11 +1,11 @@ """Test Home Assistant package util methods.""" import os import pkg_resources -import subprocess import unittest +from subprocess import PIPE from distutils.sysconfig import get_python_lib -from unittest.mock import call, patch +from unittest.mock import call, patch, Mock import homeassistant.util.package as package @@ -18,13 +18,20 @@ TEST_ZIP_REQ = 'file://{}#{}' \ .format(os.path.join(RESOURCE_DIR, 'pyhelloworld3.zip'), TEST_NEW_REQ) -@patch('homeassistant.util.package.subprocess.call') +@patch('homeassistant.util.package.Popen') @patch('homeassistant.util.package.check_package_exists') class TestPackageUtilInstallPackage(unittest.TestCase): """Test for homeassistant.util.package module.""" - def test_install_existing_package(self, mock_exists, mock_subprocess): + def setUp(self): + """Setup the tests.""" + self.mock_process = Mock() + self.mock_process.communicate.return_value = (b'message', b'error') + self.mock_process.returncode = 0 + + def test_install_existing_package(self, mock_exists, mock_popen): """Test an install attempt on an existing package.""" + mock_popen.return_value = self.mock_process mock_exists.return_value = True self.assertTrue(package.install_package(TEST_EXIST_REQ)) @@ -32,52 +39,54 @@ class TestPackageUtilInstallPackage(unittest.TestCase): self.assertEqual(mock_exists.call_count, 1) self.assertEqual(mock_exists.call_args, call(TEST_EXIST_REQ, None)) - self.assertEqual(mock_subprocess.call_count, 0) + self.assertEqual(self.mock_process.communicate.call_count, 0) @patch('homeassistant.util.package.sys') - def test_install(self, mock_sys, mock_exists, mock_subprocess): + def test_install(self, mock_sys, mock_exists, mock_popen): """Test an install attempt on a package that doesn't exist.""" mock_exists.return_value = False - mock_subprocess.return_value = 0 + mock_popen.return_value = self.mock_process self.assertTrue(package.install_package(TEST_NEW_REQ, False)) self.assertEqual(mock_exists.call_count, 1) - self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual(self.mock_process.communicate.call_count, 1) + self.assertEqual(mock_popen.call_count, 1) self.assertEqual( - mock_subprocess.call_args, + mock_popen.call_args, call([ mock_sys.executable, '-m', 'pip', 'install', '--quiet', TEST_NEW_REQ - ]) + ], stdin=PIPE, stdout=PIPE, stderr=PIPE) ) @patch('homeassistant.util.package.sys') - def test_install_upgrade(self, mock_sys, mock_exists, mock_subprocess): + def test_install_upgrade(self, mock_sys, mock_exists, mock_popen): """Test an upgrade attempt on a package.""" mock_exists.return_value = False - mock_subprocess.return_value = 0 + mock_popen.return_value = self.mock_process self.assertTrue(package.install_package(TEST_NEW_REQ)) self.assertEqual(mock_exists.call_count, 1) - self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual(self.mock_process.communicate.call_count, 1) + self.assertEqual(mock_popen.call_count, 1) self.assertEqual( - mock_subprocess.call_args, + mock_popen.call_args, call([ mock_sys.executable, '-m', 'pip', 'install', '--quiet', TEST_NEW_REQ, '--upgrade' - ]) + ], stdin=PIPE, stdout=PIPE, stderr=PIPE) ) @patch('homeassistant.util.package.sys') - def test_install_target(self, mock_sys, mock_exists, mock_subprocess): + def test_install_target(self, mock_sys, mock_exists, mock_popen): """Test an install with a target.""" target = 'target_folder' mock_exists.return_value = False - mock_subprocess.return_value = 0 + mock_popen.return_value = self.mock_process self.assertTrue( package.install_package(TEST_NEW_REQ, False, target=target) @@ -85,26 +94,28 @@ class TestPackageUtilInstallPackage(unittest.TestCase): self.assertEqual(mock_exists.call_count, 1) - self.assertEqual(mock_subprocess.call_count, 1) + self.assertEqual(self.mock_process.communicate.call_count, 1) + self.assertEqual(mock_popen.call_count, 1) self.assertEqual( - mock_subprocess.call_args, + mock_popen.call_args, call([ mock_sys.executable, '-m', 'pip', 'install', '--quiet', TEST_NEW_REQ, '--target', os.path.abspath(target) - ]) + ], stdin=PIPE, stdout=PIPE, stderr=PIPE) ) @patch('homeassistant.util.package._LOGGER') @patch('homeassistant.util.package.sys') def test_install_error(self, mock_sys, mock_logger, mock_exists, - mock_subprocess): + mock_popen): """Test an install with a target.""" mock_exists.return_value = False - mock_subprocess.side_effect = [subprocess.SubprocessError] + mock_popen.return_value = self.mock_process + self.mock_process.returncode = 1 self.assertFalse(package.install_package(TEST_NEW_REQ)) - self.assertEqual(mock_logger.exception.call_count, 1) + self.assertEqual(mock_logger.error.call_count, 1) class TestPackageUtilCheckPackageExists(unittest.TestCase): diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 79fd994ce86..0ccb6f5d6d0 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -385,3 +385,11 @@ class TestSecrets(unittest.TestCase): load_yaml(self._yaml_path, 'api_password: !secret pw') assert mock_error.call_count == 1, \ "Expected an error about logger: value" + + +def test_representing_yaml_loaded_data(): + """Test we can represent YAML loaded data.""" + files = {YAML_CONFIG_FILE: 'key: [1, "2", 3]'} + with patch_yaml_files(files): + data = load_yaml_config_file(YAML_CONFIG_FILE) + assert yaml.dump(data) == "key:\n- 1\n- '2'\n- 3\n" diff --git a/virtualization/Docker/scripts/python_openzwave b/virtualization/Docker/scripts/python_openzwave index be19559029e..85a41890186 100755 --- a/virtualization/Docker/scripts/python_openzwave +++ b/virtualization/Docker/scripts/python_openzwave @@ -27,4 +27,4 @@ PYTHON_EXEC=`which python3` make build PYTHON_EXEC=`which python3` make install mkdir -p /usr/local/share/python-openzwave -cp -R openzwave/config /usr/local/share/python-openzwave/config \ No newline at end of file +cp -R openzwave/config /usr/local/share/python-openzwave/config