diff --git a/.coveragerc b/.coveragerc
index 73a79c2d87b..3d369eed073 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -251,6 +251,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
+ homeassistant/components/sisyphus.py
+ homeassistant/components/*/sisyphus.py
+
homeassistant/components/skybell.py
homeassistant/components/*/skybell.py
@@ -346,6 +349,9 @@ omit =
homeassistant/components/tuya.py
homeassistant/components/*/tuya.py
+ homeassistant/components/spider.py
+ homeassistant/components/*/spider.py
+
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -398,6 +404,8 @@ omit =
homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py
homeassistant/components/climate/zhong_hong.py
+ homeassistant/components/cover/aladdin_connect.py
+ homeassistant/components/cover/brunt.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/gogogate2.py
homeassistant/components/cover/homematic.py
@@ -461,6 +469,7 @@ omit =
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/decora.py
homeassistant/components/light/flux_led.py
+ homeassistant/components/light/futurenow.py
homeassistant/components/light/greenwave.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
@@ -657,6 +666,7 @@ omit =
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
+ homeassistant/components/sensor/magicseaweed.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mitemp_bt.py
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ad922d7045..86e212bb11d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributing to Home Assistant
-Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spent a couple of hours and help to integrate them?
+Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spend a couple of hours and help to integrate them?
The process is straight-forward.
diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py
index 496308598dc..65b1cd2ae1a 100644
--- a/homeassistant/__main__.py
+++ b/homeassistant/__main__.py
@@ -8,7 +8,7 @@ import subprocess
import sys
import threading
-from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import
+from typing import List, Dict, Any # noqa pylint: disable=unused-import
from homeassistant import monkey_patch
@@ -20,7 +20,7 @@ from homeassistant.const import (
)
-def attempt_use_uvloop():
+def attempt_use_uvloop() -> None:
"""Attempt to use uvloop."""
import asyncio
@@ -280,11 +280,11 @@ def setup_and_run_hass(config_dir: str,
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async_ import run_callback_threadsafe
- def open_browser(event):
- """Open the webinterface in a browser."""
- if hass.config.api is not None:
+ def open_browser(_: Any) -> None:
+ """Open the web interface in a browser."""
+ if hass.config.api is not None: # type: ignore
import webbrowser
- webbrowser.open(hass.config.api.base_url)
+ webbrowser.open(hass.config.api.base_url) # type: ignore
run_callback_threadsafe(
hass.loop,
diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py
index d24110a4736..e9693b09634 100644
--- a/homeassistant/auth/providers/homeassistant.py
+++ b/homeassistant/auth/providers/homeassistant.py
@@ -3,6 +3,7 @@ import base64
from collections import OrderedDict
import hashlib
import hmac
+from typing import Dict # noqa: F401 pylint: disable=unused-import
import voluptuous as vol
@@ -68,12 +69,12 @@ class Data:
"""Return users."""
return self._data['users']
- def validate_login(self, username, password):
+ def validate_login(self, username: str, password: str) -> None:
"""Validate a username and password.
Raises InvalidAuth if auth invalid.
"""
- password = self.hash_password(password)
+ hashed = self.hash_password(password)
found = None
@@ -84,33 +85,33 @@ class Data:
if found is None:
# Do one more compare to make timing the same as if user was found.
- hmac.compare_digest(password, password)
+ hmac.compare_digest(hashed, hashed)
raise InvalidAuth
- if not hmac.compare_digest(password,
+ if not hmac.compare_digest(hashed,
base64.b64decode(found['password'])):
raise InvalidAuth
- def hash_password(self, password, for_storage=False):
+ def hash_password(self, password: str, for_storage: bool = False) -> bytes:
"""Encode a password."""
hashed = hashlib.pbkdf2_hmac(
'sha512', password.encode(), self._data['salt'].encode(), 100000)
if for_storage:
- hashed = base64.b64encode(hashed).decode()
+ hashed = base64.b64encode(hashed)
return hashed
- def add_auth(self, username, password):
+ def add_auth(self, username: str, password: str) -> None:
"""Add a new authenticated user/pass."""
if any(user['username'] == username for user in self.users):
raise InvalidUser
self.users.append({
'username': username,
- 'password': self.hash_password(password, True),
+ 'password': self.hash_password(password, True).decode(),
})
@callback
- def async_remove_auth(self, username):
+ def async_remove_auth(self, username: str) -> None:
"""Remove authentication."""
index = None
for i, user in enumerate(self.users):
@@ -123,14 +124,15 @@ class Data:
self.users.pop(index)
- def change_password(self, username, new_password):
+ def change_password(self, username: str, new_password: str) -> None:
"""Update the password.
Raises InvalidUser if user cannot be found.
"""
for user in self.users:
if user['username'] == username:
- user['password'] = self.hash_password(new_password, True)
+ user['password'] = self.hash_password(
+ new_password, True).decode()
break
else:
raise InvalidUser
@@ -160,7 +162,7 @@ class HassAuthProvider(AuthProvider):
"""Return a flow to login."""
return LoginFlow(self)
- async def async_validate_login(self, username, password):
+ async def async_validate_login(self, username: str, password: str):
"""Helper to validate a username and password."""
if self.data is None:
await self.async_initialize()
@@ -225,7 +227,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
data=user_input
)
- schema = OrderedDict()
+ schema = OrderedDict() # type: Dict[str, type]
schema['username'] = str
schema['password'] = str
diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py
index a190aea9fa8..43c7168dd2e 100644
--- a/homeassistant/bootstrap.py
+++ b/homeassistant/bootstrap.py
@@ -221,8 +221,8 @@ async def async_from_config_file(config_path: str,
@core.callback
def async_enable_logging(hass: core.HomeAssistant,
verbose: bool = False,
- log_rotate_days=None,
- log_file=None,
+ log_rotate_days: Optional[int] = None,
+ log_file: Optional[str] = None,
log_no_color: bool = False) -> None:
"""Set up the logging.
@@ -291,9 +291,9 @@ def async_enable_logging(hass: core.HomeAssistant,
async_handler = AsyncHandler(hass.loop, err_handler)
- async def async_stop_async_handler(event):
+ async def async_stop_async_handler(_: Any) -> None:
"""Cleanup async handler."""
- logging.getLogger('').removeHandler(async_handler)
+ logging.getLogger('').removeHandler(async_handler) # type: ignore
await async_handler.async_close(blocking=True)
hass.bus.async_listen_once(
diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py
index f0c4f7bb3e2..06e28c42b13 100644
--- a/homeassistant/components/__init__.py
+++ b/homeassistant/components/__init__.py
@@ -167,7 +167,7 @@ def async_setup(hass, config):
def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
- hass.async_add_job(hass.async_stop())
+ hass.async_create_task(hass.async_stop())
return
try:
@@ -183,7 +183,7 @@ def async_setup(hass, config):
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
- hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
+ hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
diff --git a/homeassistant/components/abode.py b/homeassistant/components/abode.py
index 6d5feb87dc2..bafbc0781ca 100644
--- a/homeassistant/components/abode.py
+++ b/homeassistant/components/abode.py
@@ -85,7 +85,7 @@ ABODE_PLATFORMS = [
]
-class AbodeSystem(object):
+class AbodeSystem:
"""Abode System class."""
def __init__(self, username, password, cache,
diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py
index d603843f51f..100444c0211 100644
--- a/homeassistant/components/ads/__init__.py
+++ b/homeassistant/components/ads/__init__.py
@@ -110,7 +110,7 @@ NotificationItem = namedtuple(
)
-class AdsHub(object):
+class AdsHub:
"""Representation of an ADS connection."""
def __init__(self, ads_client):
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index 84a72945a7e..0a4dd6bde78 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -187,7 +187,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_disarm, code)
+ return self.hass.async_add_executor_job(self.alarm_disarm, code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
@@ -198,7 +198,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_arm_home, code)
+ return self.hass.async_add_executor_job(self.alarm_arm_home, code)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
@@ -209,7 +209,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_arm_away, code)
+ return self.hass.async_add_executor_job(self.alarm_arm_away, code)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
@@ -220,7 +220,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_arm_night, code)
+ return self.hass.async_add_executor_job(self.alarm_arm_night, code)
def alarm_trigger(self, code=None):
"""Send alarm trigger command."""
@@ -231,7 +231,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_trigger, code)
+ return self.hass.async_add_executor_job(self.alarm_trigger, code)
def alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command."""
@@ -242,7 +242,8 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
- return self.hass.async_add_job(self.alarm_arm_custom_bypass, code)
+ return self.hass.async_add_executor_job(
+ self.alarm_arm_custom_bypass, code)
@property
def state_attributes(self):
diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py
index 87e85f09da0..736334c956a 100644
--- a/homeassistant/components/alarm_control_panel/alarmdotcom.py
+++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py
@@ -83,7 +83,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@@ -92,9 +92,9 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Return the state of the device."""
if self._alarm.state.lower() == 'disarmed':
return STATE_ALARM_DISARMED
- elif self._alarm.state.lower() == 'armed stay':
+ if self._alarm.state.lower() == 'armed stay':
return STATE_ALARM_ARMED_HOME
- elif self._alarm.state.lower() == 'armed away':
+ if self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
return STATE_UNKNOWN
diff --git a/homeassistant/components/alarm_control_panel/arlo.py b/homeassistant/components/alarm_control_panel/arlo.py
index 20887157cb4..0f8913f85a0 100644
--- a/homeassistant/components/alarm_control_panel/arlo.py
+++ b/homeassistant/components/alarm_control_panel/arlo.py
@@ -122,10 +122,10 @@ class ArloBaseStation(AlarmControlPanel):
"""Convert Arlo mode to Home Assistant state."""
if mode == ARMED:
return STATE_ALARM_ARMED_AWAY
- elif mode == DISARMED:
+ if mode == DISARMED:
return STATE_ALARM_DISARMED
- elif mode == self._home_mode_name:
+ if mode == self._home_mode_name:
return STATE_ALARM_ARMED_HOME
- elif mode == self._away_mode_name:
+ if mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return mode
diff --git a/homeassistant/components/alarm_control_panel/canary.py b/homeassistant/components/alarm_control_panel/canary.py
index 2e0e9994e10..3cd44dcc84c 100644
--- a/homeassistant/components/alarm_control_panel/canary.py
+++ b/homeassistant/components/alarm_control_panel/canary.py
@@ -55,9 +55,9 @@ class CanaryAlarm(AlarmControlPanel):
mode = location.mode
if mode.name == LOCATION_MODE_AWAY:
return STATE_ALARM_ARMED_AWAY
- elif mode.name == LOCATION_MODE_HOME:
+ if mode.name == LOCATION_MODE_HOME:
return STATE_ALARM_ARMED_HOME
- elif mode.name == LOCATION_MODE_NIGHT:
+ if mode.name == LOCATION_MODE_NIGHT:
return STATE_ALARM_ARMED_NIGHT
return None
diff --git a/homeassistant/components/alarm_control_panel/demo.py b/homeassistant/components/alarm_control_panel/demo.py
index c080a136c08..d2366e5836c 100644
--- a/homeassistant/components/alarm_control_panel/demo.py
+++ b/homeassistant/components/alarm_control_panel/demo.py
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import datetime
-import homeassistant.components.alarm_control_panel.manual as manual
+from homeassistant.components.alarm_control_panel import manual
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
diff --git a/homeassistant/components/alarm_control_panel/homematicip_cloud.py b/homeassistant/components/alarm_control_panel/homematicip_cloud.py
index 893fa76c44b..79f872951db 100644
--- a/homeassistant/components/alarm_control_panel/homematicip_cloud.py
+++ b/homeassistant/components/alarm_control_panel/homematicip_cloud.py
@@ -20,7 +20,6 @@ DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
-HMIP_OPEN = 'OPEN'
HMIP_ZONE_AWAY = 'EXTERNAL'
HMIP_ZONE_HOME = 'INTERNAL'
@@ -57,14 +56,18 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
+ from homematicip.base.enums import WindowState
+
if self._device.active:
if (self._device.sabotage or self._device.motionDetected or
- self._device.windowState == HMIP_OPEN):
+ self._device.windowState == WindowState.OPEN):
return STATE_ALARM_TRIGGERED
- if self._device.label == HMIP_ZONE_HOME:
+ active = self._home.get_security_zones_activation()
+ if active == (True, True):
+ return STATE_ALARM_ARMED_AWAY
+ if active == (False, True):
return STATE_ALARM_ARMED_HOME
- return STATE_ALARM_ARMED_AWAY
return STATE_ALARM_DISARMED
@@ -79,10 +82,3 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
await self._home.set_security_zones_activation(True, True)
-
- @property
- def device_state_attributes(self):
- """Return the state attributes of the alarm control device."""
- # The base class is loading the battery property, but device doesn't
- # have this property - base class needs clean-up.
- return None
diff --git a/homeassistant/components/alarm_control_panel/ifttt.py b/homeassistant/components/alarm_control_panel/ifttt.py
index 209c5367c92..9941f70a2e4 100644
--- a/homeassistant/components/alarm_control_panel/ifttt.py
+++ b/homeassistant/components/alarm_control_panel/ifttt.py
@@ -128,7 +128,7 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py
index 2f2f89b9dfc..b2b7c45d410 100644
--- a/homeassistant/components/alarm_control_panel/manual.py
+++ b/homeassistant/components/alarm_control_panel/manual.py
@@ -205,7 +205,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/alarm_control_panel/manual_mqtt.py
index 895f5edd5da..942d0dc159a 100644
--- a/homeassistant/components/alarm_control_panel/manual_mqtt.py
+++ b/homeassistant/components/alarm_control_panel/manual_mqtt.py
@@ -19,7 +19,7 @@ from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_DELAY_TIME, CONF_PENDING_TIME,
CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.helpers.event import async_track_state_change
from homeassistant.core import callback
@@ -241,7 +241,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py
index 9f2a4176ed8..54b85ffbe23 100644
--- a/homeassistant/components/alarm_control_panel/mqtt.py
+++ b/homeassistant/components/alarm_control_panel/mqtt.py
@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.alarm_control_panel as alarm
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
@@ -49,6 +49,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT Alarm Control Panel platform."""
+ if discovery_info is not None:
+ config = PLATFORM_SCHEMA(discovery_info)
+
async_add_devices([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
@@ -123,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py
index b4906acba3c..b400a927b5e 100644
--- a/homeassistant/components/alarm_control_panel/simplisafe.py
+++ b/homeassistant/components/alarm_control_panel/simplisafe.py
@@ -9,23 +9,22 @@ import re
import voluptuous as vol
-import homeassistant.components.alarm_control_panel as alarm
-from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
+from homeassistant.components.alarm_control_panel import (
+ PLATFORM_SCHEMA, AlarmControlPanel)
from homeassistant.const import (
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
- EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
+ STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['simplisafe-python==1.0.5']
+REQUIREMENTS = ['simplisafe-python==2.0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
-DOMAIN = 'simplisafe'
-NOTIFICATION_ID = 'simplisafe_notification'
-NOTIFICATION_TITLE = 'SimpliSafe Setup'
+ATTR_ALARM_ACTIVE = "alarm_active"
+ATTR_TEMPERATURE = "temperature"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
@@ -37,36 +36,27 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SimpliSafe platform."""
- from simplipy.api import SimpliSafeApiInterface, get_systems
+ from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
- simplisafe = SimpliSafeApiInterface()
- status = simplisafe.set_credentials(username, password)
- if status:
- hass.data[DOMAIN] = simplisafe
- locations = get_systems(simplisafe)
- for location in locations:
- add_devices([SimpliSafeAlarm(location, name, code)])
- else:
- message = 'Failed to log into SimpliSafe. Check credentials.'
- _LOGGER.error(message)
- hass.components.persistent_notification.create(
- message,
- title=NOTIFICATION_TITLE,
- notification_id=NOTIFICATION_ID)
- return False
+ try:
+ simplisafe = SimpliSafeApiInterface(username, password)
+ except SimpliSafeAPIException:
+ _LOGGER.error("Failed to setup SimpliSafe")
+ return
- def logout(event):
- """Logout of the SimpliSafe API."""
- hass.data[DOMAIN].logout()
+ systems = []
- hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout)
+ for system in simplisafe.get_systems():
+ systems.append(SimpliSafeAlarm(system, name, code))
+
+ add_devices(systems)
-class SimpliSafeAlarm(alarm.AlarmControlPanel):
+class SimpliSafeAlarm(AlarmControlPanel):
"""Representation of a SimpliSafe alarm."""
def __init__(self, simplisafe, name, code):
@@ -75,31 +65,37 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
self._name = name
self._code = str(code) if code else None
+ @property
+ def unique_id(self):
+ """Return the unique ID."""
+ return self.simplisafe.location_id
+
@property
def name(self):
"""Return the name of the device."""
if self._name is not None:
return self._name
- return 'Alarm {}'.format(self.simplisafe.location_id())
+ return 'Alarm {}'.format(self.simplisafe.location_id)
@property
def code_format(self):
"""Return one or more digits/characters."""
if self._code is None:
return None
- elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
+ if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):
"""Return the state of the device."""
- status = self.simplisafe.state()
- if status == 'off':
+ status = self.simplisafe.state
+ if status.lower() == 'off':
state = STATE_ALARM_DISARMED
- elif status == 'home':
+ elif status.lower() == 'home' or status.lower() == 'home_count':
state = STATE_ALARM_ARMED_HOME
- elif status == 'away':
+ elif (status.lower() == 'away' or status.lower() == 'exitDelay' or
+ status.lower() == 'away_count'):
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
@@ -108,14 +104,13 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def device_state_attributes(self):
"""Return the state attributes."""
- return {
- 'alarm': self.simplisafe.alarm(),
- 'co': self.simplisafe.carbon_monoxide(),
- 'fire': self.simplisafe.fire(),
- 'flood': self.simplisafe.flood(),
- 'last_event': self.simplisafe.last_event(),
- 'temperature': self.simplisafe.temperature(),
- }
+ attributes = {}
+
+ attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active
+ if self.simplisafe.temperature is not None:
+ attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature
+
+ return attributes
def update(self):
"""Update alarm status."""
diff --git a/homeassistant/components/alarmdecoder.py b/homeassistant/components/alarmdecoder.py
index bc7f1910803..1377b2a6c3a 100644
--- a/homeassistant/components/alarmdecoder.py
+++ b/homeassistant/components/alarmdecoder.py
@@ -34,6 +34,8 @@ CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'
CONF_ZONE_RFID = 'rfid'
CONF_ZONES = 'zones'
+CONF_RELAY_ADDR = 'relayaddr'
+CONF_RELAY_CHAN = 'relaychan'
DEFAULT_DEVICE_TYPE = 'socket'
DEFAULT_DEVICE_HOST = 'localhost'
@@ -53,6 +55,7 @@ SIGNAL_PANEL_DISARM = 'alarmdecoder.panel_disarm'
SIGNAL_ZONE_FAULT = 'alarmdecoder.zone_fault'
SIGNAL_ZONE_RESTORE = 'alarmdecoder.zone_restore'
SIGNAL_RFX_MESSAGE = 'alarmdecoder.rfx_message'
+SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message'
DEVICE_SOCKET_SCHEMA = vol.Schema({
vol.Required(CONF_DEVICE_TYPE): 'socket',
@@ -71,7 +74,11 @@ ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONE_NAME): cv.string,
vol.Optional(CONF_ZONE_TYPE,
default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA),
- vol.Optional(CONF_ZONE_RFID): cv.string})
+ vol.Optional(CONF_ZONE_RFID): cv.string,
+ vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation',
+ 'Relay address and channel must exist together'): cv.byte,
+ vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation',
+ 'Relay address and channel must exist together'): cv.byte})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -153,6 +160,11 @@ def setup(hass, config):
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_ZONE_RESTORE, zone)
+ def handle_rel_message(sender, message):
+ """Handle relay message from AlarmDecoder."""
+ hass.helpers.dispatcher.dispatcher_send(
+ SIGNAL_REL_MESSAGE, message)
+
controller = False
if device_type == 'socket':
host = device.get(CONF_DEVICE_HOST)
@@ -171,6 +183,7 @@ def setup(hass, config):
controller.on_zone_fault += zone_fault_callback
controller.on_zone_restore += zone_restore_callback
controller.on_close += handle_closed_connection
+ controller.on_relay_changed += handle_rel_message
hass.data[DATA_AD] = controller
diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py
index 9d47e4bd322..80a02b3275d 100644
--- a/homeassistant/components/alert.py
+++ b/homeassistant/components/alert.py
@@ -68,7 +68,7 @@ def turn_on(hass, entity_id):
def async_turn_on(hass, entity_id):
"""Async reset the alert."""
data = {ATTR_ENTITY_ID: entity_id}
- hass.async_add_job(
+ hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
@@ -81,7 +81,7 @@ def turn_off(hass, entity_id):
def async_turn_off(hass, entity_id):
"""Async acknowledge the alert."""
data = {ATTR_ENTITY_ID: entity_id}
- hass.async_add_job(
+ hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
@@ -94,7 +94,7 @@ def toggle(hass, entity_id):
def async_toggle(hass, entity_id):
"""Async toggle acknowledgement of alert."""
data = {ATTR_ENTITY_ID: entity_id}
- hass.async_add_job(
+ hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
@@ -217,7 +217,7 @@ class Alert(ToggleEntity):
else:
yield from self._schedule_notify()
- self.hass.async_add_job(self.async_update_ha_state)
+ self.async_schedule_update_ha_state()
@asyncio.coroutine
def end_alerting(self):
@@ -228,7 +228,7 @@ class Alert(ToggleEntity):
self._firing = False
if self._done_message and self._send_done_message:
yield from self._notify_done_message()
- self.hass.async_add_job(self.async_update_ha_state)
+ self.async_schedule_update_ha_state()
@asyncio.coroutine
def _schedule_notify(self):
diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py
index b6d406bd550..8d4520d74e8 100644
--- a/homeassistant/components/alexa/intent.py
+++ b/homeassistant/components/alexa/intent.py
@@ -210,7 +210,7 @@ def resolve_slot_synonyms(key, request):
return resolved_value
-class AlexaResponse(object):
+class AlexaResponse:
"""Help generating the response for Alexa."""
def __init__(self, hass, intent_info):
diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py
index 9b7da71a293..042d878fceb 100644
--- a/homeassistant/components/alexa/smart_home.py
+++ b/homeassistant/components/alexa/smart_home.py
@@ -55,7 +55,7 @@ HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
-class _DisplayCategory(object):
+class _DisplayCategory:
"""Possible display categories for Discovery response.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
@@ -153,7 +153,7 @@ class _UnsupportedProperty(Exception):
"""This entity does not support the requested Smart Home API property."""
-class _AlexaEntity(object):
+class _AlexaEntity:
"""An adaptation of an entity, expressed in Alexa's terms.
The API handlers should manipulate entities only through this interface.
@@ -208,7 +208,7 @@ class _AlexaEntity(object):
raise NotImplementedError
-class _AlexaInterface(object):
+class _AlexaInterface:
def __init__(self, entity):
self.entity = entity
@@ -315,7 +315,7 @@ class _AlexaLockController(_AlexaInterface):
if self.entity.state == STATE_LOCKED:
return 'LOCKED'
- elif self.entity.state == STATE_UNLOCKED:
+ if self.entity.state == STATE_UNLOCKED:
return 'UNLOCKED'
return 'JAMMED'
@@ -615,7 +615,7 @@ class _SensorCapabilities(_AlexaEntity):
yield _AlexaTemperatureSensor(self.entity)
-class _Cause(object):
+class _Cause:
"""Possible causes for property changes.
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object
diff --git a/homeassistant/components/amcrest.py b/homeassistant/components/amcrest.py
index 820ca41ad2e..bcd0c38c3bd 100644
--- a/homeassistant/components/amcrest.py
+++ b/homeassistant/components/amcrest.py
@@ -164,7 +164,7 @@ def setup(hass, config):
return True
-class AmcrestDevice(object):
+class AmcrestDevice:
"""Representation of a base Amcrest discovery device."""
def __init__(self, camera, name, authentication, ffmpeg_arguments,
diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py
index 13fa64438d3..5da117e74c3 100644
--- a/homeassistant/components/android_ip_webcam.py
+++ b/homeassistant/components/android_ip_webcam.py
@@ -214,11 +214,11 @@ def async_setup(hass, config):
CONF_PASSWORD: password
})
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'camera', 'mjpeg', mjpeg_camera, config))
if sensors:
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
@@ -226,7 +226,7 @@ def async_setup(hass, config):
}, config))
if switches:
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'switch', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
@@ -234,7 +234,7 @@ def async_setup(hass, config):
}, config))
if motion:
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN, {
CONF_HOST: host,
CONF_NAME: name,
diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py
index 7e2b4cda28f..8808cee79a3 100644
--- a/homeassistant/components/apcupsd.py
+++ b/homeassistant/components/apcupsd.py
@@ -58,7 +58,7 @@ def setup(hass, config):
return True
-class APCUPSdData(object):
+class APCUPSdData:
"""Stores the data retrieved from APCUPSd.
For each entity to use, acts as the single point responsible for fetching
diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py
index b80a5716061..de28eeff5ca 100644
--- a/homeassistant/components/api.py
+++ b/homeassistant/components/api.py
@@ -220,7 +220,8 @@ class APIEntityStateView(HomeAssistantView):
is_new_state = hass.states.get(entity_id) is None
# Write state
- hass.states.async_set(entity_id, new_state, attributes, force_update)
+ hass.states.async_set(entity_id, new_state, attributes, force_update,
+ self.context(request))
# Read the state back for our response
status_code = HTTP_CREATED if is_new_state else 200
@@ -279,7 +280,8 @@ class APIEventView(HomeAssistantView):
event_data[key] = state
request.app['hass'].bus.async_fire(
- event_type, event_data, ha.EventOrigin.remote)
+ event_type, event_data, ha.EventOrigin.remote,
+ self.context(request))
return self.json_message("Event {} fired.".format(event_type))
@@ -316,7 +318,8 @@ class APIDomainServicesView(HomeAssistantView):
"Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
- await hass.services.async_call(domain, service, data, True)
+ await hass.services.async_call(
+ domain, service, data, True, self.context(request))
return self.json(changed_states)
diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py
index 68445092db7..97fb2363024 100644
--- a/homeassistant/components/apple_tv.py
+++ b/homeassistant/components/apple_tv.py
@@ -45,7 +45,7 @@ NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication'
NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification'
NOTIFICATION_SCAN_TITLE = 'Apple TV Scan'
-T = TypeVar('T')
+T = TypeVar('T') # pylint: disable=invalid-name
# This version of ensure_list interprets an empty dict as no value
@@ -218,10 +218,10 @@ def _setup_atv(hass, atv_config):
ATTR_POWER: power
}
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'media_player', DOMAIN, atv_config))
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'remote', DOMAIN, atv_config))
diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py
index 8625685c057..785f8c57f94 100644
--- a/homeassistant/components/arduino.py
+++ b/homeassistant/components/arduino.py
@@ -62,7 +62,7 @@ def setup(hass, config):
return True
-class ArduinoBoard(object):
+class ArduinoBoard:
"""Representation of an Arduino board."""
def __init__(self, port):
diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py
index 475e43e55a4..c6a414b9d91 100644
--- a/homeassistant/components/arlo.py
+++ b/homeassistant/components/arlo.py
@@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
-REQUIREMENTS = ['pyarlo==0.1.9']
+REQUIREMENTS = ['pyarlo==0.2.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py
index 0b5e7c1e1d7..e273d7d6f6a 100644
--- a/homeassistant/components/asterisk_mbox.py
+++ b/homeassistant/components/asterisk_mbox.py
@@ -48,7 +48,7 @@ def setup(hass, config):
return True
-class AsteriskData(object):
+class AsteriskData:
"""Store Asterisk mailbox data."""
def __init__(self, hass, host, port, password):
diff --git a/homeassistant/components/august.py b/homeassistant/components/august.py
index 2a7da86c6cf..eb25ee8fb08 100644
--- a/homeassistant/components/august.py
+++ b/homeassistant/components/august.py
@@ -123,9 +123,9 @@ def setup_august(hass, config, api, authenticator):
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
- elif state == AuthenticationState.BAD_PASSWORD:
+ if state == AuthenticationState.BAD_PASSWORD:
return False
- elif state == AuthenticationState.REQUIRES_VALIDATION:
+ if state == AuthenticationState.REQUIRES_VALIDATION:
request_configuration(hass, config, api, authenticator)
return True
diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py
index 435555c2e31..0b2b4fb1a2e 100644
--- a/homeassistant/components/auth/__init__.py
+++ b/homeassistant/components/auth/__init__.py
@@ -1,62 +1,5 @@
"""Component to allow users to login and get tokens.
-All requests will require passing in a valid client ID and secret via HTTP
-Basic Auth.
-
-# GET /auth/providers
-
-Return a list of auth providers. Example:
-
-[
- {
- "name": "Local",
- "id": null,
- "type": "local_provider",
- }
-]
-
-# POST /auth/login_flow
-
-Create a login flow. Will return the first step of the flow.
-
-Pass in parameter 'handler' to specify the auth provider to use. Auth providers
-are identified by type and id.
-
-{
- "handler": ["local_provider", null]
-}
-
-Return value will be a step in a data entry flow. See the docs for data entry
-flow for details.
-
-{
- "data_schema": [
- {"name": "username", "type": "string"},
- {"name": "password", "type": "string"}
- ],
- "errors": {},
- "flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
- "handler": ["insecure_example", null],
- "step_id": "init",
- "type": "form"
-}
-
-# POST /auth/login_flow/{flow_id}
-
-Progress the flow. Most flows will be 1 page, but could optionally add extra
-login challenges, like TFA. Once the flow has finished, the returned step will
-have type "create_entry" and "result" key will contain an authorization code.
-
-{
- "flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
- "handler": ["insecure_example", null],
- "result": "411ee2f916e648d691e937ae9344681e",
- "source": "user",
- "title": "Example",
- "type": "create_entry",
- "version": 1
-}
-
# POST /auth/token
This is an OAuth2 endpoint for granting tokens. We currently support the grant
@@ -102,24 +45,20 @@ a limited expiration.
"token_type": "Bearer"
}
"""
-from datetime import timedelta
import logging
import uuid
+from datetime import timedelta
-import aiohttp.web
import voluptuous as vol
-from homeassistant import data_entry_flow
-from homeassistant.core import callback
-from homeassistant.helpers.data_entry_flow import (
- FlowManagerIndexView, FlowManagerResourceView)
from homeassistant.components import websocket_api
-from homeassistant.components.http.view import HomeAssistantView
+from homeassistant.components.http.ban import log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
+from homeassistant.components.http.view import HomeAssistantView
+from homeassistant.core import callback
from homeassistant.util import dt as dt_util
-
from . import indieauth
-
+from . import login_flow
DOMAIN = 'auth'
DEPENDENCIES = ['http']
@@ -136,10 +75,6 @@ async def async_setup(hass, config):
"""Component to allow users to login."""
store_credentials, retrieve_credentials = _create_cred_store()
- hass.http.register_view(AuthProvidersView)
- hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow))
- hass.http.register_view(
- LoginFlowResourceView(hass.auth.login_flow, store_credentials))
hass.http.register_view(GrantTokenView(retrieve_credentials))
hass.http.register_view(LinkUserView(retrieve_credentials))
@@ -148,93 +83,11 @@ async def async_setup(hass, config):
SCHEMA_WS_CURRENT_USER
)
+ await login_flow.async_setup(hass, store_credentials)
+
return True
-class AuthProvidersView(HomeAssistantView):
- """View to get available auth providers."""
-
- url = '/auth/providers'
- name = 'api:auth:providers'
- requires_auth = False
-
- async def get(self, request):
- """Get available auth providers."""
- return self.json([{
- 'name': provider.name,
- 'id': provider.id,
- 'type': provider.type,
- } for provider in request.app['hass'].auth.auth_providers])
-
-
-class LoginFlowIndexView(FlowManagerIndexView):
- """View to create a config flow."""
-
- url = '/auth/login_flow'
- name = 'api:auth:login_flow'
- requires_auth = False
-
- async def get(self, request):
- """Do not allow index of flows in progress."""
- return aiohttp.web.Response(status=405)
-
- @RequestDataValidator(vol.Schema({
- vol.Required('client_id'): str,
- vol.Required('handler'): vol.Any(str, list),
- vol.Required('redirect_uri'): str,
- }))
- async def post(self, request, data):
- """Create a new login flow."""
- if not indieauth.verify_redirect_uri(data['client_id'],
- data['redirect_uri']):
- return self.json_message('invalid client id or redirect uri', 400)
-
- # pylint: disable=no-value-for-parameter
- return await super().post(request)
-
-
-class LoginFlowResourceView(FlowManagerResourceView):
- """View to interact with the flow manager."""
-
- url = '/auth/login_flow/{flow_id}'
- name = 'api:auth:login_flow:resource'
- requires_auth = False
-
- def __init__(self, flow_mgr, store_credentials):
- """Initialize the login flow resource view."""
- super().__init__(flow_mgr)
- self._store_credentials = store_credentials
-
- async def get(self, request, flow_id):
- """Do not allow getting status of a flow in progress."""
- return self.json_message('Invalid flow specified', 404)
-
- @RequestDataValidator(vol.Schema({
- 'client_id': str
- }, extra=vol.ALLOW_EXTRA))
- async def post(self, request, flow_id, data):
- """Handle progressing a login flow request."""
- client_id = data.pop('client_id')
-
- if not indieauth.verify_client_id(client_id):
- return self.json_message('Invalid client id', 400)
-
- try:
- result = await self._flow_mgr.async_configure(flow_id, data)
- except data_entry_flow.UnknownFlow:
- return self.json_message('Invalid flow specified', 404)
- except vol.Invalid:
- return self.json_message('User input malformed', 400)
-
- if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
- return self.json(self._prepare_result_json(result))
-
- result.pop('data')
- result['result'] = self._store_credentials(client_id, result['result'])
-
- return self.json(result)
-
-
class GrantTokenView(HomeAssistantView):
"""View to grant tokens."""
@@ -247,11 +100,26 @@ class GrantTokenView(HomeAssistantView):
"""Initialize the grant token view."""
self._retrieve_credentials = retrieve_credentials
+ @log_invalid_auth
async def post(self, request):
"""Grant a token."""
hass = request.app['hass']
data = await request.post()
+ grant_type = data.get('grant_type')
+
+ if grant_type == 'authorization_code':
+ return await self._async_handle_auth_code(hass, data)
+
+ if grant_type == 'refresh_token':
+ return await self._async_handle_refresh_token(hass, data)
+
+ return self.json({
+ 'error': 'unsupported_grant_type',
+ }, status_code=400)
+
+ async def _async_handle_auth_code(self, hass, data):
+ """Handle authorization code request."""
client_id = data.get('client_id')
if client_id is None or not indieauth.verify_client_id(client_id):
return self.json({
@@ -259,21 +127,6 @@ class GrantTokenView(HomeAssistantView):
'error_description': 'Invalid client id',
}, status_code=400)
- grant_type = data.get('grant_type')
-
- if grant_type == 'authorization_code':
- return await self._async_handle_auth_code(hass, client_id, data)
-
- elif grant_type == 'refresh_token':
- return await self._async_handle_refresh_token(
- hass, client_id, data)
-
- return self.json({
- 'error': 'unsupported_grant_type',
- }, status_code=400)
-
- async def _async_handle_auth_code(self, hass, client_id, data):
- """Handle authorization code request."""
code = data.get('code')
if code is None:
@@ -309,8 +162,15 @@ class GrantTokenView(HomeAssistantView):
int(refresh_token.access_token_expiration.total_seconds()),
})
- async def _async_handle_refresh_token(self, hass, client_id, data):
+ async def _async_handle_refresh_token(self, hass, data):
"""Handle authorization code request."""
+ client_id = data.get('client_id')
+ if client_id is not None and not indieauth.verify_client_id(client_id):
+ return self.json({
+ 'error': 'invalid_request',
+ 'error_description': 'Invalid client id',
+ }, status_code=400)
+
token = data.get('refresh_token')
if token is None:
@@ -320,11 +180,16 @@ class GrantTokenView(HomeAssistantView):
refresh_token = await hass.auth.async_get_refresh_token(token)
- if refresh_token is None or refresh_token.client_id != client_id:
+ if refresh_token is None:
return self.json({
'error': 'invalid_grant',
}, status_code=400)
+ if refresh_token.client_id != client_id:
+ return self.json({
+ 'error': 'invalid_request',
+ }, status_code=400)
+
access_token = hass.auth.async_create_access_token(refresh_token)
return self.json({
@@ -412,4 +277,7 @@ def websocket_current_user(hass, connection, msg):
'id': user.id,
'name': user.name,
'is_owner': user.is_owner,
+ 'credentials': [{'auth_provider_type': c.auth_provider_type,
+ 'auth_provider_id': c.auth_provider_id}
+ for c in user.credentials]
}))
diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py
new file mode 100644
index 00000000000..6d1b6cf4ecf
--- /dev/null
+++ b/homeassistant/components/auth/login_flow.py
@@ -0,0 +1,172 @@
+"""HTTP views handle login flow.
+
+# GET /auth/providers
+
+Return a list of auth providers. Example:
+
+[
+ {
+ "name": "Local",
+ "id": null,
+ "type": "local_provider",
+ }
+]
+
+
+# POST /auth/login_flow
+
+Create a login flow. Will return the first step of the flow.
+
+Pass in parameter 'client_id' and 'redirect_url' validate by indieauth.
+
+Pass in parameter 'handler' to specify the auth provider to use. Auth providers
+are identified by type and id.
+
+{
+ "client_id": "https://hassbian.local:8123/",
+ "handler": ["local_provider", null],
+ "redirect_url": "https://hassbian.local:8123/"
+}
+
+Return value will be a step in a data entry flow. See the docs for data entry
+flow for details.
+
+{
+ "data_schema": [
+ {"name": "username", "type": "string"},
+ {"name": "password", "type": "string"}
+ ],
+ "errors": {},
+ "flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
+ "handler": ["insecure_example", null],
+ "step_id": "init",
+ "type": "form"
+}
+
+
+# POST /auth/login_flow/{flow_id}
+
+Progress the flow. Most flows will be 1 page, but could optionally add extra
+login challenges, like TFA. Once the flow has finished, the returned step will
+have type "create_entry" and "result" key will contain an authorization code.
+
+{
+ "flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
+ "handler": ["insecure_example", null],
+ "result": "411ee2f916e648d691e937ae9344681e",
+ "source": "user",
+ "title": "Example",
+ "type": "create_entry",
+ "version": 1
+}
+"""
+import aiohttp.web
+import voluptuous as vol
+
+from homeassistant import data_entry_flow
+from homeassistant.components.http.ban import process_wrong_login, \
+ log_invalid_auth
+from homeassistant.components.http.data_validator import RequestDataValidator
+from homeassistant.components.http.view import HomeAssistantView
+from homeassistant.helpers.data_entry_flow import (
+ FlowManagerIndexView, FlowManagerResourceView)
+from . import indieauth
+
+
+async def async_setup(hass, store_credentials):
+ """Component to allow users to login."""
+ hass.http.register_view(AuthProvidersView)
+ hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow))
+ hass.http.register_view(
+ LoginFlowResourceView(hass.auth.login_flow, store_credentials))
+
+
+class AuthProvidersView(HomeAssistantView):
+ """View to get available auth providers."""
+
+ url = '/auth/providers'
+ name = 'api:auth:providers'
+ requires_auth = False
+
+ async def get(self, request):
+ """Get available auth providers."""
+ return self.json([{
+ 'name': provider.name,
+ 'id': provider.id,
+ 'type': provider.type,
+ } for provider in request.app['hass'].auth.auth_providers])
+
+
+class LoginFlowIndexView(FlowManagerIndexView):
+ """View to create a config flow."""
+
+ url = '/auth/login_flow'
+ name = 'api:auth:login_flow'
+ requires_auth = False
+
+ async def get(self, request):
+ """Do not allow index of flows in progress."""
+ return aiohttp.web.Response(status=405)
+
+ @RequestDataValidator(vol.Schema({
+ vol.Required('client_id'): str,
+ vol.Required('handler'): vol.Any(str, list),
+ vol.Required('redirect_uri'): str,
+ }))
+ @log_invalid_auth
+ async def post(self, request, data):
+ """Create a new login flow."""
+ if not indieauth.verify_redirect_uri(data['client_id'],
+ data['redirect_uri']):
+ return self.json_message('invalid client id or redirect uri', 400)
+
+ # pylint: disable=no-value-for-parameter
+ return await super().post(request)
+
+
+class LoginFlowResourceView(FlowManagerResourceView):
+ """View to interact with the flow manager."""
+
+ url = '/auth/login_flow/{flow_id}'
+ name = 'api:auth:login_flow:resource'
+ requires_auth = False
+
+ def __init__(self, flow_mgr, store_credentials):
+ """Initialize the login flow resource view."""
+ super().__init__(flow_mgr)
+ self._store_credentials = store_credentials
+
+ async def get(self, request, flow_id):
+ """Do not allow getting status of a flow in progress."""
+ return self.json_message('Invalid flow specified', 404)
+
+ @RequestDataValidator(vol.Schema({
+ 'client_id': str
+ }, extra=vol.ALLOW_EXTRA))
+ @log_invalid_auth
+ async def post(self, request, flow_id, data):
+ """Handle progressing a login flow request."""
+ client_id = data.pop('client_id')
+
+ if not indieauth.verify_client_id(client_id):
+ return self.json_message('Invalid client id', 400)
+
+ try:
+ result = await self._flow_mgr.async_configure(flow_id, data)
+ except data_entry_flow.UnknownFlow:
+ return self.json_message('Invalid flow specified', 404)
+ except vol.Invalid:
+ return self.json_message('User input malformed', 400)
+
+ if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
+ # @log_invalid_auth does not work here since it returns HTTP 200
+ # need manually log failed login attempts
+ if result['errors'] is not None and \
+ result['errors'].get('base') == 'invalid_auth':
+ await process_wrong_login(request)
+ return self.json(self._prepare_result_json(result))
+
+ result.pop('data')
+ result['result'] = self._store_credentials(client_id, result['result'])
+
+ return self.json(result)
diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py
index 2a7a3887b34..8b1cd3cad84 100644
--- a/homeassistant/components/automation/__init__.py
+++ b/homeassistant/components/automation/__init__.py
@@ -297,7 +297,7 @@ class AutomationEntity(ToggleEntity):
return
# HomeAssistant is starting up
- elif self.hass.state == CoreState.not_running:
+ if self.hass.state == CoreState.not_running:
@asyncio.coroutine
def async_enable_automation(event):
"""Start automation on startup."""
diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py
index 6b8ee577a09..74cf195bc61 100644
--- a/homeassistant/components/automation/homeassistant.py
+++ b/homeassistant/components/automation/homeassistant.py
@@ -44,7 +44,7 @@ def async_trigger(hass, config, action):
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
- elif hass.state == CoreState.starting:
+ if hass.state == CoreState.starting:
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',
diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py
index 172a368225d..60c33ca9b0e 100644
--- a/homeassistant/components/automation/mqtt.py
+++ b/homeassistant/components/automation/mqtt.py
@@ -10,7 +10,7 @@ import json
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py
index f932f239969..e3f327f1d5c 100644
--- a/homeassistant/components/bbb_gpio.py
+++ b/homeassistant/components/bbb_gpio.py
@@ -19,7 +19,7 @@ DOMAIN = 'bbb_gpio'
def setup(hass, config):
"""Set up the BeagleBone Black GPIO component."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
def cleanup_gpio(event):
"""Stuff to do before stopping."""
@@ -36,14 +36,14 @@ def setup(hass, config):
def setup_output(pin):
"""Set up a GPIO as output."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
GPIO.setup(pin, GPIO.OUT)
def setup_input(pin, pull_mode):
"""Set up a GPIO as input."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
GPIO.setup(pin, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN'
else GPIO.PUD_UP)
@@ -52,20 +52,20 @@ def setup_input(pin, pull_mode):
def write_output(pin, value):
"""Write a value to a GPIO."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
GPIO.output(pin, value)
def read_input(pin):
"""Read a value from a GPIO."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
return GPIO.input(pin) is GPIO.HIGH
def edge_detect(pin, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
# pylint: disable=import-error
- import Adafruit_BBIO.GPIO as GPIO
+ from Adafruit_BBIO import GPIO
GPIO.add_event_detect(
pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce)
diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py
index f0c8ec2d97c..fcc77d474e1 100644
--- a/homeassistant/components/binary_sensor/alarmdecoder.py
+++ b/homeassistant/components/binary_sensor/alarmdecoder.py
@@ -11,7 +11,8 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.alarmdecoder import (
ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE,
CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
- SIGNAL_RFX_MESSAGE)
+ SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR,
+ CONF_RELAY_CHAN)
DEPENDENCIES = ['alarmdecoder']
@@ -37,8 +38,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
zone_rfid = device_config_data.get(CONF_ZONE_RFID)
+ relay_addr = device_config_data.get(CONF_RELAY_ADDR)
+ relay_chan = device_config_data.get(CONF_RELAY_CHAN)
device = AlarmDecoderBinarySensor(
- zone_num, zone_name, zone_type, zone_rfid)
+ zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan)
devices.append(device)
add_devices(devices)
@@ -49,7 +52,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Representation of an AlarmDecoder binary sensor."""
- def __init__(self, zone_number, zone_name, zone_type, zone_rfid):
+ def __init__(self, zone_number, zone_name, zone_type, zone_rfid,
+ relay_addr, relay_chan):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
self._zone_type = zone_type
@@ -57,6 +61,8 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._name = zone_name
self._rfid = zone_rfid
self._rfstate = None
+ self._relay_addr = relay_addr
+ self._relay_chan = relay_chan
@asyncio.coroutine
def async_added_to_hass(self):
@@ -70,6 +76,9 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_RFX_MESSAGE, self._rfx_message_callback)
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_REL_MESSAGE, self._rel_message_callback)
+
@property
def name(self):
"""Return the name of the entity."""
@@ -122,3 +131,12 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
if self._rfid and message and message.serial_number == self._rfid:
self._rfstate = message.value
self.schedule_update_ha_state()
+
+ def _rel_message_callback(self, message):
+ """Update relay state."""
+ if (self._relay_addr == message.address and
+ self._relay_chan == message.channel):
+ _LOGGER.debug("Relay %d:%d value:%d", message.address,
+ message.channel, message.value)
+ self._state = message.value
+ self.schedule_update_ha_state()
diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py
index 73751ef14bb..0366f753ba6 100644
--- a/homeassistant/components/binary_sensor/arest.py
+++ b/homeassistant/components/binary_sensor/arest.py
@@ -89,7 +89,7 @@ class ArestBinarySensor(BinarySensorDevice):
self.arest.update()
-class ArestData(object):
+class ArestData:
"""Class for handling the data retrieval for pins."""
def __init__(self, resource, pin):
diff --git a/homeassistant/components/binary_sensor/aurora.py b/homeassistant/components/binary_sensor/aurora.py
index 772792f5785..0c33877854f 100644
--- a/homeassistant/components/binary_sensor/aurora.py
+++ b/homeassistant/components/binary_sensor/aurora.py
@@ -99,7 +99,7 @@ class AuroraSensor(BinarySensorDevice):
self.aurora_data.update()
-class AuroraData(object):
+class AuroraData:
"""Get aurora forecast."""
def __init__(self, latitude, longitude, threshold):
diff --git a/homeassistant/components/binary_sensor/bbb_gpio.py b/homeassistant/components/binary_sensor/bbb_gpio.py
index 785b178969f..690d1651db9 100644
--- a/homeassistant/components/binary_sensor/bbb_gpio.py
+++ b/homeassistant/components/binary_sensor/bbb_gpio.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.bbb_gpio as bbb_gpio
+from homeassistant.components import bbb_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME)
diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py
index 480786b2c2c..c2045c2df5e 100644
--- a/homeassistant/components/binary_sensor/command_line.py
+++ b/homeassistant/components/binary_sensor/command_line.py
@@ -25,6 +25,9 @@ DEFAULT_PAYLOAD_OFF = 'OFF'
SCAN_INTERVAL = timedelta(seconds=60)
+CONF_COMMAND_TIMEOUT = 'command_timeout'
+DEFAULT_TIMEOUT = 15
+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -32,6 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
+ vol.Optional(
+ CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})
@@ -43,9 +48,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload_on = config.get(CONF_PAYLOAD_ON)
device_class = config.get(CONF_DEVICE_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
+ command_timeout = config.get(CONF_COMMAND_TIMEOUT)
if value_template is not None:
value_template.hass = hass
- data = CommandSensorData(hass, command)
+ data = CommandSensorData(hass, command, command_timeout)
add_devices([CommandBinarySensor(
hass, data, name, device_class, payload_on, payload_off,
diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py
index f9ff4ac0a7a..de6ad8223d7 100644
--- a/homeassistant/components/binary_sensor/hikvision.py
+++ b/homeassistant/components/binary_sensor/hikvision.py
@@ -117,7 +117,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(entities)
-class HikvisionData(object):
+class HikvisionData:
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):
diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/binary_sensor/ihc.py
index 96efa6e6c19..25435d373fd 100644
--- a/homeassistant/components/binary_sensor/ihc.py
+++ b/homeassistant/components/binary_sensor/ihc.py
@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
-from xml.etree.ElementTree import Element
-
import voluptuous as vol
from homeassistant.components.binary_sensor import (
@@ -70,7 +68,7 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool,
- product: Element = None) -> None:
+ product=None) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None
diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py
index 9cb87b31749..25fc3fb5d73 100644
--- a/homeassistant/components/binary_sensor/insteon_plm.py
+++ b/homeassistant/components/binary_sensor/insteon_plm.py
@@ -17,7 +17,9 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {'openClosedSensor': 'opening',
'motionSensor': 'motion',
'doorSensor': 'door',
- 'wetLeakSensor': 'moisture'}
+ 'wetLeakSensor': 'moisture',
+ 'lightSensor': 'light',
+ 'batterySensor': 'battery'}
@asyncio.coroutine
@@ -54,4 +56,9 @@ class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
@property
def is_on(self):
"""Return the boolean response if the node is on."""
- return bool(self._insteon_device_state.value)
+ on_val = bool(self._insteon_device_state.value)
+
+ if self._insteon_device_state.name == 'lightSensor':
+ return not on_val
+
+ return on_val
diff --git a/homeassistant/components/binary_sensor/iss.py b/homeassistant/components/binary_sensor/iss.py
index d35c36a012e..d0654317248 100644
--- a/homeassistant/components/binary_sensor/iss.py
+++ b/homeassistant/components/binary_sensor/iss.py
@@ -101,7 +101,7 @@ class IssBinarySensor(BinarySensorDevice):
self.iss_data.update()
-class IssData(object):
+class IssData:
"""Get data from the ISS API."""
def __init__(self, latitude, longitude):
diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py
index deaa118f51c..b6d582b7793 100644
--- a/homeassistant/components/binary_sensor/isy994.py
+++ b/homeassistant/components/binary_sensor/isy994.py
@@ -55,7 +55,7 @@ def setup_platform(hass, config: ConfigType,
else:
device_type = _detect_device_type(node)
subnode_id = int(node.nid[-1])
- if (device_type == 'opening' or device_type == 'moisture'):
+ if device_type in ('opening', 'moisture'):
# These sensors use an optional "negative" subnode 2 to snag
# all state changes
if subnode_id == 2:
diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py
index 00dc588a468..1a45235f15a 100644
--- a/homeassistant/components/binary_sensor/modbus.py
+++ b/homeassistant/components/binary_sensor/modbus.py
@@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.modbus/
import logging
import voluptuous as vol
-import homeassistant.components.modbus as modbus
+from homeassistant.components import modbus
from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py
index d2533eb8f5b..cb943ac3f18 100644
--- a/homeassistant/components/binary_sensor/mqtt.py
+++ b/homeassistant/components/binary_sensor/mqtt.py
@@ -11,7 +11,7 @@ from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py
index 7c3a3e1dd30..73a373a15ff 100644
--- a/homeassistant/components/binary_sensor/netatmo.py
+++ b/homeassistant/components/binary_sensor/netatmo.py
@@ -142,7 +142,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Return the class of this sensor, from DEVICE_CLASSES."""
if self._cameratype == 'NACamera':
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
- elif self._cameratype == 'NOC':
+ if self._cameratype == 'NOC':
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
return TAG_SENSOR_TYPES.get(self._sensor_name)
diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py
index 0830d86dc2a..bb597f208e6 100644
--- a/homeassistant/components/binary_sensor/ping.py
+++ b/homeassistant/components/binary_sensor/ping.py
@@ -96,7 +96,7 @@ class PingBinarySensor(BinarySensorDevice):
self.ping.update()
-class PingData(object):
+class PingData:
"""The Class for handling the data retrieval."""
def __init__(self, host, count):
diff --git a/homeassistant/components/binary_sensor/rachio.py b/homeassistant/components/binary_sensor/rachio.py
index cc3079c6e53..59bf8a21064 100644
--- a/homeassistant/components/binary_sensor/rachio.py
+++ b/homeassistant/components/binary_sensor/rachio.py
@@ -111,11 +111,10 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
if data[KEY_STATUS] == STATUS_ONLINE:
return True
- elif data[KEY_STATUS] == STATUS_OFFLINE:
+ if data[KEY_STATUS] == STATUS_OFFLINE:
return False
- else:
- _LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
- data[KEY_STATUS])
+ _LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
+ data[KEY_STATUS])
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""
diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py
index 288b46c2370..3cbd179154f 100644
--- a/homeassistant/components/binary_sensor/raincloud.py
+++ b/homeassistant/components/binary_sensor/raincloud.py
@@ -67,6 +67,6 @@ class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice):
"""Return the icon of this device."""
if self._sensor_type == 'is_watering':
return 'mdi:water' if self.is_on else 'mdi:water-off'
- elif self._sensor_type == 'status':
+ if self._sensor_type == 'status':
return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected'
return ICON_MAP.get(self._sensor_type)
diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py
index 4072f4ae234..31a518dc1dc 100644
--- a/homeassistant/components/binary_sensor/rpi_gpio.py
+++ b/homeassistant/components/binary_sensor/rpi_gpio.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.rpi_gpio as rpi_gpio
+from homeassistant.components import rpi_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME
diff --git a/homeassistant/components/binary_sensor/rpi_pfio.py b/homeassistant/components/binary_sensor/rpi_pfio.py
index 1abfa25c82b..a1126bdd2f9 100644
--- a/homeassistant/components/binary_sensor/rpi_pfio.py
+++ b/homeassistant/components/binary_sensor/rpi_pfio.py
@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
-import homeassistant.components.rpi_pfio as rpi_pfio
+from homeassistant.components import rpi_pfio
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/binary_sensor/tahoma.py b/homeassistant/components/binary_sensor/tahoma.py
new file mode 100644
index 00000000000..efcfb629f39
--- /dev/null
+++ b/homeassistant/components/binary_sensor/tahoma.py
@@ -0,0 +1,98 @@
+"""
+Support for Tahoma binary sensors.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/binary_sensor.tahoma/
+"""
+
+import logging
+from datetime import timedelta
+
+from homeassistant.components.binary_sensor import (
+ BinarySensorDevice)
+from homeassistant.components.tahoma import (
+ DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
+from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL)
+
+DEPENDENCIES = ['tahoma']
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = timedelta(seconds=120)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up Tahoma controller devices."""
+ _LOGGER.debug("Setup Tahoma Binary sensor platform")
+ controller = hass.data[TAHOMA_DOMAIN]['controller']
+ devices = []
+ for device in hass.data[TAHOMA_DOMAIN]['devices']['smoke']:
+ devices.append(TahomaBinarySensor(device, controller))
+ add_devices(devices, True)
+
+
+class TahomaBinarySensor(TahomaDevice, BinarySensorDevice):
+ """Representation of a Tahoma Binary Sensor."""
+
+ def __init__(self, tahoma_device, controller):
+ """Initialize the sensor."""
+ super().__init__(tahoma_device, controller)
+
+ self._state = None
+ self._icon = None
+ self._battery = None
+
+ @property
+ def is_on(self):
+ """Return the state of the sensor."""
+ return bool(self._state == STATE_ON)
+
+ @property
+ def device_class(self):
+ """Return the class of the device."""
+ if self.tahoma_device.type == 'rtds:RTDSSmokeSensor':
+ return 'smoke'
+ return None
+
+ @property
+ def icon(self):
+ """Icon for device by its type."""
+ return self._icon
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes."""
+ attr = {}
+ super_attr = super().device_state_attributes
+ if super_attr is not None:
+ attr.update(super_attr)
+
+ if self._battery is not None:
+ attr[ATTR_BATTERY_LEVEL] = self._battery
+ return attr
+
+ def update(self):
+ """Update the state."""
+ self.controller.get_states([self.tahoma_device])
+ if self.tahoma_device.type == 'rtds:RTDSSmokeSensor':
+ if self.tahoma_device.active_states['core:SmokeState']\
+ == 'notDetected':
+ self._state = STATE_OFF
+ else:
+ self._state = STATE_ON
+
+ if 'core:SensorDefectState' in self.tahoma_device.active_states:
+ # Set to 'lowBattery' for low battery warning.
+ self._battery = self.tahoma_device.active_states[
+ 'core:SensorDefectState']
+ else:
+ self._battery = None
+
+ if self._state == STATE_ON:
+ self._icon = "mdi:fire"
+ elif self._battery == 'lowBattery':
+ self._icon = "mdi:battery-alert"
+ else:
+ self._icon = None
+
+ _LOGGER.debug("Update %s, state: %s", self._name, self._state)
diff --git a/homeassistant/components/binary_sensor/tapsaff.py b/homeassistant/components/binary_sensor/tapsaff.py
index c0f6ca3f112..5b8e133b5f4 100644
--- a/homeassistant/components/binary_sensor/tapsaff.py
+++ b/homeassistant/components/binary_sensor/tapsaff.py
@@ -63,7 +63,7 @@ class TapsAffSensor(BinarySensorDevice):
self.data.update()
-class TapsAffData(object):
+class TapsAffData:
"""Class for handling the data retrieval for pins."""
def __init__(self, location):
diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py
index 79c36fb2ef2..360671d1cea 100644
--- a/homeassistant/components/binary_sensor/threshold.py
+++ b/homeassistant/components/binary_sensor/threshold.py
@@ -129,9 +129,9 @@ class ThresholdSensor(BinarySensorDevice):
if self._threshold_lower is not None and \
self._threshold_upper is not None:
return TYPE_RANGE
- elif self._threshold_lower is not None:
+ if self._threshold_lower is not None:
return TYPE_LOWER
- elif self._threshold_upper is not None:
+ if self._threshold_upper is not None:
return TYPE_UPPER
@property
diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py
index 6a53569798b..78f471d125b 100644
--- a/homeassistant/components/binary_sensor/trend.py
+++ b/homeassistant/components/binary_sensor/trend.py
@@ -23,7 +23,7 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import utcnow
-REQUIREMENTS = ['numpy==1.14.5']
+REQUIREMENTS = ['numpy==1.15.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py
index 39f520ddc6d..402feefa99f 100644
--- a/homeassistant/components/binary_sensor/volvooncall.py
+++ b/homeassistant/components/binary_sensor/volvooncall.py
@@ -28,7 +28,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
val = getattr(self.vehicle, self._attribute)
if self._attribute == 'bulb_failures':
return bool(val)
- elif self._attribute in ['doors', 'windows']:
+ if self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
return val != 'Normal'
diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py
index e6eff0d9bb5..a589ab4e8c8 100644
--- a/homeassistant/components/binary_sensor/wemo.py
+++ b/homeassistant/components/binary_sensor/wemo.py
@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors."""
- import pywemo.discovery as discovery
+ from pywemo import discovery
if discovery_info is not None:
location = discovery_info['ssdp_description']
diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py
index b37be3f6cb6..00d2a95e356 100644
--- a/homeassistant/components/binary_sensor/workday.py
+++ b/homeassistant/components/binary_sensor/workday.py
@@ -135,7 +135,7 @@ class IsWorkdaySensor(BinarySensorDevice):
"""Check if given day is in the includes list."""
if day in self._workdays:
return True
- elif 'holiday' in self._workdays and now in self._obj_holidays:
+ if 'holiday' in self._workdays and now in self._obj_holidays:
return True
return False
@@ -144,7 +144,7 @@ class IsWorkdaySensor(BinarySensorDevice):
"""Check if given day is in the excludes list."""
if day in self._excludes:
return True
- elif 'holiday' in self._excludes and now in self._obj_holidays:
+ if 'holiday' in self._excludes and now in self._obj_holidays:
return True
return False
diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/binary_sensor/xiaomi_aqara.py
index be5d9a689d1..2a9746b4a01 100644
--- a/homeassistant/components/binary_sensor/xiaomi_aqara.py
+++ b/homeassistant/components/binary_sensor/xiaomi_aqara.py
@@ -124,7 +124,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
return False
self._state = True
return True
- elif value == '0':
+ if value == '0':
if self._state:
self._state = False
return True
@@ -184,7 +184,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
return False
self._state = True
return True
- elif value == NO_MOTION:
+ if value == NO_MOTION:
if not self._state:
return False
self._state = False
@@ -224,7 +224,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
return False
self._state = True
return True
- elif value == 'close':
+ if value == 'close':
self._open_since = 0
if self._state:
self._state = False
@@ -254,7 +254,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
return False
self._state = True
return True
- elif value == 'no_leak':
+ if value == 'no_leak':
if self._state:
self._state = False
return True
@@ -290,7 +290,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
return False
self._state = True
return True
- elif value == '0':
+ if value == '0':
if self._state:
self._state = False
return True
diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py
index fc18648f907..784a96d8615 100644
--- a/homeassistant/components/binary_sensor/zwave.py
+++ b/homeassistant/components/binary_sensor/zwave.py
@@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py
index a44f0163787..e84643711eb 100644
--- a/homeassistant/components/blink.py
+++ b/homeassistant/components/blink.py
@@ -40,7 +40,7 @@ SNAP_PICTURE_SCHEMA = vol.Schema({
})
-class BlinkSystem(object):
+class BlinkSystem:
"""Blink System class."""
def __init__(self, config_info):
diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py
index bc9d3acf54f..00377b3f12b 100644
--- a/homeassistant/components/bloomsky.py
+++ b/homeassistant/components/bloomsky.py
@@ -50,7 +50,7 @@ def setup(hass, config):
return True
-class BloomSky(object):
+class BloomSky:
"""Handle all communication with the BloomSky API."""
# API documentation at http://weatherlution.com/bloomsky-api/
diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py
index a7ed262ac2c..061b09c1b3b 100644
--- a/homeassistant/components/bmw_connected_drive/__init__.py
+++ b/homeassistant/components/bmw_connected_drive/__init__.py
@@ -118,7 +118,7 @@ def setup_account(account_config: dict, hass, name: str) \
return cd_account
-class BMWConnectedDriveAccount(object):
+class BMWConnectedDriveAccount:
"""Representation of a BMW vehicle."""
def __init__(self, username: str, password: str, region_str: str,
diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py
index 35566b0cbed..9d105fb02d0 100644
--- a/homeassistant/components/calendar/__init__.py
+++ b/homeassistant/components/calendar/__init__.py
@@ -130,7 +130,7 @@ class CalendarEventDevice(Entity):
now = dt.now()
- if start <= now and end > now:
+ if start <= now < end:
return STATE_ON
if now >= end:
diff --git a/homeassistant/components/calendar/caldav.py b/homeassistant/components/calendar/caldav.py
index 9c30d1481f8..3db24790aaf 100644
--- a/homeassistant/components/calendar/caldav.py
+++ b/homeassistant/components/calendar/caldav.py
@@ -125,7 +125,7 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
return await self.data.async_get_events(hass, start_date, end_date)
-class WebDavCalendarData(object):
+class WebDavCalendarData:
"""Class to utilize the calendar dav client object to get next event."""
def __init__(self, calendar, include_all_day, search):
diff --git a/homeassistant/components/calendar/demo.py b/homeassistant/components/calendar/demo.py
index 53129d3316c..0bf09f6f2c7 100644
--- a/homeassistant/components/calendar/demo.py
+++ b/homeassistant/components/calendar/demo.py
@@ -28,7 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
-class DemoGoogleCalendarData(object):
+class DemoGoogleCalendarData:
"""Representation of a Demo Calendar element."""
event = {}
diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py
index 279fb1e2694..925bbcacddf 100644
--- a/homeassistant/components/calendar/google.py
+++ b/homeassistant/components/calendar/google.py
@@ -55,7 +55,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
return await self.data.async_get_events(hass, start_date, end_date)
-class GoogleCalendarData(object):
+class GoogleCalendarData:
"""Class to utilize calendar service object to get next event."""
def __init__(self, calendar_service, calendar_id, search,
diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py
index 71a6a17de10..30c5a6177b4 100644
--- a/homeassistant/components/calendar/todoist.py
+++ b/homeassistant/components/calendar/todoist.py
@@ -26,6 +26,9 @@ CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
CONF_PROJECT_WHITELIST = 'include_projects'
+# https://github.com/PyCQA/pylint/pull/2320
+# pylint: disable=fixme
+
# Calendar Platform: Does this calendar event last all day?
ALL_DAY = 'all_day'
# Attribute: All tasks in this project
@@ -280,7 +283,7 @@ class TodoistProjectDevice(CalendarEventDevice):
return attributes
-class TodoistProjectData(object):
+class TodoistProjectData:
"""
Class used by the Task Device service object to hold all Todoist Tasks.
@@ -503,7 +506,7 @@ class TodoistProjectData(object):
time_format = '%a %d %b %Y %H:%M:%S %z'
for task in project_task_data:
due_date = datetime.strptime(task['due_date_utc'], time_format)
- if due_date > start_date and due_date < end_date:
+ if start_date < due_date < end_date:
event = {
'uid': task['id'],
'title': task['content'],
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index 22354b51956..736bcec1e9c 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -19,7 +19,8 @@ import async_timeout
import voluptuous as vol
from homeassistant.core import callback
-from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
+ SERVICE_TURN_ON
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import Entity
@@ -47,6 +48,9 @@ STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
+# Bitfield of features supported by the camera entity
+SUPPORT_ON_OFF = 1
+
DEFAULT_CONTENT_TYPE = 'image/jpeg'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
@@ -79,6 +83,35 @@ class Image:
content = attr.ib(type=bytes)
+@bind_hass
+def turn_off(hass, entity_id=None):
+ """Turn off camera."""
+ hass.add_job(async_turn_off, hass, entity_id)
+
+
+@bind_hass
+async def async_turn_off(hass, entity_id=None):
+ """Turn off camera."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def turn_on(hass, entity_id=None):
+ """Turn on camera."""
+ hass.add_job(async_turn_on, hass, entity_id)
+
+
+@bind_hass
+async def async_turn_on(hass, entity_id=None):
+ """Turn on camera, and set operation mode."""
+ data = {}
+ if entity_id is not None:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)
+
+
@bind_hass
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
@@ -119,6 +152,9 @@ async def async_get_image(hass, entity_id, timeout=10):
if camera is None:
raise HomeAssistantError('Camera not found')
+ if not camera.is_on:
+ raise HomeAssistantError('Camera is off')
+
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(timeout, loop=hass.loop):
image = await camera.async_camera_image()
@@ -163,6 +199,12 @@ async def async_setup(hass, config):
await camera.async_enable_motion_detection()
elif service.service == SERVICE_DISABLE_MOTION:
await camera.async_disable_motion_detection()
+ elif service.service == SERVICE_TURN_OFF and \
+ camera.supported_features & SUPPORT_ON_OFF:
+ await camera.async_turn_off()
+ elif service.service == SERVICE_TURN_ON and \
+ camera.supported_features & SUPPORT_ON_OFF:
+ await camera.async_turn_on()
if not camera.should_poll:
continue
@@ -200,6 +242,12 @@ async def async_setup(hass, config):
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
+ hass.services.async_register(
+ DOMAIN, SERVICE_TURN_OFF, async_handle_camera_service,
+ schema=CAMERA_SERVICE_SCHEMA)
+ hass.services.async_register(
+ DOMAIN, SERVICE_TURN_ON, async_handle_camera_service,
+ schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_ENABLE_MOTION, async_handle_camera_service,
schema=CAMERA_SERVICE_SCHEMA)
@@ -243,6 +291,11 @@ class Camera(Entity):
"""Return a link to the camera feed as entity picture."""
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1])
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ return 0
+
@property
def is_recording(self):
"""Return true if the device is recording."""
@@ -301,32 +354,23 @@ class Camera(Entity):
last_image = None
- try:
- while True:
- img_bytes = await self.async_camera_image()
- if not img_bytes:
- break
+ while True:
+ img_bytes = await self.async_camera_image()
+ if not img_bytes:
+ break
- if img_bytes and img_bytes != last_image:
+ if img_bytes and img_bytes != last_image:
+ await write_to_mjpeg_stream(img_bytes)
+
+ # Chrome seems to always ignore first picture,
+ # print it twice.
+ if last_image is None:
await write_to_mjpeg_stream(img_bytes)
+ last_image = img_bytes
- # Chrome seems to always ignore first picture,
- # print it twice.
- if last_image is None:
- await write_to_mjpeg_stream(img_bytes)
+ await asyncio.sleep(interval)
- last_image = img_bytes
-
- await asyncio.sleep(interval)
-
- except asyncio.CancelledError:
- _LOGGER.debug("Stream closed by frontend.")
- response = None
- raise
-
- finally:
- if response is not None:
- await response.write_eof()
+ return response
async def handle_async_mjpeg_stream(self, request):
"""Serve an HTTP MJPEG stream from the camera.
@@ -342,14 +386,38 @@ class Camera(Entity):
"""Return the camera state."""
if self.is_recording:
return STATE_RECORDING
- elif self.is_streaming:
+ if self.is_streaming:
return STATE_STREAMING
return STATE_IDLE
+ @property
+ def is_on(self):
+ """Return true if on."""
+ return True
+
+ def turn_off(self):
+ """Turn off camera."""
+ raise NotImplementedError()
+
+ @callback
+ def async_turn_off(self):
+ """Turn off camera."""
+ return self.hass.async_add_job(self.turn_off)
+
+ def turn_on(self):
+ """Turn off camera."""
+ raise NotImplementedError()
+
+ @callback
+ def async_turn_on(self):
+ """Turn off camera."""
+ return self.hass.async_add_job(self.turn_on)
+
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
raise NotImplementedError()
+ @callback
def async_enable_motion_detection(self):
"""Call the job and enable motion detection."""
return self.hass.async_add_job(self.enable_motion_detection)
@@ -358,6 +426,7 @@ class Camera(Entity):
"""Disable motion detection in camera."""
raise NotImplementedError()
+ @callback
def async_disable_motion_detection(self):
"""Call the job and disable motion detection."""
return self.hass.async_add_job(self.disable_motion_detection)
@@ -402,17 +471,19 @@ class CameraView(HomeAssistantView):
camera = self.component.get_entity(entity_id)
if camera is None:
- status = 404 if request[KEY_AUTHENTICATED] else 401
- return web.Response(status=status)
+ raise web.HTTPNotFound()
authenticated = (request[KEY_AUTHENTICATED] or
request.query.get('token') in camera.access_tokens)
if not authenticated:
- return web.Response(status=401)
+ raise web.HTTPUnauthorized()
- response = await self.handle(request, camera)
- return response
+ if not camera.is_on:
+ _LOGGER.debug('Camera is off.')
+ raise web.HTTPServiceUnavailable()
+
+ return await self.handle(request, camera)
async def handle(self, request, camera):
"""Handle the camera request."""
@@ -435,7 +506,7 @@ class CameraImageView(CameraView):
return web.Response(body=image,
content_type=camera.content_type)
- return web.Response(status=500)
+ raise web.HTTPInternalServerError()
class CameraMjpegStream(CameraView):
@@ -448,8 +519,7 @@ class CameraMjpegStream(CameraView):
"""Serve camera stream, possibly with interval."""
interval = request.query.get('interval')
if interval is None:
- await camera.handle_async_mjpeg_stream(request)
- return
+ return await camera.handle_async_mjpeg_stream(request)
try:
# Compose camera stream from stills
@@ -457,10 +527,9 @@ class CameraMjpegStream(CameraView):
if interval < MIN_STREAM_INTERVAL:
raise ValueError("Stream interval must be be > {}"
.format(MIN_STREAM_INTERVAL))
- await camera.handle_async_still_stream(request, interval)
- return
+ return await camera.handle_async_still_stream(request, interval)
except ValueError:
- return web.Response(status=400)
+ raise web.HTTPBadRequest()
@callback
diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py
index 3c63e56b319..4cb218bc019 100644
--- a/homeassistant/components/camera/amcrest.py
+++ b/homeassistant/components/camera/amcrest.py
@@ -64,7 +64,7 @@ class AmcrestCam(Camera):
yield from super().handle_async_mjpeg_stream(request)
return
- elif self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
+ if self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
# stream an MJPEG image stream directly from the camera
websession = async_get_clientsession(self.hass)
streaming_url = self._camera.mjpeg_url(typeno=self._resolution)
diff --git a/homeassistant/components/camera/axis.py b/homeassistant/components/camera/axis.py
index 51c3bc89b05..5b39718939a 100644
--- a/homeassistant/components/camera/axis.py
+++ b/homeassistant/components/camera/axis.py
@@ -23,7 +23,7 @@ def _get_image_url(host, port, mode):
"""Set the URL to get the image."""
if mode == 'mjpeg':
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
- elif mode == 'single':
+ if mode == 'single':
return 'http://{}:{}/axis-cgi/jpg/image.cgi'.format(host, port)
diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py
index 3c1477d1828..0e77e6e95ad 100644
--- a/homeassistant/components/camera/demo.py
+++ b/homeassistant/components/camera/demo.py
@@ -4,10 +4,10 @@ Demo camera platform that has a fake camera.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
-import os
import logging
-import homeassistant.util.dt as dt_util
-from homeassistant.components.camera import Camera
+import os
+
+from homeassistant.components.camera import Camera, SUPPORT_ON_OFF
_LOGGER = logging.getLogger(__name__)
@@ -16,26 +16,29 @@ async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Demo camera platform."""
async_add_devices([
- DemoCamera(hass, config, 'Demo camera')
+ DemoCamera('Demo camera')
])
class DemoCamera(Camera):
"""The representation of a Demo camera."""
- def __init__(self, hass, config, name):
+ def __init__(self, name):
"""Initialize demo camera component."""
super().__init__()
- self._parent = hass
self._name = name
self._motion_status = False
+ self.is_streaming = True
+ self._images_index = 0
def camera_image(self):
"""Return a faked still image response."""
- now = dt_util.utcnow()
+ self._images_index = (self._images_index + 1) % 4
image_path = os.path.join(
- os.path.dirname(__file__), 'demo_{}.jpg'.format(now.second % 4))
+ os.path.dirname(__file__),
+ 'demo_{}.jpg'.format(self._images_index))
+ _LOGGER.debug('Loading camera_image: %s', image_path)
with open(image_path, 'rb') as file:
return file.read()
@@ -46,8 +49,21 @@ class DemoCamera(Camera):
@property
def should_poll(self):
- """Camera should poll periodically."""
- return True
+ """Demo camera doesn't need poll.
+
+ Need explicitly call schedule_update_ha_state() after state changed.
+ """
+ return False
+
+ @property
+ def supported_features(self):
+ """Camera support turn on/off features."""
+ return SUPPORT_ON_OFF
+
+ @property
+ def is_on(self):
+ """Whether camera is on (streaming)."""
+ return self.is_streaming
@property
def motion_detection_enabled(self):
@@ -57,7 +73,19 @@ class DemoCamera(Camera):
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
+ self.schedule_update_ha_state()
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
+ self.schedule_update_ha_state()
+
+ def turn_off(self):
+ """Turn off camera."""
+ self.is_streaming = False
+ self.schedule_update_ha_state()
+
+ def turn_on(self):
+ """Turn on camera."""
+ self.is_streaming = True
+ self.schedule_update_ha_state()
diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py
index 1bbd263e585..3da0f19fbf0 100644
--- a/homeassistant/components/camera/ffmpeg.py
+++ b/homeassistant/components/camera/ffmpeg.py
@@ -29,8 +29,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_devices,
+ discovery_info=None):
"""Set up a FFmpeg camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
@@ -49,30 +49,30 @@ class FFmpegCamera(Camera):
self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
- image = yield from asyncio.shield(ffmpeg.get_image(
+ image = await asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
return image
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
- yield from stream.open_camera(
+ await stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
- yield from async_aiohttp_proxy_stream(
- self.hass, request, stream,
- 'multipart/x-mixed-replace;boundary=ffserver')
- yield from stream.close()
+ try:
+ return await async_aiohttp_proxy_stream(
+ self.hass, request, stream,
+ 'multipart/x-mixed-replace;boundary=ffserver')
+ finally:
+ await stream.close()
@property
def name(self):
diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py
index a5ed0cdc02c..757a1b5fc09 100644
--- a/homeassistant/components/camera/mjpeg.py
+++ b/homeassistant/components/camera/mjpeg.py
@@ -123,19 +123,18 @@ class MjpegCamera(Camera):
with closing(req) as response:
return extract_image_from_mjpeg(response.iter_content(102400))
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
# aiohttp don't support DigestAuth -> Fallback
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
- yield from super().handle_async_mjpeg_stream(request)
+ await super().handle_async_mjpeg_stream(request)
return
# connect to stream
websession = async_get_clientsession(self.hass)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
- yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
+ return await async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):
diff --git a/homeassistant/components/camera/mqtt.py b/homeassistant/components/camera/mqtt.py
index b2a27230a02..dc991644b8e 100644
--- a/homeassistant/components/camera/mqtt.py
+++ b/homeassistant/components/camera/mqtt.py
@@ -11,7 +11,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.helpers import config_validation as cv
diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py
index ab26df5caf0..bf6700371fd 100644
--- a/homeassistant/components/camera/nest.py
+++ b/homeassistant/components/camera/nest.py
@@ -9,8 +9,9 @@ from datetime import timedelta
import requests
-import homeassistant.components.nest as nest
-from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
+from homeassistant.components import nest
+from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera,
+ SUPPORT_ON_OFF)
from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
@@ -76,7 +77,36 @@ class NestCamera(Camera):
"""Return the brand of the camera."""
return NEST_BRAND
- # This doesn't seem to be getting called regularly, for some reason
+ @property
+ def supported_features(self):
+ """Nest Cam support turn on and off."""
+ return SUPPORT_ON_OFF
+
+ @property
+ def is_on(self):
+ """Return true if on."""
+ return self._online and self._is_streaming
+
+ def turn_off(self):
+ """Turn off camera."""
+ _LOGGER.debug('Turn off camera %s', self._name)
+ # Calling Nest API in is_streaming setter.
+ # device.is_streaming would not immediately change until the process
+ # finished in Nest Cam.
+ self.device.is_streaming = False
+
+ def turn_on(self):
+ """Turn on camera."""
+ if not self._online:
+ _LOGGER.error('Camera %s is offline.', self._name)
+ return
+
+ _LOGGER.debug('Turn on camera %s', self._name)
+ # Calling Nest API in is_streaming setter.
+ # device.is_streaming would not immediately change until the process
+ # finished in Nest Cam.
+ self.device.is_streaming = True
+
def update(self):
"""Cache value from Python-nest."""
self._location = self.device.where
diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py
index 34a78e19f9f..1c7dc4c7ce0 100644
--- a/homeassistant/components/camera/netatmo.py
+++ b/homeassistant/components/camera/netatmo.py
@@ -105,6 +105,6 @@ class NetatmoCamera(Camera):
"""Return the camera model."""
if self._cameratype == "NOC":
return "Presence"
- elif self._cameratype == "NACamera":
+ if self._cameratype == "NACamera":
return "Welcome"
return None
diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py
index 447f4e1e56a..a695848d1fa 100644
--- a/homeassistant/components/camera/proxy.py
+++ b/homeassistant/components/camera/proxy.py
@@ -2,56 +2,53 @@
Proxy camera platform that enables image processing of camera data.
For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/proxy
+https://www.home-assistant.io/components/camera.proxy/
"""
-import logging
import asyncio
+import logging
+
import aiohttp
import async_timeout
-
import voluptuous as vol
-from homeassistant.util.async_ import run_coroutine_threadsafe
+from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
+from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
from homeassistant.helpers import config_validation as cv
-
-import homeassistant.util.dt as dt_util
-from homeassistant.const import (
- CONF_NAME, CONF_ENTITY_ID, HTTP_HEADER_HA_AUTH)
-from homeassistant.components.camera import (
- PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
- async_get_clientsession, async_aiohttp_proxy_web)
+ async_aiohttp_proxy_web, async_get_clientsession)
+from homeassistant.util.async_ import run_coroutine_threadsafe
+import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pillow==5.0.0']
+REQUIREMENTS = ['pillow==5.2.0']
_LOGGER = logging.getLogger(__name__)
-CONF_MAX_IMAGE_WIDTH = "max_image_width"
-CONF_IMAGE_QUALITY = "image_quality"
-CONF_IMAGE_REFRESH_RATE = "image_refresh_rate"
-CONF_FORCE_RESIZE = "force_resize"
-CONF_MAX_STREAM_WIDTH = "max_stream_width"
-CONF_STREAM_QUALITY = "stream_quality"
-CONF_CACHE_IMAGES = "cache_images"
+CONF_CACHE_IMAGES = 'cache_images'
+CONF_FORCE_RESIZE = 'force_resize'
+CONF_IMAGE_QUALITY = 'image_quality'
+CONF_IMAGE_REFRESH_RATE = 'image_refresh_rate'
+CONF_MAX_IMAGE_WIDTH = 'max_image_width'
+CONF_MAX_STREAM_WIDTH = 'max_stream_width'
+CONF_STREAM_QUALITY = 'stream_quality'
DEFAULT_BASENAME = "Camera Proxy"
DEFAULT_QUALITY = 75
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
- vol.Optional(CONF_NAME): cv.string,
- vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
+ vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
+ vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
vol.Optional(CONF_IMAGE_QUALITY): int,
vol.Optional(CONF_IMAGE_REFRESH_RATE): float,
- vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
- vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
+ vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
vol.Optional(CONF_MAX_STREAM_WIDTH): int,
+ vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_STREAM_QUALITY): int,
})
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
"""Set up the Proxy camera platform."""
async_add_devices([ProxyCamera(hass, config)])
@@ -69,7 +66,7 @@ def _resize_image(image, opts):
img = Image.open(io.BytesIO(image))
imgfmt = str(img.format)
- if imgfmt != 'PNG' and imgfmt != 'JPEG':
+ if imgfmt not in ('PNG', 'JPEG'):
_LOGGER.debug("Image is of unsupported type: %s", imgfmt)
return image
@@ -77,7 +74,7 @@ def _resize_image(image, opts):
old_size = len(image)
if old_width <= new_width:
if opts.quality is None:
- _LOGGER.debug("Image is smaller-than / equal-to requested width")
+ _LOGGER.debug("Image is smaller-than/equal-to requested width")
return image
new_width = old_width
@@ -86,7 +83,7 @@ def _resize_image(image, opts):
img = img.resize((new_width, new_height), Image.ANTIALIAS)
imgbuf = io.BytesIO()
- img.save(imgbuf, "JPEG", optimize=True, quality=quality)
+ img.save(imgbuf, 'JPEG', optimize=True, quality=quality)
newimage = imgbuf.getvalue()
if not opts.force_resize and len(newimage) >= old_size:
_LOGGER.debug("Using original image(%d bytes) "
@@ -94,11 +91,9 @@ def _resize_image(image, opts):
old_size, len(newimage))
return image
- _LOGGER.debug("Resized image "
- "from (%dx%d - %d bytes) "
- "to (%dx%d - %d bytes)",
- old_width, old_height, old_size,
- new_width, new_height, len(newimage))
+ _LOGGER.debug(
+ "Resized image from (%dx%d - %d bytes) to (%dx%d - %d bytes)",
+ old_width, old_height, old_size, new_width, new_height, len(newimage))
return newimage
@@ -112,7 +107,7 @@ class ImageOpts():
self.force_resize = force_resize
def __bool__(self):
- """Bool evalution rules."""
+ """Bool evaluation rules."""
return bool(self.max_width or self.quality)
@@ -133,8 +128,7 @@ class ProxyCamera(Camera):
config.get(CONF_FORCE_RESIZE))
self._stream_opts = ImageOpts(
- config.get(CONF_MAX_STREAM_WIDTH),
- config.get(CONF_STREAM_QUALITY),
+ config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY),
True)
self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE)
@@ -145,8 +139,7 @@ class ProxyCamera(Camera):
self._last_image = None
self._headers = (
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
- if self.hass.config.api.api_password is not None
- else None)
+ if self.hass.config.api.api_password is not None else None)
def camera_image(self):
"""Return camera image."""
@@ -191,12 +184,12 @@ class ProxyCamera(Camera):
stream_coro = websession.get(url, headers=self._headers)
if not self._stream_opts:
- await async_aiohttp_proxy_web(self.hass, request, stream_coro)
- return
+ return await async_aiohttp_proxy_web(
+ self.hass, request, stream_coro)
response = aiohttp.web.StreamResponse()
- response.content_type = ('multipart/x-mixed-replace; '
- 'boundary=--frameboundary')
+ response.content_type = (
+ 'multipart/x-mixed-replace; boundary=--frameboundary')
await response.prepare(request)
async def write(img_bytes):
@@ -229,15 +222,10 @@ class ProxyCamera(Camera):
_resize_image, image, self._stream_opts)
await write(image)
data = data[jpg_end + 2:]
- except asyncio.CancelledError:
- _LOGGER.debug("Stream closed by frontend.")
- req.close()
- response = None
- raise
-
finally:
- if response is not None:
- await response.write_eof()
+ req.close()
+
+ return response
@property
def name(self):
diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml
index 544fd0e6b8a..b977fcd5c52 100644
--- a/homeassistant/components/camera/services.yaml
+++ b/homeassistant/components/camera/services.yaml
@@ -1,5 +1,19 @@
# Describes the format for available camera services
+turn_off:
+ description: Turn off camera.
+ fields:
+ entity_id:
+ description: Entity id.
+ example: 'camera.living_room'
+
+turn_on:
+ description: Turn on camera.
+ fields:
+ entity_id:
+ description: Entity id.
+ example: 'camera.living_room'
+
enable_motion_detection:
description: Enable the motion detection in a camera.
fields:
diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py
index e992020e2b2..b5306c31c84 100644
--- a/homeassistant/components/camera/uvc.py
+++ b/homeassistant/components/camera/uvc.py
@@ -171,10 +171,9 @@ class UnifiVideoCamera(Camera):
if retry:
self._login()
return _get_image(retry=False)
- else:
- _LOGGER.error(
- "Unable to log into camera, unable to get snapshot")
- raise
+ _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 b637858303e..554f877d0bd 100644
--- a/homeassistant/components/camera/verisure.py
+++ b/homeassistant/components/camera/verisure.py
@@ -66,8 +66,7 @@ class VerisureSmartcam(Camera):
if not image_ids:
return
new_image_id = image_ids[0]
- if (new_image_id == '-1' or
- self._image_id == new_image_id):
+ if new_image_id in ('-1', self._image_id):
_LOGGER.debug("The image is the same, or loading image_id")
return
_LOGGER.debug("Download new image %s", new_image_id)
diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py
index 90ef08c24fe..be59a1c1f50 100644
--- a/homeassistant/components/camera/zoneminder.py
+++ b/homeassistant/components/camera/zoneminder.py
@@ -12,7 +12,7 @@ from homeassistant.const import CONF_NAME
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
-import homeassistant.components.zoneminder as zoneminder
+from homeassistant.components import zoneminder
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/canary.py b/homeassistant/components/canary.py
index 4d0fbe617b2..04c33d83f3d 100644
--- a/homeassistant/components/canary.py
+++ b/homeassistant/components/canary.py
@@ -65,7 +65,7 @@ def setup(hass, config):
return True
-class CanaryData(object):
+class CanaryData:
"""Get the latest data and update the states."""
def __init__(self, username, password, timeout):
diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py
index 86c6152b6e2..aadf0103c5a 100644
--- a/homeassistant/components/cast/__init__.py
+++ b/homeassistant/components/cast/__init__.py
@@ -22,7 +22,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
"""Set up Cast from a config entry."""
- hass.async_add_job(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'media_player'))
return True
@@ -31,7 +31,7 @@ async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
- return await hass.async_add_job(discover_chromecasts)
+ return await hass.async_add_executor_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(
diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/climate/daikin.py
index 2c49b25a39d..50501025f0c 100644
--- a/homeassistant/components/climate/daikin.py
+++ b/homeassistant/components/climate/daikin.py
@@ -145,7 +145,7 @@ class DaikinClimate(ClimateDevice):
if value is None:
_LOGGER.error("Invalid value requested for key %s", key)
else:
- if value == "-" or value == "--":
+ if value in ("-", "--"):
value = None
elif cast_to_float:
try:
diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py
index e64c2d5000e..71878827153 100644
--- a/homeassistant/components/climate/ecobee.py
+++ b/homeassistant/components/climate/ecobee.py
@@ -177,7 +177,7 @@ class Thermostat(ClimateDevice):
return None
if self.current_operation == STATE_HEAT:
return self.thermostat['runtime']['desiredHeat'] / 10.0
- elif self.current_operation == STATE_COOL:
+ if self.current_operation == STATE_COOL:
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@@ -217,15 +217,15 @@ class Thermostat(ClimateDevice):
return 'away'
# A permanent hold from away climate
return AWAY_MODE
- elif event['holdClimateRef'] != "":
+ if event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
- elif event['type'].startswith('auto'):
+ if event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
- elif event['type'] == 'vacation':
+ if event['type'] == 'vacation':
self.vacation = event['name']
return VACATION_HOLD
return None
@@ -317,7 +317,7 @@ class Thermostat(ClimateDevice):
if hold == hold_mode:
# no change, so no action required
return
- elif hold_mode == 'None' or hold_mode is None:
+ if hold_mode == 'None' or hold_mode is None:
if hold == VACATION_HOLD:
self.data.ecobee.delete_vacation(
self.thermostat_index, self.vacation)
diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py
index 565e913319f..6c340e4a5f0 100644
--- a/homeassistant/components/climate/flexit.py
+++ b/homeassistant/components/climate/flexit.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE)
-import homeassistant.components.modbus as modbus
+from homeassistant.components import modbus
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyflexit==0.3']
diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py
index 92e363228a8..12057e88647 100644
--- a/homeassistant/components/climate/heatmiser.py
+++ b/homeassistant/components/climate/heatmiser.py
@@ -50,7 +50,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
])
- return
class HeatmiserV3Thermostat(ClimateDevice):
diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py
index b8fb7a984fa..a2725f6f3aa 100644
--- a/homeassistant/components/climate/homematic.py
+++ b/homeassistant/components/climate/homematic.py
@@ -87,7 +87,7 @@ class HMThermostat(HMDevice, ClimateDevice):
# HM ip etrv 2 uses the set_point_mode to say if its
# auto or manual
- elif not set_point_mode == -1:
+ if not set_point_mode == -1:
code = set_point_mode
# Other devices use the control_mode
else:
diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py
index 11a507aded2..04d705d6b49 100644
--- a/homeassistant/components/climate/honeywell.py
+++ b/homeassistant/components/climate/honeywell.py
@@ -165,7 +165,7 @@ class RoundThermostat(ClimateDevice):
self.client.set_temperature(self._name, temperature)
@property
- def current_operation(self: ClimateDevice) -> str:
+ def current_operation(self) -> str:
"""Get the current operation of the system."""
return getattr(self.client, ATTR_SYSTEM_MODE, None)
@@ -174,7 +174,7 @@ class RoundThermostat(ClimateDevice):
"""Return true if away mode is on."""
return self._away
- def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
+ def set_operation_mode(self, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.client, ATTR_SYSTEM_MODE):
self.client.system_mode = operation_mode
@@ -280,7 +280,7 @@ class HoneywellUSThermostat(ClimateDevice):
return self._device.setpoint_heat
@property
- def current_operation(self: ClimateDevice) -> str:
+ def current_operation(self) -> str:
"""Return current operation ie. heat, cool, idle."""
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
if oper == "off":
@@ -373,7 +373,7 @@ class HoneywellUSThermostat(ClimateDevice):
except somecomfort.SomeComfortError:
_LOGGER.error('Can not stop hold mode')
- def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
+ def set_operation_mode(self, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode
diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py
index 9c005b62dcc..a0adc12bfbf 100644
--- a/homeassistant/components/climate/melissa.py
+++ b/homeassistant/components/climate/melissa.py
@@ -192,9 +192,9 @@ class MelissaClimate(ClimateDevice):
"""Translate Melissa states to hass states."""
if state == self._api.STATE_ON:
return STATE_ON
- elif state == self._api.STATE_OFF:
+ if state == self._api.STATE_OFF:
return STATE_OFF
- elif state == self._api.STATE_IDLE:
+ if state == self._api.STATE_IDLE:
return STATE_IDLE
return None
@@ -202,11 +202,11 @@ class MelissaClimate(ClimateDevice):
"""Translate Melissa modes to hass states."""
if mode == self._api.MODE_HEAT:
return STATE_HEAT
- elif mode == self._api.MODE_COOL:
+ if mode == self._api.MODE_COOL:
return STATE_COOL
- elif mode == self._api.MODE_DRY:
+ if mode == self._api.MODE_DRY:
return STATE_DRY
- elif mode == self._api.MODE_FAN:
+ if mode == self._api.MODE_FAN:
return STATE_FAN_ONLY
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
@@ -216,11 +216,11 @@ class MelissaClimate(ClimateDevice):
"""Translate Melissa fan modes to hass modes."""
if fan == self._api.FAN_AUTO:
return STATE_AUTO
- elif fan == self._api.FAN_LOW:
+ if fan == self._api.FAN_LOW:
return SPEED_LOW
- elif fan == self._api.FAN_MEDIUM:
+ if fan == self._api.FAN_MEDIUM:
return SPEED_MEDIUM
- elif fan == self._api.FAN_HIGH:
+ if fan == self._api.FAN_HIGH:
return SPEED_HIGH
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
return None
@@ -229,24 +229,22 @@ class MelissaClimate(ClimateDevice):
"""Translate hass states to melissa modes."""
if mode == STATE_HEAT:
return self._api.MODE_HEAT
- elif mode == STATE_COOL:
+ if mode == STATE_COOL:
return self._api.MODE_COOL
- elif mode == STATE_DRY:
+ if mode == STATE_DRY:
return self._api.MODE_DRY
- elif mode == STATE_FAN_ONLY:
+ if mode == STATE_FAN_ONLY:
return self._api.MODE_FAN
- else:
- _LOGGER.warning("Melissa have no setting for %s mode", mode)
+ _LOGGER.warning("Melissa have no setting for %s mode", mode)
def hass_fan_to_melissa(self, fan):
"""Translate hass fan modes to melissa modes."""
if fan == STATE_AUTO:
return self._api.FAN_AUTO
- elif fan == SPEED_LOW:
+ if fan == SPEED_LOW:
return self._api.FAN_LOW
- elif fan == SPEED_MEDIUM:
+ if fan == SPEED_MEDIUM:
return self._api.FAN_MEDIUM
- elif fan == SPEED_HIGH:
+ if fan == SPEED_HIGH:
return self._api.FAN_HIGH
- else:
- _LOGGER.warning("Melissa have no setting for %s fan mode", fan)
+ _LOGGER.warning("Melissa have no setting for %s fan mode", fan)
diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py
index 7d392e5a40f..e567340efc9 100644
--- a/homeassistant/components/climate/modbus.py
+++ b/homeassistant/components/climate/modbus.py
@@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
-import homeassistant.components.modbus as modbus
+from homeassistant.components import modbus
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['modbus']
diff --git a/homeassistant/components/climate/mqtt.py b/homeassistant/components/climate/mqtt.py
index fbe5460979b..1426ff31af9 100644
--- a/homeassistant/components/climate/mqtt.py
+++ b/homeassistant/components/climate/mqtt.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice,
diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py
index dc1f74613bc..fa3943c3e27 100644
--- a/homeassistant/components/climate/nest.py
+++ b/homeassistant/components/climate/nest.py
@@ -147,7 +147,7 @@ class NestThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
return self._mode
- elif self._mode == NEST_MODE_HEAT_COOL:
+ if self._mode == NEST_MODE_HEAT_COOL:
return STATE_AUTO
return STATE_UNKNOWN
diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py
index a4b921037db..b4bed367878 100644
--- a/homeassistant/components/climate/netatmo.py
+++ b/homeassistant/components/climate/netatmo.py
@@ -99,7 +99,7 @@ class NetatmoThermostat(ClimateDevice):
state = self._data.thermostatdata.relay_cmd
if state == 0:
return STATE_IDLE
- elif state == 100:
+ if state == 100:
return STATE_HEAT
@property
@@ -140,7 +140,7 @@ class NetatmoThermostat(ClimateDevice):
self._away = self._data.setpoint_mode == 'away'
-class ThermostatData(object):
+class ThermostatData:
"""Get the latest data from Netatmo."""
def __init__(self, auth, device=None):
diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py
index 34fcfd667b6..9338c219fe5 100644
--- a/homeassistant/components/climate/proliphix.py
+++ b/homeassistant/components/climate/proliphix.py
@@ -102,9 +102,9 @@ class ProliphixThermostat(ClimateDevice):
state = self._pdp.hvac_state
if state in (1, 2):
return STATE_IDLE
- elif state == 3:
+ if state == 3:
return STATE_HEAT
- elif state == 6:
+ if state == 6:
return STATE_COOL
def set_temperature(self, **kwargs):
diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py
index 032d85637ef..b3043689f8c 100644
--- a/homeassistant/components/climate/radiotherm.py
+++ b/homeassistant/components/climate/radiotherm.py
@@ -308,7 +308,7 @@ class RadioThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off)."""
- if operation_mode == STATE_OFF or operation_mode == STATE_AUTO:
+ if operation_mode in (STATE_OFF, STATE_AUTO):
self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
# Setting t_cool or t_heat automatically changes tmode.
diff --git a/homeassistant/components/climate/spider.py b/homeassistant/components/climate/spider.py
new file mode 100644
index 00000000000..a6916b22a25
--- /dev/null
+++ b/homeassistant/components/climate/spider.py
@@ -0,0 +1,127 @@
+"""
+Support for Spider thermostats.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/climate.spider/
+"""
+
+import logging
+
+from homeassistant.components.climate import (
+ ATTR_TEMPERATURE, STATE_COOL, STATE_HEAT, STATE_IDLE,
+ SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
+from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN
+from homeassistant.const import TEMP_CELSIUS
+
+DEPENDENCIES = ['spider']
+
+OPERATION_LIST = [
+ STATE_HEAT,
+ STATE_COOL,
+]
+
+HA_STATE_TO_SPIDER = {
+ STATE_COOL: 'Cool',
+ STATE_HEAT: 'Heat',
+ STATE_IDLE: 'Idle'
+}
+
+SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()}
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the Spider thermostat."""
+ if discovery_info is None:
+ return
+
+ devices = [SpiderThermostat(hass.data[SPIDER_DOMAIN]['controller'], device)
+ for device in hass.data[SPIDER_DOMAIN]['thermostats']]
+ add_devices(devices, True)
+
+
+class SpiderThermostat(ClimateDevice):
+ """Representation of a thermostat."""
+
+ def __init__(self, api, thermostat):
+ """Initialize the thermostat."""
+ self.api = api
+ self.thermostat = thermostat
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ supports = SUPPORT_TARGET_TEMPERATURE
+
+ if self.thermostat.has_operation_mode:
+ supports = supports | SUPPORT_OPERATION_MODE
+
+ return supports
+
+ @property
+ def unique_id(self):
+ """Return the id of the thermostat, if any."""
+ return self.thermostat.id
+
+ @property
+ def name(self):
+ """Return the name of the thermostat, if any."""
+ return self.thermostat.name
+
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement."""
+ return TEMP_CELSIUS
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ return self.thermostat.current_temperature
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ return self.thermostat.target_temperature
+
+ @property
+ def target_temperature_step(self):
+ """Return the supported step of target temperature."""
+ return self.thermostat.temperature_steps
+
+ @property
+ def min_temp(self):
+ """Return the minimum temperature."""
+ return self.thermostat.minimum_temperature
+
+ @property
+ def max_temp(self):
+ """Return the maximum temperature."""
+ return self.thermostat.maximum_temperature
+
+ @property
+ def current_operation(self):
+ """Return current operation ie. heat, cool, idle."""
+ return SPIDER_STATE_TO_HA[self.thermostat.operation_mode]
+
+ @property
+ def operation_list(self):
+ """Return the list of available operation modes."""
+ return OPERATION_LIST
+
+ def set_temperature(self, **kwargs):
+ """Set new target temperature."""
+ temperature = kwargs.get(ATTR_TEMPERATURE)
+ if temperature is None:
+ return
+
+ self.thermostat.set_temperature(temperature)
+
+ def set_operation_mode(self, operation_mode):
+ """Set new target operation mode."""
+ self.thermostat.set_operation_mode(
+ HA_STATE_TO_SPIDER.get(operation_mode))
+
+ def update(self):
+ """Get the latest data."""
+ self.thermostat = self.api.get_thermostat(self.unique_id)
diff --git a/homeassistant/components/climate/tuya.py b/homeassistant/components/climate/tuya.py
new file mode 100644
index 00000000000..19267d693a0
--- /dev/null
+++ b/homeassistant/components/climate/tuya.py
@@ -0,0 +1,173 @@
+"""
+Support for the Tuya climate devices.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/climate.tuya/
+"""
+
+from homeassistant.components.climate import (
+ ATTR_TEMPERATURE, ENTITY_ID_FORMAT, STATE_AUTO, STATE_COOL, STATE_ECO,
+ STATE_ELECTRIC, STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP,
+ STATE_HIGH_DEMAND, STATE_PERFORMANCE, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
+ SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
+from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH
+from homeassistant.components.tuya import DATA_TUYA, TuyaDevice
+
+from homeassistant.const import (
+ PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
+
+DEPENDENCIES = ['tuya']
+DEVICE_TYPE = 'climate'
+
+HA_STATE_TO_TUYA = {
+ STATE_AUTO: 'auto',
+ STATE_COOL: 'cold',
+ STATE_ECO: 'eco',
+ STATE_ELECTRIC: 'electric',
+ STATE_FAN_ONLY: 'wind',
+ STATE_GAS: 'gas',
+ STATE_HEAT: 'hot',
+ STATE_HEAT_PUMP: 'heat_pump',
+ STATE_HIGH_DEMAND: 'high_demand',
+ STATE_PERFORMANCE: 'performance',
+}
+
+TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
+
+FAN_MODES = {SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH}
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up Tuya Climate devices."""
+ if discovery_info is None:
+ return
+ tuya = hass.data[DATA_TUYA]
+ dev_ids = discovery_info.get('dev_ids')
+ devices = []
+ for dev_id in dev_ids:
+ device = tuya.get_device_by_id(dev_id)
+ if device is None:
+ continue
+ devices.append(TuyaClimateDevice(device))
+ add_devices(devices)
+
+
+class TuyaClimateDevice(TuyaDevice, ClimateDevice):
+ """Tuya climate devices,include air conditioner,heater."""
+
+ def __init__(self, tuya):
+ """Init climate device."""
+ super().__init__(tuya)
+ self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
+ self.operations = []
+
+ async def async_added_to_hass(self):
+ """Create operation list when add to hass."""
+ await super().async_added_to_hass()
+ modes = self.tuya.operation_list()
+ if modes is None:
+ return
+ for mode in modes:
+ if mode in TUYA_STATE_TO_HA:
+ self.operations.append(TUYA_STATE_TO_HA[mode])
+
+ @property
+ def is_on(self):
+ """Return true if climate is on."""
+ return self.tuya.state()
+
+ @property
+ def precision(self):
+ """Return the precision of the system."""
+ return PRECISION_WHOLE
+
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement used by the platform."""
+ unit = self.tuya.temperature_unit()
+ if unit == 'CELSIUS':
+ return TEMP_CELSIUS
+ if unit == 'FAHRENHEIT':
+ return TEMP_FAHRENHEIT
+ return TEMP_CELSIUS
+
+ @property
+ def current_operation(self):
+ """Return current operation ie. heat, cool, idle."""
+ mode = self.tuya.current_operation()
+ if mode is None:
+ return None
+ return TUYA_STATE_TO_HA.get(mode)
+
+ @property
+ def operation_list(self):
+ """Return the list of available operation modes."""
+ return self.operations
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ return self.tuya.current_temperature()
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ return self.tuya.target_temperature()
+
+ @property
+ def target_temperature_step(self):
+ """Return the supported step of target temperature."""
+ return self.tuya.target_temperature_step()
+
+ @property
+ def current_fan_mode(self):
+ """Return the fan setting."""
+ return self.tuya.current_fan_mode()
+
+ @property
+ def fan_list(self):
+ """Return the list of available fan modes."""
+ return self.tuya.fan_list()
+
+ def set_temperature(self, **kwargs):
+ """Set new target temperature."""
+ if ATTR_TEMPERATURE in kwargs:
+ self.tuya.set_temperature(kwargs[ATTR_TEMPERATURE])
+
+ def set_fan_mode(self, fan_mode):
+ """Set new target fan mode."""
+ self.tuya.set_fan_mode(fan_mode)
+
+ def set_operation_mode(self, operation_mode):
+ """Set new target operation mode."""
+ self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(operation_mode))
+
+ def turn_on(self):
+ """Turn device on."""
+ self.tuya.turn_on()
+
+ def turn_off(self):
+ """Turn device off."""
+ self.tuya.turn_off()
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ supports = SUPPORT_ON_OFF
+ if self.tuya.support_target_temperature():
+ supports = supports | SUPPORT_TARGET_TEMPERATURE
+ if self.tuya.support_mode():
+ supports = supports | SUPPORT_OPERATION_MODE
+ if self.tuya.support_wind_speed():
+ supports = supports | SUPPORT_FAN_MODE
+ return supports
+
+ @property
+ def min_temp(self):
+ """Return the minimum temperature."""
+ return self.tuya.min_temp()
+
+ @property
+ def max_temp(self):
+ """Return the maximum temperature."""
+ return self.tuya.max_temp()
diff --git a/homeassistant/components/climate/venstar.py b/homeassistant/components/climate/venstar.py
index c2b82e1cc84..4bacf64cf9e 100644
--- a/homeassistant/components/climate/venstar.py
+++ b/homeassistant/components/climate/venstar.py
@@ -152,9 +152,9 @@ class VenstarThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
if self._client.mode == self._client.MODE_HEAT:
return STATE_HEAT
- elif self._client.mode == self._client.MODE_COOL:
+ if self._client.mode == self._client.MODE_COOL:
return STATE_COOL
- elif self._client.mode == self._client.MODE_AUTO:
+ if self._client.mode == self._client.MODE_AUTO:
return STATE_AUTO
return STATE_OFF
@@ -178,7 +178,7 @@ class VenstarThermostat(ClimateDevice):
"""Return the target temperature we try to reach."""
if self._client.mode == self._client.MODE_HEAT:
return self._client.heattemp
- elif self._client.mode == self._client.MODE_COOL:
+ if self._client.mode == self._client.MODE_COOL:
return self._client.cooltemp
return None
diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py
index 4deb4d9ea2e..0f89b15e5a1 100644
--- a/homeassistant/components/climate/vera.py
+++ b/homeassistant/components/climate/vera.py
@@ -55,11 +55,11 @@ class VeraThermostat(VeraDevice, ClimateDevice):
mode = self.vera_device.get_hvac_mode()
if mode == 'HeatOn':
return OPERATION_LIST[0] # heat
- elif mode == 'CoolOn':
+ if mode == 'CoolOn':
return OPERATION_LIST[1] # cool
- elif mode == 'AutoChangeOver':
+ if mode == 'AutoChangeOver':
return OPERATION_LIST[2] # auto
- elif mode == 'Off':
+ if mode == 'Off':
return OPERATION_LIST[3] # off
return 'Off'
@@ -74,9 +74,9 @@ class VeraThermostat(VeraDevice, ClimateDevice):
mode = self.vera_device.get_fan_mode()
if mode == "ContinuousOn":
return FAN_OPERATION_LIST[0] # on
- elif mode == "Auto":
+ if mode == "Auto":
return FAN_OPERATION_LIST[1] # auto
- elif mode == "PeriodicOn":
+ if mode == "PeriodicOn":
return FAN_OPERATION_LIST[2] # cycle
return "Auto"
diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py
index 12a6960f833..15e555db8b9 100644
--- a/homeassistant/components/climate/wink.py
+++ b/homeassistant/components/climate/wink.py
@@ -224,7 +224,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
if self.current_operation != STATE_AUTO and not self.is_away_mode_on:
if self.current_operation == STATE_COOL:
return self.wink.current_max_set_point()
- elif self.current_operation == STATE_HEAT:
+ if self.current_operation == STATE_HEAT:
return self.wink.current_min_set_point()
return None
@@ -311,7 +311,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
"""Return whether the fan is on."""
if self.wink.current_fan_mode() == 'on':
return STATE_ON
- elif self.wink.current_fan_mode() == 'auto':
+ if self.wink.current_fan_mode() == 'auto':
return STATE_AUTO
# No Fan available so disable slider
return None
@@ -483,7 +483,7 @@ class WinkAC(WinkDevice, ClimateDevice):
speed = self.wink.current_fan_speed()
if speed <= 0.33:
return SPEED_LOW
- elif speed <= 0.66:
+ if speed <= 0.66:
return SPEED_MEDIUM
return SPEED_HIGH
diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py
index 52c544256b6..f87f2e83f5d 100644
--- a/homeassistant/components/climate/zwave.py
+++ b/homeassistant/components/climate/zwave.py
@@ -11,7 +11,7 @@ from homeassistant.components.climate import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@@ -186,7 +186,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return the unit of measurement."""
if self._unit == 'C':
return TEMP_CELSIUS
- elif self._unit == 'F':
+ if self._unit == 'F':
return TEMP_FAHRENHEIT
return self._unit
diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py
index 12b81c9003b..f4ce7bb3d1a 100644
--- a/homeassistant/components/cloud/iot.py
+++ b/homeassistant/components/cloud/iot.py
@@ -253,5 +253,3 @@ def async_handle_cloud(hass, cloud, payload):
payload['reason'])
else:
_LOGGER.warning("Received unknown cloud action: %s", action)
-
- return None
diff --git a/homeassistant/components/coinbase.py b/homeassistant/components/coinbase.py
index c40bd99b542..154320b4abd 100644
--- a/homeassistant/components/coinbase.py
+++ b/homeassistant/components/coinbase.py
@@ -69,7 +69,7 @@ def setup(hass, config):
return True
-class CoinbaseData(object):
+class CoinbaseData:
"""Get the latest data and update the states."""
def __init__(self, api_key, api_secret):
diff --git a/homeassistant/components/comfoconnect.py b/homeassistant/components/comfoconnect.py
index 425ed6f9c9a..69d88274f29 100644
--- a/homeassistant/components/comfoconnect.py
+++ b/homeassistant/components/comfoconnect.py
@@ -88,7 +88,7 @@ def setup(hass, config):
return True
-class ComfoConnectBridge(object):
+class ComfoConnectBridge:
"""Representation of a ComfoConnect bridge."""
def __init__(self, hass, bridge, name, token, friendly_name, pin):
diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py
index c594bf1f99e..7c0867e3852 100644
--- a/homeassistant/components/config/entity_registry.py
+++ b/homeassistant/components/config/entity_registry.py
@@ -20,6 +20,7 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('entity_id'): cv.entity_id,
# If passed in, we update value. Passing None will remove old value.
vol.Optional('name'): vol.Any(str, None),
+ vol.Optional('new_entity_id'): str,
})
@@ -74,13 +75,28 @@ def websocket_update_entity(hass, connection, msg):
msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
return
- entry = registry.async_update_entity(
- msg['entity_id'], name=msg['name'])
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], _entry_dict(entry)
- ))
+ changes = {}
- hass.async_add_job(update_entity())
+ if 'name' in msg:
+ changes['name'] = msg['name']
+
+ if 'new_entity_id' in msg:
+ changes['new_entity_id'] = msg['new_entity_id']
+
+ try:
+ if changes:
+ entry = registry.async_update_entity(
+ msg['entity_id'], **changes)
+ except ValueError as err:
+ connection.send_message_outside(websocket_api.error_message(
+ msg['id'], 'invalid_info', str(err)
+ ))
+ else:
+ connection.send_message_outside(websocket_api.result_message(
+ msg['id'], _entry_dict(entry)
+ ))
+
+ hass.async_create_task(update_entity())
@callback
diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py
index c839ab7bc6e..84927712741 100644
--- a/homeassistant/components/config/zwave.py
+++ b/homeassistant/components/config/zwave.py
@@ -29,6 +29,7 @@ def async_setup(hass):
hass.http.register_view(ZWaveUserCodeView)
hass.http.register_view(ZWaveLogView)
hass.http.register_view(ZWaveConfigWriteView)
+ hass.http.register_view(ZWaveProtectionView)
return True
@@ -196,3 +197,59 @@ class ZWaveUserCodeView(HomeAssistantView):
'label': value.label,
'length': len(value.data)}
return self.json(usercodes)
+
+
+class ZWaveProtectionView(HomeAssistantView):
+ """View for the protection commandclass of a node."""
+
+ url = r"/api/zwave/protection/{node_id:\d+}"
+ name = "api:zwave:protection"
+
+ async def get(self, request, node_id):
+ """Retrieve the protection commandclass options of node."""
+ nodeid = int(node_id)
+ hass = request.app['hass']
+ network = hass.data.get(const.DATA_NETWORK)
+
+ def _fetch_protection():
+ """Helper to get protection data."""
+ node = network.nodes.get(nodeid)
+ if node is None:
+ return self.json_message('Node not found', HTTP_NOT_FOUND)
+ protection_options = {}
+ if not node.has_command_class(const.COMMAND_CLASS_PROTECTION):
+ return self.json(protection_options)
+ protections = node.get_protections()
+ protection_options = {
+ 'value_id': '{0:d}'.format(list(protections)[0]),
+ 'selected': node.get_protection_item(list(protections)[0]),
+ 'options': node.get_protection_items(list(protections)[0])}
+ return self.json(protection_options)
+
+ return await hass.async_add_executor_job(_fetch_protection)
+
+ async def post(self, request, node_id):
+ """Change the selected option in protection commandclass."""
+ nodeid = int(node_id)
+ hass = request.app['hass']
+ network = hass.data.get(const.DATA_NETWORK)
+ protection_data = await request.json()
+
+ def _set_protection():
+ """Helper to get protection data."""
+ node = network.nodes.get(nodeid)
+ selection = protection_data["selection"]
+ value_id = int(protection_data[const.ATTR_VALUE_ID])
+ if node is None:
+ return self.json_message('Node not found', HTTP_NOT_FOUND)
+ if not node.has_command_class(const.COMMAND_CLASS_PROTECTION):
+ return self.json_message(
+ 'No protection commandclass on this node', HTTP_NOT_FOUND)
+ state = node.set_protection(value_id, selection)
+ if not state:
+ return self.json_message(
+ 'Protection setting did not complete', 202)
+ return self.json_message(
+ 'Protection setting succsessfully set', HTTP_OK)
+
+ return await hass.async_add_executor_job(_set_protection)
diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py
index 2c159633a9b..56fb7b4247b 100644
--- a/homeassistant/components/configurator.py
+++ b/homeassistant/components/configurator.py
@@ -128,7 +128,7 @@ def async_setup(hass, config):
return True
-class Configurator(object):
+class Configurator:
"""The class to keep track of current configuration requests."""
def __init__(self, hass):
diff --git a/homeassistant/components/cover/aladdin_connect.py b/homeassistant/components/cover/aladdin_connect.py
new file mode 100644
index 00000000000..efaea39bb86
--- /dev/null
+++ b/homeassistant/components/cover/aladdin_connect.py
@@ -0,0 +1,115 @@
+"""
+Platform for the Aladdin Connect cover component.
+
+For more details about this platform, please refer to the documentation
+https://home-assistant.io/components/cover.aladdin_connect/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA,
+ SUPPORT_OPEN, SUPPORT_CLOSE)
+from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED,
+ STATE_OPENING, STATE_CLOSING, STATE_OPEN)
+import homeassistant.helpers.config_validation as cv
+
+REQUIREMENTS = ['aladdin_connect==0.1']
+
+_LOGGER = logging.getLogger(__name__)
+
+NOTIFICATION_ID = 'aladdin_notification'
+NOTIFICATION_TITLE = 'Aladdin Connect Cover Setup'
+
+STATES_MAP = {
+ 'open': STATE_OPEN,
+ 'opening': STATE_OPENING,
+ 'closed': STATE_CLOSED,
+ 'closing': STATE_CLOSING
+}
+
+SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the Aladdin Connect platform."""
+ from aladdin_connect import AladdinConnectClient
+
+ username = config.get(CONF_USERNAME)
+ password = config.get(CONF_PASSWORD)
+ acc = AladdinConnectClient(username, password)
+
+ try:
+ if not acc.login():
+ raise ValueError("Username or Password is incorrect")
+ add_devices(AladdinDevice(acc, door) for door in acc.get_doors())
+ except (TypeError, KeyError, NameError, ValueError) as ex:
+ _LOGGER.error("%s", ex)
+ hass.components.persistent_notification.create(
+ 'Error: {}
'
+ 'You will need to restart hass after fixing.'
+ ''.format(ex),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+
+
+class AladdinDevice(CoverDevice):
+ """Representation of Aladdin Connect cover."""
+
+ def __init__(self, acc, device):
+ """Initialize the cover."""
+ self._acc = acc
+ self._device_id = device['device_id']
+ self._number = device['door_number']
+ self._name = device['name']
+ self._status = STATES_MAP.get(device['status'])
+
+ @property
+ def device_class(self):
+ """Define this cover as a garage door."""
+ return 'garage'
+
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ return SUPPORTED_FEATURES
+
+ @property
+ def name(self):
+ """Return the name of the garage door."""
+ return self._name
+
+ @property
+ def is_opening(self):
+ """Return if the cover is opening or not."""
+ return self._status == STATE_OPENING
+
+ @property
+ def is_closing(self):
+ """Return if the cover is closing or not."""
+ return self._status == STATE_CLOSING
+
+ @property
+ def is_closed(self):
+ """Return None if status is unknown, True if closed, else False."""
+ if self._status is None:
+ return None
+ return self._status == STATE_CLOSED
+
+ def close_cover(self, **kwargs):
+ """Issue close command to cover."""
+ self._acc.close_door(self._device_id, self._number)
+
+ def open_cover(self, **kwargs):
+ """Issue open command to cover."""
+ self._acc.open_door(self._device_id, self._number)
+
+ def update(self):
+ """Update status of cover."""
+ acc_status = self._acc.get_door_status(self._device_id, self._number)
+ self._status = STATES_MAP.get(acc_status)
diff --git a/homeassistant/components/cover/brunt.py b/homeassistant/components/cover/brunt.py
new file mode 100644
index 00000000000..713f06db735
--- /dev/null
+++ b/homeassistant/components/cover/brunt.py
@@ -0,0 +1,182 @@
+"""
+Support for Brunt Blind Engine covers.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/cover.brunt
+"""
+
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME)
+from homeassistant.components.cover import (
+ ATTR_POSITION, CoverDevice,
+ PLATFORM_SCHEMA, SUPPORT_CLOSE,
+ SUPPORT_OPEN, SUPPORT_SET_POSITION
+)
+import homeassistant.helpers.config_validation as cv
+
+REQUIREMENTS = ['brunt==0.1.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
+DEVICE_CLASS = 'window'
+
+ATTR_REQUEST_POSITION = 'request_position'
+NOTIFICATION_ID = 'brunt_notification'
+NOTIFICATION_TITLE = 'Brunt Cover Setup'
+ATTRIBUTION = 'Based on an unofficial Brunt SDK.'
+
+CLOSED_POSITION = 0
+OPEN_POSITION = 100
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the brunt platform."""
+ # pylint: disable=no-name-in-module
+ from brunt import BruntAPI
+ username = config[CONF_USERNAME]
+ password = config[CONF_PASSWORD]
+
+ bapi = BruntAPI(username=username, password=password)
+ try:
+ things = bapi.getThings()['things']
+ if not things:
+ _LOGGER.error("No things present in account.")
+ else:
+ add_devices([BruntDevice(
+ bapi, thing['NAME'],
+ thing['thingUri']) for thing in things], True)
+ except (TypeError, KeyError, NameError, ValueError) as ex:
+ _LOGGER.error("%s", ex)
+ hass.components.persistent_notification.create(
+ 'Error: {}
'
+ 'You will need to restart hass after fixing.'
+ ''.format(ex),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+
+
+class BruntDevice(CoverDevice):
+ """
+ Representation of a Brunt cover device.
+
+ Contains the common logic for all Brunt devices.
+ """
+
+ def __init__(self, bapi, name, thing_uri):
+ """Init the Brunt device."""
+ self._bapi = bapi
+ self._name = name
+ self._thing_uri = thing_uri
+
+ self._state = {}
+ self._available = None
+
+ @property
+ def name(self):
+ """Return the name of the device as reported by tellcore."""
+ return self._name
+
+ @property
+ def available(self):
+ """Could the device be accessed during the last update call."""
+ return self._available
+
+ @property
+ def current_cover_position(self):
+ """
+ Return current position of cover.
+
+ None is unknown, 0 is closed, 100 is fully open.
+ """
+ pos = self._state.get('currentPosition')
+ return int(pos) if pos else None
+
+ @property
+ def request_cover_position(self):
+ """
+ Return request position of cover.
+
+ The request position is the position of the last request
+ to Brunt, at times there is a diff of 1 to current
+ None is unknown, 0 is closed, 100 is fully open.
+ """
+ pos = self._state.get('requestPosition')
+ return int(pos) if pos else None
+
+ @property
+ def move_state(self):
+ """
+ Return current moving state of cover.
+
+ None is unknown, 0 when stopped, 1 when opening, 2 when closing
+ """
+ mov = self._state.get('moveState')
+ return int(mov) if mov else None
+
+ @property
+ def is_opening(self):
+ """Return if the cover is opening or not."""
+ return self.move_state == 1
+
+ @property
+ def is_closing(self):
+ """Return if the cover is closing or not."""
+ return self.move_state == 2
+
+ @property
+ def device_state_attributes(self):
+ """Return the detailed device state attributes."""
+ return {
+ ATTR_ATTRIBUTION: ATTRIBUTION,
+ ATTR_REQUEST_POSITION: self.request_cover_position
+ }
+
+ @property
+ def device_class(self):
+ """Return the class of this device, from component DEVICE_CLASSES."""
+ return DEVICE_CLASS
+
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ return COVER_FEATURES
+
+ @property
+ def is_closed(self):
+ """Return true if cover is closed, else False."""
+ return self.current_cover_position == CLOSED_POSITION
+
+ def update(self):
+ """Poll the current state of the device."""
+ try:
+ self._state = self._bapi.getState(
+ thingUri=self._thing_uri).get('thing')
+ self._available = True
+ except (TypeError, KeyError, NameError, ValueError) as ex:
+ _LOGGER.error("%s", ex)
+ self._available = False
+
+ def open_cover(self, **kwargs):
+ """Set the cover to the open position."""
+ self._bapi.changeRequestPosition(
+ OPEN_POSITION, thingUri=self._thing_uri)
+
+ def close_cover(self, **kwargs):
+ """Set the cover to the closed position."""
+ self._bapi.changeRequestPosition(
+ CLOSED_POSITION, thingUri=self._thing_uri)
+
+ def set_cover_position(self, **kwargs):
+ """Set the cover to a specific position."""
+ self._bapi.changeRequestPosition(
+ kwargs[ATTR_POSITION], thingUri=self._thing_uri)
diff --git a/homeassistant/components/cover/demo.py b/homeassistant/components/cover/demo.py
index b1533bd68c8..b81ac4e45e1 100644
--- a/homeassistant/components/cover/demo.py
+++ b/homeassistant/components/cover/demo.py
@@ -97,7 +97,7 @@ class DemoCover(CoverDevice):
"""Close the cover."""
if self._position == 0:
return
- elif self._position is None:
+ if self._position is None:
self._closed = True
self.schedule_update_ha_state()
return
@@ -119,7 +119,7 @@ class DemoCover(CoverDevice):
"""Open the cover."""
if self._position == 100:
return
- elif self._position is None:
+ if self._position is None:
self._closed = False
self.schedule_update_ha_state()
return
diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py
index 62e1069e18b..e1775e2f968 100644
--- a/homeassistant/components/cover/mqtt.py
+++ b/homeassistant/components/cover/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.cover import (
CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT,
SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION,
diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py
index aefb7ab89d7..5079a3b60c2 100644
--- a/homeassistant/components/cover/rfxtrx.py
+++ b/homeassistant/components/cover/rfxtrx.py
@@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.rfxtrx/
"""
import voluptuous as vol
-import homeassistant.components.rfxtrx as rfxtrx
+from homeassistant.components import rfxtrx
from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
from homeassistant.components.rfxtrx import (
diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py
index 384f96f3f52..2f6951cfc0d 100644
--- a/homeassistant/components/cover/rpi_gpio.py
+++ b/homeassistant/components/cover/rpi_gpio.py
@@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
-import homeassistant.components.rpi_gpio as rpi_gpio
+from homeassistant.components import rpi_gpio
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py
index ac4fddf98bb..04bf0ef1d32 100644
--- a/homeassistant/components/cover/scsgate.py
+++ b/homeassistant/components/cover/scsgate.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.scsgate as scsgate
+from homeassistant.components import scsgate
from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_DEVICES, CONF_NAME)
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/cover/tuya.py b/homeassistant/components/cover/tuya.py
new file mode 100644
index 00000000000..7b5fefee58a
--- /dev/null
+++ b/homeassistant/components/cover/tuya.py
@@ -0,0 +1,60 @@
+"""
+Support for Tuya cover.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/cover.tuya/
+"""
+from homeassistant.components.cover import (
+ CoverDevice, ENTITY_ID_FORMAT, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP)
+from homeassistant.components.tuya import DATA_TUYA, TuyaDevice
+
+DEPENDENCIES = ['tuya']
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up Tuya cover devices."""
+ if discovery_info is None:
+ return
+ tuya = hass.data[DATA_TUYA]
+ dev_ids = discovery_info.get('dev_ids')
+ devices = []
+ for dev_id in dev_ids:
+ device = tuya.get_device_by_id(dev_id)
+ if device is None:
+ continue
+ devices.append(TuyaCover(device))
+ add_devices(devices)
+
+
+class TuyaCover(TuyaDevice, CoverDevice):
+ """Tuya cover devices."""
+
+ def __init__(self, tuya):
+ """Init tuya cover device."""
+ super().__init__(tuya)
+ self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
+
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
+ if self.tuya.support_stop():
+ supported_features |= SUPPORT_STOP
+ return supported_features
+
+ @property
+ def is_closed(self):
+ """Return if the cover is closed or not."""
+ return None
+
+ def open_cover(self, **kwargs):
+ """Open the cover."""
+ self.tuya.open_cover()
+
+ def close_cover(self, **kwargs):
+ """Close cover."""
+ self.tuya.close_cover()
+
+ def stop_cover(self, **kwargs):
+ """Stop the cover."""
+ self.tuya.stop_cover()
diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py
index c29c11c5b6b..8c8c88ecb87 100644
--- a/homeassistant/components/cover/zwave.py
+++ b/homeassistant/components/cover/zwave.py
@@ -11,7 +11,7 @@ from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.zwave import workaround
from homeassistant.components.cover import CoverDevice
@@ -27,11 +27,10 @@ def get_device(hass, values, node_config, **kwargs):
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and values.primary.index == 0):
return ZwaveRollershutter(hass, values, invert_buttons)
- elif (values.primary.command_class ==
- zwave.const.COMMAND_CLASS_SWITCH_BINARY):
+ if values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY:
return ZwaveGarageDoorSwitch(values)
- elif (values.primary.command_class ==
- zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
+ if values.primary.command_class == \
+ zwave.const.COMMAND_CLASS_BARRIER_OPERATOR:
return ZwaveGarageDoorBarrier(values)
return None
@@ -84,7 +83,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
if self._current_position is not None:
if self._current_position <= 5:
return 0
- elif self._current_position >= 95:
+ if self._current_position >= 95:
return 100
return self._current_position
diff --git a/homeassistant/components/daikin.py b/homeassistant/components/daikin.py
index 5808528ca5a..8983ecf82d8 100644
--- a/homeassistant/components/daikin.py
+++ b/homeassistant/components/daikin.py
@@ -115,7 +115,7 @@ def daikin_api_setup(hass, host, name=None):
return api
-class DaikinApi(object):
+class DaikinApi:
"""Keep the Daikin instance in one place and centralize the update."""
def __init__(self, device, name):
diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py
index 88174b9d612..e0982c65f33 100644
--- a/homeassistant/components/deconz/__init__.py
+++ b/homeassistant/components/deconz/__init__.py
@@ -97,7 +97,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[DATA_DECONZ_UNSUB] = []
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
- hass.async_add_job(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, component))
@callback
@@ -178,7 +178,7 @@ async def async_unload_entry(hass, config_entry):
return True
-class DeconzEvent(object):
+class DeconzEvent:
"""When you want signals instead of entities.
Stateless sensors such as remotes are expected to generate an event
diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py
index b67d32508be..a6f67506227 100644
--- a/homeassistant/components/deconz/config_flow.py
+++ b/homeassistant/components/deconz/config_flow.py
@@ -57,7 +57,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
if len(self.bridges) == 1:
self.deconz_config = self.bridges[0]
return await self.async_step_link()
- elif len(self.bridges) > 1:
+ if len(self.bridges) > 1:
hosts = []
for bridge in self.bridges:
hosts.append(bridge[CONF_HOST])
diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py
index 64ce3cda073..c2c7866148f 100644
--- a/homeassistant/components/demo.py
+++ b/homeassistant/components/demo.py
@@ -7,7 +7,7 @@ https://home-assistant.io/components/demo/
import asyncio
import time
-import homeassistant.bootstrap as bootstrap
+from homeassistant import bootstrap
import homeassistant.core as ha
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py
index 580c0272e46..74cb0a77fef 100644
--- a/homeassistant/components/device_tracker/__init__.py
+++ b/homeassistant/components/device_tracker/__init__.py
@@ -24,7 +24,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
-import homeassistant.util as util
+from homeassistant import util
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
from homeassistant.util.yaml import dump
@@ -231,7 +231,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
return True
-class DeviceTracker(object):
+class DeviceTracker:
"""Representation of a device tracker."""
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
@@ -537,7 +537,7 @@ class Device(Entity):
"""
if not self.last_seen:
return
- elif self.location_name:
+ if self.location_name:
self._state = self.location_name
elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS:
zone_state = async_active_zone(
@@ -577,7 +577,7 @@ class Device(Entity):
state.attributes[ATTR_LONGITUDE])
-class DeviceScanner(object):
+class DeviceScanner:
"""Device scanner object."""
hass = None # type: HomeAssistantType
diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py
index 61eee99e721..142842b12d2 100644
--- a/homeassistant/components/device_tracker/aruba.py
+++ b/homeassistant/components/device_tracker/aruba.py
@@ -94,10 +94,10 @@ class ArubaDeviceScanner(DeviceScanner):
if query == 1:
_LOGGER.error("Timeout")
return
- elif query == 2:
+ if query == 2:
_LOGGER.error("Unexpected response from router")
return
- elif query == 3:
+ if query == 3:
ssh.sendline('yes')
ssh.expect('password:')
elif query == 4:
diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py
index 607f236f920..4fcc550d7db 100644
--- a/homeassistant/components/device_tracker/automatic.py
+++ b/homeassistant/components/device_tracker/automatic.py
@@ -193,7 +193,7 @@ class AutomaticAuthCallbackView(HomeAssistantView):
return response
-class AutomaticData(object):
+class AutomaticData:
"""A class representing an Automatic cloud service connection."""
def __init__(self, hass, client, session, devices, async_see):
diff --git a/homeassistant/components/device_tracker/bmw_connected_drive.py b/homeassistant/components/device_tracker/bmw_connected_drive.py
index f36afc622ee..02a12653180 100644
--- a/homeassistant/components/device_tracker/bmw_connected_drive.py
+++ b/homeassistant/components/device_tracker/bmw_connected_drive.py
@@ -27,7 +27,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
return True
-class BMWDeviceTracker(object):
+class BMWDeviceTracker:
"""BMW Connected Drive device tracker."""
def __init__(self, see, vehicle):
diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/device_tracker/bt_home_hub_5.py
index 707850d2215..93bc9270650 100644
--- a/homeassistant/components/device_tracker/bt_home_hub_5.py
+++ b/homeassistant/components/device_tracker/bt_home_hub_5.py
@@ -94,8 +94,7 @@ def _get_homehub_data(url):
return
if response.status_code == 200:
return _parse_homehub_response(response.text)
- else:
- _LOGGER.error("Invalid response from Home Hub: %s", response)
+ _LOGGER.error("Invalid response from Home Hub: %s", response)
def _parse_homehub_response(data_str):
diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py
index 3e17fdd3329..539d4fde5ef 100644
--- a/homeassistant/components/device_tracker/ddwrt.py
+++ b/homeassistant/components/device_tracker/ddwrt.py
@@ -131,13 +131,12 @@ class DdWrtDeviceScanner(DeviceScanner):
return
if response.status_code == 200:
return _parse_ddwrt_response(response.text)
- elif response.status_code == 401:
+ if response.status_code == 401:
# Authentication error
_LOGGER.exception(
"Failed to authenticate, check your username and password")
return
- else:
- _LOGGER.error("Invalid response from DD-WRT: %s", response)
+ _LOGGER.error("Invalid response from DD-WRT: %s", response)
def _parse_ddwrt_response(data_str):
diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/device_tracker/freebox.py
index b278c421925..2cac81fd405 100644
--- a/homeassistant/components/device_tracker/freebox.py
+++ b/homeassistant/components/device_tracker/freebox.py
@@ -62,7 +62,7 @@ def _build_device(device_dict):
device_dict['l3connectivities'][0]['addr'])
-class FreeboxDeviceScanner(object):
+class FreeboxDeviceScanner:
"""This class scans for devices connected to the Freebox."""
def __init__(self, hass, config, async_see):
diff --git a/homeassistant/components/device_tracker/geofency.py b/homeassistant/components/device_tracker/geofency.py
index adb5c6f6d28..7231c5127be 100644
--- a/homeassistant/components/device_tracker/geofency.py
+++ b/homeassistant/components/device_tracker/geofency.py
@@ -70,16 +70,15 @@ class GeofencyView(HomeAssistantView):
if self._is_mobile_beacon(data):
return (yield from self._set_location(hass, data, None))
+ if data['entry'] == LOCATION_ENTRY:
+ location_name = data['name']
else:
- if data['entry'] == LOCATION_ENTRY:
- location_name = data['name']
- else:
- location_name = STATE_NOT_HOME
- if ATTR_CURRENT_LATITUDE in data:
- data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE]
- data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE]
+ location_name = STATE_NOT_HOME
+ if ATTR_CURRENT_LATITUDE in data:
+ data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE]
+ data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE]
- return (yield from self._set_location(hass, data, location_name))
+ return (yield from self._set_location(hass, data, location_name))
@staticmethod
def _validate_data(data):
diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py
index 5f06946fc44..d0669ab4967 100644
--- a/homeassistant/components/device_tracker/google_maps.py
+++ b/homeassistant/components/device_tracker/google_maps.py
@@ -42,7 +42,7 @@ def setup_scanner(hass, config: ConfigType, see, discovery_info=None):
return scanner.success_init
-class GoogleMapsScanner(object):
+class GoogleMapsScanner:
"""Representation of an Google Maps location sharing account."""
def __init__(self, hass, config: ConfigType, see) -> None:
diff --git a/homeassistant/components/device_tracker/linksys_ap.py b/homeassistant/components/device_tracker/linksys_ap.py
index bf3916f3abe..a2a371163fd 100644
--- a/homeassistant/components/device_tracker/linksys_ap.py
+++ b/homeassistant/components/device_tracker/linksys_ap.py
@@ -19,7 +19,7 @@ from homeassistant.const import (
INTERFACES = 2
DEFAULT_TIMEOUT = 10
-REQUIREMENTS = ['beautifulsoup4==4.6.0']
+REQUIREMENTS = ['beautifulsoup4==4.6.1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py
index aee584aa953..354d3b0980c 100644
--- a/homeassistant/components/device_tracker/locative.py
+++ b/homeassistant/components/device_tracker/locative.py
@@ -84,7 +84,7 @@ class LocativeView(HomeAssistantView):
gps=gps_location))
return 'Setting location to {}'.format(location_name)
- elif direction == 'exit':
+ if direction == 'exit':
current_state = hass.states.get(
'{}.{}'.format(DOMAIN, device))
@@ -102,7 +102,7 @@ class LocativeView(HomeAssistantView):
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
- elif direction == 'test':
+ if direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
return 'Received test message.'
diff --git a/homeassistant/components/device_tracker/meraki.py b/homeassistant/components/device_tracker/meraki.py
index 9bbc6bf9ffe..c996b7e643b 100644
--- a/homeassistant/components/device_tracker/meraki.py
+++ b/homeassistant/components/device_tracker/meraki.py
@@ -74,17 +74,16 @@ class MerakiView(HomeAssistantView):
_LOGGER.error("Invalid Secret received from Meraki")
return self.json_message('Invalid secret',
HTTP_UNPROCESSABLE_ENTITY)
- elif data['version'] != VERSION:
+ if data['version'] != VERSION:
_LOGGER.error("Invalid API version: %s", data['version'])
return self.json_message('Invalid version',
HTTP_UNPROCESSABLE_ENTITY)
- else:
- _LOGGER.debug('Valid Secret')
- if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'):
- _LOGGER.error("Unknown Device %s", data['type'])
- return self.json_message('Invalid device type',
- HTTP_UNPROCESSABLE_ENTITY)
- _LOGGER.debug("Processing %s", data['type'])
+ _LOGGER.debug('Valid Secret')
+ if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'):
+ _LOGGER.error("Unknown Device %s", data['type'])
+ return self.json_message('Invalid device type',
+ HTTP_UNPROCESSABLE_ENTITY)
+ _LOGGER.debug("Processing %s", data['type'])
if not data["data"]["observations"]:
_LOGGER.debug("No observations found")
return
diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py
index 2e2d9b10d98..b5031e8ccfb 100644
--- a/homeassistant/components/device_tracker/mqtt.py
+++ b/homeassistant/components/device_tracker/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.core import callback
from homeassistant.const import CONF_DEVICES
from homeassistant.components.mqtt import CONF_QOS
diff --git a/homeassistant/components/device_tracker/mqtt_json.py b/homeassistant/components/device_tracker/mqtt_json.py
index 9a5532fc9f4..7e5ae7c9227 100644
--- a/homeassistant/components/device_tracker/mqtt_json.py
+++ b/homeassistant/components/device_tracker/mqtt_json.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.core import callback
from homeassistant.components.mqtt import CONF_QOS
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py
index e99524c36db..2d7f1e80406 100644
--- a/homeassistant/components/device_tracker/owntracks.py
+++ b/homeassistant/components/device_tracker/owntracks.py
@@ -12,7 +12,7 @@ from collections import defaultdict
import voluptuous as vol
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import (
diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/device_tracker/ping.py
index 6a0cb18d55e..d09e1930d4f 100644
--- a/homeassistant/components/device_tracker/ping.py
+++ b/homeassistant/components/device_tracker/ping.py
@@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-class Host(object):
+class Host:
"""Host object with ping detection."""
def __init__(self, ip_address, dev_id, hass, config):
diff --git a/homeassistant/components/device_tracker/sky_hub.py b/homeassistant/components/device_tracker/sky_hub.py
index 0c289ce9a82..deab486ec6e 100644
--- a/homeassistant/components/device_tracker/sky_hub.py
+++ b/homeassistant/components/device_tracker/sky_hub.py
@@ -91,8 +91,7 @@ def _get_skyhub_data(url):
return
if response.status_code == 200:
return _parse_skyhub_response(response.text)
- else:
- _LOGGER.error("Invalid response from Sky Hub: %s", response)
+ _LOGGER.error("Invalid response from Sky Hub: %s", response)
def _parse_skyhub_response(data_str):
diff --git a/homeassistant/components/device_tracker/tesla.py b/homeassistant/components/device_tracker/tesla.py
index ba9bc8c2631..c08ddb4047b 100644
--- a/homeassistant/components/device_tracker/tesla.py
+++ b/homeassistant/components/device_tracker/tesla.py
@@ -23,7 +23,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
return True
-class TeslaDeviceTracker(object):
+class TeslaDeviceTracker:
"""A class representing a Tesla device."""
def __init__(self, hass, config, see, tesla_devices):
diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/device_tracker/tile.py
index 6df9f3c9974..07f15e7e88a 100644
--- a/homeassistant/components/device_tracker/tile.py
+++ b/homeassistant/components/device_tracker/tile.py
@@ -74,7 +74,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
return await scanner.async_init()
-class TileScanner(object):
+class TileScanner:
"""Define an object to retrieve Tile data."""
def __init__(self, client, hass, async_see, types, show_inactive):
@@ -109,7 +109,7 @@ class TileScanner(object):
_LOGGER.debug('Updating Tile data')
try:
- await self._client.asayn_init()
+ await self._client.async_init()
tiles = await self._client.tiles.all(
whitelist=self._types, show_inactive=self._show_inactive)
except SessionExpiredError:
diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py
index 01ae2977f6d..718adad4212 100644
--- a/homeassistant/components/device_tracker/tomato.py
+++ b/homeassistant/components/device_tracker/tomato.py
@@ -102,12 +102,12 @@ class TomatoDeviceScanner(DeviceScanner):
for param, value in \
self.parse_api_pattern.findall(response.text):
- if param == 'wldev' or param == 'dhcpd_lease':
+ if param in ('wldev', 'dhcpd_lease'):
self.last_results[param] = \
json.loads(value.replace("'", '"'))
return True
- elif response.status_code == 401:
+ if response.status_code == 401:
# Authentication error
_LOGGER.exception((
"Failed to authenticate, "
diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py
index 5266b9c6f57..346f381db34 100644
--- a/homeassistant/components/device_tracker/tplink.py
+++ b/homeassistant/components/device_tracker/tplink.py
@@ -22,6 +22,8 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, HTTP_HEADER_X_REQUESTED_WITH)
import homeassistant.helpers.config_validation as cv
+REQUIREMENTS = ['tplink==0.2.1']
+
_LOGGER = logging.getLogger(__name__)
HTTP_HEADER_NO_CACHE = 'no-cache'
@@ -34,10 +36,22 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_scanner(hass, config):
- """Validate the configuration and return a TP-Link scanner."""
- for cls in [Tplink5DeviceScanner, Tplink4DeviceScanner,
- Tplink3DeviceScanner, Tplink2DeviceScanner,
- TplinkDeviceScanner]:
+ """
+ Validate the configuration and return a TP-Link scanner.
+
+ The default way of integrating devices is to use a pypi
+
+ package, The TplinkDeviceScanner has been refactored
+
+ to depend on a pypi package, the other implementations
+
+ should be gradually migrated in the pypi package
+
+ """
+ for cls in [
+ TplinkDeviceScanner, Tplink5DeviceScanner, Tplink4DeviceScanner,
+ Tplink3DeviceScanner, Tplink2DeviceScanner, Tplink1DeviceScanner
+ ]:
scanner = cls(config[DOMAIN])
if scanner.success_init:
return scanner
@@ -46,6 +60,46 @@ def get_scanner(hass, config):
class TplinkDeviceScanner(DeviceScanner):
+ """Queries the router for connected devices."""
+
+ def __init__(self, config):
+ """Initialize the scanner."""
+ from tplink.tplink import TpLinkClient
+ host = config[CONF_HOST]
+ password = config[CONF_PASSWORD]
+ username = config[CONF_USERNAME]
+
+ self.tplink_client = TpLinkClient(
+ password, host=host, username=username)
+
+ self.last_results = {}
+ self.success_init = self._update_info()
+
+ def scan_devices(self):
+ """Scan for new devices and return a list with found device IDs."""
+ self._update_info()
+ return self.last_results.keys()
+
+ def get_device_name(self, device):
+ """Get the name of the device."""
+ return self.last_results.get(device)
+
+ def _update_info(self):
+ """Ensure the information from the TP-Link router is up to date.
+
+ Return boolean if scanning successful.
+ """
+ _LOGGER.info("Loading wireless clients...")
+ result = self.tplink_client.get_connected_devices()
+
+ if result:
+ self.last_results = result
+ return True
+
+ return False
+
+
+class Tplink1DeviceScanner(DeviceScanner):
"""This class queries a wireless router running TP-Link firmware."""
def __init__(self, config):
@@ -94,7 +148,7 @@ class TplinkDeviceScanner(DeviceScanner):
return False
-class Tplink2DeviceScanner(TplinkDeviceScanner):
+class Tplink2DeviceScanner(Tplink1DeviceScanner):
"""This class queries a router with newer version of TP-Link firmware."""
def scan_devices(self):
@@ -147,7 +201,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
return False
-class Tplink3DeviceScanner(TplinkDeviceScanner):
+class Tplink3DeviceScanner(Tplink1DeviceScanner):
"""This class queries the Archer C9 router with version 150811 or high."""
def __init__(self, config):
@@ -256,7 +310,7 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
self.sysauth = ''
-class Tplink4DeviceScanner(TplinkDeviceScanner):
+class Tplink4DeviceScanner(Tplink1DeviceScanner):
"""This class queries an Archer C7 router with TP-Link firmware 150427."""
def __init__(self, config):
@@ -337,7 +391,7 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
return True
-class Tplink5DeviceScanner(TplinkDeviceScanner):
+class Tplink5DeviceScanner(Tplink1DeviceScanner):
"""This class queries a TP-Link EAP-225 AP with newer TP-Link FW."""
def scan_devices(self):
diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/device_tracker/trackr.py
index 84fb449c070..08d3a4c9445 100644
--- a/homeassistant/components/device_tracker/trackr.py
+++ b/homeassistant/components/device_tracker/trackr.py
@@ -30,7 +30,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
return True
-class TrackRDeviceScanner(object):
+class TrackRDeviceScanner:
"""A class representing a TrackR device."""
def __init__(self, hass, config: dict, see) -> None:
diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py
index f265014657b..94e3b407d13 100644
--- a/homeassistant/components/device_tracker/ubus.py
+++ b/homeassistant/components/device_tracker/ubus.py
@@ -56,7 +56,7 @@ def _refresh_on_access_denied(func):
try:
return func(self, *args, **kwargs)
except PermissionError:
- _LOGGER.warning("Invalid session detected." +
+ _LOGGER.warning("Invalid session detected."
" Trying to refresh session_id and re-run RPC")
self.session_id = _get_session_id(
self.url, self.username, self.password)
diff --git a/homeassistant/components/dialogflow.py b/homeassistant/components/dialogflow.py
index 28b3a05e403..0f275a7fe66 100644
--- a/homeassistant/components/dialogflow.py
+++ b/homeassistant/components/dialogflow.py
@@ -119,7 +119,7 @@ async def async_handle_message(hass, message):
return dialogflow_response.as_dict()
-class DialogflowResponse(object):
+class DialogflowResponse:
"""Help generating the response for Dialogflow."""
def __init__(self, parameters):
diff --git a/homeassistant/components/digital_ocean.py b/homeassistant/components/digital_ocean.py
index a0f50842649..c0c9d95586c 100644
--- a/homeassistant/components/digital_ocean.py
+++ b/homeassistant/components/digital_ocean.py
@@ -65,7 +65,7 @@ def setup(hass, config):
return True
-class DigitalOcean(object):
+class DigitalOcean:
"""Handle all communication with the Digital Ocean API."""
def __init__(self, access_token):
diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py
index 96f094b527d..3829c2caebd 100644
--- a/homeassistant/components/ecobee.py
+++ b/homeassistant/components/ecobee.py
@@ -84,7 +84,7 @@ def setup_ecobee(hass, network, config):
discovery.load_platform(hass, 'weather', DOMAIN, {}, config)
-class EcobeeData(object):
+class EcobeeData:
"""Get the latest data and update the states."""
def __init__(self, config_file):
diff --git a/homeassistant/components/egardia.py b/homeassistant/components/egardia.py
index f350ea56bb4..b7da671bb15 100644
--- a/homeassistant/components/egardia.py
+++ b/homeassistant/components/egardia.py
@@ -81,7 +81,7 @@ def setup(hass, config):
device = hass.data[EGARDIA_DEVICE] = egardiadevice.EgardiaDevice(
host, port, username, password, '', version)
except requests.exceptions.RequestException:
- _LOGGER.error("An error occurred accessing your Egardia device. " +
+ _LOGGER.error("An error occurred accessing your Egardia device. "
"Please check config.")
return False
except egardiadevice.UnauthorizedError:
@@ -108,7 +108,7 @@ def setup(hass, config):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
except IOError:
- _LOGGER.error("Binding error occurred while starting " +
+ _LOGGER.error("Binding error occurred while starting "
"EgardiaServer.")
return False
diff --git a/homeassistant/components/eight_sleep.py b/homeassistant/components/eight_sleep.py
index 704eab1846b..209fa7ba879 100644
--- a/homeassistant/components/eight_sleep.py
+++ b/homeassistant/components/eight_sleep.py
@@ -143,12 +143,12 @@ async def async_setup(hass, config):
# No users, cannot continue
return False
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {
CONF_SENSORS: sensors,
}, config))
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN, {
CONF_BINARY_SENSORS: binary_sensors,
}, config))
diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py
index 36ce1c392f9..8a67b933b9f 100644
--- a/homeassistant/components/emulated_hue/__init__.py
+++ b/homeassistant/components/emulated_hue/__init__.py
@@ -136,7 +136,7 @@ def setup(hass, yaml_config):
return True
-class Config(object):
+class Config:
"""Hold configuration variables for the emulated hue bridge."""
def __init__(self, hass, conf):
diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py
index 2b74984e4ca..f7fbe2e15e3 100644
--- a/homeassistant/components/emulated_hue/hue_api.py
+++ b/homeassistant/components/emulated_hue/hue_api.py
@@ -237,11 +237,11 @@ class HueOneLightChangeView(HomeAssistantView):
# Convert 0-100 to a fan speed
if brightness == 0:
data[ATTR_SPEED] = SPEED_OFF
- elif brightness <= 33.3 and brightness > 0:
+ elif 0 < brightness <= 33.3:
data[ATTR_SPEED] = SPEED_LOW
- elif brightness <= 66.6 and brightness > 33.3:
+ elif 33.3 < brightness <= 66.6:
data[ATTR_SPEED] = SPEED_MEDIUM
- elif brightness <= 100 and brightness > 66.6:
+ elif 66.6 < brightness <= 100:
data[ATTR_SPEED] = SPEED_HIGH
if entity.domain in config.off_maps_to_on_domains:
diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py
index 5ffd97ef0e3..7dd4e7dc32a 100644
--- a/homeassistant/components/envisalink.py
+++ b/homeassistant/components/envisalink.py
@@ -167,21 +167,21 @@ def async_setup(hass, config):
# Load sub-components for Envisalink
if partitions:
- hass.async_add_job(async_load_platform(
+ hass.async_create_task(async_load_platform(
hass, 'alarm_control_panel', 'envisalink', {
CONF_PARTITIONS: partitions,
CONF_CODE: code,
CONF_PANIC: panic_type
}, config
))
- hass.async_add_job(async_load_platform(
+ hass.async_create_task(async_load_platform(
hass, 'sensor', 'envisalink', {
CONF_PARTITIONS: partitions,
CONF_CODE: code
}, config
))
if zones:
- hass.async_add_job(async_load_platform(
+ hass.async_create_task(async_load_platform(
hass, 'binary_sensor', 'envisalink', {
CONF_ZONES: zones
}, config
diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py
index 66790d02687..db0e8c590fd 100644
--- a/homeassistant/components/fan/__init__.py
+++ b/homeassistant/components/fan/__init__.py
@@ -235,11 +235,11 @@ def async_setup(hass, config: dict):
class FanEntity(ToggleEntity):
"""Representation of a fan."""
- def set_speed(self: ToggleEntity, speed: str) -> None:
+ def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
raise NotImplementedError()
- def async_set_speed(self: ToggleEntity, speed: str):
+ def async_set_speed(self, speed: str):
"""Set the speed of the fan.
This method must be run in the event loop and returns a coroutine.
@@ -248,11 +248,11 @@ class FanEntity(ToggleEntity):
return self.async_turn_off()
return self.hass.async_add_job(self.set_speed, speed)
- def set_direction(self: ToggleEntity, direction: str) -> None:
+ def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
raise NotImplementedError()
- def async_set_direction(self: ToggleEntity, direction: str):
+ def async_set_direction(self, direction: str):
"""Set the direction of the fan.
This method must be run in the event loop and returns a coroutine.
@@ -260,12 +260,12 @@ class FanEntity(ToggleEntity):
return self.hass.async_add_job(self.set_direction, direction)
# pylint: disable=arguments-differ
- def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None:
+ def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
raise NotImplementedError()
# pylint: disable=arguments-differ
- def async_turn_on(self: ToggleEntity, speed: str = None, **kwargs):
+ def async_turn_on(self, speed: str = None, **kwargs):
"""Turn on the fan.
This method must be run in the event loop and returns a coroutine.
@@ -275,11 +275,11 @@ class FanEntity(ToggleEntity):
return self.hass.async_add_job(
ft.partial(self.turn_on, speed, **kwargs))
- def oscillate(self: ToggleEntity, oscillating: bool) -> None:
+ def oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
pass
- def async_oscillate(self: ToggleEntity, oscillating: bool):
+ def async_oscillate(self, oscillating: bool):
"""Oscillate the fan.
This method must be run in the event loop and returns a coroutine.
@@ -297,7 +297,7 @@ class FanEntity(ToggleEntity):
return None
@property
- def speed_list(self: ToggleEntity) -> list:
+ def speed_list(self) -> list:
"""Get the list of available speeds."""
return []
@@ -307,7 +307,7 @@ class FanEntity(ToggleEntity):
return None
@property
- def state_attributes(self: ToggleEntity) -> dict:
+ def state_attributes(self) -> dict:
"""Return optional state attributes."""
data = {} # type: dict
@@ -322,6 +322,6 @@ class FanEntity(ToggleEntity):
return data
@property
- def supported_features(self: ToggleEntity) -> int:
+ def supported_features(self) -> int:
"""Flag supported features."""
return 0
diff --git a/homeassistant/components/fan/comfoconnect.py b/homeassistant/components/fan/comfoconnect.py
index 12dc0b1104f..fd3265b8230 100644
--- a/homeassistant/components/fan/comfoconnect.py
+++ b/homeassistant/components/fan/comfoconnect.py
@@ -31,7 +31,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
ccb = hass.data[DOMAIN]
add_devices([ComfoConnectFan(hass, name=ccb.name, ccb=ccb)], True)
- return
class ComfoConnectFan(FanEntity):
diff --git a/homeassistant/components/fan/dyson.py b/homeassistant/components/fan/dyson.py
index 5b689ece6ed..fbe9ffc948c 100644
--- a/homeassistant/components/fan/dyson.py
+++ b/homeassistant/components/fan/dyson.py
@@ -8,12 +8,11 @@ import logging
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.dyson import DYSON_DEVICES
from homeassistant.components.fan import (
DOMAIN, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity)
from homeassistant.const import CONF_ENTITY_ID
-import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
@@ -100,7 +99,7 @@ class DysonPureCoolLinkDevice(FanEntity):
"""Return the display name of this fan."""
return self._device.name
- def set_speed(self: ToggleEntity, speed: str) -> None:
+ def set_speed(self, speed: str) -> None:
"""Set the speed of the fan. Never called ??."""
from libpurecoollink.const import FanSpeed, FanMode
@@ -113,7 +112,7 @@ class DysonPureCoolLinkDevice(FanEntity):
self._device.set_configuration(
fan_mode=FanMode.FAN, fan_speed=fan_speed)
- def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None:
+ def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
from libpurecoollink.const import FanSpeed, FanMode
@@ -129,14 +128,14 @@ class DysonPureCoolLinkDevice(FanEntity):
# Speed not set, just turn on
self._device.set_configuration(fan_mode=FanMode.FAN)
- def turn_off(self: ToggleEntity, **kwargs) -> None:
+ def turn_off(self, **kwargs) -> None:
"""Turn off the fan."""
from libpurecoollink.const import FanMode
_LOGGER.debug("Turn off fan %s", self.name)
self._device.set_configuration(fan_mode=FanMode.OFF)
- def oscillate(self: ToggleEntity, oscillating: bool) -> None:
+ def oscillate(self, oscillating: bool) -> None:
"""Turn on/off oscillating."""
from libpurecoollink.const import Oscillation
@@ -183,7 +182,7 @@ class DysonPureCoolLinkDevice(FanEntity):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
- def night_mode(self: ToggleEntity, night_mode: bool) -> None:
+ def night_mode(self, night_mode: bool) -> None:
"""Turn fan in night mode."""
from libpurecoollink.const import NightMode
@@ -198,7 +197,7 @@ class DysonPureCoolLinkDevice(FanEntity):
"""Return auto mode."""
return self._device.state.fan_mode == "AUTO"
- def auto_mode(self: ToggleEntity, auto_mode: bool) -> None:
+ def auto_mode(self, auto_mode: bool) -> None:
"""Turn fan in auto mode."""
from libpurecoollink.const import FanMode
@@ -209,7 +208,7 @@ class DysonPureCoolLinkDevice(FanEntity):
self._device.set_configuration(fan_mode=FanMode.FAN)
@property
- def speed_list(self: ToggleEntity) -> list:
+ def speed_list(self) -> list:
"""Get the list of available speeds."""
from libpurecoollink.const import FanSpeed
@@ -230,6 +229,6 @@ class DysonPureCoolLinkDevice(FanEntity):
return supported_speeds
@property
- def supported_features(self: ToggleEntity) -> int:
+ def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED
diff --git a/homeassistant/components/fan/insteon_local.py b/homeassistant/components/fan/insteon_local.py
index b8a5c99add4..28b93c86ed7 100644
--- a/homeassistant/components/fan/insteon_local.py
+++ b/homeassistant/components/fan/insteon_local.py
@@ -7,11 +7,10 @@ https://home-assistant.io/components/fan.insteon_local/
import logging
from datetime import timedelta
+from homeassistant import util
from homeassistant.components.fan import (
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED, FanEntity)
-from homeassistant.helpers.entity import ToggleEntity
-import homeassistant.util as util
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -68,7 +67,7 @@ class InsteonLocalFanDevice(FanEntity):
return self._speed
@property
- def speed_list(self: ToggleEntity) -> list:
+ def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@@ -91,21 +90,18 @@ class InsteonLocalFanDevice(FanEntity):
"""Flag supported features."""
return SUPPORT_INSTEON_LOCAL
- def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None:
+ def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn device on."""
if speed is None:
- if ATTR_SPEED in kwargs:
- speed = kwargs[ATTR_SPEED]
- else:
- speed = SPEED_MEDIUM
+ speed = kwargs.get(ATTR_SPEED, SPEED_MEDIUM)
self.set_speed(speed)
- def turn_off(self: ToggleEntity, **kwargs) -> None:
+ def turn_off(self, **kwargs) -> None:
"""Turn device off."""
self.node.off()
- def set_speed(self: ToggleEntity, speed: str) -> None:
+ def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
if self.node.on(speed):
self._speed = speed
diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py
index 6fa506edec6..5faa735801d 100644
--- a/homeassistant/components/fan/mqtt.py
+++ b/homeassistant/components/fan/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
diff --git a/homeassistant/components/fan/template.py b/homeassistant/components/fan/template.py
index a40437e719b..74fb73dae1d 100644
--- a/homeassistant/components/fan/template.py
+++ b/homeassistant/components/fan/template.py
@@ -8,21 +8,17 @@ import logging
import voluptuous as vol
-from homeassistant.core import callback
-from homeassistant.const import (
- CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, CONF_ENTITY_ID,
- STATE_ON, STATE_OFF, MATCH_ALL, EVENT_HOMEASSISTANT_START,
- STATE_UNKNOWN)
-
-from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
-from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.fan import (
SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
FanEntity, ATTR_SPEED, ATTR_OSCILLATING, ENTITY_ID_FORMAT,
SUPPORT_DIRECTION, DIRECTION_FORWARD, DIRECTION_REVERSE, ATTR_DIRECTION)
-
+from homeassistant.const import (
+ CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, CONF_ENTITY_ID,
+ STATE_ON, STATE_OFF, MATCH_ALL, EVENT_HOMEASSISTANT_START,
+ STATE_UNKNOWN)
+from homeassistant.core import callback
+from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script
@@ -65,7 +61,7 @@ FAN_SCHEMA = vol.Schema({
vol.Optional(CONF_ENTITY_ID): cv.entity_ids
})
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Required(CONF_FANS): vol.Schema({cv.slug: FAN_SCHEMA}),
})
@@ -196,7 +192,7 @@ class TemplateFan(FanEntity):
return self._supported_features
@property
- def speed_list(self: ToggleEntity) -> list:
+ def speed_list(self) -> list:
"""Get the list of available speeds."""
return self._speed_list
@@ -250,8 +246,7 @@ class TemplateFan(FanEntity):
await self._set_speed_script.async_run({ATTR_SPEED: speed})
else:
_LOGGER.error(
- 'Received invalid speed: %s. ' +
- 'Expected: %s.',
+ 'Received invalid speed: %s. Expected: %s.',
speed, self._speed_list)
async def async_oscillate(self, oscillating: bool) -> None:
@@ -265,8 +260,7 @@ class TemplateFan(FanEntity):
{ATTR_OSCILLATING: oscillating})
else:
_LOGGER.error(
- 'Received invalid oscillating value: %s. ' +
- 'Expected: %s.',
+ 'Received invalid oscillating value: %s. Expected: %s.',
oscillating, ', '.join(_VALID_OSC))
async def async_set_direction(self, direction: str) -> None:
@@ -280,8 +274,7 @@ class TemplateFan(FanEntity):
{ATTR_DIRECTION: direction})
else:
_LOGGER.error(
- 'Received invalid direction: %s. ' +
- 'Expected: %s.',
+ 'Received invalid direction: %s. Expected: %s.',
direction, ', '.join(_VALID_DIRECTIONS))
async def async_added_to_hass(self):
@@ -319,8 +312,7 @@ class TemplateFan(FanEntity):
self._state = None
else:
_LOGGER.error(
- 'Received invalid fan is_on state: %s. ' +
- 'Expected: %s.',
+ 'Received invalid fan is_on state: %s. Expected: %s.',
state, ', '.join(_VALID_STATES))
self._state = None
@@ -340,8 +332,7 @@ class TemplateFan(FanEntity):
self._speed = None
else:
_LOGGER.error(
- 'Received invalid speed: %s. ' +
- 'Expected: %s.',
+ 'Received invalid speed: %s. Expected: %s.',
speed, self._speed_list)
self._speed = None
@@ -363,8 +354,8 @@ class TemplateFan(FanEntity):
self._oscillating = None
else:
_LOGGER.error(
- 'Received invalid oscillating: %s. ' +
- 'Expected: True/False.', oscillating)
+ 'Received invalid oscillating: %s. Expected: True/False.',
+ oscillating)
self._oscillating = None
# Update direction if 'direction_template' is configured
@@ -383,7 +374,6 @@ class TemplateFan(FanEntity):
self._direction = None
else:
_LOGGER.error(
- 'Received invalid direction: %s. ' +
- 'Expected: %s.',
+ 'Received invalid direction: %s. Expected: %s.',
direction, ', '.join(_VALID_DIRECTIONS))
self._direction = None
diff --git a/homeassistant/components/fan/tuya.py b/homeassistant/components/fan/tuya.py
new file mode 100644
index 00000000000..f19a9e5a5f7
--- /dev/null
+++ b/homeassistant/components/fan/tuya.py
@@ -0,0 +1,99 @@
+"""
+Support for Tuya fans.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/fan.tuya/
+"""
+
+from homeassistant.components.fan import (
+ ENTITY_ID_FORMAT, FanEntity, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED)
+from homeassistant.components.tuya import DATA_TUYA, TuyaDevice
+from homeassistant.const import STATE_OFF
+
+DEPENDENCIES = ['tuya']
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up Tuya fan platform."""
+ if discovery_info is None:
+ return
+ tuya = hass.data[DATA_TUYA]
+ dev_ids = discovery_info.get('dev_ids')
+ devices = []
+ for dev_id in dev_ids:
+ device = tuya.get_device_by_id(dev_id)
+ if device is None:
+ continue
+ devices.append(TuyaFanDevice(device))
+ add_devices(devices)
+
+
+class TuyaFanDevice(TuyaDevice, FanEntity):
+ """Tuya fan devices."""
+
+ def __init__(self, tuya):
+ """Init Tuya fan device."""
+ super().__init__(tuya)
+ self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
+ self.speeds = [STATE_OFF]
+
+ async def async_added_to_hass(self):
+ """Create fan list when add to hass."""
+ await super().async_added_to_hass()
+ self.speeds.extend(self.tuya.speed_list())
+
+ def set_speed(self, speed: str) -> None:
+ """Set the speed of the fan."""
+ if speed == STATE_OFF:
+ self.turn_off()
+ else:
+ self.tuya.set_speed(speed)
+
+ def turn_on(self, speed: str = None, **kwargs) -> None:
+ """Turn on the fan."""
+ if speed is not None:
+ self.set_speed(speed)
+ else:
+ self.tuya.turn_on()
+
+ def turn_off(self, **kwargs) -> None:
+ """Turn the entity off."""
+ self.tuya.turn_off()
+
+ def oscillate(self, oscillating) -> None:
+ """Oscillate the fan."""
+ self.tuya.oscillate(oscillating)
+
+ @property
+ def oscillating(self):
+ """Return current oscillating status."""
+ if self.supported_features & SUPPORT_OSCILLATE == 0:
+ return None
+ if self.speed == STATE_OFF:
+ return False
+ return self.tuya.oscillating()
+
+ @property
+ def is_on(self):
+ """Return true if the entity is on."""
+ return self.tuya.state()
+
+ @property
+ def speed(self) -> str:
+ """Return the current speed."""
+ if self.is_on:
+ return self.tuya.speed()
+ return STATE_OFF
+
+ @property
+ def speed_list(self) -> list:
+ """Get the list of available speeds."""
+ return self.speeds
+
+ @property
+ def supported_features(self) -> int:
+ """Flag supported features."""
+ supports = SUPPORT_SET_SPEED
+ if self.tuya.support_oscillate():
+ supports = supports | SUPPORT_OSCILLATE
+ return supports
diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py
index 0cebd9cb9f8..4eebacbbbf2 100644
--- a/homeassistant/components/fan/wink.py
+++ b/homeassistant/components/fan/wink.py
@@ -11,7 +11,6 @@ from homeassistant.components.fan import (
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, STATE_UNKNOWN, SUPPORT_DIRECTION,
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.components.wink import DOMAIN, WinkDevice
-from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
@@ -39,19 +38,19 @@ class WinkFanDevice(WinkDevice, FanEntity):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['fan'].append(self)
- def set_direction(self: ToggleEntity, direction: str) -> None:
+ def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
self.wink.set_fan_direction(direction)
- def set_speed(self: ToggleEntity, speed: str) -> None:
+ def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
self.wink.set_state(True, speed)
- def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None:
+ def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
self.wink.set_state(True, speed)
- def turn_off(self: ToggleEntity, **kwargs) -> None:
+ def turn_off(self, **kwargs) -> None:
"""Turn off the fan."""
self.wink.set_state(False)
@@ -82,7 +81,7 @@ class WinkFanDevice(WinkDevice, FanEntity):
return self.wink.current_fan_direction()
@property
- def speed_list(self: ToggleEntity) -> list:
+ def speed_list(self) -> list:
"""Get the list of available speeds."""
wink_supported_speeds = self.wink.fan_speeds()
supported_speeds = []
@@ -99,6 +98,6 @@ class WinkFanDevice(WinkDevice, FanEntity):
return supported_speeds
@property
- def supported_features(self: ToggleEntity) -> int:
+ def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORTED_FEATURES
diff --git a/homeassistant/components/fan/zha.py b/homeassistant/components/fan/zha.py
index 01b1d0a92cf..983bc3a79d7 100644
--- a/homeassistant/components/fan/zha.py
+++ b/homeassistant/components/fan/zha.py
@@ -89,7 +89,7 @@ class ZhaFan(zha.Entity, FanEntity):
yield from self.async_set_speed(SPEED_OFF)
@asyncio.coroutine
- def async_set_speed(self: FanEntity, speed: str) -> None:
+ def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
yield from self._endpoint.fan.write_attributes({
'fan_mode': SPEED_TO_VALUE[speed]})
diff --git a/homeassistant/components/fan/zwave.py b/homeassistant/components/fan/zwave.py
index 364306ff8dd..645cb033e13 100644
--- a/homeassistant/components/fan/zwave.py
+++ b/homeassistant/components/fan/zwave.py
@@ -11,7 +11,7 @@ from homeassistant.components.fan import (
DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED)
from homeassistant.components import zwave
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/feedreader.py b/homeassistant/components/feedreader.py
index 73ab9e8123c..782fd8ac8dd 100644
--- a/homeassistant/components/feedreader.py
+++ b/homeassistant/components/feedreader.py
@@ -53,7 +53,7 @@ def setup(hass, config):
return len(feeds) > 0
-class FeedManager(object):
+class FeedManager:
"""Abstraction over Feedparser module."""
def __init__(self, url, scan_interval, max_entries, hass, storage):
@@ -170,7 +170,7 @@ class FeedManager(object):
self._firstrun = False
-class StoredData(object):
+class StoredData:
"""Abstraction over pickle data storage."""
def __init__(self, data_file):
@@ -189,7 +189,7 @@ class StoredData(object):
with self._lock, open(self._data_file, 'rb') as myfile:
self._data = pickle.load(myfile) or {}
self._cache_outdated = False
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_LOGGER.error("Error loading data from pickled file %s",
self._data_file)
@@ -207,7 +207,7 @@ class StoredData(object):
feed_id, self._data_file)
try:
pickle.dump(self._data, myfile)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_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 e083affe92b..9aaae16ee21 100644
--- a/homeassistant/components/ffmpeg.py
+++ b/homeassistant/components/ffmpeg.py
@@ -116,7 +116,7 @@ def async_setup(hass, config):
return True
-class FFmpegManager(object):
+class FFmpegManager:
"""Helper for ha-ffmpeg."""
def __init__(self, hass, ffmpeg_bin, run_test):
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index fb59d6254b0..5bf2b193957 100644
--- a/homeassistant/components/frontend/__init__.py
+++ b/homeassistant/components/frontend/__init__.py
@@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
from homeassistant.util.yaml import load_yaml
-REQUIREMENTS = ['home-assistant-frontend==20180720.0']
+REQUIREMENTS = ['home-assistant-frontend==20180726.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
@@ -352,7 +352,7 @@ class IndexView(HomeAssistantView):
def get_template(self, latest):
"""Get template."""
if self.repo_path is not None:
- root = self.repo_path
+ root = os.path.join(self.repo_path, 'hass_frontend')
elif latest:
import hass_frontend
root = hass_frontend.where()
diff --git a/homeassistant/components/gc100.py b/homeassistant/components/gc100.py
index 25bcb5b0f79..0d4b19da030 100644
--- a/homeassistant/components/gc100.py
+++ b/homeassistant/components/gc100.py
@@ -53,7 +53,7 @@ def setup(hass, base_config):
return True
-class GC100Device(object):
+class GC100Device:
"""The GC100 component."""
def __init__(self, hass, gc_device):
diff --git a/homeassistant/components/google.py b/homeassistant/components/google.py
index fdbc3382072..e37b3ba7ff7 100644
--- a/homeassistant/components/google.py
+++ b/homeassistant/components/google.py
@@ -231,7 +231,7 @@ def do_setup(hass, config):
return True
-class GoogleCalendarService(object):
+class GoogleCalendarService:
"""Calendar service interface to Google."""
def __init__(self, token_file):
diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py
index 927139a483e..63a3e641170 100644
--- a/homeassistant/components/google_assistant/smart_home.py
+++ b/homeassistant/components/google_assistant/smart_home.py
@@ -1,5 +1,5 @@
"""Support for Google Assistant Smart Home API."""
-import collections
+from collections.abc import Mapping
from itertools import product
import logging
@@ -50,7 +50,7 @@ DOMAIN_TO_GOOGLE_TYPES = {
def deep_update(target, source):
"""Update a nested dictionary with another nested dictionary."""
for key, value in source.items():
- if isinstance(value, collections.Mapping):
+ if isinstance(value, Mapping):
target[key] = deep_update(target.get(key, {}), value)
else:
target[key] = value
diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py
index 2f60f226042..74ee55c5e93 100644
--- a/homeassistant/components/google_assistant/trait.py
+++ b/homeassistant/components/google_assistant/trait.py
@@ -106,9 +106,9 @@ class BrightnessTrait(_Trait):
"""Test if state is supported."""
if domain == light.DOMAIN:
return features & light.SUPPORT_BRIGHTNESS
- elif domain == cover.DOMAIN:
+ if domain == cover.DOMAIN:
return features & cover.SUPPORT_SET_POSITION
- elif domain == media_player.DOMAIN:
+ if domain == media_player.DOMAIN:
return features & media_player.SUPPORT_VOLUME_SET
return False
diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py
index e4626d0f016..2b768bc3786 100644
--- a/homeassistant/components/graphite.py
+++ b/homeassistant/components/graphite.py
@@ -137,8 +137,8 @@ class GraphiteFeeder(threading.Thread):
_LOGGER.debug("Event processing thread stopped")
self._queue.task_done()
return
- elif (event.event_type == EVENT_STATE_CHANGED and
- event.data.get('new_state')):
+ if event.event_type == EVENT_STATE_CHANGED and \
+ event.data.get('new_state'):
_LOGGER.debug("Processing STATE_CHANGED event for %s",
event.data['entity_id'])
try:
diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py
index 6ab86435371..3a3e19fb484 100644
--- a/homeassistant/components/hassio/__init__.py
+++ b/homeassistant/components/hassio/__init__.py
@@ -27,6 +27,8 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'hassio'
DEPENDENCIES = ['http']
+STORAGE_KEY = DOMAIN
+STORAGE_VERSION = 1
CONF_FRONTEND_REPO = 'development_repo'
@@ -140,7 +142,7 @@ def async_check_config(hass):
if not result:
return "Hass.io config check API error"
- elif result['result'] == "error":
+ if result['result'] == "error":
return result['message']
return None
@@ -167,6 +169,21 @@ def async_setup(hass, config):
_LOGGER.error("Not connected with Hass.io")
return False
+ store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+ data = yield from store.async_load()
+
+ if data is None:
+ data = {}
+
+ if 'hassio_user' in data:
+ user = yield from hass.auth.async_get_user(data['hassio_user'])
+ refresh_token = list(user.refresh_tokens.values())[0]
+ else:
+ user = yield from hass.auth.async_create_system_user('Hass.io')
+ refresh_token = yield from hass.auth.async_create_refresh_token(user)
+ data['hassio_user'] = user.id
+ yield from store.async_save(data)
+
# This overrides the normal API call that would be forwarded
development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO)
if development_repo is not None:
@@ -186,8 +203,13 @@ def async_setup(hass, config):
embed_iframe=True,
)
- if 'http' in config:
- yield from hassio.update_hass_api(config['http'])
+ # Temporary. No refresh token tells supervisor to use API password.
+ if hass.auth.active:
+ token = refresh_token.token
+ else:
+ token = None
+
+ yield from hassio.update_hass_api(config.get('http', {}), token)
if 'homeassistant' in config:
yield from hassio.update_hass_timezone(config['homeassistant'])
diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py
index c3caf40ba62..d75529a99b0 100644
--- a/homeassistant/components/hassio/handler.py
+++ b/homeassistant/components/hassio/handler.py
@@ -23,10 +23,9 @@ X_HASSIO = 'X-HASSIO-KEY'
def _api_bool(funct):
"""Return a boolean."""
- @asyncio.coroutine
- def _wrapper(*argv, **kwargs):
+ async def _wrapper(*argv, **kwargs):
"""Wrap function."""
- data = yield from funct(*argv, **kwargs)
+ data = await funct(*argv, **kwargs)
return data and data['result'] == "ok"
return _wrapper
@@ -34,10 +33,9 @@ def _api_bool(funct):
def _api_data(funct):
"""Return data of an api."""
- @asyncio.coroutine
- def _wrapper(*argv, **kwargs):
+ async def _wrapper(*argv, **kwargs):
"""Wrap function."""
- data = yield from funct(*argv, **kwargs)
+ data = await funct(*argv, **kwargs)
if data and data['result'] == "ok":
return data['data']
return None
@@ -45,7 +43,7 @@ def _api_data(funct):
return _wrapper
-class HassIO(object):
+class HassIO:
"""Small API wrapper for Hass.io."""
def __init__(self, loop, websession, ip):
@@ -94,24 +92,23 @@ class HassIO(object):
return self.send_command("/homeassistant/check", timeout=300)
@_api_bool
- def update_hass_api(self, http_config):
- """Update Home Assistant API data on Hass.io.
-
- This method return a coroutine.
- """
+ async def update_hass_api(self, http_config, refresh_token):
+ """Update Home Assistant API data on Hass.io."""
port = http_config.get(CONF_SERVER_PORT) or SERVER_PORT
options = {
'ssl': CONF_SSL_CERTIFICATE in http_config,
'port': port,
'password': http_config.get(CONF_API_PASSWORD),
'watchdog': True,
+ 'refresh_token': refresh_token,
}
if CONF_SERVER_HOST in http_config:
options['watchdog'] = False
_LOGGER.warning("Don't use 'server_host' options with Hass.io")
- return self.send_command("/homeassistant/options", payload=options)
+ return await self.send_command("/homeassistant/options",
+ payload=options)
@_api_bool
def update_hass_timezone(self, core_config):
diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py
index 7ee1c70487f..21d4cdc6e56 100644
--- a/homeassistant/components/history.py
+++ b/homeassistant/components/history.py
@@ -353,7 +353,7 @@ class HistoryPeriodView(HomeAssistantView):
return await hass.async_add_job(self.json, result)
-class Filters(object):
+class Filters:
"""Container for the configured include and exclude filters."""
def __init__(self):
diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py
index 611043c1b16..ad2f8b4ac6d 100644
--- a/homeassistant/components/homekit/__init__.py
+++ b/homeassistant/components/homekit/__init__.py
@@ -9,7 +9,7 @@ from zlib import adler32
import voluptuous as vol
-import homeassistant.components.cover as cover
+from homeassistant.components import cover
from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, CONF_TYPE, DEVICE_CLASS_HUMIDITY,
@@ -173,7 +173,7 @@ def get_accessory(hass, driver, state, aid, config):
def generate_aid(entity_id):
"""Generate accessory aid with zlib adler32."""
aid = adler32(entity_id.encode('utf-8'))
- if aid == 0 or aid == 1:
+ if aid in (0, 1):
return None
return aid
diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py
index 373c1188f2d..d4c2cb58209 100644
--- a/homeassistant/components/homekit/type_sensors.py
+++ b/homeassistant/components/homekit/type_sensors.py
@@ -181,6 +181,6 @@ class BinarySensor(HomeAccessory):
def update_state(self, new_state):
"""Update accessory after state change."""
state = new_state.state
- detected = (state == STATE_ON) or (state == STATE_HOME)
+ detected = state in (STATE_ON, STATE_HOME)
self.char_detected.set_value(detected)
_LOGGER.debug('%s: Set to %d', self.entity_id, detected)
diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py
index 73a29990fba..8517122f6a8 100644
--- a/homeassistant/components/homekit/type_thermostats.py
+++ b/homeassistant/components/homekit/type_thermostats.py
@@ -124,8 +124,7 @@ class Thermostat(HomeAccessory):
if hass_value == STATE_OFF:
self.hass.services.call(DOMAIN, SERVICE_TURN_OFF, params)
return
- else:
- self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params)
+ self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params)
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_OPERATION_MODE: hass_value}
self.hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, params)
diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py
index 6a43a0c6228..23a907d43f7 100644
--- a/homeassistant/components/homekit/util.py
+++ b/homeassistant/components/homekit/util.py
@@ -3,7 +3,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.media_player as media_player
+from homeassistant.components import media_player
from homeassistant.core import split_entity_id
from homeassistant.const import (
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS)
@@ -142,10 +142,10 @@ def density_to_air_quality(density):
"""Map PM2.5 density to HomeKit AirQuality level."""
if density <= 35:
return 1
- elif density <= 75:
+ if density <= 75:
return 2
- elif density <= 115:
+ if density <= 115:
return 3
- elif density <= 150:
+ if density <= 150:
return 4
return 5
diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py
index 6754db05f77..f737e2ad7d2 100644
--- a/homeassistant/components/homematic/__init__.py
+++ b/homeassistant/components/homematic/__init__.py
@@ -20,7 +20,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.loader import bind_hass
-REQUIREMENTS = ['pyhomematic==0.1.45']
+REQUIREMENTS = ['pyhomematic==0.1.46']
_LOGGER = logging.getLogger(__name__)
@@ -61,7 +61,8 @@ SERVICE_SET_INSTALL_MODE = 'set_install_mode'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren',
- 'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
+ 'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic',
+ 'IPKeySwitchPowermeter'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer'],
DISCOVER_SENSORS: [
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP',
@@ -71,7 +72,8 @@ HM_DEVICE_TYPES = {
'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch',
'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall',
'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat',
- 'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor'],
+ 'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor',
+ 'IPKeySwitchPowermeter'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
@@ -80,7 +82,8 @@ HM_DEVICE_TYPES = {
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor',
'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain',
- 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor'],
+ 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor',
+ 'SmartwareMotion'],
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'],
DISCOVER_LOCKS: ['KeyMatic']
}
diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py
index 9e5356d914a..3be89172e27 100644
--- a/homeassistant/components/homematicip_cloud/config_flow.py
+++ b/homeassistant/components/homematicip_cloud/config_flow.py
@@ -70,8 +70,7 @@ class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler):
HMIPC_NAME: self.auth.config.get(HMIPC_NAME)
})
return self.async_abort(reason='conection_aborted')
- else:
- errors['base'] = 'press_the_button'
+ errors['base'] = 'press_the_button'
return self.async_show_form(step_id='link', errors=errors)
diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py
index 94fe5f40be8..bb21e1df3d5 100644
--- a/homeassistant/components/homematicip_cloud/device.py
+++ b/homeassistant/components/homematicip_cloud/device.py
@@ -62,10 +62,21 @@ class HomematicipGenericDevice(Entity):
"""Device available."""
return not self._device.unreach
+ @property
+ def icon(self):
+ """Return the icon."""
+ if hasattr(self._device, 'lowBat') and self._device.lowBat:
+ return 'mdi:battery-outline'
+ if hasattr(self._device, 'sabotage') and self._device.sabotage:
+ return 'mdi:alert'
+ return None
+
@property
def device_state_attributes(self):
"""Return the state attributes of the generic device."""
- return {
- ATTR_LOW_BATTERY: self._device.lowBat,
- ATTR_MODEL_TYPE: self._device.modelType
- }
+ attr = {ATTR_MODEL_TYPE: self._device.modelType}
+ if hasattr(self._device, 'lowBat') and self._device.lowBat:
+ attr.update({ATTR_LOW_BATTERY: self._device.lowBat})
+ if hasattr(self._device, 'sabotage') and self._device.sabotage:
+ attr.update({ATTR_SABOTAGE: self._device.sabotage})
+ return attr
diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py
index a4e3e78e860..9715a5fc024 100644
--- a/homeassistant/components/homematicip_cloud/hap.py
+++ b/homeassistant/components/homematicip_cloud/hap.py
@@ -14,7 +14,7 @@ from .errors import HmipcConnectionError
_LOGGER = logging.getLogger(__name__)
-class HomematicipAuth(object):
+class HomematicipAuth:
"""Manages HomematicIP client registration."""
def __init__(self, hass, config):
@@ -73,7 +73,7 @@ class HomematicipAuth(object):
return auth
-class HomematicipHAP(object):
+class HomematicipHAP:
"""Manages HomematicIP http and websocket connection."""
def __init__(self, hass, config_entry):
@@ -117,7 +117,7 @@ class HomematicipHAP(object):
self.config_entry.data.get(HMIPC_HAPID))
for component in COMPONENTS:
- self.hass.async_add_job(
+ self.hass.async_create_task(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, component)
)
diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py
index 0cbee628a8a..42629f752ad 100644
--- a/homeassistant/components/http/__init__.py
+++ b/homeassistant/components/http/__init__.py
@@ -152,7 +152,7 @@ async def async_setup(hass, config):
return True
-class HomeAssistantHTTP(object):
+class HomeAssistantHTTP:
"""HTTP server for Home Assistant."""
def __init__(self, hass, api_password,
diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py
index 2cc62dce38e..77621e3bc7c 100644
--- a/homeassistant/components/http/auth.py
+++ b/homeassistant/components/http/auth.py
@@ -20,6 +20,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_auth(app, trusted_networks, use_auth,
support_legacy=False, api_password=None):
"""Create auth middleware for the app."""
+ old_auth_warning = set()
+
@middleware
async def auth_middleware(request, handler):
"""Authenticate as middleware."""
@@ -27,8 +29,10 @@ def setup_auth(app, trusted_networks, use_auth,
if use_auth and (HTTP_HEADER_HA_AUTH in request.headers or
DATA_API_PASSWORD in request.query):
- _LOGGER.warning('Please change to use bearer token access %s',
- request.path)
+ if request.path not in old_auth_warning:
+ _LOGGER.warning('Please change to use bearer token access %s',
+ request.path)
+ old_auth_warning.add(request.path)
legacy_auth = (not use_auth or support_legacy) and api_password
if (hdrs.AUTHORIZATION in request.headers and
@@ -109,7 +113,7 @@ async def async_validate_auth_header(request, api_password=None):
request['hass_user'] = access_token.refresh_token.user
return True
- elif auth_type == 'Basic' and api_password is not None:
+ if auth_type == 'Basic' and api_password is not None:
decoded = base64.b64decode(auth_val).decode('utf-8')
try:
username, password = decoded.split(':', 1)
@@ -123,5 +127,4 @@ async def async_validate_auth_header(request, api_password=None):
return hmac.compare_digest(api_password.encode('utf-8'),
password.encode('utf-8'))
- else:
- return False
+ return False
diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py
index e05f951322e..ab582066a22 100644
--- a/homeassistant/components/http/ban.py
+++ b/homeassistant/components/http/ban.py
@@ -1,5 +1,4 @@
"""Ban logic for HTTP component."""
-
from collections import defaultdict
from datetime import datetime
from ipaddress import ip_address
@@ -71,6 +70,17 @@ async def ban_middleware(request, handler):
raise
+def log_invalid_auth(func):
+ """Decorator to handle invalid auth or failed login attempts."""
+ async def handle_req(view, request, *args, **kwargs):
+ """Try to log failed login attempts if response status >= 400."""
+ resp = await func(view, request, *args, **kwargs)
+ if resp.status >= 400:
+ await process_wrong_login(request)
+ return resp
+ return handle_req
+
+
async def process_wrong_login(request):
"""Process a wrong login attempt.
diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py
index cd07ab6df69..8b28a7cf288 100644
--- a/homeassistant/components/http/static.py
+++ b/homeassistant/components/http/static.py
@@ -31,10 +31,9 @@ class CachingStaticResource(StaticResource):
if filepath.is_dir():
return await super()._handle(request)
- elif filepath.is_file():
+ if filepath.is_file():
return CachingFileResponse(filepath, chunk_size=self._chunk_size)
- else:
- raise HTTPNotFound
+ raise HTTPNotFound
# pylint: disable=too-many-ancestors
diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py
index 98f5a5b0a84..22ef34de54a 100644
--- a/homeassistant/components/http/view.py
+++ b/homeassistant/components/http/view.py
@@ -13,7 +13,7 @@ from aiohttp.web_exceptions import HTTPUnauthorized, HTTPInternalServerError
import homeassistant.remote as rem
from homeassistant.components.http.ban import process_success_login
-from homeassistant.core import is_callback
+from homeassistant.core import Context, is_callback
from homeassistant.const import CONTENT_TYPE_JSON
from .const import KEY_AUTHENTICATED, KEY_REAL_IP
@@ -22,7 +22,7 @@ from .const import KEY_AUTHENTICATED, KEY_REAL_IP
_LOGGER = logging.getLogger(__name__)
-class HomeAssistantView(object):
+class HomeAssistantView:
"""Base view for all views."""
url = None
@@ -32,6 +32,14 @@ class HomeAssistantView(object):
cors_allowed = False
# pylint: disable=no-self-use
+ def context(self, request):
+ """Generate a context from a request."""
+ user = request.get('hass_user')
+ if user is None:
+ return Context()
+
+ return Context(user_id=user.id)
+
def json(self, result, status_code=200, headers=None):
"""Return a JSON response."""
try:
diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py
index 8710b2561b0..b7cf0e1de07 100644
--- a/homeassistant/components/hue/bridge.py
+++ b/homeassistant/components/hue/bridge.py
@@ -19,7 +19,7 @@ SCENE_SCHEMA = vol.Schema({
})
-class HueBridge(object):
+class HueBridge:
"""Manages a single Hue bridge."""
def __init__(self, hass, config_entry, allow_unreachable, allow_groups):
@@ -78,7 +78,7 @@ class HueBridge(object):
host)
return False
- hass.async_add_job(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
self.config_entry, 'light'))
hass.services.async_register(
diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py
index af67a594495..a7fe3ff04e0 100644
--- a/homeassistant/components/hue/config_flow.py
+++ b/homeassistant/components/hue/config_flow.py
@@ -84,7 +84,7 @@ class HueFlowHandler(data_entry_flow.FlowHandler):
reason='all_configured'
)
- elif len(hosts) == 1:
+ if len(hosts) == 1:
self.host = hosts[0]
return await self.async_step_link()
diff --git a/homeassistant/components/hydrawise.py b/homeassistant/components/hydrawise.py
index a60e3d5b8fc..0c4db63034e 100644
--- a/homeassistant/components/hydrawise.py
+++ b/homeassistant/components/hydrawise.py
@@ -101,7 +101,7 @@ def setup(hass, config):
return True
-class HydrawiseHub(object):
+class HydrawiseHub:
"""Representation of a base Hydrawise device."""
def __init__(self, data):
diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py
index 0a4ad66ce56..9497282ab21 100644
--- a/homeassistant/components/ifttt.py
+++ b/homeassistant/components/ifttt.py
@@ -63,7 +63,7 @@ def setup(hass, config):
value3 = call.data.get(ATTR_VALUE3)
try:
- import pyfttt as pyfttt
+ import pyfttt
pyfttt.send_event(key, event, value1, value2, value3)
except requests.exceptions.RequestException:
_LOGGER.exception("Error communicating with IFTTT")
diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py
index 0c0100bc9f5..672964f765e 100644
--- a/homeassistant/components/ihc/__init__.py
+++ b/homeassistant/components/ihc/__init__.py
@@ -167,7 +167,10 @@ def get_discovery_info(component_setup, groups):
name = '{}_{}'.format(groupname, ihc_id)
device = {
'ihc_id': ihc_id,
- 'product': product,
+ 'product': {
+ 'name': product.attrib['name'],
+ 'note': product.attrib['note'],
+ 'position': product.attrib['position']},
'product_cfg': product_cfg}
discovery_data[name] = device
return discovery_data
diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py
index de6db875def..2ccca366d90 100644
--- a/homeassistant/components/ihc/ihcdevice.py
+++ b/homeassistant/components/ihc/ihcdevice.py
@@ -1,6 +1,5 @@
"""Implementation of a base class for all IHC devices."""
import asyncio
-from xml.etree.ElementTree import Element
from homeassistant.helpers.entity import Entity
@@ -14,16 +13,16 @@ class IHCDevice(Entity):
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
- product: Element = None) -> None:
+ product=None) -> None:
"""Initialize IHC attributes."""
self.ihc_controller = ihc_controller
self._name = name
self.ihc_id = ihc_id
self.info = info
if product:
- self.ihc_name = product.attrib['name']
- self.ihc_note = product.attrib['note']
- self.ihc_position = product.attrib['position']
+ self.ihc_name = product['name']
+ self.ihc_note = product['note']
+ self.ihc_position = product['position']
else:
self.ihc_name = ''
self.ihc_note = ''
diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py
index bda0e1bc550..0b57dba8bca 100644
--- a/homeassistant/components/image_processing/microsoft_face_detect.py
+++ b/homeassistant/components/image_processing/microsoft_face_detect.py
@@ -103,7 +103,7 @@ class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
_LOGGER.error("Can't process image on microsoft face: %s", err)
return
- if face_data is None or len(face_data) < 1:
+ if not face_data:
return
faces = []
diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py
index 8984f25cdf2..9479a804a44 100644
--- a/homeassistant/components/image_processing/microsoft_face_identify.py
+++ b/homeassistant/components/image_processing/microsoft_face_identify.py
@@ -90,7 +90,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
face_data = yield from self._api.call_api(
'post', 'detect', image, binary=True)
- if face_data is None or len(face_data) < 1:
+ if not face_data:
return
face_ids = [data['faceId'] for data in face_data]
diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/image_processing/opencv.py
index ca0f3527f73..00ae01f1123 100644
--- a/homeassistant/components/image_processing/opencv.py
+++ b/homeassistant/components/image_processing/opencv.py
@@ -16,7 +16,7 @@ from homeassistant.components.image_processing import (
from homeassistant.core import split_entity_id
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['numpy==1.14.5']
+REQUIREMENTS = ['numpy==1.15.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py
index 82fc6b02266..055015b74f5 100644
--- a/homeassistant/components/insteon_plm/__init__.py
+++ b/homeassistant/components/insteon_plm/__init__.py
@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['insteonplm==0.11.3']
+REQUIREMENTS = ['insteonplm==0.11.7']
_LOGGER = logging.getLogger(__name__)
@@ -55,6 +55,11 @@ SRV_HOUSECODE = 'housecode'
HOUSECODES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']
+BUTTON_PRESSED_STATE_NAME = 'onLevelButton'
+EVENT_BUTTON_ON = 'insteon_plm.button_on'
+EVENT_BUTTON_OFF = 'insteon_plm.button_off'
+EVENT_CONF_BUTTON = 'button'
+
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
cv.deprecated(CONF_PLATFORM), vol.Schema({
vol.Required(CONF_ADDRESS): cv.string,
@@ -130,15 +135,20 @@ def async_setup(hass, config):
"""Detect device from transport to be delegated to platform."""
for state_key in device.states:
platform_info = ipdb[device.states[state_key]]
- if platform_info:
+ if platform_info and platform_info.platform:
platform = platform_info.platform
- if platform:
+
+ if platform == 'on_off_events':
+ device.states[state_key].register_updates(
+ _fire_button_on_off_event)
+
+ else:
_LOGGER.info("New INSTEON PLM device: %s (%s) %s",
device.address,
device.states[state_key].name,
platform)
- hass.async_add_job(
+ hass.async_create_task(
discovery.async_load_platform(
hass, platform, DOMAIN,
discovered={'address': device.address.id,
@@ -223,6 +233,23 @@ def async_setup(hass, config):
schema=X10_HOUSECODE_SCHEMA)
_LOGGER.debug("Insteon_plm Services registered")
+ def _fire_button_on_off_event(address, group, val):
+ # Firing an event when a button is pressed.
+ device = plm.devices[address.hex]
+ state_name = device.states[group].name
+ button = ("" if state_name == BUTTON_PRESSED_STATE_NAME
+ else state_name[-1].lower())
+ schema = {CONF_ADDRESS: address.hex}
+ if button != "":
+ schema[EVENT_CONF_BUTTON] = button
+ if val:
+ event = EVENT_BUTTON_ON
+ else:
+ event = EVENT_BUTTON_OFF
+ _LOGGER.debug('Firing event %s with address %s and button %s',
+ event, address.hex, button)
+ hass.bus.fire(event, schema)
+
_LOGGER.info("Looking for PLM on %s", port)
conn = yield from insteonplm.Connection.create(
device=port,
@@ -289,7 +316,7 @@ def async_setup(hass, config):
State = collections.namedtuple('Product', 'stateType platform')
-class IPDB(object):
+class IPDB:
"""Embodies the INSTEON Product Database static data and access methods."""
def __init__(self):
@@ -329,7 +356,7 @@ class IPDB(object):
State(DimmableSwitch_Fan, 'fan'),
State(DimmableSwitch, 'light'),
- State(DimmableRemote, 'binary_sensor'),
+ State(DimmableRemote, 'on_off_events'),
State(X10DimmableSwitch, 'light'),
State(X10OnOffSwitch, 'switch'),
diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote.py
index bbd7bc44082..9a7cc7caecb 100644
--- a/homeassistant/components/keyboard_remote.py
+++ b/homeassistant/components/keyboard_remote.py
@@ -163,7 +163,7 @@ class KeyboardRemoteThread(threading.Thread):
)
-class KeyboardRemote(object):
+class KeyboardRemote:
"""Sets up one thread per device."""
def __init__(self, hass, config):
diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py
index 61f8ca90137..5b3af3029b4 100644
--- a/homeassistant/components/knx.py
+++ b/homeassistant/components/knx.py
@@ -107,7 +107,7 @@ async def async_setup(hass, config):
('scene', 'Scene'),
('notify', 'Notification')):
found_devices = _get_devices(hass, discovery_type)
- hass.async_add_job(
+ hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
}, config))
@@ -129,7 +129,7 @@ def _get_devices(hass, discovery_type):
hass.data[DATA_KNX].xknx.devices)))
-class KNXModule(object):
+class KNXModule:
"""Representation of KNX Object."""
def __init__(self, hass, config):
@@ -172,7 +172,7 @@ class KNXModule(object):
"""Return the connection_config."""
if CONF_KNX_TUNNELING in self.config[DOMAIN]:
return self.connection_config_tunneling()
- elif CONF_KNX_ROUTING in self.config[DOMAIN]:
+ if CONF_KNX_ROUTING in self.config[DOMAIN]:
return self.connection_config_routing()
return self.connection_config_auto()
@@ -284,7 +284,7 @@ class KNXAutomation():
device.actions.append(self.action)
-class KNXExposeTime(object):
+class KNXExposeTime:
"""Object to Expose Time/Date object to KNX bus."""
def __init__(self, xknx, expose_type, address):
@@ -308,7 +308,7 @@ class KNXExposeTime(object):
self.xknx.devices.add(self.device)
-class KNXExposeSensor(object):
+class KNXExposeSensor:
"""Object to Expose HASS entity to KNX bus."""
def __init__(self, hass, xknx, expose_type, entity_id, address):
diff --git a/homeassistant/components/konnected.py b/homeassistant/components/konnected.py
index 26fe356d772..a3e9ff86ed0 100644
--- a/homeassistant/components/konnected.py
+++ b/homeassistant/components/konnected.py
@@ -114,7 +114,7 @@ async def async_setup(hass, config):
return True
-class KonnectedDevice(object):
+class KonnectedDevice:
"""A representation of a single Konnected device."""
def __init__(self, hass, host, port, config):
diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py
index b8a97607215..8b4b2137711 100644
--- a/homeassistant/components/light/__init__.py
+++ b/homeassistant/components/light/__init__.py
@@ -83,17 +83,6 @@ COLOR_GROUP = "Color descriptors"
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,
- 'hs_color': ATTR_HS_COLOR,
- 'white_value': ATTR_WHITE_VALUE,
- 'effect_list': ATTR_EFFECT_LIST,
- 'effect': ATTR_EFFECT,
-}
-
# Service call validation schemas
VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
@@ -357,7 +346,12 @@ async def async_setup(hass, config):
update_tasks = []
for light in target_lights:
if service.service == SERVICE_TURN_ON:
- await light.async_turn_on(**params)
+ pars = params
+ if not pars:
+ pars = params.copy()
+ pars[ATTR_PROFILE] = Profiles.get_default(light.entity_id)
+ preprocess_turn_on_alternatives(pars)
+ await light.async_turn_on(**pars)
elif service.service == SERVICE_TURN_OFF:
await light.async_turn_off(**params)
else:
@@ -365,7 +359,9 @@ async def async_setup(hass, config):
if not light.should_poll:
continue
- update_tasks.append(light.async_update_ha_state(True))
+
+ update_tasks.append(
+ light.async_update_ha_state(True, service.context))
if update_tasks:
await asyncio.wait(update_tasks, loop=hass.loop)
@@ -442,6 +438,18 @@ class Profiles:
"""Return a named profile."""
return cls._all.get(name)
+ @classmethod
+ def get_default(cls, entity_id):
+ """Return the default turn-on profile for the given light."""
+ # pylint: disable=unsupported-membership-test
+ name = entity_id + ".default"
+ if name in cls._all:
+ return name
+ name = ENTITY_ID_ALL_LIGHTS + ".default"
+ if name in cls._all:
+ return name
+ return None
+
class Light(ToggleEntity):
"""Representation of a light."""
@@ -494,29 +502,37 @@ class Light(ToggleEntity):
def state_attributes(self):
"""Return optional state attributes."""
data = {}
+ supported_features = self.supported_features
- if self.supported_features & SUPPORT_COLOR_TEMP:
+ if supported_features & SUPPORT_COLOR_TEMP:
data[ATTR_MIN_MIREDS] = self.min_mireds
data[ATTR_MAX_MIREDS] = self.max_mireds
if self.is_on:
- for prop, attr in PROP_TO_ATTR.items():
- value = getattr(self, prop)
- if value is not None:
- data[attr] = value
+ if supported_features & SUPPORT_BRIGHTNESS:
+ data[ATTR_BRIGHTNESS] = self.brightness
- # Expose current color also as RGB and XY
- if ATTR_HS_COLOR in data:
- data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(
- *data[ATTR_HS_COLOR])
- data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(
- *data[ATTR_HS_COLOR])
+ if supported_features & SUPPORT_COLOR_TEMP:
+ data[ATTR_COLOR_TEMP] = self.color_temp
+
+ if self.supported_features & SUPPORT_COLOR and self.hs_color:
+ # pylint: disable=unsubscriptable-object,not-an-iterable
+ hs_color = self.hs_color
data[ATTR_HS_COLOR] = (
- round(data[ATTR_HS_COLOR][0], 3),
- round(data[ATTR_HS_COLOR][1], 3),
+ round(hs_color[0], 3),
+ round(hs_color[1], 3),
)
+ data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
+ data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
- return data
+ if supported_features & SUPPORT_WHITE_VALUE:
+ data[ATTR_WHITE_VALUE] = self.white_value
+
+ if supported_features & SUPPORT_EFFECT:
+ data[ATTR_EFFECT_LIST] = self.effect_list
+ data[ATTR_EFFECT] = self.effect
+
+ return {key: val for key, val in data.items() if val is not None}
@property
def supported_features(self):
diff --git a/homeassistant/components/light/abode.py b/homeassistant/components/light/abode.py
index 8b7e09d86bc..431f5d12ff0 100644
--- a/homeassistant/components/light/abode.py
+++ b/homeassistant/components/light/abode.py
@@ -88,7 +88,7 @@ class AbodeLight(AbodeDevice, Light):
"""Flag supported features."""
if self._device.is_dimmable and self._device.has_color:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
- elif self._device.is_dimmable:
+ if self._device.is_dimmable:
return SUPPORT_BRIGHTNESS
return 0
diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py
index c5cd9a8c4fd..2b53fb65054 100644
--- a/homeassistant/components/light/flux_led.py
+++ b/homeassistant/components/light/flux_led.py
@@ -245,7 +245,7 @@ class FluxLight(Light):
return
# Effect selection
- elif effect in EFFECT_MAP:
+ if effect in EFFECT_MAP:
self._bulb.setPresetPattern(EFFECT_MAP[effect], 50)
return
diff --git a/homeassistant/components/light/futurenow.py b/homeassistant/components/light/futurenow.py
new file mode 100644
index 00000000000..1777376881e
--- /dev/null
+++ b/homeassistant/components/light/futurenow.py
@@ -0,0 +1,130 @@
+"""
+Support for FutureNow Ethernet unit outputs as Lights.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/light.futurenow/
+"""
+
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_NAME, CONF_HOST, CONF_PORT, CONF_DEVICES)
+from homeassistant.components.light import (
+ ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light,
+ PLATFORM_SCHEMA)
+import homeassistant.helpers.config_validation as cv
+
+REQUIREMENTS = ['pyfnip==0.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_DRIVER = 'driver'
+CONF_DRIVER_FNIP6X10AD = 'FNIP6x10ad'
+CONF_DRIVER_FNIP8X10A = 'FNIP8x10a'
+CONF_DRIVER_TYPES = [CONF_DRIVER_FNIP6X10AD, CONF_DRIVER_FNIP8X10A]
+
+DEVICE_SCHEMA = vol.Schema({
+ vol.Required(CONF_NAME): cv.string,
+ vol.Optional('dimmable', default=False): cv.boolean,
+})
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_DRIVER): vol.In(CONF_DRIVER_TYPES),
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_PORT): cv.port,
+ vol.Required(CONF_DEVICES): {cv.string: DEVICE_SCHEMA},
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the light platform for each FutureNow unit."""
+ lights = []
+ for channel, device_config in config[CONF_DEVICES].items():
+ device = {}
+ device['name'] = device_config[CONF_NAME]
+ device['dimmable'] = device_config['dimmable']
+ device['channel'] = channel
+ device['driver'] = config[CONF_DRIVER]
+ device['host'] = config[CONF_HOST]
+ device['port'] = config[CONF_PORT]
+ lights.append(FutureNowLight(device))
+
+ add_devices(lights, True)
+
+
+def to_futurenow_level(level):
+ """Convert the given HASS light level (0-255) to FutureNow (0-100)."""
+ return int((level * 100) / 255)
+
+
+def to_hass_level(level):
+ """Convert the given FutureNow (0-100) light level to HASS (0-255)."""
+ return int((level * 255) / 100)
+
+
+class FutureNowLight(Light):
+ """Representation of an FutureNow light."""
+
+ def __init__(self, device):
+ """Initialize the light."""
+ import pyfnip
+
+ self._name = device['name']
+ self._dimmable = device['dimmable']
+ self._channel = device['channel']
+ self._brightness = None
+ self._last_brightness = 255
+ self._state = None
+
+ if device['driver'] == CONF_DRIVER_FNIP6X10AD:
+ self._light = pyfnip.FNIP6x2adOutput(device['host'],
+ device['port'],
+ self._channel)
+ if device['driver'] == CONF_DRIVER_FNIP8X10A:
+ self._light = pyfnip.FNIP8x10aOutput(device['host'],
+ device['port'],
+ self._channel)
+
+ @property
+ def name(self):
+ """Return the name of the device if any."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return true if device is on."""
+ return self._state
+
+ @property
+ def brightness(self):
+ """Return the brightness of this light between 0..255."""
+ return self._brightness
+
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ if self._dimmable:
+ return SUPPORT_BRIGHTNESS
+ return 0
+
+ def turn_on(self, **kwargs):
+ """Turn the light on."""
+ if self._dimmable:
+ level = kwargs.get(ATTR_BRIGHTNESS, self._last_brightness)
+ else:
+ level = 255
+ self._light.turn_on(to_futurenow_level(level))
+
+ def turn_off(self, **kwargs):
+ """Turn the light off."""
+ self._light.turn_off()
+ if self._brightness:
+ self._last_brightness = self._brightness
+
+ def update(self):
+ """Fetch new state data for this light."""
+ state = int(self._light.is_on())
+ self._state = bool(state)
+ self._brightness = to_hass_level(state)
diff --git a/homeassistant/components/light/greenwave.py b/homeassistant/components/light/greenwave.py
index 8e9d93657ce..52a70532005 100644
--- a/homeassistant/components/light/greenwave.py
+++ b/homeassistant/components/light/greenwave.py
@@ -121,7 +121,7 @@ class GreenwaveLight(Light):
self._name = bulbs[self._did]['name']
-class GatewayData(object):
+class GatewayData:
"""Handle Gateway data and limit updates."""
def __init__(self, host, token):
diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py
index 837a6f82510..0da59b6f100 100644
--- a/homeassistant/components/light/hue.py
+++ b/homeassistant/components/light/hue.py
@@ -11,7 +11,7 @@ import random
import async_timeout
-import homeassistant.components.hue as hue
+from homeassistant.components import hue
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_TRANSITION, ATTR_HS_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM,
diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py
index 8ba2329af7e..cbac8cf4e20 100644
--- a/homeassistant/components/light/hyperion.py
+++ b/homeassistant/components/light/hyperion.py
@@ -146,10 +146,7 @@ class Hyperion(Light):
else:
rgb_color = self._rgb_mem
- if ATTR_BRIGHTNESS in kwargs:
- brightness = kwargs[ATTR_BRIGHTNESS]
- else:
- brightness = self._brightness
+ brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness)
if ATTR_EFFECT in kwargs:
self._skip_update = True
diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/light/ihc.py
index c9ceda8651a..5a7e85d50dc 100644
--- a/homeassistant/components/light/ihc.py
+++ b/homeassistant/components/light/ihc.py
@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.ihc/
"""
-from xml.etree.ElementTree import Element
-
import voluptuous as vol
from homeassistant.components.ihc import (
@@ -64,7 +62,7 @@ class IhcLight(IHCDevice, Light):
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
- dimmable=False, product: Element = None) -> None:
+ dimmable=False, product=None) -> None:
"""Initialize the light."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._brightness = 0
diff --git a/homeassistant/components/light/insteon_local.py b/homeassistant/components/light/insteon_local.py
index bd7814df8f3..e2bc54de517 100644
--- a/homeassistant/components/light/insteon_local.py
+++ b/homeassistant/components/light/insteon_local.py
@@ -9,7 +9,7 @@ from datetime import timedelta
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
-import homeassistant.util as util
+from homeassistant import util
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py
index 9b2c183c1d1..3738fd8f004 100644
--- a/homeassistant/components/light/lifx.py
+++ b/homeassistant/components/light/lifx.py
@@ -204,7 +204,7 @@ def merge_hsbk(base, change):
return [b if c is None else c for b, c in zip(base, change)]
-class LIFXManager(object):
+class LIFXManager:
"""Representation of all known LIFX entities."""
def __init__(self, hass, async_add_devices):
diff --git a/homeassistant/components/light/lifx_legacy.py b/homeassistant/components/light/lifx_legacy.py
index 182d7536dc4..3ad75a1cea4 100644
--- a/homeassistant/components/light/lifx_legacy.py
+++ b/homeassistant/components/light/lifx_legacy.py
@@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
lifx_library.probe()
-class LIFX(object):
+class LIFX:
"""Representation of a LIFX light."""
def __init__(self, add_devices_callback, server_addr=None,
diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/light/litejet.py
index 2ebe766c8c5..b8491b6f0f5 100644
--- a/homeassistant/components/light/litejet.py
+++ b/homeassistant/components/light/litejet.py
@@ -6,7 +6,7 @@ https://home-assistant.io/components/light.litejet/
"""
import logging
-import homeassistant.components.litejet as litejet
+from homeassistant.components import litejet
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/light/lutron_caseta.py
index 09f0a337cc3..29186b8fcd2 100644
--- a/homeassistant/components/light/lutron_caseta.py
+++ b/homeassistant/components/light/lutron_caseta.py
@@ -48,10 +48,7 @@ class LutronCasetaLight(LutronCasetaDevice, Light):
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the light on."""
- if ATTR_BRIGHTNESS in kwargs:
- brightness = kwargs[ATTR_BRIGHTNESS]
- else:
- brightness = 255
+ brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
self._smartbridge.set_value(self._device_id,
to_lutron_level(brightness))
diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py
index c0e363f85d6..09fa094c1b2 100644
--- a/homeassistant/components/light/mqtt.py
+++ b/homeassistant/components/light/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR,
ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
@@ -205,7 +205,7 @@ class MqttLight(MqttAvailability, Light):
topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None and
SUPPORT_COLOR_TEMP)
self._supported_features |= (
- topic[CONF_EFFECT_STATE_TOPIC] is not None and
+ topic[CONF_EFFECT_COMMAND_TOPIC] is not None and
SUPPORT_EFFECT)
self._supported_features |= (
topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None and
diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py
index 705e106fdff..d17c7dd73bf 100644
--- a/homeassistant/components/light/mqtt_json.py
+++ b/homeassistant/components/light/mqtt_json.py
@@ -9,7 +9,7 @@ import json
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_HS_COLOR,
diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt_template.py
index f6b3fbe8b70..ffa73aca915 100644
--- a/homeassistant/components/light/mqtt_template.py
+++ b/homeassistant/components/light/mqtt_template.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light, PLATFORM_SCHEMA,
diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py
index cdfe2fe5671..293783ee3ab 100644
--- a/homeassistant/components/light/rfxtrx.py
+++ b/homeassistant/components/light/rfxtrx.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.rfxtrx as rfxtrx
+from homeassistant.components import rfxtrx
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME
diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py
index 214a2d99449..3d567afe09e 100644
--- a/homeassistant/components/light/scsgate.py
+++ b/homeassistant/components/light/scsgate.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.scsgate as scsgate
+from homeassistant.components import scsgate
from homeassistant.components.light import (Light, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME)
diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py
new file mode 100644
index 00000000000..ded78716317
--- /dev/null
+++ b/homeassistant/components/light/sisyphus.py
@@ -0,0 +1,78 @@
+"""
+Support for the light on the Sisyphus Kinetic Art Table.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/light.sisyphus/
+"""
+import logging
+
+from homeassistant.const import CONF_NAME
+from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light
+from homeassistant.components.sisyphus import DATA_SISYPHUS
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['sisyphus']
+
+SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up a single Sisyphus table."""
+ name = discovery_info[CONF_NAME]
+ add_devices(
+ [SisyphusLight(name, hass.data[DATA_SISYPHUS][name])],
+ update_before_add=True)
+
+
+class SisyphusLight(Light):
+ """Represents a Sisyphus table as a light."""
+
+ def __init__(self, name, table):
+ """
+ Constructor.
+
+ :param name: name of the table
+ :param table: sisyphus-control Table object
+ """
+ self._name = name
+ self._table = table
+
+ async def async_added_to_hass(self):
+ """Add listeners after this object has been initialized."""
+ self._table.add_listener(
+ lambda: self.async_schedule_update_ha_state(False))
+
+ @property
+ def name(self):
+ """Return the ame of the table."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return True if the table is on."""
+ return not self._table.is_sleeping
+
+ @property
+ def brightness(self):
+ """Return the current brightness of the table's ring light."""
+ return self._table.brightness * 255
+
+ @property
+ def supported_features(self):
+ """Return the features supported by the table; i.e. brightness."""
+ return SUPPORTED_FEATURES
+
+ async def async_turn_off(self, **kwargs):
+ """Put the table to sleep."""
+ await self._table.sleep()
+ _LOGGER.debug("Sisyphus table %s: sleep")
+
+ async def async_turn_on(self, **kwargs):
+ """Wake up the table if necessary, optionally changes brightness."""
+ if not self.is_on:
+ await self._table.wakeup()
+ _LOGGER.debug("Sisyphus table %s: wakeup")
+
+ if "brightness" in kwargs:
+ await self._table.set_brightness(kwargs["brightness"] / 255.0)
diff --git a/homeassistant/components/light/template.py b/homeassistant/components/light/template.py
index 38cac649a1a..ad77b734fbb 100644
--- a/homeassistant/components/light/template.py
+++ b/homeassistant/components/light/template.py
@@ -248,8 +248,7 @@ class LightTemplate(Light):
self._state = state in ('true', STATE_ON)
else:
_LOGGER.error(
- 'Received invalid light is_on state: %s. ' +
- 'Expected: %s',
+ 'Received invalid light is_on state: %s. Expected: %s',
state, ', '.join(_VALID_STATES))
self._state = None
@@ -264,8 +263,7 @@ class LightTemplate(Light):
self._brightness = int(brightness)
else:
_LOGGER.error(
- 'Received invalid brightness : %s' +
- 'Expected: 0-255',
+ 'Received invalid brightness : %s. Expected: 0-255',
brightness)
self._brightness = None
diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py
index 669901f5b57..9374c1418f0 100644
--- a/homeassistant/components/light/tplink.py
+++ b/homeassistant/components/light/tplink.py
@@ -140,8 +140,6 @@ class TPLinkSmartBulb(Light):
"""Update the TP-Link Bulb's state."""
from pyHS100 import SmartDeviceException
try:
- self._available = True
-
if self._supported_features == 0:
self.get_features()
@@ -182,9 +180,13 @@ class TPLinkSmartBulb(Light):
# device returned no daily/monthly history
pass
+ self._available = True
+
except (SmartDeviceException, OSError) as ex:
- _LOGGER.warning("Could not read state for %s: %s", self._name, ex)
- self._available = False
+ if self._available:
+ _LOGGER.warning(
+ "Could not read state for %s: %s", self._name, ex)
+ self._available = False
@property
def supported_features(self):
diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py
index 4cd34b698da..4c912d60fb7 100644
--- a/homeassistant/components/light/wemo.py
+++ b/homeassistant/components/light/wemo.py
@@ -8,7 +8,7 @@ import asyncio
import logging
from datetime import timedelta
-import homeassistant.util as util
+from homeassistant import util
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, SUPPORT_TRANSITION)
@@ -27,7 +27,7 @@ SUPPORT_WEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR |
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up discovered WeMo switches."""
- import pywemo.discovery as discovery
+ from pywemo import discovery
if discovery_info is not None:
location = discovery_info['ssdp_description']
diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py
index f468e8c25ef..55feef496f8 100644
--- a/homeassistant/components/light/zwave.py
+++ b/homeassistant/components/light/zwave.py
@@ -12,7 +12,7 @@ from homeassistant.components.light import (
ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR,
SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, DOMAIN, Light)
from homeassistant.components import zwave
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.const import STATE_OFF, STATE_ON
import homeassistant.util.color as color_util
diff --git a/homeassistant/components/linode.py b/homeassistant/components/linode.py
index 962e30774b8..c98ef16c7ed 100644
--- a/homeassistant/components/linode.py
+++ b/homeassistant/components/linode.py
@@ -62,7 +62,7 @@ def setup(hass, config):
return True
-class Linode(object):
+class Linode:
"""Handle all communication with the Linode API."""
def __init__(self, access_token):
diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py
index d8af22cd5c3..45029e679a5 100644
--- a/homeassistant/components/lock/mqtt.py
+++ b/homeassistant/components/lock/mqtt.py
@@ -17,7 +17,7 @@ from homeassistant.components.mqtt import (
MqttAvailability)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py
index eb2e8391221..c4fcf53a9c1 100644
--- a/homeassistant/components/logbook.py
+++ b/homeassistant/components/logbook.py
@@ -140,7 +140,7 @@ class LogbookView(HomeAssistantView):
return await hass.async_add_job(json_events)
-class Entry(object):
+class Entry:
"""A human readable version of the log."""
def __init__(self, when=None, name=None, message=None, domain=None,
@@ -380,16 +380,16 @@ def _entry_message_from_state(domain, state):
return 'is away'
return 'is at {}'.format(state.state)
- elif domain == 'sun':
+ if domain == 'sun':
if state.state == sun.STATE_ABOVE_HORIZON:
return 'has risen'
return 'has set'
- elif state.state == STATE_ON:
+ if state.state == STATE_ON:
# Future: combine groups and its entity entries ?
return "turned on"
- elif state.state == STATE_OFF:
+ if state.state == STATE_OFF:
return "turned off"
return "changed to {}".format(state.state)
diff --git a/homeassistant/components/lutron_caseta.py b/homeassistant/components/lutron_caseta.py
index 7b1b7417cfd..2535fb76120 100644
--- a/homeassistant/components/lutron_caseta.py
+++ b/homeassistant/components/lutron_caseta.py
@@ -63,8 +63,8 @@ def async_setup(hass, base_config):
_LOGGER.info("Connected to Lutron smartbridge at %s", config[CONF_HOST])
for component in LUTRON_CASETA_COMPONENTS:
- hass.async_add_job(discovery.async_load_platform(hass, component,
- DOMAIN, {}, config))
+ hass.async_create_task(discovery.async_load_platform(
+ hass, component, DOMAIN, {}, config))
return True
diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py
index 8ff3746889e..6a648e4dc47 100644
--- a/homeassistant/components/mailbox/__init__.py
+++ b/homeassistant/components/mailbox/__init__.py
@@ -132,7 +132,7 @@ class MailboxEntity(Entity):
self.message_count = len(messages)
-class Mailbox(object):
+class Mailbox:
"""Represent a mailbox device."""
def __init__(self, hass, name):
diff --git a/homeassistant/components/mailbox/demo.py b/homeassistant/components/mailbox/demo.py
index ccb371de2f8..8096a4fabb7 100644
--- a/homeassistant/components/mailbox/demo.py
+++ b/homeassistant/components/mailbox/demo.py
@@ -9,7 +9,7 @@ import logging
import os
from hashlib import sha1
-import homeassistant.util.dt as dt
+from homeassistant.util import dt
from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
StreamError)
diff --git a/homeassistant/components/mailgun.py b/homeassistant/components/mailgun.py
index ec480ac12d6..7cb7ef7151d 100644
--- a/homeassistant/components/mailgun.py
+++ b/homeassistant/components/mailgun.py
@@ -48,4 +48,3 @@ class MailgunReceiveMessageView(HomeAssistantView):
hass = request.app['hass']
data = yield from request.post()
hass.bus.async_fire(MESSAGE_RECEIVED, dict(data))
- return
diff --git a/homeassistant/components/matrix.py b/homeassistant/components/matrix.py
index b2805c994e8..5f6c30aaeba 100644
--- a/homeassistant/components/matrix.py
+++ b/homeassistant/components/matrix.py
@@ -96,7 +96,7 @@ def setup(hass, config):
return True
-class MatrixBot(object):
+class MatrixBot:
"""The Matrix Bot."""
def __init__(self, hass, config_file, homeserver, verify_ssl,
diff --git a/homeassistant/components/maxcube.py b/homeassistant/components/maxcube.py
index bca7a1b4ab7..b574f0bcb15 100644
--- a/homeassistant/components/maxcube.py
+++ b/homeassistant/components/maxcube.py
@@ -79,7 +79,7 @@ def setup(hass, config):
return True
-class MaxCubeHandle(object):
+class MaxCubeHandle:
"""Keep the cube instance in one place and centralize the update."""
def __init__(self, cube, scan_interval):
diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py
index 21accdf84b3..029d10ea00a 100644
--- a/homeassistant/components/media_extractor.py
+++ b/homeassistant/components/media_extractor.py
@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
SERVICE_PLAY_MEDIA)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ['youtube_dl==2018.07.04']
+REQUIREMENTS = ['youtube_dl==2018.07.29']
_LOGGER = logging.getLogger(__name__)
@@ -58,7 +58,7 @@ class MEQueryException(Exception):
pass
-class MediaExtractor(object):
+class MediaExtractor:
"""Class which encapsulates all extraction logic."""
def __init__(self, hass, component_config, call_data):
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index d314dec65ea..c475291227a 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -6,12 +6,13 @@ https://home-assistant.io/components/media_player/
"""
import asyncio
import base64
+import collections
from datetime import timedelta
import functools as ft
-import collections
import hashlib
import logging
from random import SystemRandom
+from urllib.parse import urlparse
from aiohttp import web
from aiohttp.hdrs import CONTENT_TYPE, CACHE_CONTROL
@@ -956,6 +957,9 @@ async def _async_fetch_image(hass, url):
cache_images = ENTITY_IMAGE_CACHE[CACHE_IMAGES]
cache_maxsize = ENTITY_IMAGE_CACHE[CACHE_MAXSIZE]
+ if urlparse(url).hostname is None:
+ url = hass.config.api.base_url + url
+
if url not in cache_images:
cache_images[url] = {CACHE_LOCK: asyncio.Lock(loop=hass.loop)}
diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py
index 474751c2574..a74629917b3 100644
--- a/homeassistant/components/media_player/anthemav.py
+++ b/homeassistant/components/media_player/anthemav.py
@@ -100,7 +100,7 @@ class AnthemAVR(MediaPlayerDevice):
if pwrstate is True:
return STATE_ON
- elif pwrstate is False:
+ if pwrstate is False:
return STATE_OFF
return STATE_UNKNOWN
diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py
index 37a50b39e95..d4a7ad19807 100644
--- a/homeassistant/components/media_player/apple_tv.py
+++ b/homeassistant/components/media_player/apple_tv.py
@@ -100,15 +100,14 @@ class AppleTvDevice(MediaPlayerDevice):
if self._playing:
from pyatv import const
state = self._playing.play_state
- if state == const.PLAY_STATE_IDLE or \
- state == const.PLAY_STATE_NO_MEDIA or \
- state == const.PLAY_STATE_LOADING:
+ if state in (const.PLAY_STATE_IDLE, const.PLAY_STATE_NO_MEDIA,
+ const.PLAY_STATE_LOADING):
return STATE_IDLE
- elif state == const.PLAY_STATE_PLAYING:
+ if state == const.PLAY_STATE_PLAYING:
return STATE_PLAYING
- elif state == const.PLAY_STATE_PAUSED or \
- state == const.PLAY_STATE_FAST_FORWARD or \
- state == const.PLAY_STATE_FAST_BACKWARD:
+ if state in (const.PLAY_STATE_PAUSED,
+ const.PLAY_STATE_FAST_FORWARD,
+ const.PLAY_STATE_FAST_BACKWARD):
# Catch fast forward/backward here so "play" is default action
return STATE_PAUSED
return STATE_STANDBY # Bad or unknown state?
@@ -141,9 +140,9 @@ class AppleTvDevice(MediaPlayerDevice):
media_type = self._playing.media_type
if media_type == const.MEDIA_TYPE_VIDEO:
return MEDIA_TYPE_VIDEO
- elif media_type == const.MEDIA_TYPE_MUSIC:
+ if media_type == const.MEDIA_TYPE_MUSIC:
return MEDIA_TYPE_MUSIC
- elif media_type == const.MEDIA_TYPE_TV:
+ if media_type == const.MEDIA_TYPE_TV:
return MEDIA_TYPE_TVSHOW
@property
@@ -162,7 +161,7 @@ class AppleTvDevice(MediaPlayerDevice):
def media_position_updated_at(self):
"""Last valid time of media position."""
state = self.state
- if state == STATE_PLAYING or state == STATE_PAUSED:
+ if state in (STATE_PLAYING, STATE_PAUSED):
return dt_util.utcnow()
@asyncio.coroutine
@@ -222,7 +221,7 @@ class AppleTvDevice(MediaPlayerDevice):
state = self.state
if state == STATE_PAUSED:
return self.atv.remote_control.play()
- elif state == STATE_PLAYING:
+ if state == STATE_PLAYING:
return self.atv.remote_control.pause()
def async_media_play(self):
diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py
index 283c4af032e..5631ec06cf1 100644
--- a/homeassistant/components/media_player/bluesound.py
+++ b/homeassistant/components/media_player/bluesound.py
@@ -216,12 +216,8 @@ class BluesoundPlayer(MediaPlayerDevice):
async def force_update_sync_status(
self, on_updated_cb=None, raise_timeout=False):
"""Update the internal status."""
- resp = None
- try:
- resp = await self.send_bluesound_command(
- 'SyncStatus', raise_timeout, raise_timeout)
- except Exception:
- raise
+ resp = await self.send_bluesound_command(
+ 'SyncStatus', raise_timeout, raise_timeout)
if not resp:
return None
@@ -333,10 +329,10 @@ class BluesoundPlayer(MediaPlayerDevice):
if response.status == 200:
result = await response.text()
- if len(result) < 1:
- data = None
- else:
+ if result:
data = xmltodict.parse(result)
+ else:
+ data = None
elif response.status == 595:
_LOGGER.info("Status 595 returned, treating as timeout")
raise BluesoundPlayer._TimeoutException()
@@ -528,9 +524,9 @@ class BluesoundPlayer(MediaPlayerDevice):
return STATE_GROUPED
status = self._status.get('state', None)
- if status == 'pause' or status == 'stop':
+ if status in ('pause', 'stop'):
return STATE_PAUSED
- elif status == 'stream' or status == 'play':
+ if status in ('stream', 'play'):
return STATE_PLAYING
return STATE_IDLE
@@ -640,7 +636,7 @@ class BluesoundPlayer(MediaPlayerDevice):
volume = self.volume_level
if not volume:
return None
- return volume < 0.001 and volume >= 0
+ return 0 <= volume < 0.001
@property
def name(self):
@@ -847,12 +843,12 @@ class BluesoundPlayer(MediaPlayerDevice):
items = [x for x in self._preset_items if x['title'] == source]
- if len(items) < 1:
+ if not items:
items = [x for x in self._services_items if x['title'] == source]
- if len(items) < 1:
+ if not items:
items = [x for x in self._capture_items if x['title'] == source]
- if len(items) < 1:
+ if not items:
return
selected_source = items[0]
@@ -974,6 +970,5 @@ class BluesoundPlayer(MediaPlayerDevice):
if volume > 0:
self._lastvol = volume
return await self.send_bluesound_command('Volume?level=0')
- else:
- return await self.send_bluesound_command(
- 'Volume?level=' + str(float(self._lastvol) * 100))
+ return await self.send_bluesound_command(
+ 'Volume?level=' + str(float(self._lastvol) * 100))
diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py
index 464baed1686..07a379db45c 100644
--- a/homeassistant/components/media_player/braviatv.py
+++ b/homeassistant/components/media_player/braviatv.py
@@ -88,23 +88,23 @@ def setup_bravia(config, pin, hass, add_devices):
if pin is None:
request_configuration(config, hass, add_devices)
return
- else:
- mac = _get_mac_address(host)
- if mac is not None:
- mac = mac.decode('utf8')
- # If we came here and configuring this host, mark as done
- if host in _CONFIGURING:
- request_id = _CONFIGURING.pop(host)
- configurator = hass.components.configurator
- configurator.request_done(request_id)
- _LOGGER.info("Discovery configuration done")
- # Save config
- save_json(
- hass.config.path(BRAVIA_CONFIG_FILE),
- {host: {'pin': pin, 'host': host, 'mac': mac}})
+ mac = _get_mac_address(host)
+ if mac is not None:
+ mac = mac.decode('utf8')
+ # If we came here and configuring this host, mark as done
+ if host in _CONFIGURING:
+ request_id = _CONFIGURING.pop(host)
+ configurator = hass.components.configurator
+ configurator.request_done(request_id)
+ _LOGGER.info("Discovery configuration done")
- add_devices([BraviaTVDevice(host, mac, name, pin)])
+ # Save config
+ save_json(
+ hass.config.path(BRAVIA_CONFIG_FILE),
+ {host: {'pin': pin, 'host': host, 'mac': mac}})
+
+ add_devices([BraviaTVDevice(host, mac, name, pin)])
def request_configuration(config, hass, add_devices):
diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py
index 4e24d5f2f71..099b365c50b 100644
--- a/homeassistant/components/media_player/cast.py
+++ b/homeassistant/components/media_player/cast.py
@@ -63,7 +63,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@attr.s(slots=True, frozen=True)
-class ChromecastInfo(object):
+class ChromecastInfo:
"""Class to hold all data about a chromecast for creating connections.
This also has the same attributes as the mDNS fields by zeroconf.
@@ -258,7 +258,7 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType,
hass.async_add_job(_discover_chromecast, hass, info)
-class CastStatusListener(object):
+class CastStatusListener:
"""Helper class to handle pychromecast status callbacks.
Necessary because a CastDevice entity can create a new socket client
@@ -499,13 +499,13 @@ class CastDevice(MediaPlayerDevice):
"""Return the state of the player."""
if self.media_status is None:
return None
- elif self.media_status.player_is_playing:
+ if self.media_status.player_is_playing:
return STATE_PLAYING
- elif self.media_status.player_is_paused:
+ if self.media_status.player_is_paused:
return STATE_PAUSED
- elif self.media_status.player_is_idle:
+ if self.media_status.player_is_idle:
return STATE_IDLE
- elif self._chromecast is not None and self._chromecast.is_idle:
+ if self._chromecast is not None and self._chromecast.is_idle:
return STATE_OFF
return None
@@ -534,11 +534,11 @@ class CastDevice(MediaPlayerDevice):
"""Content type of current playing media."""
if self.media_status is None:
return None
- elif self.media_status.media_is_tvshow:
+ if self.media_status.media_is_tvshow:
return MEDIA_TYPE_TVSHOW
- elif self.media_status.media_is_movie:
+ if self.media_status.media_is_movie:
return MEDIA_TYPE_MOVIE
- elif self.media_status.media_is_musictrack:
+ if self.media_status.media_is_musictrack:
return MEDIA_TYPE_MUSIC
return None
diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/media_player/channels.py
index 41713e0c5bc..6ccc6061703 100644
--- a/homeassistant/components/media_player/channels.py
+++ b/homeassistant/components/media_player/channels.py
@@ -217,7 +217,7 @@ class ChannelsPlayer(MediaPlayerDevice):
"""Image url of current playing media."""
if self.now_playing_image_url:
return self.now_playing_image_url
- elif self.channel_image_url:
+ if self.channel_image_url:
return self.channel_image_url
return 'https://getchannels.com/assets/img/icon-1024.png'
diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/media_player/cmus.py
index 0758b5f3058..978a1088aa6 100644
--- a/homeassistant/components/media_player/cmus.py
+++ b/homeassistant/components/media_player/cmus.py
@@ -91,7 +91,7 @@ class CmusDevice(MediaPlayerDevice):
"""Return the media state."""
if self.status.get('status') == 'playing':
return STATE_PLAYING
- elif self.status.get('status') == 'paused':
+ if self.status.get('status') == 'paused':
return STATE_PAUSED
return STATE_OFF
diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py
index 2b2b9eb5c28..604fb91451e 100644
--- a/homeassistant/components/media_player/denonavr.py
+++ b/homeassistant/components/media_player/denonavr.py
@@ -21,7 +21,7 @@ from homeassistant.const import (
CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['denonavr==0.7.4']
+REQUIREMENTS = ['denonavr==0.7.5']
_LOGGER = logging.getLogger(__name__)
@@ -261,7 +261,7 @@ class DenonDevice(MediaPlayerDevice):
"""Title of current playing media."""
if self._current_source not in self._receiver.playing_func_list:
return self._current_source
- elif self._title is not None:
+ if self._title is not None:
return self._title
return self._frequency
diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py
index 0adb02b6a65..89547892550 100644
--- a/homeassistant/components/media_player/directv.py
+++ b/homeassistant/components/media_player/directv.py
@@ -140,7 +140,7 @@ class DirecTvDevice(MediaPlayerDevice):
"""Return the title of current episode of TV show."""
if self._is_standby:
return None
- elif 'episodeTitle' in self._current:
+ if 'episodeTitle' in self._current:
return self._current['episodeTitle']
return None
diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py
index 4f9a4019268..1dfb19a33be 100644
--- a/homeassistant/components/media_player/emby.py
+++ b/homeassistant/components/media_player/emby.py
@@ -206,11 +206,11 @@ class EmbyDevice(MediaPlayerDevice):
state = self.device.state
if state == 'Paused':
return STATE_PAUSED
- elif state == 'Playing':
+ if state == 'Playing':
return STATE_PLAYING
- elif state == 'Idle':
+ if state == 'Idle':
return STATE_IDLE
- elif state == 'Off':
+ if state == 'Off':
return STATE_OFF
@property
@@ -230,15 +230,15 @@ class EmbyDevice(MediaPlayerDevice):
media_type = self.device.media_type
if media_type == 'Episode':
return MEDIA_TYPE_TVSHOW
- elif media_type == 'Movie':
+ if media_type == 'Movie':
return MEDIA_TYPE_MOVIE
- elif media_type == 'Trailer':
+ if media_type == 'Trailer':
return MEDIA_TYPE_TRAILER
- elif media_type == 'Music':
+ if media_type == 'Music':
return MEDIA_TYPE_MUSIC
- elif media_type == 'Video':
+ if media_type == 'Video':
return MEDIA_TYPE_GENERIC_VIDEO
- elif media_type == 'Audio':
+ if media_type == 'Audio':
return MEDIA_TYPE_MUSIC
return None
diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py
index 280a84f0828..979aec57c74 100644
--- a/homeassistant/components/media_player/firetv.py
+++ b/homeassistant/components/media_player/firetv.py
@@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("Could not connect to firetv-server at %s", host)
-class FireTV(object):
+class FireTV:
"""The firetv-server client.
Should a native Python 3 ADB module become available, python-firetv can
diff --git a/homeassistant/components/media_player/horizon.py b/homeassistant/components/media_player/horizon.py
index 4b0f9d0cf21..9be4143ef2b 100644
--- a/homeassistant/components/media_player/horizon.py
+++ b/homeassistant/components/media_player/horizon.py
@@ -18,7 +18,7 @@ from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF,
STATE_PAUSED, STATE_PLAYING)
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-import homeassistant.util as util
+from homeassistant import util
REQUIREMENTS = ['einder==0.3.1']
diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py
index ca0979f1752..e5f7a2f9432 100644
--- a/homeassistant/components/media_player/itunes.py
+++ b/homeassistant/components/media_player/itunes.py
@@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-class Itunes(object):
+class Itunes:
"""The iTunes API client."""
def __init__(self, host, port, use_ssl):
diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py
index 7fa8d5b3fe8..8758e969db1 100644
--- a/homeassistant/components/media_player/kodi.py
+++ b/homeassistant/components/media_player/kodi.py
@@ -749,7 +749,7 @@ class KodiDevice(MediaPlayerDevice):
if media_type == "CHANNEL":
return self.server.Player.Open(
{"item": {"channelid": int(media_id)}})
- elif media_type == "PLAYLIST":
+ if media_type == "PLAYLIST":
return self.server.Player.Open(
{"item": {"playlistid": int(media_id)}})
@@ -759,7 +759,7 @@ class KodiDevice(MediaPlayerDevice):
@asyncio.coroutine
def async_set_shuffle(self, shuffle):
"""Set shuffle mode, for the first player."""
- if len(self._players) < 1:
+ if not self._players:
raise RuntimeError("Error: No active player.")
yield from self.server.Player.SetShuffle(
{"playerid": self._players[0]['playerid'], "shuffle": shuffle})
diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py
index df1ee662124..955ba7ccb32 100644
--- a/homeassistant/components/media_player/lg_netcast.py
+++ b/homeassistant/components/media_player/lg_netcast.py
@@ -18,7 +18,7 @@ from homeassistant.components.media_player import (
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN,
STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
-import homeassistant.util as util
+from homeassistant import util
REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0']
diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py
index 6b161f86ab0..1b5948c964a 100644
--- a/homeassistant/components/media_player/liveboxplaytv.py
+++ b/homeassistant/components/media_player/liveboxplaytv.py
@@ -202,7 +202,7 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
state = self._client.media_state
if state == 'PLAY':
return STATE_PLAYING
- elif state == 'PAUSE':
+ if state == 'PAUSE':
return STATE_PAUSED
return STATE_ON if self._client.is_on else STATE_OFF
diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/media_player/mpchc.py
index ad8dd0bf056..773825e0d57 100644
--- a/homeassistant/components/media_player/mpchc.py
+++ b/homeassistant/components/media_player/mpchc.py
@@ -93,7 +93,7 @@ class MpcHcDevice(MediaPlayerDevice):
return STATE_OFF
if state == 'playing':
return STATE_PLAYING
- elif state == 'paused':
+ if state == 'paused':
return STATE_PAUSED
return STATE_IDLE
diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py
index 73417e5f25d..4b3dfc2ccbb 100644
--- a/homeassistant/components/media_player/mpd.py
+++ b/homeassistant/components/media_player/mpd.py
@@ -141,11 +141,11 @@ class MpdDevice(MediaPlayerDevice):
"""Return the media state."""
if self._status is None:
return STATE_OFF
- elif self._status['state'] == 'play':
+ if self._status['state'] == 'play':
return STATE_PLAYING
- elif self._status['state'] == 'pause':
+ if self._status['state'] == 'pause':
return STATE_PAUSED
- elif self._status['state'] == 'stop':
+ if self._status['state'] == 'stop':
return STATE_OFF
return STATE_OFF
@@ -182,9 +182,9 @@ class MpdDevice(MediaPlayerDevice):
if file_name is None:
return "None"
return os.path.basename(file_name)
- elif name is None:
+ if name is None:
return title
- elif title is None:
+ if title is None:
return name
return '{}: {}'.format(name, title)
diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py
index 90638cd9dfc..c4d8b778095 100644
--- a/homeassistant/components/media_player/pandora.py
+++ b/homeassistant/components/media_player/pandora.py
@@ -253,9 +253,11 @@ class PandoraMediaPlayer(MediaPlayerDevice):
_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
+ # pylint: disable=assignment-from-none
response = self.update_playing_status()
elif match_idx == 3:
_LOGGER.debug("Received new playlist list")
+ # pylint: disable=assignment-from-none
response = self.update_playing_status()
else:
response = self._pianobar.before.decode('utf-8')
@@ -294,8 +296,7 @@ class PandoraMediaPlayer(MediaPlayerDevice):
time_remaining = int(cur_minutes) * 60 + int(cur_seconds)
self._media_duration = int(total_minutes) * 60 + int(total_seconds)
- if (time_remaining != self._time_remaining and
- time_remaining != self._media_duration):
+ if time_remaining not in (self._time_remaining, self._media_duration):
self._player_state = STATE_PLAYING
elif self._player_state == STATE_PLAYING:
self._player_state = STATE_PAUSED
diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py
index ca6b9722a49..e3c6f453c35 100644
--- a/homeassistant/components/media_player/plex.py
+++ b/homeassistant/components/media_player/plex.py
@@ -573,11 +573,11 @@ class PlexClient(MediaPlayerDevice):
_LOGGER.debug("Clip content type detected, "
"compatibility may vary: %s", self.entity_id)
return MEDIA_TYPE_TVSHOW
- elif self._session_type == 'episode':
+ if self._session_type == 'episode':
return MEDIA_TYPE_TVSHOW
- elif self._session_type == 'movie':
+ if self._session_type == 'movie':
return MEDIA_TYPE_MOVIE
- elif self._session_type == 'track':
+ if self._session_type == 'track':
return MEDIA_TYPE_MUSIC
return None
@@ -654,7 +654,7 @@ class PlexClient(MediaPlayerDevice):
if not self._make:
return None
# no mute support
- elif self.make.lower() == "shield android tv":
+ if self.make.lower() == "shield android tv":
_LOGGER.debug(
"Shield Android TV client detected, disabling mute "
"controls: %s", self.entity_id)
@@ -663,7 +663,7 @@ class PlexClient(MediaPlayerDevice):
SUPPORT_VOLUME_SET | SUPPORT_PLAY |
SUPPORT_TURN_OFF)
# Only supports play,pause,stop (and off which really is stop)
- elif self.make.lower().startswith("tivo"):
+ if self.make.lower().startswith("tivo"):
_LOGGER.debug(
"Tivo client detected, only enabling pause, play, "
"stop, and off controls: %s", self.entity_id)
@@ -671,7 +671,7 @@ class PlexClient(MediaPlayerDevice):
SUPPORT_TURN_OFF)
# Not all devices support playback functionality
# Playback includes volume, stop/play/pause, etc.
- elif self.device and 'playback' in self._device_protocol_capabilities:
+ if self.device and 'playback' in self._device_protocol_capabilities:
return (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK |
SUPPORT_NEXT_TRACK | SUPPORT_STOP |
SUPPORT_VOLUME_SET | SUPPORT_PLAY |
diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py
index a46e781de59..5f28660f4bd 100644
--- a/homeassistant/components/media_player/roku.py
+++ b/homeassistant/components/media_player/roku.py
@@ -134,9 +134,9 @@ class RokuDevice(MediaPlayerDevice):
if (self.current_app.name == "Power Saver" or
self.current_app.is_screensaver):
return STATE_IDLE
- elif self.current_app.name == "Roku":
+ if self.current_app.name == "Roku":
return STATE_HOME
- elif self.current_app.name is not None:
+ if self.current_app.name is not None:
return STATE_PLAYING
return STATE_UNKNOWN
@@ -156,9 +156,9 @@ class RokuDevice(MediaPlayerDevice):
"""Content type of current playing media."""
if self.current_app is None:
return None
- elif self.current_app.name == "Power Saver":
+ if self.current_app.name == "Power Saver":
return None
- elif self.current_app.name == "Roku":
+ if self.current_app.name == "Roku":
return None
return MEDIA_TYPE_MOVIE
@@ -167,11 +167,11 @@ class RokuDevice(MediaPlayerDevice):
"""Image url of current playing media."""
if self.current_app is None:
return None
- elif self.current_app.name == "Roku":
+ if self.current_app.name == "Roku":
return None
- elif self.current_app.name == "Power Saver":
+ if self.current_app.name == "Power Saver":
return None
- elif self.current_app.id is None:
+ if self.current_app.id is None:
return None
return 'http://{0}:{1}/query/icon/{2}'.format(
diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/media_player/russound_rio.py
index 31b04ceb3cd..e9f8ab5f199 100644
--- a/homeassistant/components/media_player/russound_rio.py
+++ b/homeassistant/components/media_player/russound_rio.py
@@ -100,8 +100,7 @@ class RussoundZoneDevice(MediaPlayerDevice):
if value in (None, "", "------"):
return None
return value
- else:
- return None
+ return None
def _zone_callback_handler(self, zone_id, *args):
if zone_id == self._zone_id:
@@ -134,7 +133,7 @@ class RussoundZoneDevice(MediaPlayerDevice):
status = self._zone_var('status', "OFF")
if status == 'ON':
return STATE_ON
- elif status == 'OFF':
+ if status == 'OFF':
return STATE_OFF
@property
diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py
index c3de341d607..55b3fb0ea4f 100644
--- a/homeassistant/components/media_player/samsungtv.py
+++ b/homeassistant/components/media_player/samsungtv.py
@@ -153,7 +153,7 @@ class SamsungTVDevice(MediaPlayerDevice):
def send_key(self, key):
"""Send a key to the tv and handles exceptions."""
if self._power_off_in_progress() \
- and not (key == 'KEY_POWER' or key == 'KEY_POWEROFF'):
+ and key not in ('KEY_POWER', 'KEY_POWEROFF'):
_LOGGER.info("TV is powering off, not sending command: %s", key)
return
try:
diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py
new file mode 100644
index 00000000000..9a94da158a1
--- /dev/null
+++ b/homeassistant/components/media_player/sisyphus.py
@@ -0,0 +1,197 @@
+"""
+Support for track controls on the Sisyphus Kinetic Art Table.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/media_player.sisyphus/
+"""
+import logging
+
+from homeassistant.components.media_player import (
+ SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE,
+ SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SHUFFLE_SET,
+ SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
+from homeassistant.components.sisyphus import DATA_SISYPHUS
+from homeassistant.const import CONF_HOST, CONF_NAME, STATE_PLAYING, \
+ STATE_PAUSED, STATE_IDLE, STATE_OFF
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['sisyphus']
+
+MEDIA_TYPE_TRACK = "sisyphus_track"
+
+SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE \
+ | SUPPORT_VOLUME_SET \
+ | SUPPORT_TURN_OFF \
+ | SUPPORT_TURN_ON \
+ | SUPPORT_PAUSE \
+ | SUPPORT_SHUFFLE_SET \
+ | SUPPORT_PREVIOUS_TRACK \
+ | SUPPORT_NEXT_TRACK \
+ | SUPPORT_PLAY
+
+
+# pylint: disable=unused-argument
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up a media player entity for a Sisyphus table."""
+ name = discovery_info[CONF_NAME]
+ host = discovery_info[CONF_HOST]
+ add_devices(
+ [SisyphusPlayer(name, host, hass.data[DATA_SISYPHUS][name])],
+ update_before_add=True)
+
+
+class SisyphusPlayer(MediaPlayerDevice):
+ """Represents a single Sisyphus table as a media player device."""
+
+ def __init__(self, name, host, table):
+ """
+ Constructor.
+
+ :param name: name of the table
+ :param host: hostname or ip address
+ :param table: sisyphus-control Table object
+ """
+ self._name = name
+ self._host = host
+ self._table = table
+
+ async def async_added_to_hass(self):
+ """Add listeners after this object has been initialized."""
+ self._table.add_listener(
+ lambda: self.async_schedule_update_ha_state(False))
+
+ @property
+ def name(self):
+ """Return the name of the table."""
+ return self._name
+
+ @property
+ def state(self):
+ """Return the current state of the table; sleeping maps to off."""
+ if self._table.state in ["homing", "playing"]:
+ return STATE_PLAYING
+ if self._table.state == "paused":
+ if self._table.is_sleeping:
+ return STATE_OFF
+
+ return STATE_PAUSED
+ if self._table.state == "waiting":
+ return STATE_IDLE
+
+ return None
+
+ @property
+ def volume_level(self):
+ """Return the current playback speed (0..1)."""
+ return self._table.speed
+
+ @property
+ def shuffle(self):
+ """Return True if the current playlist is in shuffle mode."""
+ return self._table.is_shuffle
+
+ async def async_set_shuffle(self, shuffle):
+ """
+ Change the shuffle mode of the current playlist.
+
+ :param shuffle: True to shuffle, False not to
+ """
+ await self._table.set_shuffle(shuffle)
+
+ @property
+ def media_playlist(self):
+ """Return the name of the current playlist."""
+ return self._table.active_playlist.name \
+ if self._table.active_playlist \
+ else None
+
+ @property
+ def media_title(self):
+ """Return the title of the current track."""
+ return self._table.active_track.name \
+ if self._table.active_track \
+ else None
+
+ @property
+ def media_content_type(self):
+ """Return the content type currently playing; i.e. a Sisyphus track."""
+ return MEDIA_TYPE_TRACK
+
+ @property
+ def media_content_id(self):
+ """Return the track ID of the current track."""
+ return self._table.active_track.id \
+ if self._table.active_track \
+ else None
+
+ @property
+ def supported_features(self):
+ """Return the features supported by this table."""
+ return SUPPORTED_FEATURES
+
+ @property
+ def media_image_url(self):
+ """Return the URL for a thumbnail image of the current track."""
+ from sisyphus_control import Track
+ if self._table.active_track:
+ return self._table.active_track.get_thumbnail_url(
+ Track.ThumbnailSize.LARGE)
+
+ return super.media_image_url()
+
+ async def async_turn_on(self):
+ """Wake up a sleeping table."""
+ await self._table.wakeup()
+
+ async def async_turn_off(self):
+ """Put the table to sleep."""
+ await self._table.sleep()
+
+ async def async_volume_down(self):
+ """Slow down playback."""
+ await self._table.set_speed(max(0, self._table.speed - 0.1))
+
+ async def async_volume_up(self):
+ """Speed up playback."""
+ await self._table.set_speed(min(1.0, self._table.speed + 0.1))
+
+ async def async_set_volume_level(self, volume):
+ """Set playback speed (0..1)."""
+ await self._table.set_speed(volume)
+
+ async def async_media_play(self):
+ """Start playing."""
+ await self._table.play()
+
+ async def async_media_pause(self):
+ """Pause."""
+ await self._table.pause()
+
+ async def async_media_next_track(self):
+ """Skip to next track."""
+ cur_track_index = self._get_current_track_index()
+
+ await self._table.active_playlist.play(
+ self._table.active_playlist.tracks[cur_track_index + 1])
+
+ async def async_media_previous_track(self):
+ """Skip to previous track."""
+ cur_track_index = self._get_current_track_index()
+
+ await self._table.active_playlist.play(
+ self._table.active_playlist.tracks[cur_track_index - 1])
+
+ def _get_current_track_index(self):
+ for index, track in enumerate(self._table.active_playlist.tracks):
+ if track.id == self._table.active_track.id:
+ return index
+
+ return -1
diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py
index 8a92d89ce67..5375001f75c 100644
--- a/homeassistant/components/media_player/sonos.py
+++ b/homeassistant/components/media_player/sonos.py
@@ -447,11 +447,15 @@ class SonosDevice(MediaPlayerDevice):
self.update_volume()
- self._favorites = []
+ self._set_favorites()
+
+ def _set_favorites(self):
+ """Set available favorites."""
# SoCo 0.14 raises a generic Exception on invalid xml in favorites.
# Filter those out now so our list is safe to use.
# pylint: disable=broad-except
try:
+ self._favorites = []
for fav in self.soco.music_library.get_sonos_favorites():
try:
if fav.reference.get_uri():
@@ -493,6 +497,9 @@ class SonosDevice(MediaPlayerDevice):
queue = _ProcessSonosEventQueue(self.update_groups)
player.zoneGroupTopology.subscribe(auto_renew=True, event_queue=queue)
+ queue = _ProcessSonosEventQueue(self.update_content)
+ player.contentDirectory.subscribe(auto_renew=True, event_queue=queue)
+
def update(self):
"""Retrieve latest state."""
available = self._check_available()
@@ -735,6 +742,11 @@ class SonosDevice(MediaPlayerDevice):
slave._sonos_group = sonos_group
slave.schedule_update_ha_state()
+ def update_content(self, event=None):
+ """Update information about available content."""
+ self._set_favorites()
+ self.schedule_update_ha_state()
+
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py
index 9c4a0e9fa17..8f14031481a 100644
--- a/homeassistant/components/media_player/soundtouch.py
+++ b/homeassistant/components/media_player/soundtouch.py
@@ -269,7 +269,7 @@ class SoundTouchDevice(MediaPlayerDevice):
"""Title of current playing media."""
if self._status.station_name is not None:
return self._status.station_name
- elif self._status.artist is not None:
+ if self._status.artist is not None:
return self._status.artist + " - " + self._status.track
return None
diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py
index 371ad890364..8eb4c85f6b2 100644
--- a/homeassistant/components/media_player/squeezebox.py
+++ b/homeassistant/components/media_player/squeezebox.py
@@ -143,7 +143,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return True
-class LogitechMediaServer(object):
+class LogitechMediaServer:
"""Representation of a Logitech media server."""
def __init__(self, hass, host, port, username, password):
diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py
index 81e4c3541d3..046aecbb92e 100644
--- a/homeassistant/components/media_player/vizio.py
+++ b/homeassistant/components/media_player/vizio.py
@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON,
STATE_UNKNOWN)
from homeassistant.helpers import config_validation as cv
-import homeassistant.util as util
+from homeassistant import util
REQUIREMENTS = ['pyvizio==0.0.3']
@@ -95,7 +95,7 @@ class VizioDevice(MediaPlayerDevice):
if is_on is None:
self._state = STATE_UNKNOWN
return
- elif is_on is False:
+ if is_on is False:
self._state = STATE_OFF
else:
self._state = STATE_ON
diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py
index 11ab1615617..c4ddd38fc4f 100644
--- a/homeassistant/components/media_player/volumio.py
+++ b/homeassistant/components/media_player/volumio.py
@@ -142,7 +142,7 @@ class Volumio(MediaPlayerDevice):
status = self._state.get('status', None)
if status == 'pause':
return STATE_PAUSED
- elif status == 'play':
+ if status == 'play':
return STATE_PLAYING
return STATE_IDLE
diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py
index 42d0ae85ab3..362095daee6 100644
--- a/homeassistant/components/media_player/webostv.py
+++ b/homeassistant/components/media_player/webostv.py
@@ -24,7 +24,7 @@ from homeassistant.const import (
STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
-import homeassistant.util as util
+from homeassistant import util
REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==3.2']
diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face.py
index 847f4131f43..e0e0e716d2e 100644
--- a/homeassistant/components/microsoft_face.py
+++ b/homeassistant/components/microsoft_face.py
@@ -289,7 +289,7 @@ class MicrosoftFaceGroupEntity(Entity):
return attr
-class MicrosoftFace(object):
+class MicrosoftFace:
"""Microsoft Face api for HomeAssistant."""
def __init__(self, hass, server_loc, api_key, timeout, entities):
diff --git a/homeassistant/components/mochad.py b/homeassistant/components/mochad.py
index 9f53f84e020..7e6738b95f8 100644
--- a/homeassistant/components/mochad.py
+++ b/homeassistant/components/mochad.py
@@ -61,7 +61,7 @@ def setup(hass, config):
return True
-class MochadCtrl(object):
+class MochadCtrl:
"""Mochad controller."""
def __init__(self, host, port):
diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py
index fc6db96e029..f484cb31a6c 100644
--- a/homeassistant/components/modbus.py
+++ b/homeassistant/components/modbus.py
@@ -157,7 +157,7 @@ def setup(hass, config):
return True
-class ModbusHub(object):
+class ModbusHub:
"""Thread safe wrapper class for pymodbus."""
def __init__(self, modbus_client):
diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py
index 55d99a0817e..3928eb945aa 100644
--- a/homeassistant/components/mqtt/__init__.py
+++ b/homeassistant/components/mqtt/__init__.py
@@ -462,7 +462,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
@attr.s(slots=True, frozen=True)
-class Subscription(object):
+class Subscription:
"""Class to hold data about an active subscription."""
topic = attr.ib(type=str)
@@ -472,7 +472,7 @@ class Subscription(object):
@attr.s(slots=True, frozen=True)
-class Message(object):
+class Message:
"""MQTT Message."""
topic = attr.ib(type=str)
@@ -481,7 +481,7 @@ class Message(object):
retain = attr.ib(type=bool, default=False)
-class MQTT(object):
+class MQTT:
"""Home Assistant MQTT client."""
def __init__(self, hass: HomeAssistantType, broker: str, port: int,
diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py
index 3916714b8d1..128c45f1311 100644
--- a/homeassistant/components/mqtt/discovery.py
+++ b/homeassistant/components/mqtt/discovery.py
@@ -8,7 +8,7 @@ import json
import logging
import re
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.const import CONF_PLATFORM
from homeassistant.components.mqtt import CONF_STATE_TOPIC
@@ -21,7 +21,8 @@ TOPIC_MATCHER = re.compile(
SUPPORTED_COMPONENTS = [
'binary_sensor', 'camera', 'cover', 'fan',
- 'light', 'sensor', 'switch', 'lock', 'climate']
+ 'light', 'sensor', 'switch', 'lock', 'climate',
+ 'alarm_control_panel']
ALLOWED_PLATFORMS = {
'binary_sensor': ['mqtt'],
@@ -33,6 +34,7 @@ ALLOWED_PLATFORMS = {
'sensor': ['mqtt'],
'switch': ['mqtt'],
'climate': ['mqtt'],
+ 'alarm_control_panel': ['mqtt'],
}
ALREADY_DISCOVERED = 'mqtt_discovered_components'
diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy.py
index 3531c6b4919..292e56418fc 100644
--- a/homeassistant/components/mychevy.py
+++ b/homeassistant/components/mychevy.py
@@ -41,7 +41,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-class EVSensorConfig(object):
+class EVSensorConfig:
"""The EV sensor configuration."""
def __init__(self, name, attr, unit_of_measurement=None, icon=None):
@@ -52,7 +52,7 @@ class EVSensorConfig(object):
self.icon = icon
-class EVBinarySensorConfig(object):
+class EVBinarySensorConfig:
"""The EV binary sensor configuration."""
def __init__(self, name, attr, device_class=None):
diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py
index 3066819638f..980efcf5805 100644
--- a/homeassistant/components/mysensors/__init__.py
+++ b/homeassistant/components/mysensors/__init__.py
@@ -52,9 +52,8 @@ def is_persistence_file(value):
"""Validate that persistence file path ends in either .pickle or .json."""
if value.endswith(('.json', '.pickle')):
return value
- else:
- raise vol.Invalid(
- '{} does not end in either `.json` or `.pickle`'.format(value))
+ raise vol.Invalid(
+ '{} does not end in either `.json` or `.pickle`'.format(value))
def deprecated(key):
diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py
index b0770f90c1d..3ae99f61d17 100644
--- a/homeassistant/components/mysensors/device.py
+++ b/homeassistant/components/mysensors/device.py
@@ -25,7 +25,7 @@ def get_mysensors_devices(hass, domain):
return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)]
-class MySensorsDevice(object):
+class MySensorsDevice:
"""Representation of a MySensors device."""
def __init__(self, gateway, node_id, child_id, name, value_type):
diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py
index a7719a80d99..8c80604d188 100644
--- a/homeassistant/components/mysensors/gateway.py
+++ b/homeassistant/components/mysensors/gateway.py
@@ -38,10 +38,8 @@ def is_serial_port(value):
ports = ('COM{}'.format(idx + 1) for idx in range(256))
if value in ports:
return value
- else:
- raise vol.Invalid('{} is not a serial port'.format(value))
- else:
- return cv.isdevice(value)
+ raise vol.Invalid('{} is not a serial port'.format(value))
+ return cv.isdevice(value)
def is_socket_address(value):
@@ -80,7 +78,7 @@ async def setup_gateways(hass, config):
async def _get_gateway(hass, config, gateway_conf, persistence_file):
"""Return gateway after setup of the gateway."""
- import mysensors.mysensors as mysensors
+ from mysensors import mysensors
conf = config[DOMAIN]
persistence = conf[CONF_PERSISTENCE]
@@ -180,7 +178,7 @@ async def _discover_persistent_devices(hass, gateway):
@callback
def _discover_mysensors_platform(hass, platform, new_devices):
"""Discover a MySensors platform."""
- task = hass.async_add_job(discovery.async_load_platform(
+ task = hass.async_create_task(discovery.async_load_platform(
hass, platform, DOMAIN,
{ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}))
return task
diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py
index fc407de0a6b..25da38e7f75 100644
--- a/homeassistant/components/neato.py
+++ b/homeassistant/components/neato.py
@@ -17,7 +17,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pybotvac==0.0.7']
+REQUIREMENTS = ['pybotvac==0.0.9']
DOMAIN = 'neato'
NEATO_ROBOTS = 'neato_robots'
@@ -118,7 +118,7 @@ def setup(hass, config):
return True
-class NeatoHub(object):
+class NeatoHub:
"""A My Neato hub wrapper class."""
def __init__(self, hass, domain_config, neato):
diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index 58fa1953ef0..4406062c821 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -127,7 +127,7 @@ async def async_setup_entry(hass, entry):
return False
for component in 'climate', 'camera', 'sensor', 'binary_sensor':
- hass.async_add_job(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, component))
def set_mode(service):
@@ -183,7 +183,7 @@ async def async_setup_entry(hass, entry):
return True
-class NestDevice(object):
+class NestDevice:
"""Structure Nest functions for hass."""
def __init__(self, hass, conf, nest):
diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py
index b5c095f34b8..f97e0dc8ff5 100644
--- a/homeassistant/components/nest/config_flow.py
+++ b/homeassistant/components/nest/config_flow.py
@@ -65,14 +65,14 @@ class NestFlowHandler(data_entry_flow.FlowHandler):
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason='already_setup')
- elif not flows:
+ if not flows:
return self.async_abort(reason='no_flows')
- elif len(flows) == 1:
+ if len(flows) == 1:
self.flow_impl = list(flows)[0]
return await self.async_step_link()
- elif user_input is not None:
+ if user_input is not None:
self.flow_impl = user_input['flow_impl']
return await self.async_step_link()
diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py
index a635d1820db..c25b57fbd62 100644
--- a/homeassistant/components/netatmo.py
+++ b/homeassistant/components/netatmo.py
@@ -16,7 +16,7 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyatmo==1.0.0']
+REQUIREMENTS = ['pyatmo==1.1.1']
_LOGGER = logging.getLogger(__name__)
@@ -64,7 +64,7 @@ def setup(hass, config):
return True
-class CameraData(object):
+class CameraData:
"""Get the latest data from Netatmo."""
def __init__(self, auth, home=None):
diff --git a/homeassistant/components/netgear_lte.py b/homeassistant/components/netgear_lte.py
index 23a01d37c2b..7f54e6fd6f9 100644
--- a/homeassistant/components/netgear_lte.py
+++ b/homeassistant/components/netgear_lte.py
@@ -17,7 +17,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.util import Throttle
-REQUIREMENTS = ['eternalegypt==0.0.2']
+REQUIREMENTS = ['eternalegypt==0.0.3']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
@@ -37,6 +37,7 @@ class ModemData:
"""Class for modem state."""
modem = attr.ib()
+ serial_number = attr.ib(init=False)
unread_count = attr.ib(init=False)
usage = attr.ib(init=False)
@@ -44,6 +45,7 @@ class ModemData:
async def async_update(self):
"""Call the API to update the data."""
information = await self.modem.information()
+ self.serial_number = information.serial_number
self.unread_count = sum(1 for x in information.sms if x.unread)
self.usage = information.usage
@@ -59,7 +61,7 @@ class LTEData:
"""Get the requested or the only modem_data value."""
if CONF_HOST in config:
return self.modem_data.get(config[CONF_HOST])
- elif len(self.modem_data) == 1:
+ if len(self.modem_data) == 1:
return next(iter(self.modem_data.values()))
return None
diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py
index 41198d1f296..13cd6203ed4 100644
--- a/homeassistant/components/notify/__init__.py
+++ b/homeassistant/components/notify/__init__.py
@@ -174,7 +174,7 @@ def async_setup(hass, config):
return True
-class BaseNotificationService(object):
+class BaseNotificationService:
"""An abstract class for notification services."""
hass = None
diff --git a/homeassistant/components/notify/apns.py b/homeassistant/components/notify/apns.py
index 9cca81e1485..8fabfc3aefb 100644
--- a/homeassistant/components/notify/apns.py
+++ b/homeassistant/components/notify/apns.py
@@ -56,7 +56,7 @@ def get_service(hass, config, discovery_info=None):
return service
-class ApnsDevice(object):
+class ApnsDevice:
"""
The APNS Device class.
diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/notify/group.py
index a98bb6c2317..94856c730b1 100644
--- a/homeassistant/components/notify/group.py
+++ b/homeassistant/components/notify/group.py
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.group/
"""
import asyncio
-import collections
+from collections.abc import Mapping
from copy import deepcopy
import logging
import voluptuous as vol
@@ -33,7 +33,7 @@ def update(input_dict, update_source):
Async friendly.
"""
for key, val in update_source.items():
- if isinstance(val, collections.Mapping):
+ if isinstance(val, Mapping):
recurse = update(input_dict.get(key, {}), val)
input_dict[key] = recurse
else:
diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py
index 7529608387d..e280aa67e40 100644
--- a/homeassistant/components/notify/html5.py
+++ b/homeassistant/components/notify/html5.py
@@ -280,7 +280,7 @@ class HTML5PushCallbackView(HomeAssistantView):
return self.json_message('Authorization header must '
'start with Bearer',
status_code=HTTP_UNAUTHORIZED)
- elif len(parts) != 2:
+ if len(parts) != 2:
return self.json_message('Authorization header must '
'be Bearer token',
status_code=HTTP_UNAUTHORIZED)
diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/notify/rest.py
index 40b09dc3c72..dd35f986f78 100644
--- a/homeassistant/components/notify/rest.py
+++ b/homeassistant/components/notify/rest.py
@@ -95,7 +95,7 @@ class RestNotificationService(BaseNotificationService):
"""Recursive template creator helper function."""
if isinstance(value, list):
return [_data_template_creator(item) for item in value]
- elif isinstance(value, dict):
+ if isinstance(value, dict):
return {key: _data_template_creator(item)
for key, item in value.items()}
value.hass = self._hass
diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py
index 899ccf9b09a..b012506acd9 100644
--- a/homeassistant/components/notify/telegram.py
+++ b/homeassistant/components/notify/telegram.py
@@ -73,7 +73,7 @@ class TelegramNotificationService(BaseNotificationService):
self.hass.services.call(
DOMAIN, 'send_photo', service_data=service_data)
return
- elif data is not None and ATTR_VIDEO in data:
+ if data is not None and ATTR_VIDEO in data:
videos = data.get(ATTR_VIDEO, None)
videos = videos if isinstance(videos, list) else [videos]
for video_data in videos:
@@ -81,11 +81,11 @@ class TelegramNotificationService(BaseNotificationService):
self.hass.services.call(
DOMAIN, 'send_video', service_data=service_data)
return
- elif data is not None and ATTR_LOCATION in data:
+ if data is not None and ATTR_LOCATION in data:
service_data.update(data.get(ATTR_LOCATION))
return self.hass.services.call(
DOMAIN, 'send_location', service_data=service_data)
- elif data is not None and ATTR_DOCUMENT in data:
+ if data is not None and ATTR_DOCUMENT in data:
service_data.update(data.get(ATTR_DOCUMENT))
return self.hass.services.call(
DOMAIN, 'send_document', service_data=service_data)
diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/notify/twitter.py
index e38e7fcaa0f..6076cd5393a 100644
--- a/homeassistant/components/notify/twitter.py
+++ b/homeassistant/components/notify/twitter.py
@@ -194,9 +194,9 @@ class TwitterNotificationService(BaseNotificationService):
if media_type.startswith('image/gif'):
return 'tweet_gif'
- elif media_type.startswith('video/'):
+ if media_type.startswith('video/'):
return 'tweet_video'
- elif media_type.startswith('image/'):
+ if media_type.startswith('image/'):
return 'tweet_image'
return None
diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py
index 12ddf49fca8..c5678dff351 100644
--- a/homeassistant/components/notify/xmpp.py
+++ b/homeassistant/components/notify/xmpp.py
@@ -110,6 +110,5 @@ def send_message(sender, password, recipient, use_tls,
def discard_ssl_invalid_cert(event):
"""Do nothing if ssl certificate is invalid."""
_LOGGER.info('Ignoring invalid ssl certificate as requested.')
- return
SendNotificationBot()
diff --git a/homeassistant/components/nuimo_controller.py b/homeassistant/components/nuimo_controller.py
index e7ab86a5f35..0f8fbb39073 100644
--- a/homeassistant/components/nuimo_controller.py
+++ b/homeassistant/components/nuimo_controller.py
@@ -52,7 +52,7 @@ def setup(hass, config):
return True
-class NuimoLogger(object):
+class NuimoLogger:
"""Handle Nuimo Controller event callbacks."""
def __init__(self, hass, name):
@@ -167,7 +167,7 @@ HOMEASSIST_LOGO = (
".........")
-class DiscoveryLogger(object):
+class DiscoveryLogger:
"""Handle Nuimo Discovery callbacks."""
# pylint: disable=no-self-use
diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py
index c1059227f7a..ff52ad94d8b 100644
--- a/homeassistant/components/octoprint.py
+++ b/homeassistant/components/octoprint.py
@@ -50,7 +50,7 @@ def setup(hass, config):
return True
-class OctoPrintAPI(object):
+class OctoPrintAPI:
"""Simple JSON wrapper for OctoPrint's API."""
def __init__(self, api_url, key, bed, number_of_tools):
@@ -154,7 +154,7 @@ def get_value_from_json(json_dict, sensor_type, group, tool):
return 0
return json_dict[group][sensor_type]
- elif tool is not None:
+ if tool is not None:
if sensor_type in json_dict[group][tool]:
return json_dict[group][tool][sensor_type]
diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight.py
index 344c750c0ec..d307a428e0e 100644
--- a/homeassistant/components/pilight.py
+++ b/homeassistant/components/pilight.py
@@ -118,7 +118,7 @@ def setup(hass, config):
return True
-class CallRateDelayThrottle(object):
+class CallRateDelayThrottle:
"""Helper class to provide service call rate throttling.
This class provides a decorator to decorate service methods that need
diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant.py
index 048851e97f5..84dc8402742 100644
--- a/homeassistant/components/plant.py
+++ b/homeassistant/components/plant.py
@@ -324,7 +324,7 @@ class Plant(Entity):
return attrib
-class DailyHistory(object):
+class DailyHistory:
"""Stores one measurement per day for a maximum number of days.
At the moment only the maximum value per day is kept.
diff --git a/homeassistant/components/prometheus.py b/homeassistant/components/prometheus.py
index 0a6c959f243..da986f024a4 100644
--- a/homeassistant/components/prometheus.py
+++ b/homeassistant/components/prometheus.py
@@ -55,7 +55,7 @@ def setup(hass, config):
return True
-class PrometheusMetrics(object):
+class PrometheusMetrics:
"""Model all of the metrics which should be exposed to Prometheus."""
def __init__(self, prometheus_client, entity_filter, namespace):
diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py
index 3a804c50c74..0e67e15d5c0 100644
--- a/homeassistant/components/rachio.py
+++ b/homeassistant/components/rachio.py
@@ -9,7 +9,7 @@ import logging
from aiohttp import web
import voluptuous as vol
-
+from typing import Optional
from homeassistant.auth.util import generate_secret
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
@@ -122,15 +122,14 @@ def setup(hass, config) -> bool:
_LOGGER.error("No Rachio devices found in account %s",
person.username)
return False
- else:
- _LOGGER.info("%d Rachio device(s) found", len(person.controllers))
+ _LOGGER.info("%d Rachio device(s) found", len(person.controllers))
# Enable component
hass.data[DOMAIN] = person
return True
-class RachioPerson(object):
+class RachioPerson:
"""Represent a Rachio user."""
def __init__(self, hass, rachio):
@@ -162,7 +161,7 @@ class RachioPerson(object):
return self._controllers
-class RachioIro(object):
+class RachioIro:
"""Represent a Rachio Iro."""
def __init__(self, hass, rachio, data):
@@ -242,7 +241,7 @@ class RachioIro(object):
# Only enabled zones
return [z for z in self._zones if z[KEY_ENABLED]]
- def get_zone(self, zone_id) -> dict or None:
+ def get_zone(self, zone_id) -> Optional[dict]:
"""Return the zone with the given ID."""
for zone in self.list_zones(include_disabled=True):
if zone[KEY_ID] == zone_id:
diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py
index a04f4926b76..53cd8e79d7e 100644
--- a/homeassistant/components/raincloud.py
+++ b/homeassistant/components/raincloud.py
@@ -124,7 +124,7 @@ def setup(hass, config):
return True
-class RainCloudHub(object):
+class RainCloudHub:
"""Representation of a base RainCloud device."""
def __init__(self, data):
diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py
index 22fc427ccce..9f15c8b373f 100644
--- a/homeassistant/components/rainmachine/__init__.py
+++ b/homeassistant/components/rainmachine/__init__.py
@@ -151,7 +151,7 @@ async def async_setup(hass, config):
('sensor', conf[CONF_SENSORS]),
('switch', conf[CONF_SWITCHES]),
]:
- hass.async_add_job(
+ hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, schema,
config))
@@ -201,7 +201,7 @@ async def async_setup(hass, config):
return True
-class RainMachine(object):
+class RainMachine:
"""Define a generic RainMachine object."""
def __init__(self, client):
diff --git a/homeassistant/components/raspihats.py b/homeassistant/components/raspihats.py
index 41480c09a32..f43263bf4bf 100644
--- a/homeassistant/components/raspihats.py
+++ b/homeassistant/components/raspihats.py
@@ -63,7 +63,7 @@ class I2CHatsException(Exception):
"""I2C-HATs exception."""
-class I2CHatsDIScanner(object):
+class I2CHatsDIScanner:
"""Scan Digital Inputs and fire callbacks."""
_DIGITAL_INPUTS = "di"
diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py
index 43c2aa5c7b1..f3d8e269a42 100644
--- a/homeassistant/components/recorder/__init__.py
+++ b/homeassistant/components/recorder/__init__.py
@@ -35,7 +35,7 @@ from . import migration, purge
from .const import DATA_INSTANCE
from .util import session_scope
-REQUIREMENTS = ['sqlalchemy==1.2.9']
+REQUIREMENTS = ['sqlalchemy==1.2.10']
_LOGGER = logging.getLogger(__name__)
@@ -284,7 +284,7 @@ class Recorder(threading.Thread):
self._close_connection()
self.queue.task_done()
return
- elif isinstance(event, PurgeTask):
+ if isinstance(event, PurgeTask):
purge.purge_old_data(self, event.keep_days, event.repack)
self.queue.task_done()
continue
diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py
index 32d6291b90c..e7948446231 100644
--- a/homeassistant/components/recorder/models.py
+++ b/homeassistant/components/recorder/models.py
@@ -168,7 +168,7 @@ def _process_timestamp(ts):
"""Process a timestamp into datetime object."""
if ts is None:
return None
- elif ts.tzinfo is None:
+ if ts.tzinfo is None:
return dt_util.UTC.localize(ts)
return dt_util.as_utc(ts)
diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py
index 98cd937de3c..a94e8e95c6f 100644
--- a/homeassistant/components/remember_the_milk/__init__.py
+++ b/homeassistant/components/remember_the_milk/__init__.py
@@ -138,7 +138,7 @@ def _register_new_account(hass, account_name, api_key, shared_secret,
)
-class RememberTheMilkConfiguration(object):
+class RememberTheMilkConfiguration:
"""Internal configuration data for RememberTheMilk class.
This class stores the authentication token it get from the backend.
diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py
index 842dce087e8..a63b7325035 100644
--- a/homeassistant/components/remote/harmony.py
+++ b/homeassistant/components/remote/harmony.py
@@ -10,7 +10,7 @@ import time
import voluptuous as vol
-import homeassistant.components.remote as remote
+from homeassistant.components import remote
from homeassistant.components.remote import (
ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_NUM_REPEATS,
DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA)
diff --git a/homeassistant/components/remote/itach.py b/homeassistant/components/remote/itach.py
index 78d277ca65f..829a038953c 100644
--- a/homeassistant/components/remote/itach.py
+++ b/homeassistant/components/remote/itach.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
-import homeassistant.components.remote as remote
+from homeassistant.components import remote
from homeassistant.const import (
DEVICE_DEFAULT_NAME, CONF_NAME, CONF_MAC, CONF_HOST, CONF_PORT,
CONF_DEVICES)
diff --git a/homeassistant/components/remote/kira.py b/homeassistant/components/remote/kira.py
index 42d4ce77054..dc37eb760f7 100644
--- a/homeassistant/components/remote/kira.py
+++ b/homeassistant/components/remote/kira.py
@@ -7,7 +7,7 @@ https://home-assistant.io/components/remote.kira/
import functools as ft
import logging
-import homeassistant.components.remote as remote
+from homeassistant.components import remote
from homeassistant.const import CONF_DEVICE, CONF_NAME
from homeassistant.helpers.entity import Entity
diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py
index 59a2dc861a6..eda09e3af64 100644
--- a/homeassistant/components/remote/xiaomi_miio.py
+++ b/homeassistant/components/remote/xiaomi_miio.py
@@ -232,13 +232,13 @@ class XiaomiMiioRemote(RemoteDevice):
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the device on."""
- _LOGGER.error("Device does not support turn_on, " +
+ _LOGGER.error("Device does not support turn_on, "
"please use 'remote.send_command' to send commands.")
@asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Turn the device off."""
- _LOGGER.error("Device does not support turn_off, " +
+ _LOGGER.error("Device does not support turn_off, "
"please use 'remote.send_command' to send commands.")
def _send_command(self, payload):
diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py
index 272a5b868ec..b8af971b3ff 100644
--- a/homeassistant/components/rflink.py
+++ b/homeassistant/components/rflink.py
@@ -100,7 +100,7 @@ def identify_event_type(event):
"""
if EVENT_KEY_COMMAND in event:
return EVENT_KEY_COMMAND
- elif EVENT_KEY_SENSOR in event:
+ if EVENT_KEY_SENSOR in event:
return EVENT_KEY_SENSOR
return 'unknown'
diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py
index 5cb7bb337ce..824ec46d636 100644
--- a/homeassistant/components/rpi_gpio.py
+++ b/homeassistant/components/rpi_gpio.py
@@ -19,7 +19,7 @@ DOMAIN = 'rpi_gpio'
def setup(hass, config):
"""Set up the Raspberry PI GPIO component."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
def cleanup_gpio(event):
"""Stuff to do before stopping."""
@@ -36,32 +36,32 @@ def setup(hass, config):
def setup_output(port):
"""Set up a GPIO as output."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
GPIO.setup(port, GPIO.OUT)
def setup_input(port, pull_mode):
"""Set up a GPIO as input."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
GPIO.setup(port, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP)
def write_output(port, value):
"""Write a value to a GPIO."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
GPIO.output(port, value)
def read_input(port):
"""Read a value from a GPIO."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
return GPIO.input(port)
def edge_detect(port, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
- import RPi.GPIO as GPIO
+ from RPi import GPIO
GPIO.add_event_detect(
port,
GPIO.BOTH,
diff --git a/homeassistant/components/sabnzbd.py b/homeassistant/components/sabnzbd.py
index a7b33b4c697..b9c75c87c1d 100644
--- a/homeassistant/components/sabnzbd.py
+++ b/homeassistant/components/sabnzbd.py
@@ -134,7 +134,7 @@ def async_setup_sabnzbd(hass, sab_api, config, name):
if config.get(CONF_SENSORS):
hass.data[DATA_SABNZBD] = sab_api_data
- hass.async_add_job(
+ hass.async_create_task(
discovery.async_load_platform(hass, 'sensor', DOMAIN, {}, config))
async def async_service_handler(service):
diff --git a/homeassistant/components/satel_integra.py b/homeassistant/components/satel_integra.py
index 4247855da39..128377d19f7 100644
--- a/homeassistant/components/satel_integra.py
+++ b/homeassistant/components/satel_integra.py
@@ -98,10 +98,10 @@ def async_setup(hass, config):
conf,
conf.get(CONF_ARM_HOME_MODE))
- task_control_panel = hass.async_add_job(
+ task_control_panel = hass.async_create_task(
async_load_platform(hass, 'alarm_control_panel', DOMAIN, conf, config))
- task_zones = hass.async_add_job(
+ task_zones = hass.async_create_task(
async_load_platform(hass, 'binary_sensor', DOMAIN,
{CONF_ZONES: zones}, config))
diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/scene/lifx_cloud.py
index 6fe91d0acd2..a9ec1ef679c 100644
--- a/homeassistant/components/scene/lifx_cloud.py
+++ b/homeassistant/components/scene/lifx_cloud.py
@@ -58,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
devices.append(LifxCloudScene(hass, headers, timeout, scene))
async_add_devices(devices)
return True
- elif status == 401:
+ if status == 401:
_LOGGER.error("Unauthorized (bad token?) on %s", url)
return False
diff --git a/homeassistant/components/scene/litejet.py b/homeassistant/components/scene/litejet.py
index 37fb58d8dc7..87539e2dded 100644
--- a/homeassistant/components/scene/litejet.py
+++ b/homeassistant/components/scene/litejet.py
@@ -6,7 +6,7 @@ https://home-assistant.io/components/scene.litejet/
"""
import logging
-import homeassistant.components.litejet as litejet
+from homeassistant.components import litejet
from homeassistant.components.scene import Scene
DEPENDENCIES = ['litejet']
diff --git a/homeassistant/components/scene/tuya.py b/homeassistant/components/scene/tuya.py
new file mode 100644
index 00000000000..3990a7da206
--- /dev/null
+++ b/homeassistant/components/scene/tuya.py
@@ -0,0 +1,40 @@
+"""
+Support for the Tuya scene.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/scene.tuya/
+"""
+from homeassistant.components.scene import Scene, DOMAIN
+from homeassistant.components.tuya import DATA_TUYA, TuyaDevice
+
+DEPENDENCIES = ['tuya']
+
+ENTITY_ID_FORMAT = DOMAIN + '.{}'
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up Tuya scenes."""
+ if discovery_info is None:
+ return
+ tuya = hass.data[DATA_TUYA]
+ dev_ids = discovery_info.get('dev_ids')
+ devices = []
+ for dev_id in dev_ids:
+ device = tuya.get_device_by_id(dev_id)
+ if device is None:
+ continue
+ devices.append(TuyaScene(device))
+ add_devices(devices)
+
+
+class TuyaScene(TuyaDevice, Scene):
+ """Tuya Scene."""
+
+ def __init__(self, tuya):
+ """Init Tuya scene."""
+ super().__init__(tuya)
+ self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
+
+ def activate(self):
+ """Activate the scene."""
+ self.tuya.activate()
diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py
index a7193b40949..dcea69cbb48 100644
--- a/homeassistant/components/scsgate.py
+++ b/homeassistant/components/scsgate.py
@@ -60,7 +60,7 @@ def setup(hass, config):
return True
-class SCSGate(object):
+class SCSGate:
"""The class for dealing with the SCSGate device via scsgate.Reactor."""
def __init__(self, device, logger):
diff --git a/homeassistant/components/sensor/abode.py b/homeassistant/components/sensor/abode.py
index b51ab288c1a..26247c77454 100644
--- a/homeassistant/components/sensor/abode.py
+++ b/homeassistant/components/sensor/abode.py
@@ -67,9 +67,9 @@ class AbodeSensor(AbodeDevice):
"""Return the state of the sensor."""
if self._sensor_type == 'temp':
return self._device.temp
- elif self._sensor_type == 'humidity':
+ if self._sensor_type == 'humidity':
return self._device.humidity
- elif self._sensor_type == 'lux':
+ if self._sensor_type == 'lux':
return self._device.lux
@property
@@ -77,7 +77,7 @@ class AbodeSensor(AbodeDevice):
"""Return the units of measurement."""
if self._sensor_type == 'temp':
return self._device.temp_unit
- elif self._sensor_type == 'humidity':
+ if self._sensor_type == 'humidity':
return self._device.humidity_unit
- elif self._sensor_type == 'lux':
+ if self._sensor_type == 'lux':
return self._device.lux_unit
diff --git a/homeassistant/components/sensor/airvisual.py b/homeassistant/components/sensor/airvisual.py
index 0002274833f..403722c7b6a 100644
--- a/homeassistant/components/sensor/airvisual.py
+++ b/homeassistant/components/sensor/airvisual.py
@@ -248,7 +248,7 @@ class AirVisualSensor(Entity):
})
-class AirVisualData(object):
+class AirVisualData:
"""Define an object to hold sensor data."""
def __init__(self, client, **kwargs):
diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py
index a8ef179280b..0d193dee79b 100644
--- a/homeassistant/components/sensor/api_streams.py
+++ b/homeassistant/components/sensor/api_streams.py
@@ -38,9 +38,9 @@ class StreamHandler(logging.Handler):
else:
if not record.msg.startswith('WS'):
return
- elif len(record.args) < 2:
+ if len(record.args) < 2:
return
- elif record.args[1] == 'Connected':
+ if record.args[1] == 'Connected':
self.entity.count += 1
elif record.args[1] == 'Closed connection':
self.entity.count -= 1
diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py
index f49d8e76f6c..d4d8ea09d29 100644
--- a/homeassistant/components/sensor/arduino.py
+++ b/homeassistant/components/sensor/arduino.py
@@ -11,7 +11,7 @@ import logging
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
-import homeassistant.components.arduino as arduino
+from homeassistant.components import arduino
from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py
index 19860ba84fd..751f0f11171 100644
--- a/homeassistant/components/sensor/arest.py
+++ b/homeassistant/components/sensor/arest.py
@@ -158,7 +158,7 @@ class ArestSensor(Entity):
return self.arest.available
-class ArestData(object):
+class ArestData:
"""The Class for handling the data retrieval for variables."""
def __init__(self, resource, pin=None):
diff --git a/homeassistant/components/sensor/arlo.py b/homeassistant/components/sensor/arlo.py
index 609887e9690..6d764b1c916 100644
--- a/homeassistant/components/sensor/arlo.py
+++ b/homeassistant/components/sensor/arlo.py
@@ -56,9 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
SENSOR_TYPES[sensor_type][0], arlo, sensor_type))
else:
for camera in arlo.cameras:
- if sensor_type == 'temperature' or \
- sensor_type == 'humidity' or \
- sensor_type == 'air_quality':
+ if sensor_type in ('temperature', 'humidity', 'air_quality'):
continue
name = '{0} {1}'.format(
@@ -66,10 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors.append(ArloSensor(name, camera, sensor_type))
for base_station in arlo.base_stations:
- if ((sensor_type == 'temperature' or
- sensor_type == 'humidity' or
- sensor_type == 'air_quality') and
- base_station.model_id == 'ABC1000'):
+ if sensor_type in ('temperature', 'humidity', 'air_quality') \
+ and base_station.model_id == 'ABC1000':
name = '{0} {1}'.format(
SENSOR_TYPES[sensor_type][0], base_station.name)
sensors.append(ArloSensor(name, base_station, sensor_type))
@@ -127,7 +123,7 @@ class ArloSensor(Entity):
"""Return the device class of the sensor."""
if self._sensor_type == 'temperature':
return DEVICE_CLASS_TEMPERATURE
- elif self._sensor_type == 'humidity':
+ if self._sensor_type == 'humidity':
return DEVICE_CLASS_HUMIDITY
return None
diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/sensor/arwn.py
index 7308cd4f791..6b0d3e569d7 100644
--- a/homeassistant/components/sensor/arwn.py
+++ b/homeassistant/components/sensor/arwn.py
@@ -8,7 +8,7 @@ import asyncio
import json
import logging
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.core import callback
from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
diff --git a/homeassistant/components/sensor/bbox.py b/homeassistant/components/sensor/bbox.py
index 3689e94b05d..d24621becc9 100644
--- a/homeassistant/components/sensor/bbox.py
+++ b/homeassistant/components/sensor/bbox.py
@@ -125,7 +125,7 @@ class BboxSensor(Entity):
2)
-class BboxData(object):
+class BboxData:
"""Get data from the Bbox."""
def __init__(self):
diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py
index bd23b9850f7..f51b7dcd5bd 100644
--- a/homeassistant/components/sensor/bitcoin.py
+++ b/homeassistant/components/sensor/bitcoin.py
@@ -169,7 +169,7 @@ class BitcoinSensor(Entity):
self._state = '{0:.2f}'.format(stats.market_price_usd)
-class BitcoinData(object):
+class BitcoinData:
"""Get the latest data and update the states."""
def __init__(self):
diff --git a/homeassistant/components/sensor/bme280.py b/homeassistant/components/sensor/bme280.py
index 8f3949046ca..1685d34c0ec 100644
--- a/homeassistant/components/sensor/bme280.py
+++ b/homeassistant/components/sensor/bme280.py
@@ -117,7 +117,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
except KeyError:
pass
- async_add_devices(dev)
+ async_add_devices(dev, True)
class BME280Handler:
diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/sensor/bom.py
index 5cec528d26a..eb63e116254 100644
--- a/homeassistant/components/sensor/bom.py
+++ b/homeassistant/components/sensor/bom.py
@@ -180,7 +180,7 @@ class BOMCurrentSensor(Entity):
self.bom_data.update()
-class BOMCurrentData(object):
+class BOMCurrentData:
"""Get data from BOM."""
def __init__(self, hass, station_id):
diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/sensor/broadlink.py
index 8806fae5974..06d7f512c9f 100644
--- a/homeassistant/components/sensor/broadlink.py
+++ b/homeassistant/components/sensor/broadlink.py
@@ -96,7 +96,7 @@ class BroadlinkSensor(Entity):
self._state = self._broadlink_data.data[self._type]
-class BroadlinkData(object):
+class BroadlinkData:
"""Representation of a Broadlink data object."""
def __init__(self, interval, ip_addr, mac_addr, timeout):
diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py
index 10a96ded437..992c27bbe2e 100644
--- a/homeassistant/components/sensor/buienradar.py
+++ b/homeassistant/components/sensor/buienradar.py
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.buienradar/
"""
import asyncio
-from datetime import timedelta
+from datetime import datetime, timedelta
import logging
import async_timeout
@@ -262,13 +262,13 @@ class BrSensor(Entity):
self._entity_picture = img
return True
return False
- else:
- try:
- self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
- return True
- except IndexError:
- _LOGGER.warning("No forecast for fcday=%s...", fcday)
- return False
+
+ try:
+ self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
+ return True
+ except IndexError:
+ _LOGGER.warning("No forecast for fcday=%s...", fcday)
+ return False
if self.type == SYMBOL or self.type.startswith(CONDITION):
# update weather symbol & status text
@@ -374,7 +374,7 @@ class BrSensor(Entity):
return self._force_update
-class BrData(object):
+class BrData:
"""Get the latest data and updates the states."""
def __init__(self, hass, coordinates, timeframe, devices):
@@ -481,9 +481,10 @@ class BrData(object):
_LOGGER.debug("Buienradar parsed data: %s", result)
if result.get(SUCCESS) is not True:
- _LOGGER.warning("Unable to parse data from Buienradar."
- "(Msg: %s)",
- result.get(MESSAGE),)
+ if int(datetime.now().strftime('%H')) > 0:
+ _LOGGER.warning("Unable to parse data from Buienradar."
+ "(Msg: %s)",
+ result.get(MESSAGE),)
yield from self.schedule_update(SCHEDULE_NOK)
return
diff --git a/homeassistant/components/sensor/citybikes.py b/homeassistant/components/sensor/citybikes.py
index 24f8ea7e6a9..c9a69923135 100644
--- a/homeassistant/components/sensor/citybikes.py
+++ b/homeassistant/components/sensor/citybikes.py
@@ -186,19 +186,14 @@ class CityBikesNetwork:
networks = yield from async_citybikes_request(
hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST]
- networks_list = cls.NETWORKS_LIST
- network = networks_list[0]
- result = network[ATTR_ID]
- minimum_dist = location.distance(
- latitude, longitude,
- network[ATTR_LOCATION][ATTR_LATITUDE],
- network[ATTR_LOCATION][ATTR_LONGITUDE])
- for network in networks_list[1:]:
+ result = None
+ minimum_dist = None
+ for network in cls.NETWORKS_LIST:
network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
dist = location.distance(
latitude, longitude, network_latitude, network_longitude)
- if dist < minimum_dist:
+ if minimum_dist is None or dist < minimum_dist:
minimum_dist = dist
result = network[ATTR_ID]
diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/sensor/coinmarketcap.py
index f4b666f1e5c..c4f38b1be02 100644
--- a/homeassistant/components/sensor/coinmarketcap.py
+++ b/homeassistant/components/sensor/coinmarketcap.py
@@ -140,7 +140,7 @@ class CoinMarketCapSensor(Entity):
self._ticker = self.data.ticker.get('data')
-class CoinMarketCapData(object):
+class CoinMarketCapData:
"""Get the latest data and update the states."""
def __init__(self, currency_id, display_currency):
diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py
index 4a26a1dc9fc..846604a9ff5 100644
--- a/homeassistant/components/sensor/command_line.py
+++ b/homeassistant/components/sensor/command_line.py
@@ -4,31 +4,39 @@ Allows to configure custom shell commands to turn a value for a sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.command_line/
"""
-import logging
-import subprocess
-import shlex
-
+import collections
from datetime import timedelta
+import json
+import logging
+import shlex
+import subprocess
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
-from homeassistant.helpers import template
-from homeassistant.exceptions import TemplateError
from homeassistant.const import (
- CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND,
+ CONF_COMMAND, CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
STATE_UNKNOWN)
+from homeassistant.exceptions import TemplateError
+from homeassistant.helpers import template
+import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
+CONF_COMMAND_TIMEOUT = 'command_timeout'
+CONF_JSON_ATTRIBUTES = 'json_attributes'
+
DEFAULT_NAME = 'Command Sensor'
+DEFAULT_TIMEOUT = 15
SCAN_INTERVAL = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
+ vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT):
+ cv.positive_int,
+ vol.Optional(CONF_JSON_ATTRIBUTES): cv.ensure_list_csv,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@@ -41,20 +49,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
command = config.get(CONF_COMMAND)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE)
+ command_timeout = config.get(CONF_COMMAND_TIMEOUT)
if value_template is not None:
value_template.hass = hass
- data = CommandSensorData(hass, command)
+ json_attributes = config.get(CONF_JSON_ATTRIBUTES)
+ data = CommandSensorData(hass, command, command_timeout)
- add_devices([CommandSensor(hass, data, name, unit, value_template)], True)
+ add_devices([CommandSensor(
+ hass, data, name, unit, value_template, json_attributes)], True)
class CommandSensor(Entity):
"""Representation of a sensor that is using shell commands."""
- def __init__(self, hass, data, name, unit_of_measurement, value_template):
+ def __init__(self, hass, data, name, unit_of_measurement, value_template,
+ json_attributes):
"""Initialize the sensor."""
self._hass = hass
self.data = data
+ self._attributes = None
+ self._json_attributes = json_attributes
self._name = name
self._state = None
self._unit_of_measurement = unit_of_measurement
@@ -75,11 +89,33 @@ class CommandSensor(Entity):
"""Return the state of the device."""
return self._state
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ return self._attributes
+
def update(self):
"""Get the latest data and updates the state."""
self.data.update()
value = self.data.value
+ if self._json_attributes:
+ self._attributes = {}
+ if value:
+ try:
+ json_dict = json.loads(value)
+ if isinstance(json_dict, collections.Mapping):
+ self._attributes = {k: json_dict[k] for k in
+ self._json_attributes
+ if k in json_dict}
+ else:
+ _LOGGER.warning("JSON result was not a dictionary")
+ except ValueError:
+ _LOGGER.warning(
+ "Unable to parse output as JSON: %s", value)
+ else:
+ _LOGGER.warning("Empty reply found when expecting JSON data")
+
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
@@ -89,14 +125,15 @@ class CommandSensor(Entity):
self._state = value
-class CommandSensorData(object):
+class CommandSensorData:
"""The class for handling the data retrieval."""
- def __init__(self, hass, command):
+ def __init__(self, hass, command, command_timeout):
"""Initialize the data object."""
self.value = None
self.hass = hass
self.command = command
+ self.timeout = command_timeout
def update(self):
"""Get the latest data with a shell command."""
@@ -135,7 +172,7 @@ class CommandSensorData(object):
try:
_LOGGER.info("Running command: %s", command)
return_value = subprocess.check_output(
- command, shell=shell, timeout=15)
+ command, shell=shell, timeout=self.timeout)
self.value = return_value.strip().decode('utf-8')
except subprocess.CalledProcessError:
_LOGGER.error("Command failed: %s", command)
diff --git a/homeassistant/components/sensor/cups.py b/homeassistant/components/sensor/cups.py
index 6d55853d724..846b109afca 100644
--- a/homeassistant/components/sensor/cups.py
+++ b/homeassistant/components/sensor/cups.py
@@ -129,7 +129,7 @@ class CupsSensor(Entity):
# pylint: disable=no-name-in-module
-class CupsData(object):
+class CupsData:
"""Get the latest data from CUPS and update the state."""
def __init__(self, host, port):
diff --git a/homeassistant/components/sensor/currencylayer.py b/homeassistant/components/sensor/currencylayer.py
index f5d6f278da0..4a7face0156 100644
--- a/homeassistant/components/sensor/currencylayer.py
+++ b/homeassistant/components/sensor/currencylayer.py
@@ -54,8 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors.append(CurrencylayerSensor(rest, base, variable))
if 'error' in response.json():
return False
- else:
- add_devices(sensors, True)
+ add_devices(sensors, True)
class CurrencylayerSensor(Entity):
@@ -104,7 +103,7 @@ class CurrencylayerSensor(Entity):
value['{}{}'.format(self._base, self._quote)], 4)
-class CurrencylayerData(object):
+class CurrencylayerData:
"""Get data from Currencylayer.org."""
def __init__(self, resource, parameters):
diff --git a/homeassistant/components/sensor/daikin.py b/homeassistant/components/sensor/daikin.py
index e045043e09c..2da5cb5cdf0 100644
--- a/homeassistant/components/sensor/daikin.py
+++ b/homeassistant/components/sensor/daikin.py
@@ -85,7 +85,7 @@ class DaikinClimateSensor(Entity):
if value is None:
_LOGGER.warning("Invalid value requested for key %s", key)
else:
- if value == "-" or value == "--":
+ if value in ("-", "--"):
value = None
elif cast_to_float:
try:
diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py
index e75f36d59f7..b2bb7bb4da2 100644
--- a/homeassistant/components/sensor/darksky.py
+++ b/homeassistant/components/sensor/darksky.py
@@ -352,12 +352,12 @@ class DarkSkySensor(Entity):
# percentages
if self.type in ['precip_probability', 'cloud_cover', 'humidity']:
return round(state * 100, 1)
- elif (self.type in ['dew_point', 'temperature', 'apparent_temperature',
- 'temperature_min', 'temperature_max',
- 'apparent_temperature_min',
- 'apparent_temperature_max',
- 'precip_accumulation',
- 'pressure', 'ozone', 'uvIndex']):
+ if self.type in ['dew_point', 'temperature', 'apparent_temperature',
+ 'temperature_min', 'temperature_max',
+ 'apparent_temperature_min',
+ 'apparent_temperature_max',
+ 'precip_accumulation',
+ 'pressure', 'ozone', 'uvIndex']:
return round(state, 1)
return state
@@ -372,7 +372,7 @@ def convert_to_camel(data):
return components[0] + "".join(x.title() for x in components[1:])
-class DarkSkyData(object):
+class DarkSkyData:
"""Get the latest data from Darksky."""
def __init__(self, api_key, latitude, longitude, units, language,
diff --git a/homeassistant/components/sensor/deutsche_bahn.py b/homeassistant/components/sensor/deutsche_bahn.py
index ec9b14883a9..0e6ab164d4f 100644
--- a/homeassistant/components/sensor/deutsche_bahn.py
+++ b/homeassistant/components/sensor/deutsche_bahn.py
@@ -85,7 +85,7 @@ class DeutscheBahnSensor(Entity):
self._state += " + {}".format(self.data.connections[0]['delay'])
-class SchieneData(object):
+class SchieneData:
"""Pull data from the bahn.de web page."""
def __init__(self, start, goal, only_direct):
diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py
index b22e4df9a50..6770594b919 100644
--- a/homeassistant/components/sensor/dht.py
+++ b/homeassistant/components/sensor/dht.py
@@ -128,7 +128,7 @@ class DHTSensor(Entity):
temperature = data[SENSOR_TEMPERATURE]
_LOGGER.debug("Temperature %.1f \u00b0C + offset %.1f",
temperature, temperature_offset)
- if (temperature >= -20) and (temperature < 80):
+ if -20 <= temperature < 80:
self._state = round(temperature + temperature_offset, 1)
if self.temp_unit == TEMP_FAHRENHEIT:
self._state = round(celsius_to_fahrenheit(temperature), 1)
@@ -136,11 +136,11 @@ class DHTSensor(Entity):
humidity = data[SENSOR_HUMIDITY]
_LOGGER.debug("Humidity %.1f%% + offset %.1f",
humidity, humidity_offset)
- if (humidity >= 0) and (humidity <= 100):
+ if 0 <= humidity <= 100:
self._state = round(humidity + humidity_offset, 1)
-class DHTClient(object):
+class DHTClient:
"""Get the latest data from the DHT sensor."""
def __init__(self, adafruit_dht, sensor, pin):
diff --git a/homeassistant/components/sensor/dovado.py b/homeassistant/components/sensor/dovado.py
index ee2292d4122..2a78d4ad864 100644
--- a/homeassistant/components/sensor/dovado.py
+++ b/homeassistant/components/sensor/dovado.py
@@ -129,17 +129,16 @@ class DovadoSensor(Entity):
if self._sensor == SENSOR_NETWORK:
match = re.search(r"\((.+)\)", state)
return match.group(1) if match else None
- elif self._sensor == SENSOR_SIGNAL:
+ if self._sensor == SENSOR_SIGNAL:
try:
return int(state.split()[0])
except ValueError:
return 0
- elif self._sensor == SENSOR_SMS_UNREAD:
+ if self._sensor == SENSOR_SMS_UNREAD:
return int(state)
- elif self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
+ if self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
return round(float(state) / 1e6, 1)
- else:
- return state
+ return state
def update(self):
"""Update sensor values."""
diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py
index d7982f1c9db..3a1bf1da39e 100644
--- a/homeassistant/components/sensor/dsmr.py
+++ b/homeassistant/components/sensor/dsmr.py
@@ -255,7 +255,7 @@ class DSMREntity(Entity):
return ICON_POWER_FAILURE
if 'Power' in self._name:
return ICON_POWER
- elif 'Gas' in self._name:
+ if 'Gas' in self._name:
return ICON_GAS
@property
@@ -285,7 +285,7 @@ class DSMREntity(Entity):
# used for normal rate.
if value == '0002':
return 'normal'
- elif value == '0001':
+ if value == '0001':
return 'low'
return STATE_UNKNOWN
diff --git a/homeassistant/components/sensor/dublin_bus_transport.py b/homeassistant/components/sensor/dublin_bus_transport.py
index f6d791f9fd6..a443c78b2b1 100644
--- a/homeassistant/components/sensor/dublin_bus_transport.py
+++ b/homeassistant/components/sensor/dublin_bus_transport.py
@@ -125,7 +125,7 @@ class DublinPublicTransportSensor(Entity):
pass
-class PublicTransportData(object):
+class PublicTransportData:
"""The Class for handling the data retrieval."""
def __init__(self, stop, route):
diff --git a/homeassistant/components/sensor/dwd_weather_warnings.py b/homeassistant/components/sensor/dwd_weather_warnings.py
index e023dfcc49f..4f9664617a3 100644
--- a/homeassistant/components/sensor/dwd_weather_warnings.py
+++ b/homeassistant/components/sensor/dwd_weather_warnings.py
@@ -163,7 +163,7 @@ class DwdWeatherWarningsSensor(Entity):
self._api.update()
-class DwdWeatherWarningsAPI(object):
+class DwdWeatherWarningsAPI:
"""Get the latest data and update the states."""
def __init__(self, region_name):
diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py
index cca06bd9782..065c88d8332 100644
--- a/homeassistant/components/sensor/dweet.py
+++ b/homeassistant/components/sensor/dweet.py
@@ -99,7 +99,7 @@ class DweetSensor(Entity):
values, STATE_UNKNOWN)
-class DweetData(object):
+class DweetData:
"""The class for handling the data retrieval."""
def __init__(self, device):
diff --git a/homeassistant/components/sensor/ebox.py b/homeassistant/components/sensor/ebox.py
index d7b867081a3..218968ecee8 100644
--- a/homeassistant/components/sensor/ebox.py
+++ b/homeassistant/components/sensor/ebox.py
@@ -129,7 +129,7 @@ class EBoxSensor(Entity):
self._state = round(self.ebox_data.data[self.type], 2)
-class EBoxData(object):
+class EBoxData:
"""Get data from Ebox."""
def __init__(self, username, password, httpsession):
diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py
index 978b8db669a..4c209d17d07 100644
--- a/homeassistant/components/sensor/eddystone_temperature.py
+++ b/homeassistant/components/sensor/eddystone_temperature.py
@@ -120,7 +120,7 @@ class EddystoneTemp(Entity):
return False
-class Monitor(object):
+class Monitor:
"""Continuously scan for BLE advertisements."""
def __init__(self, hass, devices, bt_device_id):
diff --git a/homeassistant/components/sensor/eight_sleep.py b/homeassistant/components/sensor/eight_sleep.py
index fd7c1aee3ae..5899ef267cb 100644
--- a/homeassistant/components/sensor/eight_sleep.py
+++ b/homeassistant/components/sensor/eight_sleep.py
@@ -149,7 +149,7 @@ class EightUserSensor(EightSleepUserEntity):
"""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 'bed_temp' in self._sensor:
if self._units == 'si':
return '°C'
return '°F'
diff --git a/homeassistant/components/sensor/emoncms.py b/homeassistant/components/sensor/emoncms.py
index cd02137f4d5..a62eaba7df8 100644
--- a/homeassistant/components/sensor/emoncms.py
+++ b/homeassistant/components/sensor/emoncms.py
@@ -190,7 +190,7 @@ class EmonCmsSensor(Entity):
self._state = round(float(elem["value"]), DECIMALS)
-class EmonCmsData(object):
+class EmonCmsData:
"""The class for handling the data retrieval."""
def __init__(self, hass, url, apikey, interval):
diff --git a/homeassistant/components/sensor/envirophat.py b/homeassistant/components/sensor/envirophat.py
index 265350f3e95..bf4ee55c446 100644
--- a/homeassistant/components/sensor/envirophat.py
+++ b/homeassistant/components/sensor/envirophat.py
@@ -138,7 +138,7 @@ class EnvirophatSensor(Entity):
self._state = self.data.voltage_3
-class EnvirophatData(object):
+class EnvirophatData:
"""Get the latest data and update."""
def __init__(self, envirophat, use_leds):
diff --git a/homeassistant/components/sensor/fail2ban.py b/homeassistant/components/sensor/fail2ban.py
index 87c301d34f5..bf868d49201 100644
--- a/homeassistant/components/sensor/fail2ban.py
+++ b/homeassistant/components/sensor/fail2ban.py
@@ -112,7 +112,7 @@ class BanSensor(Entity):
self.last_ban = 'None'
-class BanLogParser(object):
+class BanLogParser:
"""Class to parse fail2ban logs."""
def __init__(self, interval, log_file):
diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py
index 9143ccaf23f..65474cd4bf6 100644
--- a/homeassistant/components/sensor/fastdotcom.py
+++ b/homeassistant/components/sensor/fastdotcom.py
@@ -102,7 +102,7 @@ class SpeedtestSensor(Entity):
return ICON
-class SpeedtestData(object):
+class SpeedtestData:
"""Get the latest data from fast.com."""
def __init__(self, hass, config):
diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/sensor/fido.py
index a2ee18b3659..4f724b5b851 100644
--- a/homeassistant/components/sensor/fido.py
+++ b/homeassistant/components/sensor/fido.py
@@ -147,7 +147,7 @@ class FidoSensor(Entity):
self._state = round(self._state, 2)
-class FidoData(object):
+class FidoData:
"""Get data from Fido."""
def __init__(self, username, password, httpsession):
diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py
index 261f6e2b510..15059b08a17 100644
--- a/homeassistant/components/sensor/filter.py
+++ b/homeassistant/components/sensor/filter.py
@@ -23,7 +23,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util.decorator import Registry
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_state_change
-import homeassistant.components.history as history
+from homeassistant.components import history
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -258,7 +258,7 @@ class SensorFilter(Entity):
return state_attr
-class FilterState(object):
+class FilterState:
"""State abstraction for filter usage."""
def __init__(self, state):
@@ -283,7 +283,7 @@ class FilterState(object):
return "{} : {}".format(self.timestamp, self.state)
-class Filter(object):
+class Filter:
"""Filter skeleton.
Args:
diff --git a/homeassistant/components/sensor/fints.py b/homeassistant/components/sensor/fints.py
index 13129919139..ef064e84228 100644
--- a/homeassistant/components/sensor/fints.py
+++ b/homeassistant/components/sensor/fints.py
@@ -101,7 +101,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(accounts, True)
-class FinTsClient(object):
+class FinTsClient:
"""Wrapper around the FinTS3PinTanClient.
Use this class as Context Manager to get the FinTS3Client object.
diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/sensor/fixer.py
index 438366ae555..5a6f8da79b2 100644
--- a/homeassistant/components/sensor/fixer.py
+++ b/homeassistant/components/sensor/fixer.py
@@ -102,7 +102,7 @@ class ExchangeRateSensor(Entity):
self._state = round(self.data.rate['rates'][self._target], 3)
-class ExchangeData(object):
+class ExchangeData:
"""Get the latest data and update the states."""
def __init__(self, target_currency, api_key):
diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/sensor/fritzbox_callmonitor.py
index b443bd56f03..3da9c512ebd 100644
--- a/homeassistant/components/sensor/fritzbox_callmonitor.py
+++ b/homeassistant/components/sensor/fritzbox_callmonitor.py
@@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
phonebook = FritzBoxPhonebook(
host=host, port=port, username=username, password=password,
phonebook_id=phonebook_id, prefixes=prefixes)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
phonebook = None
_LOGGER.warning("Phonebook with ID %s not found on Fritz!Box",
phonebook_id)
@@ -143,7 +143,7 @@ class FritzBoxCallSensor(Entity):
self.phonebook.update_phonebook()
-class FritzBoxCallMonitor(object):
+class FritzBoxCallMonitor:
"""Event listener to monitor calls on the Fritz!Box."""
def __init__(self, host, port, sensor):
@@ -187,7 +187,6 @@ class FritzBoxCallMonitor(object):
line = response.split("\n", 1)[0]
self._parse(line)
time.sleep(1)
- return
def _parse(self, line):
"""Parse the call information and set the sensor states."""
@@ -225,7 +224,7 @@ class FritzBoxCallMonitor(object):
self._sensor.schedule_update_ha_state()
-class FritzBoxPhonebook(object):
+class FritzBoxPhonebook:
"""This connects to a FritzBox router and downloads its phone book."""
def __init__(self, host, port, username, password,
diff --git a/homeassistant/components/sensor/fritzbox_netmonitor.py b/homeassistant/components/sensor/fritzbox_netmonitor.py
index 857e6cc4a07..b980323abe1 100644
--- a/homeassistant/components/sensor/fritzbox_netmonitor.py
+++ b/homeassistant/components/sensor/fritzbox_netmonitor.py
@@ -65,8 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if fstatus is None:
_LOGGER.error("Failed to establish connection to FRITZ!Box: %s", host)
return 1
- else:
- _LOGGER.info("Successfully connected to FRITZ!Box")
+ _LOGGER.info("Successfully connected to FRITZ!Box")
add_devices([FritzboxMonitorSensor(name, fstatus)], True)
diff --git a/homeassistant/components/sensor/geizhals.py b/homeassistant/components/sensor/geizhals.py
index 94f3f1884d1..06062b26b00 100644
--- a/homeassistant/components/sensor/geizhals.py
+++ b/homeassistant/components/sensor/geizhals.py
@@ -15,7 +15,7 @@ from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.const import (CONF_DOMAIN, CONF_NAME)
-REQUIREMENTS = ['beautifulsoup4==4.6.0']
+REQUIREMENTS = ['beautifulsoup4==4.6.1']
_LOGGER = logging.getLogger(__name__)
CONF_PRODUCT_ID = 'product_id'
@@ -98,7 +98,7 @@ class Geizwatch(Entity):
self._state = self.data.prices[0]
-class GeizParser(object):
+class GeizParser:
"""Pull data from the geizhals website."""
def __init__(self, product_id, domain, regex):
diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/sensor/geo_rss_events.py
index c8c4db17c8d..b79e6e69adf 100644
--- a/homeassistant/components/sensor/geo_rss_events.py
+++ b/homeassistant/components/sensor/geo_rss_events.py
@@ -149,7 +149,7 @@ class GeoRssServiceSensor(Entity):
self._state_attributes = matrix
-class GeoRssServiceData(object):
+class GeoRssServiceData:
"""Provide access to GeoRSS feed and stores the latest data."""
def __init__(self, home_latitude, home_longitude, url, radius_in_km):
diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py
index bd6e91c7b53..a6dfd89e45a 100644
--- a/homeassistant/components/sensor/glances.py
+++ b/homeassistant/components/sensor/glances.py
@@ -176,7 +176,7 @@ class GlancesSensor(Entity):
self._state = round(use / 1024**2, 1)
-class GlancesData(object):
+class GlancesData:
"""The class for handling the data retrieval."""
def __init__(self, resource):
diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py
index e7d25872701..d14a70ecc84 100644
--- a/homeassistant/components/sensor/google_travel_time.py
+++ b/homeassistant/components/sensor/google_travel_time.py
@@ -17,7 +17,7 @@ from homeassistant.const import (
ATTR_LONGITUDE, CONF_MODE)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-import homeassistant.helpers.location as location
+from homeassistant.helpers import location
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['googlemaps==2.5.1']
diff --git a/homeassistant/components/sensor/google_wifi.py b/homeassistant/components/sensor/google_wifi.py
index c070a3e990f..cc5461ed548 100644
--- a/homeassistant/components/sensor/google_wifi.py
+++ b/homeassistant/components/sensor/google_wifi.py
@@ -10,7 +10,7 @@ from datetime import timedelta
import voluptuous as vol
import requests
-import homeassistant.util.dt as dt
+from homeassistant.util import dt
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
@@ -138,7 +138,7 @@ class GoogleWifiSensor(Entity):
self._state = STATE_UNKNOWN
-class GoogleWifiAPI(object):
+class GoogleWifiAPI:
"""Get the latest data and update the states."""
def __init__(self, host, conditions):
diff --git a/homeassistant/components/sensor/gpsd.py b/homeassistant/components/sensor/gpsd.py
index 1d270419933..f463d0fb8d1 100644
--- a/homeassistant/components/sensor/gpsd.py
+++ b/homeassistant/components/sensor/gpsd.py
@@ -91,7 +91,7 @@ class GpsdSensor(Entity):
"""Return the state of GPSD."""
if self.agps_thread.data_stream.mode == 3:
return "3D Fix"
- elif self.agps_thread.data_stream.mode == 2:
+ if self.agps_thread.data_stream.mode == 2:
return "2D Fix"
return STATE_UNKNOWN
diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py
index 93e15b9cd5e..120fe8fdb22 100644
--- a/homeassistant/components/sensor/gtfs.py
+++ b/homeassistant/components/sensor/gtfs.py
@@ -176,7 +176,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
gtfs = pygtfs.Schedule(joined_path)
# pylint: disable=no-member
- if len(gtfs.feeds) < 1:
+ if not gtfs.feeds:
pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data))
add_devices([GTFSDepartureSensor(gtfs, name, origin, destination, offset)])
diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/sensor/haveibeenpwned.py
index c1fe7ab4880..bc79c4d0c1d 100644
--- a/homeassistant/components/sensor/haveibeenpwned.py
+++ b/homeassistant/components/sensor/haveibeenpwned.py
@@ -122,7 +122,7 @@ class HaveIBeenPwnedSensor(Entity):
self._state = len(self._data.data[self._email])
-class HaveIBeenPwnedData(object):
+class HaveIBeenPwnedData:
"""Class for handling the data retrieval."""
def __init__(self, emails):
diff --git a/homeassistant/components/sensor/hddtemp.py b/homeassistant/components/sensor/hddtemp.py
index 006542a777f..f8afe9c7637 100644
--- a/homeassistant/components/sensor/hddtemp.py
+++ b/homeassistant/components/sensor/hddtemp.py
@@ -108,7 +108,7 @@ class HddTempSensor(Entity):
self._state = None
-class HddTempData(object):
+class HddTempData:
"""Get the latest data from HDDTemp and update the states."""
def __init__(self, host, port):
diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/sensor/history_stats.py
index 7af858b9d94..c3d0fe8f1b6 100644
--- a/homeassistant/components/sensor/history_stats.py
+++ b/homeassistant/components/sensor/history_stats.py
@@ -10,7 +10,7 @@ import math
import voluptuous as vol
-import homeassistant.components.history as history
+from homeassistant.components import history
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.sensor import PLATFORM_SCHEMA
@@ -294,7 +294,7 @@ class HistoryStatsHelper:
minutes, seconds = divmod(seconds, 60)
if days > 0:
return '%dd %dh %dm' % (days, hours, minutes)
- elif hours > 0:
+ if hours > 0:
return '%dh %dm' % (hours, minutes)
return '%dm' % minutes
diff --git a/homeassistant/components/sensor/hive.py b/homeassistant/components/sensor/hive.py
index 8c9409ef5ff..2d609070415 100644
--- a/homeassistant/components/sensor/hive.py
+++ b/homeassistant/components/sensor/hive.py
@@ -55,7 +55,7 @@ class HiveSensorEntity(Entity):
"""Return the state of the sensor."""
if self.device_type == "Hub_OnlineStatus":
return self.session.sensor.hub_online_status(self.node_id)
- elif self.device_type == "Hive_OutsideTemperature":
+ if self.device_type == "Hive_OutsideTemperature":
return self.session.weather.temperature()
@property
diff --git a/homeassistant/components/sensor/homematicip_cloud.py b/homeassistant/components/sensor/homematicip_cloud.py
index 87021e9c7c5..7292e3b2f40 100644
--- a/homeassistant/components/sensor/homematicip_cloud.py
+++ b/homeassistant/components/sensor/homematicip_cloud.py
@@ -80,11 +80,6 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice):
"""Return the unit this state is expressed in."""
return '%'
- @property
- def device_state_attributes(self):
- """Return the state attributes of the access point."""
- return {}
-
class HomematicipHeatingThermostat(HomematicipGenericDevice):
"""MomematicIP heating thermostat representation."""
@@ -98,6 +93,8 @@ class HomematicipHeatingThermostat(HomematicipGenericDevice):
"""Return the icon."""
from homematicip.base.enums import ValveState
+ if super().icon:
+ return super().icon
if self._device.valveState != ValveState.ADAPTION_DONE:
return 'mdi:alert'
return 'mdi:radiator'
diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py
index acd10fe08af..98ee83f8958 100644
--- a/homeassistant/components/sensor/hp_ilo.py
+++ b/homeassistant/components/sensor/hp_ilo.py
@@ -147,7 +147,7 @@ class HpIloSensor(Entity):
self._state = ilo_data
-class HpIloData(object):
+class HpIloData:
"""Gets the latest data from HP ILO."""
def __init__(self, host, port, login, password):
diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py
index 2195153ab1e..db75d51fbad 100644
--- a/homeassistant/components/sensor/hydroquebec.py
+++ b/homeassistant/components/sensor/hydroquebec.py
@@ -162,7 +162,7 @@ class HydroQuebecSensor(Entity):
self._state = round(self.hydroquebec_data.data[self.type], 2)
-class HydroquebecData(object):
+class HydroquebecData:
"""Get data from HydroQuebec."""
def __init__(self, username, password, httpsession, contract=None):
diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/sensor/ihc.py
index b30a242c17c..2dcf2c3f7be 100644
--- a/homeassistant/components/sensor/ihc.py
+++ b/homeassistant/components/sensor/ihc.py
@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ihc/
"""
-from xml.etree.ElementTree import Element
-
import voluptuous as vol
from homeassistant.components.ihc import (
@@ -62,7 +60,7 @@ class IHCSensor(IHCDevice, Entity):
"""Implementation of the IHC sensor."""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
- unit, product: Element = None) -> None:
+ unit, product=None) -> None:
"""Initialize the IHC sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None
diff --git a/homeassistant/components/sensor/imap_email_content.py b/homeassistant/components/sensor/imap_email_content.py
index c0c9bf62efd..a1a604df3e4 100644
--- a/homeassistant/components/sensor/imap_email_content.py
+++ b/homeassistant/components/sensor/imap_email_content.py
@@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
-class EmailReader(object):
+class EmailReader:
"""A class to read emails from an IMAP server."""
def __init__(self, user, password, server, port):
diff --git a/homeassistant/components/sensor/influxdb.py b/homeassistant/components/sensor/influxdb.py
index c0d492984e0..8bfbaf49837 100644
--- a/homeassistant/components/sensor/influxdb.py
+++ b/homeassistant/components/sensor/influxdb.py
@@ -155,7 +155,7 @@ class InfluxSensor(Entity):
self._state = value
-class InfluxSensorData(object):
+class InfluxSensorData:
"""Class for handling the data retrieval."""
def __init__(self, influx, group, field, measurement, where):
diff --git a/homeassistant/components/sensor/ios.py b/homeassistant/components/sensor/ios.py
index 398c0b350ee..1fd556b17c0 100644
--- a/homeassistant/components/sensor/ios.py
+++ b/homeassistant/components/sensor/ios.py
@@ -86,8 +86,8 @@ class IOSSensor(Entity):
battery_level = device_battery[ios.ATTR_BATTERY_LEVEL]
charging = True
icon_state = DEFAULT_ICON_STATE
- if (battery_state == ios.ATTR_BATTERY_STATE_FULL or
- battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED):
+ if battery_state in (ios.ATTR_BATTERY_STATE_FULL,
+ ios.ATTR_BATTERY_STATE_UNPLUGGED):
charging = False
icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN:
diff --git a/homeassistant/components/sensor/irish_rail_transport.py b/homeassistant/components/sensor/irish_rail_transport.py
index 603d82359de..5febebeec87 100644
--- a/homeassistant/components/sensor/irish_rail_transport.py
+++ b/homeassistant/components/sensor/irish_rail_transport.py
@@ -132,7 +132,7 @@ class IrishRailTransportSensor(Entity):
self._state = None
-class IrishRailTransportData(object):
+class IrishRailTransportData:
"""The Class for handling the data retrieval."""
def __init__(self, irish_rail, station, direction, destination, stops_at):
diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py
index 1048c04d43d..19dcfc87014 100644
--- a/homeassistant/components/sensor/isy994.py
+++ b/homeassistant/components/sensor/isy994.py
@@ -259,14 +259,11 @@ class ISYSensorDevice(ISYDevice):
if len(self._node.uom) == 1:
if self._node.uom[0] in UOM_FRIENDLY_NAME:
friendly_name = UOM_FRIENDLY_NAME.get(self._node.uom[0])
- if friendly_name == TEMP_CELSIUS or \
- friendly_name == TEMP_FAHRENHEIT:
+ if friendly_name in (TEMP_CELSIUS, TEMP_FAHRENHEIT):
friendly_name = self.hass.config.units.temperature_unit
return friendly_name
- else:
- return self._node.uom[0]
- else:
- return None
+ return self._node.uom[0]
+ return None
@property
def state(self) -> str:
diff --git a/homeassistant/components/sensor/london_air.py b/homeassistant/components/sensor/london_air.py
index 2ffbb914275..bbb5993b064 100644
--- a/homeassistant/components/sensor/london_air.py
+++ b/homeassistant/components/sensor/london_air.py
@@ -71,7 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(sensors, True)
-class APIData(object):
+class APIData:
"""Get the latest data for all authorities."""
def __init__(self):
diff --git a/homeassistant/components/sensor/london_underground.py b/homeassistant/components/sensor/london_underground.py
index fe13c0db8a7..4619eda0611 100644
--- a/homeassistant/components/sensor/london_underground.py
+++ b/homeassistant/components/sensor/london_underground.py
@@ -95,7 +95,7 @@ class LondonTubeSensor(Entity):
self._description = self._data.data[self.name]['Description']
-class TubeData(object):
+class TubeData:
"""Get the latest tube data from TFL."""
def __init__(self):
diff --git a/homeassistant/components/sensor/luftdaten.py b/homeassistant/components/sensor/luftdaten.py
index 9952e2a1c24..c9bc7205ce6 100644
--- a/homeassistant/components/sensor/luftdaten.py
+++ b/homeassistant/components/sensor/luftdaten.py
@@ -137,7 +137,7 @@ class LuftdatenSensor(Entity):
await self.luftdaten.async_update()
-class LuftdatenData(object):
+class LuftdatenData:
"""Class for handling the data retrieval."""
def __init__(self, data):
diff --git a/homeassistant/components/sensor/lyft.py b/homeassistant/components/sensor/lyft.py
index c2f6412049c..57e5f1c6b02 100644
--- a/homeassistant/components/sensor/lyft.py
+++ b/homeassistant/components/sensor/lyft.py
@@ -183,7 +183,7 @@ class LyftSensor(Entity):
estimate.get('estimated_cost_cents_max', 0)) / 2) / 100)
-class LyftEstimate(object):
+class LyftEstimate:
"""The class for handling the time and price estimate."""
def __init__(self, session, start_latitude, start_longitude,
diff --git a/homeassistant/components/sensor/magicseaweed.py b/homeassistant/components/sensor/magicseaweed.py
new file mode 100644
index 00000000000..02c61024e30
--- /dev/null
+++ b/homeassistant/components/sensor/magicseaweed.py
@@ -0,0 +1,201 @@
+"""
+Support for magicseaweed data from magicseaweed.com.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.magicseaweed/
+"""
+from datetime import timedelta
+import logging
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION)
+import homeassistant.helpers.config_validation as cv
+import homeassistant.util.dt as dt_util
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import Throttle
+
+REQUIREMENTS = ['magicseaweed==1.0.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_HOURS = 'hours'
+CONF_SPOT_ID = 'spot_id'
+CONF_UNITS = 'units'
+CONF_UPDATE_INTERVAL = 'update_interval'
+
+DEFAULT_UNIT = 'us'
+DEFAULT_NAME = 'MSW'
+DEFAULT_ATTRIBUTION = "Data provided by magicseaweed.com"
+
+ICON = 'mdi:waves'
+
+HOURS = ['12AM', '3AM', '6AM', '9AM', '12PM', '3PM', '6PM', '9PM']
+
+SENSOR_TYPES = {
+ 'max_breaking_swell': ['Max'],
+ 'min_breaking_swell': ['Min'],
+ 'swell_forecast': ['Forecast'],
+}
+
+UNITS = ['eu', 'uk', 'us']
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_MONITORED_CONDITIONS):
+ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Required(CONF_SPOT_ID): vol.All(cv.ensure_list, [cv.string]),
+ vol.Optional(CONF_HOURS, default=None):
+ vol.All(cv.ensure_list, [vol.In(HOURS)]),
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_UNITS): vol.In(UNITS),
+})
+
+# 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):
+ """Set up the Magicseaweed sensor."""
+ name = config.get(CONF_NAME)
+ spot_id = config[CONF_SPOT_ID]
+ api_key = config[CONF_API_KEY]
+ hours = config.get(CONF_HOURS)
+
+ if CONF_UNITS in config:
+ units = config.get(CONF_UNITS)
+ elif hass.config.units.is_metric:
+ units = UNITS[0]
+ else:
+ units = UNITS[2]
+
+ forecast_data = MagicSeaweedData(
+ api_key=api_key,
+ spot_id=spot_id,
+ units=units)
+ forecast_data.update()
+
+ # If connection failed don't setup platform.
+ if forecast_data.currently is None or forecast_data.hourly is None:
+ return
+
+ sensors = []
+ for variable in config[CONF_MONITORED_CONDITIONS]:
+ sensors.append(MagicSeaweedSensor(forecast_data, variable, name,
+ units))
+ if 'forecast' not in variable and hours is not None:
+ for hour in hours:
+ sensors.append(MagicSeaweedSensor(
+ forecast_data, variable, name, units, hour))
+ add_devices(sensors, True)
+
+
+class MagicSeaweedSensor(Entity):
+ """Implementation of a MagicSeaweed sensor."""
+
+ def __init__(self, forecast_data, sensor_type, name, unit_system,
+ hour=None):
+ """Initialize the sensor."""
+ self.client_name = name
+ self.data = forecast_data
+ self.hour = hour
+ self.type = sensor_type
+ self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
+ self._name = SENSOR_TYPES[sensor_type][0]
+ self._icon = None
+ self._state = None
+ self._unit_system = unit_system
+ self._unit_of_measurement = None
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ if self.hour is None and 'forecast' in self.type:
+ return "{} {}".format(self.client_name, self._name)
+ if self.hour is None:
+ return "Current {} {}".format(self.client_name, self._name)
+ return "{} {} {}".format(
+ self.hour, self.client_name, self._name)
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return self._state
+
+ @property
+ def unit_system(self):
+ """Return the unit system of this entity."""
+ return self._unit_system
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement of this entity, if any."""
+ return self._unit_of_measurement
+
+ @property
+ def icon(self):
+ """Return the entity weather icon, if any."""
+ return ICON
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ return self._attrs
+
+ def update(self):
+ """Get the latest data from Magicseaweed and updates the states."""
+ self.data.update()
+ if self.hour is None:
+ forecast = self.data.currently
+ else:
+ forecast = self.data.hourly[self.hour]
+
+ self._unit_of_measurement = forecast.swell_unit
+ if self.type == 'min_breaking_swell':
+ self._state = forecast.swell_minBreakingHeight
+ elif self.type == 'max_breaking_swell':
+ self._state = forecast.swell_maxBreakingHeight
+ elif self.type == 'swell_forecast':
+ summary = "{} - {}".format(
+ forecast.swell_minBreakingHeight,
+ forecast.swell_maxBreakingHeight)
+ self._state = summary
+ if self.hour is None:
+ for hour, data in self.data.hourly.items():
+ occurs = hour
+ hr_summary = "{} - {} {}".format(
+ data.swell_minBreakingHeight,
+ data.swell_maxBreakingHeight,
+ data.swell_unit)
+ self._attrs[occurs] = hr_summary
+
+ if self.type != 'swell_forecast':
+ self._attrs.update(forecast.attrs)
+
+
+class MagicSeaweedData:
+ """Get the latest data from MagicSeaweed."""
+
+ def __init__(self, api_key, spot_id, units):
+ """Initialize the data object."""
+ import magicseaweed
+ self._msw = magicseaweed.MSW_Forecast(api_key, spot_id,
+ None, units)
+ self.currently = None
+ self.hourly = {}
+
+ # Apply throttling to methods using configured interval
+ self.update = Throttle(MIN_TIME_BETWEEN_UPDATES)(self._update)
+
+ def _update(self):
+ """Get the latest data from MagicSeaweed."""
+ try:
+ forecasts = self._msw.get_future()
+ self.currently = forecasts.data[0]
+ for forecast in forecasts.data[:8]:
+ hour = dt_util.utc_from_timestamp(
+ forecast.localTimestamp).strftime("%-I%p")
+ self.hourly[hour] = forecast
+ except ConnectionError:
+ _LOGGER.error("Unable to retrieve data from Magicseaweed")
diff --git a/homeassistant/components/sensor/metoffice.py b/homeassistant/components/sensor/metoffice.py
index b6366de6432..ec3d3f47ba7 100644
--- a/homeassistant/components/sensor/metoffice.py
+++ b/homeassistant/components/sensor/metoffice.py
@@ -174,7 +174,7 @@ class MetOfficeCurrentSensor(Entity):
self.data.update()
-class MetOfficeCurrentData(object):
+class MetOfficeCurrentData:
"""Get data from Datapoint."""
def __init__(self, hass, datapoint, site):
diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py
index ab6bd8270ce..f575768b505 100644
--- a/homeassistant/components/sensor/mfi.py
+++ b/homeassistant/components/sensor/mfi.py
@@ -96,7 +96,7 @@ class MfiSensor(Entity):
tag = None
if tag is None:
return STATE_OFF
- elif self._port.model == 'Input Digital':
+ if self._port.model == 'Input Digital':
return STATE_ON if self._port.value > 0 else STATE_OFF
digits = DIGITS.get(self._port.tag, 0)
return round(self._port.value, digits)
@@ -111,9 +111,9 @@ class MfiSensor(Entity):
if tag == 'temperature':
return TEMP_CELSIUS
- elif tag == 'active_pwr':
+ if tag == 'active_pwr':
return 'Watts'
- elif self._port.model == 'Input Digital':
+ if self._port.model == 'Input Digital':
return 'State'
return tag
diff --git a/homeassistant/components/sensor/mhz19.py b/homeassistant/components/sensor/mhz19.py
index cd559d3bbd2..60f6598ab21 100644
--- a/homeassistant/components/sensor/mhz19.py
+++ b/homeassistant/components/sensor/mhz19.py
@@ -117,7 +117,7 @@ class MHZ19Sensor(Entity):
return result
-class MHZClient(object):
+class MHZClient:
"""Get the latest data from the MH-Z sensor."""
def __init__(self, co2sensor, serial):
diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py
index f1f8adab062..6f50a57b3ab 100644
--- a/homeassistant/components/sensor/miflora.py
+++ b/homeassistant/components/sensor/miflora.py
@@ -62,7 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MiFlora sensor."""
from miflora import miflora_poller
try:
- import bluepy.btle # noqa: F401 # pylint: disable=unused-variable
+ import bluepy.btle # noqa: F401 pylint: disable=unused-variable
from btlewrap import BluepyBackend
backend = BluepyBackend
except ImportError:
diff --git a/homeassistant/components/sensor/mitemp_bt.py b/homeassistant/components/sensor/mitemp_bt.py
index 3628765293b..249a69578db 100644
--- a/homeassistant/components/sensor/mitemp_bt.py
+++ b/homeassistant/components/sensor/mitemp_bt.py
@@ -60,7 +60,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MiTempBt sensor."""
from mitemp_bt import mitemp_bt_poller
try:
- import bluepy.btle # noqa: F401 # pylint: disable=unused-variable
+ import bluepy.btle # noqa: F401 pylint: disable=unused-variable
from btlewrap import BluepyBackend
backend = BluepyBackend
except ImportError:
diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py
index c4014fbd1dd..5f404ccd5f7 100644
--- a/homeassistant/components/sensor/modbus.py
+++ b/homeassistant/components/sensor/modbus.py
@@ -9,7 +9,7 @@ import struct
import voluptuous as vol
-import homeassistant.components.modbus as modbus
+from homeassistant.components import modbus
from homeassistant.const import (
CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE,
CONF_STRUCTURE)
diff --git a/homeassistant/components/sensor/modem_callerid.py b/homeassistant/components/sensor/modem_callerid.py
index f80ea5853c8..58e8becd6bb 100644
--- a/homeassistant/components/sensor/modem_callerid.py
+++ b/homeassistant/components/sensor/modem_callerid.py
@@ -95,7 +95,6 @@ class ModemCalleridSensor(Entity):
if self.modem:
self.modem.close()
self.modem = None
- return
def _incomingcallcallback(self, newstate):
"""Handle new states."""
@@ -117,4 +116,3 @@ class ModemCalleridSensor(Entity):
elif newstate == self.modem.STATE_IDLE:
self._state = STATE_IDLE
self.schedule_update_ha_state()
- return
diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py
index 2822ce01dca..319185923cd 100644
--- a/homeassistant/components/sensor/mold_indicator.py
+++ b/homeassistant/components/sensor/mold_indicator.py
@@ -10,7 +10,7 @@ import math
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
-import homeassistant.util as util
+from homeassistant import util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_state_change
from homeassistant.const import (
@@ -107,11 +107,10 @@ class MoldIndicator(Entity):
# convert to celsius if necessary
if unit == TEMP_FAHRENHEIT:
return util.temperature.fahrenheit_to_celsius(temp)
- elif unit == TEMP_CELSIUS:
+ if 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
diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/sensor/moon.py
index 0c57c98c0af..50f4f72078c 100644
--- a/homeassistant/components/sensor/moon.py
+++ b/homeassistant/components/sensor/moon.py
@@ -50,20 +50,20 @@ class MoonSensor(Entity):
def state(self):
"""Return the state of the device."""
if self._state == 0:
- return 'New moon'
- elif self._state < 7:
- return 'Waxing crescent'
- elif self._state == 7:
- return 'First quarter'
- elif self._state < 14:
- return 'Waxing gibbous'
- elif self._state == 14:
- return 'Full moon'
- elif self._state < 21:
- return 'Waning gibbous'
- elif self._state == 21:
- return 'Last quarter'
- return 'Waning crescent'
+ return 'new_moon'
+ if self._state < 7:
+ return 'waxing_crescent'
+ if self._state == 7:
+ return 'first_quarter'
+ if self._state < 14:
+ return 'waxing_gibbous'
+ if self._state == 14:
+ return 'full_moon'
+ if self._state < 21:
+ return 'waning_gibbous'
+ if self._state == 21:
+ return 'last_quarter'
+ return 'waning_crescent'
@property
def icon(self):
diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py
index 3e1887cfd59..81c48555cfc 100644
--- a/homeassistant/components/sensor/mopar.py
+++ b/homeassistant/components/sensor/mopar.py
@@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for index, _ in enumerate(data.vehicles)], True)
-class MoparData(object):
+class MoparData:
"""Container for Mopar vehicle data.
Prevents session expiry re-login race condition.
diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py
index 997fd312a6a..0e29c55d39d 100644
--- a/homeassistant/components/sensor/mqtt.py
+++ b/homeassistant/components/sensor/mqtt.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN,
CONF_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_DEVICE_CLASS)
from homeassistant.helpers.entity import Entity
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.helpers.event import async_track_point_in_utc_time
diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py
index 2c0f8eb4d5a..2a61c1143ee 100644
--- a/homeassistant/components/sensor/mqtt_room.py
+++ b/homeassistant/components/sensor/mqtt_room.py
@@ -11,7 +11,7 @@ from datetime import timedelta
import voluptuous as vol
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.components.mqtt import CONF_STATE_TOPIC
from homeassistant.components.sensor import PLATFORM_SCHEMA
diff --git a/homeassistant/components/sensor/mvglive.py b/homeassistant/components/sensor/mvglive.py
index 81c7173e4d0..e066bb5e0b9 100644
--- a/homeassistant/components/sensor/mvglive.py
+++ b/homeassistant/components/sensor/mvglive.py
@@ -7,6 +7,7 @@ https://home-assistant.io/components/sensor.mvglive/
import logging
from datetime import timedelta
+from copy import deepcopy
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@@ -28,6 +29,7 @@ CONF_DIRECTIONS = 'directions'
CONF_LINES = 'lines'
CONF_PRODUCTS = 'products'
CONF_TIMEOFFSET = 'timeoffset'
+CONF_NUMBER = 'number'
DEFAULT_PRODUCT = ['U-Bahn', 'Tram', 'Bus', 'S-Bahn']
@@ -52,6 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PRODUCTS, default=DEFAULT_PRODUCT):
cv.ensure_list_csv,
vol.Optional(CONF_TIMEOFFSET, default=0): cv.positive_int,
+ vol.Optional(CONF_NUMBER, default=1): cv.positive_int,
vol.Optional(CONF_NAME): cv.string}]
})
@@ -68,6 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
nextdeparture.get(CONF_LINES),
nextdeparture.get(CONF_PRODUCTS),
nextdeparture.get(CONF_TIMEOFFSET),
+ nextdeparture.get(CONF_NUMBER),
nextdeparture.get(CONF_NAME)))
add_devices(sensors, True)
@@ -76,12 +80,12 @@ class MVGLiveSensor(Entity):
"""Implementation of an MVG Live sensor."""
def __init__(self, station, destinations, directions,
- lines, products, timeoffset, name):
+ lines, products, timeoffset, number, name):
"""Initialize the sensor."""
self._station = station
self._name = name
self.data = MVGLiveData(station, destinations, directions,
- lines, products, timeoffset)
+ lines, products, timeoffset, number)
self._state = STATE_UNKNOWN
self._icon = ICONS['-']
@@ -98,9 +102,14 @@ class MVGLiveSensor(Entity):
return self._state
@property
- def state_attributes(self):
+ def device_state_attributes(self):
"""Return the state attributes."""
- return self.data.departures
+ dep = self.data.departures
+ if not dep:
+ return None
+ attr = dep[0] # next depature attributes
+ attr['departures'] = deepcopy(dep) # all departures dictionary
+ return attr
@property
def icon(self):
@@ -119,15 +128,15 @@ class MVGLiveSensor(Entity):
self._state = '-'
self._icon = ICONS['-']
else:
- self._state = self.data.departures.get('time', '-')
- self._icon = ICONS[self.data.departures.get('product', '-')]
+ self._state = self.data.departures[0].get('time', '-')
+ self._icon = ICONS[self.data.departures[0].get('product', '-')]
-class MVGLiveData(object):
+class MVGLiveData:
"""Pull data from the mvg-live.de web page."""
def __init__(self, station, destinations, directions,
- lines, products, timeoffset):
+ lines, products, timeoffset, number):
"""Initialize the sensor."""
import MVGLive
self._station = station
@@ -136,25 +145,30 @@ class MVGLiveData(object):
self._lines = lines
self._products = products
self._timeoffset = timeoffset
+ self._number = number
self._include_ubahn = True if 'U-Bahn' in self._products else False
self._include_tram = True if 'Tram' in self._products else False
self._include_bus = True if 'Bus' in self._products else False
self._include_sbahn = True if 'S-Bahn' in self._products else False
self.mvg = MVGLive.MVGLive()
- self.departures = {}
+ self.departures = []
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,
+ station=self._station,
+ timeoffset=self._timeoffset,
+ ubahn=self._include_ubahn,
+ tram=self._include_tram,
+ bus=self._include_bus,
sbahn=self._include_sbahn)
except ValueError:
- self.departures = {}
+ self.departures = []
_LOGGER.warning("Returned data not understood")
return
- for _departure in _departures:
+ self.departures = []
+ for i, _departure in enumerate(_departures):
# find the first departure meeting the criteria
if ('' not in self._destinations[:1] and
_departure['destination'] not in self._destinations):
@@ -173,5 +187,6 @@ class MVGLiveData(object):
'product']:
_nextdep[k] = _departure.get(k, '')
_nextdep['time'] = int(_nextdep['time'])
- self.departures = _nextdep
- break
+ self.departures.append(_nextdep)
+ if i == self._number - 1:
+ break
diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py
index 54b095bb84b..3e3f7ce9486 100644
--- a/homeassistant/components/sensor/netatmo.py
+++ b/homeassistant/components/sensor/netatmo.py
@@ -287,7 +287,7 @@ class NetAtmoSensor(Entity):
self._state = "Full"
-class NetAtmoData(object):
+class NetAtmoData:
"""Get the latest data from NetAtmo."""
def __init__(self, auth, station):
@@ -340,7 +340,7 @@ class NetAtmoData(object):
# Never hammer the NetAtmo API more than
# twice per update interval
newinterval = NETATMO_UPDATE_INTERVAL / 2
- _LOGGER.warning(
+ _LOGGER.info(
"NetAtmo refresh interval reset to %d seconds",
newinterval)
else:
diff --git a/homeassistant/components/sensor/netdata.py b/homeassistant/components/sensor/netdata.py
index 2d08159967c..488b1611399 100644
--- a/homeassistant/components/sensor/netdata.py
+++ b/homeassistant/components/sensor/netdata.py
@@ -134,7 +134,7 @@ class NetdataSensor(Entity):
resource_data['dimensions'][self._element]['value'], 2)
-class NetdataData(object):
+class NetdataData:
"""The class for handling the data retrieval."""
def __init__(self, api):
diff --git a/homeassistant/components/sensor/netgear_lte.py b/homeassistant/components/sensor/netgear_lte.py
index b4a3e2a1155..dac1f81ad23 100644
--- a/homeassistant/components/sensor/netgear_lte.py
+++ b/homeassistant/components/sensor/netgear_lte.py
@@ -32,11 +32,11 @@ async def async_setup_platform(
modem_data = hass.data[DATA_KEY].get_modem_data(config)
sensors = []
- for sensortype in config[CONF_SENSORS]:
- if sensortype == SENSOR_SMS:
- sensors.append(SMSSensor(modem_data))
- elif sensortype == SENSOR_USAGE:
- sensors.append(UsageSensor(modem_data))
+ for sensor_type in config[CONF_SENSORS]:
+ if sensor_type == SENSOR_SMS:
+ sensors.append(SMSSensor(modem_data, sensor_type))
+ elif sensor_type == SENSOR_USAGE:
+ sensors.append(UsageSensor(modem_data, sensor_type))
async_add_devices(sensors, True)
@@ -46,11 +46,17 @@ class LTESensor(Entity):
"""Data usage sensor entity."""
modem_data = attr.ib()
+ sensor_type = attr.ib()
async def async_update(self):
"""Update state."""
await self.modem_data.async_update()
+ @property
+ def unique_id(self):
+ """Return a unique ID like 'usage_5TG365AB0078V'."""
+ return "{}_{}".format(self.sensor_type, self.modem_data.serial_number)
+
class SMSSensor(LTESensor):
"""Unread SMS sensor entity."""
diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py
index 5e3bf55dc9d..fd8b8d2aeec 100644
--- a/homeassistant/components/sensor/neurio_energy.py
+++ b/homeassistant/components/sensor/neurio_energy.py
@@ -69,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([NeurioEnergy(data, DAILY_NAME, DAILY_TYPE, update_daily)])
-class NeurioData(object):
+class NeurioData:
"""Stores data retrieved from Neurio sensor."""
def __init__(self, api_key, api_secret, sensor_id):
diff --git a/homeassistant/components/sensor/nsw_fuel_station.py b/homeassistant/components/sensor/nsw_fuel_station.py
index 2440dac3204..5f677d39888 100644
--- a/homeassistant/components/sensor/nsw_fuel_station.py
+++ b/homeassistant/components/sensor/nsw_fuel_station.py
@@ -73,7 +73,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
-class StationPriceData(object):
+class StationPriceData:
"""An object to store and fetch the latest data for a given station."""
def __init__(self, client, station_id: int) -> None:
diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/sensor/nut.py
index 7c7ff3480b0..7126bd89ef9 100644
--- a/homeassistant/components/sensor/nut.py
+++ b/homeassistant/components/sensor/nut.py
@@ -236,13 +236,12 @@ class NUTSensor(Entity):
"""Return UPS display state."""
if self._data.status is None:
return STATE_TYPES['OFF']
- else:
- try:
- return " ".join(
- STATE_TYPES[state]
- for state in self._data.status[KEY_STATUS].split())
- except KeyError:
- return STATE_UNKNOWN
+ try:
+ return " ".join(
+ STATE_TYPES[state]
+ for state in self._data.status[KEY_STATUS].split())
+ except KeyError:
+ return STATE_UNKNOWN
def update(self):
"""Get the latest status and use it to update our sensor state."""
@@ -260,7 +259,7 @@ class NUTSensor(Entity):
self._state = self._data.status[self.type]
-class PyNUTData(object):
+class PyNUTData:
"""Stores the data retrieved from NUT.
For each entity to use, acts as the single point responsible for fetching
diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/sensor/nzbget.py
index 0fa6362ad05..a6fee5a69e8 100644
--- a/homeassistant/components/sensor/nzbget.py
+++ b/homeassistant/components/sensor/nzbget.py
@@ -138,7 +138,7 @@ class NZBGetSensor(Entity):
self._state = value
-class NZBGetAPI(object):
+class NZBGetAPI:
"""Simple JSON-RPC wrapper for NZBGet's API."""
def __init__(self, api_url, username=None, password=None):
@@ -173,8 +173,4 @@ class NZBGetAPI(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update cached response."""
- try:
- self.status = self.post('status')['result']
- except requests.exceptions.ConnectionError:
- # failed to update status - exception already logged in self.post
- raise
+ self.status = self.post('status')['result']
diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/sensor/octoprint.py
index 20d00267dee..9e62846e4d3 100644
--- a/homeassistant/components/sensor/octoprint.py
+++ b/homeassistant/components/sensor/octoprint.py
@@ -107,13 +107,12 @@ class OctoPrintSensor(Entity):
def state(self):
"""Return the state of the sensor."""
sensor_unit = self.unit_of_measurement
- if sensor_unit == TEMP_CELSIUS or sensor_unit == "%":
+ if sensor_unit in (TEMP_CELSIUS, "%"):
# API sometimes returns null and not 0
if self._state is None:
self._state = 0
return round(self._state, 2)
- else:
- return self._state
+ return self._state
@property
def unit_of_measurement(self):
diff --git a/homeassistant/components/sensor/openexchangerates.py b/homeassistant/components/sensor/openexchangerates.py
index 741ffa2842d..5e8231bb124 100644
--- a/homeassistant/components/sensor/openexchangerates.py
+++ b/homeassistant/components/sensor/openexchangerates.py
@@ -93,7 +93,7 @@ class OpenexchangeratesSensor(Entity):
self._state = round(value[str(self._quote)], 4)
-class OpenexchangeratesData(object):
+class OpenexchangeratesData:
"""Get data from Openexchangerates.org."""
def __init__(self, resource, parameters, quote):
diff --git a/homeassistant/components/sensor/openhardwaremonitor.py b/homeassistant/components/sensor/openhardwaremonitor.py
index 1b5867836bc..1b345c752ff 100644
--- a/homeassistant/components/sensor/openhardwaremonitor.py
+++ b/homeassistant/components/sensor/openhardwaremonitor.py
@@ -101,14 +101,13 @@ class OpenHardwareMonitorDevice(Entity):
self.attributes = _attributes
return
- else:
- array = array[path_number][OHM_CHILDREN]
- _attributes.update({
- 'level_%s' % path_index: values[OHM_NAME]
- })
+ array = array[path_number][OHM_CHILDREN]
+ _attributes.update({
+ 'level_%s' % path_index: values[OHM_NAME]
+ })
-class OpenHardwareMonitorData(object):
+class OpenHardwareMonitorData:
"""Class used to pull data from OHM and create sensors."""
def __init__(self, config, hass):
diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py
index 96db4430d32..ba7fc4f9095 100644
--- a/homeassistant/components/sensor/openweathermap.py
+++ b/homeassistant/components/sensor/openweathermap.py
@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyowm==2.8.0']
+REQUIREMENTS = ['pyowm==2.9.0']
_LOGGER = logging.getLogger(__name__)
@@ -179,7 +179,7 @@ class OpenWeatherMapSensor(Entity):
self._state = fc_data.get_weathers()[0].get_detailed_status()
-class WeatherData(object):
+class WeatherData:
"""Get the latest data from OpenWeatherMap."""
def __init__(self, owm, forecast, latitude, longitude):
diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py
index 2adf5691e2e..363ada725ba 100644
--- a/homeassistant/components/sensor/pi_hole.py
+++ b/homeassistant/components/sensor/pi_hole.py
@@ -153,7 +153,7 @@ class PiHoleSensor(Entity):
self.data = self.pi_hole.api.data
-class PiHoleData(object):
+class PiHoleData:
"""Get the latest data and update the states."""
def __init__(self, api):
diff --git a/homeassistant/components/sensor/pilight.py b/homeassistant/components/sensor/pilight.py
index 9784cc3dc4c..c30f1575049 100644
--- a/homeassistant/components/sensor/pilight.py
+++ b/homeassistant/components/sensor/pilight.py
@@ -12,7 +12,7 @@ from homeassistant.const import (
CONF_NAME, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_PAYLOAD)
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
-import homeassistant.components.pilight as pilight
+from homeassistant.components import pilight
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py
index c11c83ab40e..27750c9ac61 100644
--- a/homeassistant/components/sensor/pollen.py
+++ b/homeassistant/components/sensor/pollen.py
@@ -263,7 +263,7 @@ class PollencomSensor(Entity):
self._state = average
-class PollenComData(object):
+class PollenComData:
"""Define a data object to retrieve info from Pollen.com."""
def __init__(self, client, sensor_types):
@@ -307,7 +307,7 @@ class PollenComData(object):
_LOGGER.error('Unable to get allergy history: %s', err)
self.data[TYPE_ALLERGY_HISTORIC] = {}
- if all(s in self._sensor_types
+ if any(s in self._sensor_types
for s in [TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
TYPE_ALLERGY_YESTERDAY]):
try:
diff --git a/homeassistant/components/sensor/pyload.py b/homeassistant/components/sensor/pyload.py
index cc4ce1e6448..4aa121e0895 100644
--- a/homeassistant/components/sensor/pyload.py
+++ b/homeassistant/components/sensor/pyload.py
@@ -124,7 +124,7 @@ class PyLoadSensor(Entity):
self._state = value
-class PyLoadAPI(object):
+class PyLoadAPI:
"""Simple wrapper for pyLoad's API."""
def __init__(self, api_url, username=None, password=None):
@@ -162,8 +162,4 @@ class PyLoadAPI(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update cached response."""
- try:
- self.status = self.post('speed')
- except requests.exceptions.ConnectionError:
- # Failed to update status - exception already logged in self.post
- raise
+ self.status = self.post('speed')
diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/sensor/qnap.py
index 3d9704875c9..8b25eb3de31 100644
--- a/homeassistant/components/sensor/qnap.py
+++ b/homeassistant/components/sensor/qnap.py
@@ -164,7 +164,7 @@ def round_nicely(number):
return round(number)
-class QNAPStatsAPI(object):
+class QNAPStatsAPI:
"""Class to interface with the API."""
def __init__(self, config):
@@ -192,7 +192,7 @@ class QNAPStatsAPI(object):
self.data["smart_drive_health"] = self._api.get_smart_disk_health()
self.data["volumes"] = self._api.get_volumes()
self.data["bandwidth"] = self._api.get_bandwidth()
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_LOGGER.exception("Failed to fetch QNAP stats from the NAS")
@@ -241,7 +241,7 @@ class QNAPCPUSensor(QNAPSensor):
"""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':
+ if self.var_id == 'cpu_usage':
return self._api.data['system_stats']['cpu']['usage_percent']
diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py
index 75235bedaab..8db48719a37 100644
--- a/homeassistant/components/sensor/rest.py
+++ b/homeassistant/components/sensor/rest.py
@@ -158,7 +158,7 @@ class RestSensor(Entity):
return self._attributes
-class RestData(object):
+class RestData:
"""Class for handling the data retrieval."""
def __init__(self, method, resource, auth, headers, data, verify_ssl):
diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py
index a5a6eb5f07b..b410e7e860a 100644
--- a/homeassistant/components/sensor/rfxtrx.py
+++ b/homeassistant/components/sensor/rfxtrx.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.rfxtrx as rfxtrx
+from homeassistant.components import rfxtrx
from homeassistant.components.rfxtrx import (
ATTR_DATA_TYPE, ATTR_FIRE_EVENT, CONF_AUTOMATIC_ADD, CONF_DATA_TYPE,
CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES)
diff --git a/homeassistant/components/sensor/scrape.py b/homeassistant/components/sensor/scrape.py
index 0065f3e0927..e7aace8ec6d 100644
--- a/homeassistant/components/sensor/scrape.py
+++ b/homeassistant/components/sensor/scrape.py
@@ -19,7 +19,7 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['beautifulsoup4==4.6.0']
+REQUIREMENTS = ['beautifulsoup4==4.6.1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sensor/season.py b/homeassistant/components/sensor/season.py
index b04b7727e40..f06f6a896e7 100644
--- a/homeassistant/components/sensor/season.py
+++ b/homeassistant/components/sensor/season.py
@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_TYPE
from homeassistant.helpers.entity import Entity
-import homeassistant.util as util
+from homeassistant import util
REQUIREMENTS = ['ephem==3.7.6.0']
diff --git a/homeassistant/components/sensor/sense.py b/homeassistant/components/sensor/sense.py
index 5eee9053db5..16f4ccb9b6c 100644
--- a/homeassistant/components/sensor/sense.py
+++ b/homeassistant/components/sensor/sense.py
@@ -27,7 +27,7 @@ CONSUMPTION_NAME = "Usage"
ACTIVE_TYPE = 'active'
-class SensorConfig(object):
+class SensorConfig:
"""Data structure holding sensor config."""
def __init__(self, name, sensor_type):
diff --git a/homeassistant/components/sensor/sensehat.py b/homeassistant/components/sensor/sensehat.py
index a50f4cdfd2c..f0e566f718f 100644
--- a/homeassistant/components/sensor/sensehat.py
+++ b/homeassistant/components/sensor/sensehat.py
@@ -109,7 +109,7 @@ class SenseHatSensor(Entity):
self._state = self.data.pressure
-class SenseHatData(object):
+class SenseHatData:
"""Get the latest data and update."""
def __init__(self, is_hat_attached):
diff --git a/homeassistant/components/sensor/shodan.py b/homeassistant/components/sensor/shodan.py
index bc3e127508b..541abea3091 100644
--- a/homeassistant/components/sensor/shodan.py
+++ b/homeassistant/components/sensor/shodan.py
@@ -96,7 +96,7 @@ class ShodanSensor(Entity):
self._state = self.data.details['total']
-class ShodanData(object):
+class ShodanData:
"""Get the latest data and update the states."""
def __init__(self, api, query):
diff --git a/homeassistant/components/sensor/sht31.py b/homeassistant/components/sensor/sht31.py
index e1a7f3c9e5f..2aeff8e73d8 100644
--- a/homeassistant/components/sensor/sht31.py
+++ b/homeassistant/components/sensor/sht31.py
@@ -75,7 +75,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devs)
-class SHTClient(object):
+class SHTClient:
"""Get the latest data from the SHT sensor."""
def __init__(self, adafruit_sht):
diff --git a/homeassistant/components/sensor/sigfox.py b/homeassistant/components/sensor/sigfox.py
index da8f3fcc639..408435a9667 100644
--- a/homeassistant/components/sensor/sigfox.py
+++ b/homeassistant/components/sensor/sigfox.py
@@ -55,7 +55,7 @@ def epoch_to_datetime(epoch_time):
return datetime.datetime.fromtimestamp(epoch_time).isoformat()
-class SigfoxAPI(object):
+class SigfoxAPI:
"""Class for interacting with the SigFox API."""
def __init__(self, api_login, api_password):
diff --git a/homeassistant/components/sensor/snmp.py b/homeassistant/components/sensor/snmp.py
index 95bf207acf8..5600f906f34 100644
--- a/homeassistant/components/sensor/snmp.py
+++ b/homeassistant/components/sensor/snmp.py
@@ -83,11 +83,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if errindication and not accept_errors:
_LOGGER.error("Please check the details in the configuration file")
return False
- else:
- data = SnmpData(
- host, port, community, baseoid, version, accept_errors,
- default_value)
- add_devices([SnmpSensor(data, name, unit, value_template)], True)
+ data = SnmpData(
+ host, port, community, baseoid, version, accept_errors,
+ default_value)
+ add_devices([SnmpSensor(data, name, unit, value_template)], True)
class SnmpSensor(Entity):
@@ -131,7 +130,7 @@ class SnmpSensor(Entity):
self._state = value
-class SnmpData(object):
+class SnmpData:
"""Get the latest data and update the states."""
def __init__(self, host, port, community, baseoid, version, accept_errors,
diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py
index bf2868d3b01..8c1ffc03786 100644
--- a/homeassistant/components/sensor/speedtest.py
+++ b/homeassistant/components/sensor/speedtest.py
@@ -148,7 +148,7 @@ class SpeedtestSensor(Entity):
self._state = state.state
-class SpeedtestData(object):
+class SpeedtestData:
"""Get the latest data from speedtest.net."""
def __init__(self, hass, config):
diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sensor/sql.py
index 8574a7231da..83f5478867f 100644
--- a/homeassistant/components/sensor/sql.py
+++ b/homeassistant/components/sensor/sql.py
@@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['sqlalchemy==1.2.9']
+REQUIREMENTS = ['sqlalchemy==1.2.10']
CONF_COLUMN_NAME = 'column'
CONF_QUERIES = 'queries'
diff --git a/homeassistant/components/sensor/startca.py b/homeassistant/components/sensor/startca.py
index aefbc2d4626..374e14c5ac2 100644
--- a/homeassistant/components/sensor/startca.py
+++ b/homeassistant/components/sensor/startca.py
@@ -118,7 +118,7 @@ class StartcaSensor(Entity):
self._state = round(self.startcadata.data[self.type], 2)
-class StartcaData(object):
+class StartcaData:
"""Get data from Start.ca API."""
def __init__(self, loop, websession, api_key, bandwidth_cap):
diff --git a/homeassistant/components/sensor/strings.moon.json b/homeassistant/components/sensor/strings.moon.json
new file mode 100644
index 00000000000..97d96623d88
--- /dev/null
+++ b/homeassistant/components/sensor/strings.moon.json
@@ -0,0 +1,12 @@
+{
+ "state": {
+ "new_moon": "New moon",
+ "waxing_crescent": "Waxing crescent",
+ "first_quarter": "First quarter",
+ "waxing_gibbous": "Waxing gibbous",
+ "full_moon": "Full moon",
+ "waning_gibbous": "Waning gibbous",
+ "last_quarter": "Last quarter",
+ "waning_crescent": "Waning crescent"
+ }
+}
diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/sensor/swiss_hydrological_data.py
index 63d500e2373..b4536b48c9e 100644
--- a/homeassistant/components/sensor/swiss_hydrological_data.py
+++ b/homeassistant/components/sensor/swiss_hydrological_data.py
@@ -145,7 +145,7 @@ class SwissHydrologicalDataSensor(Entity):
self._state = self.data.measurings['03']['current']
-class HydrologicalData(object):
+class HydrologicalData:
"""The Class for handling the data retrieval."""
def __init__(self, station):
diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/sensor/synologydsm.py
index e3c3a0cf5ca..d431805ab19 100644
--- a/homeassistant/components/sensor/synologydsm.py
+++ b/homeassistant/components/sensor/synologydsm.py
@@ -129,7 +129,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
-class SynoApi(object):
+class SynoApi:
"""Class to interface with Synology DSM API."""
def __init__(self, host, port, username, password, temp_unit, use_ssl):
@@ -140,7 +140,7 @@ class SynoApi(object):
try:
self._api = SynologyDSM(host, port, username, password,
use_https=use_ssl)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_LOGGER.error("Error setting up Synology DSM")
# Will be updated when update() gets called.
@@ -214,7 +214,7 @@ class SynoNasUtilSensor(SynoNasSensor):
if self.var_id in network_sensors:
return round(attr / 1024.0, 1)
- elif self.var_id in memory_sensors:
+ if self.var_id in memory_sensors:
return round(attr / 1024.0 / 1024.0, 1)
else:
return getattr(self._api.utilisation, self.var_id)
diff --git a/homeassistant/components/sensor/sytadin.py b/homeassistant/components/sensor/sytadin.py
index 9a85eb25575..ff8e7d7ddfe 100644
--- a/homeassistant/components/sensor/sytadin.py
+++ b/homeassistant/components/sensor/sytadin.py
@@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['beautifulsoup4==4.6.0']
+REQUIREMENTS = ['beautifulsoup4==4.6.1']
_LOGGER = logging.getLogger(__name__)
@@ -35,9 +35,9 @@ OPTION_MEAN_VELOCITY = 'mean_velocity'
OPTION_CONGESTION = 'congestion'
SENSOR_TYPES = {
- OPTION_TRAFFIC_JAM: ['Traffic Jam', LENGTH_KILOMETERS],
- OPTION_MEAN_VELOCITY: ['Mean Velocity', LENGTH_KILOMETERS+'/h'],
OPTION_CONGESTION: ['Congestion', ''],
+ OPTION_MEAN_VELOCITY: ['Mean Velocity', LENGTH_KILOMETERS+'/h'],
+ OPTION_TRAFFIC_JAM: ['Traffic Jam', LENGTH_KILOMETERS],
}
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
@@ -113,7 +113,7 @@ class SytadinSensor(Entity):
self._state = self.data.congestion
-class SytadinData(object):
+class SytadinData:
"""The class for handling the data retrieval."""
def __init__(self, resource):
diff --git a/homeassistant/components/sensor/tado.py b/homeassistant/components/sensor/tado.py
index 737b3d08368..aa6314b8c5b 100644
--- a/homeassistant/components/sensor/tado.py
+++ b/homeassistant/components/sensor/tado.py
@@ -122,9 +122,9 @@ class TadoSensor(Entity):
"""Return the unit of measurement."""
if self.zone_variable == "temperature":
return self.hass.config.units.temperature_unit
- elif self.zone_variable == "humidity":
+ if self.zone_variable == "humidity":
return '%'
- elif self.zone_variable == "heating":
+ if self.zone_variable == "heating":
return '%'
@property
@@ -132,7 +132,7 @@ class TadoSensor(Entity):
"""Icon for the sensor."""
if self.zone_variable == "temperature":
return 'mdi:thermometer'
- elif self.zone_variable == "humidity":
+ if self.zone_variable == "humidity":
return 'mdi:water-percent'
def update(self):
diff --git a/homeassistant/components/sensor/tahoma.py b/homeassistant/components/sensor/tahoma.py
index aedecfe61e5..6c6c296652a 100644
--- a/homeassistant/components/sensor/tahoma.py
+++ b/homeassistant/components/sensor/tahoma.py
@@ -46,11 +46,11 @@ class TahomaSensor(TahomaDevice, Entity):
"""Return the unit of measurement of this entity, if any."""
if self.tahoma_device.type == 'Temperature Sensor':
return None
- elif self.tahoma_device.type == 'io:SomfyContactIOSystemSensor':
+ if self.tahoma_device.type == 'io:SomfyContactIOSystemSensor':
return None
- elif self.tahoma_device.type == 'io:LightIOSystemSensor':
+ if self.tahoma_device.type == 'io:LightIOSystemSensor':
return 'lx'
- elif self.tahoma_device.type == 'Humidity Sensor':
+ if self.tahoma_device.type == 'Humidity Sensor':
return '%'
def update(self):
diff --git a/homeassistant/components/sensor/ted5000.py b/homeassistant/components/sensor/ted5000.py
index c2ef1d4c6b9..7298181796a 100644
--- a/homeassistant/components/sensor/ted5000.py
+++ b/homeassistant/components/sensor/ted5000.py
@@ -88,7 +88,7 @@ class Ted5000Sensor(Entity):
self._gateway.update()
-class Ted5000Gateway(object):
+class Ted5000Gateway:
"""The class for handling the data retrieval."""
def __init__(self, url):
diff --git a/homeassistant/components/sensor/teksavvy.py b/homeassistant/components/sensor/teksavvy.py
index 0bf1ef4caff..68a1cfc4fe1 100644
--- a/homeassistant/components/sensor/teksavvy.py
+++ b/homeassistant/components/sensor/teksavvy.py
@@ -119,7 +119,7 @@ class TekSavvySensor(Entity):
self._state = round(self.teksavvydata.data[self.type], 2)
-class TekSavvyData(object):
+class TekSavvyData:
"""Get data from TekSavvy API."""
def __init__(self, loop, websession, api_key, bandwidth_cap):
diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py
index 048ca988e3d..123c11021b4 100644
--- a/homeassistant/components/sensor/tellduslive.py
+++ b/homeassistant/components/sensor/tellduslive.py
@@ -96,11 +96,11 @@ class TelldusLiveSensor(TelldusLiveEntity):
"""Return the state of the sensor."""
if not self.available:
return None
- elif self._type == SENSOR_TYPE_TEMPERATURE:
+ if self._type == SENSOR_TYPE_TEMPERATURE:
return self._value_as_temperature
- elif self._type == SENSOR_TYPE_HUMIDITY:
+ if self._type == SENSOR_TYPE_HUMIDITY:
return self._value_as_humidity
- elif self._type == SENSOR_TYPE_LUMINANCE:
+ if self._type == SENSOR_TYPE_LUMINANCE:
return self._value_as_luminance
return self._value
diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py
index de929aa0942..2fc67e57162 100644
--- a/homeassistant/components/sensor/tellstick.py
+++ b/homeassistant/components/sensor/tellstick.py
@@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tellstick sensors."""
- import tellcore.telldus as telldus
+ from tellcore import telldus
import tellcore.constants as tellcore_constants
sensor_value_descriptions = {
diff --git a/homeassistant/components/sensor/thethingsnetwork.py b/homeassistant/components/sensor/thethingsnetwork.py
index 28a3b48892b..0f27b656404 100644
--- a/homeassistant/components/sensor/thethingsnetwork.py
+++ b/homeassistant/components/sensor/thethingsnetwork.py
@@ -110,7 +110,7 @@ class TtnDataSensor(Entity):
self._state = self._ttn_data_storage.data
-class TtnDataStorage(object):
+class TtnDataStorage:
"""Get the latest data from The Things Network Data Storage."""
def __init__(self, hass, app_id, device_id, access_key, values):
diff --git a/homeassistant/components/sensor/thinkingcleaner.py b/homeassistant/components/sensor/thinkingcleaner.py
index 83cf799e3cd..0b936d8c8c7 100644
--- a/homeassistant/components/sensor/thinkingcleaner.py
+++ b/homeassistant/components/sensor/thinkingcleaner.py
@@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.thinkingcleaner/
import logging
from datetime import timedelta
-import homeassistant.util as util
+from homeassistant import util
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py
index bfdf0c3c3aa..0668b5bdbce 100644
--- a/homeassistant/components/sensor/time_date.py
+++ b/homeassistant/components/sensor/time_date.py
@@ -81,7 +81,7 @@ class TimeDateSensor(Entity):
"""Icon to use in the frontend, if any."""
if 'date' in self.type and 'time' in self.type:
return 'mdi:calendar-clock'
- elif 'date' in self.type:
+ if 'date' in self.type:
return 'mdi:calendar'
return 'mdi:clock'
@@ -92,7 +92,7 @@ class TimeDateSensor(Entity):
if self.type == 'date':
now = dt_util.start_of_local_day(dt_util.as_local(now))
return now + timedelta(seconds=86400)
- elif self.type == 'beat':
+ if self.type == 'beat':
interval = 86.4
else:
interval = 60
diff --git a/homeassistant/components/sensor/toon.py b/homeassistant/components/sensor/toon.py
index cecce0d270f..a8875f6904c 100644
--- a/homeassistant/components/sensor/toon.py
+++ b/homeassistant/components/sensor/toon.py
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.toon/
"""
import logging
-import datetime as datetime
+import datetime
from homeassistant.helpers.entity import Entity
import homeassistant.components.toon as toon_main
diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py
index 4dac411d224..3e74b454913 100644
--- a/homeassistant/components/sensor/transmission.py
+++ b/homeassistant/components/sensor/transmission.py
@@ -148,7 +148,7 @@ class TransmissionSensor(Entity):
self._state = self._data.torrentCount
-class TransmissionData(object):
+class TransmissionData:
"""Get the latest data and update the states."""
def __init__(self, api):
diff --git a/homeassistant/components/sensor/uber.py b/homeassistant/components/sensor/uber.py
index e80fe7d2d82..cd476a1a226 100644
--- a/homeassistant/components/sensor/uber.py
+++ b/homeassistant/components/sensor/uber.py
@@ -175,7 +175,7 @@ class UberSensor(Entity):
self._state = 0
-class UberEstimate(object):
+class UberEstimate:
"""The class for handling the time and price estimate."""
def __init__(self, session, start_latitude, start_longitude,
diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py
index 4fc92db1d90..eaef3dcf7f7 100644
--- a/homeassistant/components/sensor/vera.py
+++ b/homeassistant/components/sensor/vera.py
@@ -51,13 +51,13 @@ class VeraSensor(VeraDevice, Entity):
import pyvera as veraApi
if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR:
return self._temperature_units
- elif self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR:
+ if self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR:
return 'lx'
- elif self.vera_device.category == veraApi.CATEGORY_UV_SENSOR:
+ if self.vera_device.category == veraApi.CATEGORY_UV_SENSOR:
return 'level'
- elif self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR:
+ if self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR:
return '%'
- elif self.vera_device.category == veraApi.CATEGORY_POWER_METER:
+ if self.vera_device.category == veraApi.CATEGORY_POWER_METER:
return 'watts'
def update(self):
diff --git a/homeassistant/components/sensor/volvooncall.py b/homeassistant/components/sensor/volvooncall.py
index 343bcdf2033..78e8a7e76c6 100644
--- a/homeassistant/components/sensor/volvooncall.py
+++ b/homeassistant/components/sensor/volvooncall.py
@@ -43,7 +43,7 @@ class VolvoSensor(VolvoEntity):
if 'mil' in self.unit_of_measurement:
return round(val, 2)
return round(val, 1)
- elif self._attribute == 'distance_to_empty':
+ if self._attribute == 'distance_to_empty':
return int(floor(val))
return int(round(val))
diff --git a/homeassistant/components/sensor/waterfurnace.py b/homeassistant/components/sensor/waterfurnace.py
index 24c45ec1ff3..76c5d2f648e 100644
--- a/homeassistant/components/sensor/waterfurnace.py
+++ b/homeassistant/components/sensor/waterfurnace.py
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
-class WFSensorConfig(object):
+class WFSensorConfig:
"""Water Furnace Sensor configuration."""
def __init__(self, friendly_name, field, icon="mdi:gauge",
diff --git a/homeassistant/components/sensor/waze_travel_time.py b/homeassistant/components/sensor/waze_travel_time.py
index 0b059379c11..70c169a1b7c 100644
--- a/homeassistant/components/sensor/waze_travel_time.py
+++ b/homeassistant/components/sensor/waze_travel_time.py
@@ -14,7 +14,7 @@ from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START,
ATTR_LATITUDE, ATTR_LONGITUDE)
import homeassistant.helpers.config_validation as cv
-import homeassistant.helpers.location as location
+from homeassistant.helpers import location
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
@@ -64,8 +64,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor = WazeTravelTime(name, origin, destination, region,
incl_filter, excl_filter)
- add_devices([sensor], True)
+ add_devices([sensor])
+ # Wait until start event is sent to load this component.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, sensor.update)
diff --git a/homeassistant/components/sensor/worldtidesinfo.py b/homeassistant/components/sensor/worldtidesinfo.py
index 05d61173da0..597a971e208 100644
--- a/homeassistant/components/sensor/worldtidesinfo.py
+++ b/homeassistant/components/sensor/worldtidesinfo.py
@@ -85,7 +85,7 @@ class WorldTidesInfoSensor(Entity):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][0]['dt']))
return "High tide at %s" % (tidetime)
- elif "Low" in str(self.data['extremes'][0]['type']):
+ if "Low" in str(self.data['extremes'][0]['type']):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][0]['dt']))
return "Low tide at %s" % (tidetime)
diff --git a/homeassistant/components/sensor/worxlandroid.py b/homeassistant/components/sensor/worxlandroid.py
index ddf506bf4eb..c49ce36bd49 100644
--- a/homeassistant/components/sensor/worxlandroid.py
+++ b/homeassistant/components/sensor/worxlandroid.py
@@ -152,11 +152,11 @@ class WorxLandroidSensor(Entity):
if state_obj[14] == 1:
return 'manual-stop'
- elif state_obj[5] == 1 and state_obj[13] == 0:
+ if state_obj[5] == 1 and state_obj[13] == 0:
return 'charging'
- elif state_obj[5] == 1 and state_obj[13] == 1:
+ if state_obj[5] == 1 and state_obj[13] == 1:
return 'charging-complete'
- elif state_obj[15] == 1:
+ if state_obj[15] == 1:
return 'going-home'
return 'mowing'
diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py
index 7f2df4bcda9..24ae2d0068f 100644
--- a/homeassistant/components/sensor/wunderground.py
+++ b/homeassistant/components/sensor/wunderground.py
@@ -40,7 +40,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
# Helper classes for declaring sensor configurations
-class WUSensorConfig(object):
+class WUSensorConfig:
"""WU Sensor Configuration.
defines basic HA properties of the weather sensor and
@@ -764,7 +764,7 @@ class WUndergroundSensor(Entity):
return self._unique_id
-class WUndergroundData(object):
+class WUndergroundData:
"""Get data from WUnderground."""
def __init__(self, hass, api_key, pws_id, lang, latitude, longitude):
diff --git a/homeassistant/components/sensor/xiaomi_aqara.py b/homeassistant/components/sensor/xiaomi_aqara.py
index 3192d0d2f60..32139b21976 100644
--- a/homeassistant/components/sensor/xiaomi_aqara.py
+++ b/homeassistant/components/sensor/xiaomi_aqara.py
@@ -91,9 +91,9 @@ class XiaomiSensor(XiaomiDevice):
value = max(value - 300, 0)
if self._data_key == 'temperature' and (value < -50 or value > 60):
return False
- elif self._data_key == 'humidity' and (value <= 0 or value > 100):
+ if self._data_key == 'humidity' and (value <= 0 or value > 100):
return False
- elif self._data_key == 'pressure' and value == 0:
+ if self._data_key == 'pressure' and value == 0:
return False
self._state = round(value, 1)
return True
diff --git a/homeassistant/components/sensor/yahoo_finance.py b/homeassistant/components/sensor/yahoo_finance.py
index 8c2cfd9923f..82cb7f845dc 100644
--- a/homeassistant/components/sensor/yahoo_finance.py
+++ b/homeassistant/components/sensor/yahoo_finance.py
@@ -104,7 +104,7 @@ class YahooFinanceSensor(Entity):
self._state = self.data.state
-class YahooFinanceData(object):
+class YahooFinanceData:
"""Get data from Yahoo Finance."""
def __init__(self, symbol):
diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py
index c7ff967723b..fcddf41af97 100644
--- a/homeassistant/components/sensor/yr.py
+++ b/homeassistant/components/sensor/yr.py
@@ -142,7 +142,7 @@ class YrSensor(Entity):
return self._unit_of_measurement
-class YrData(object):
+class YrData:
"""Get the latest data and updates the states."""
def __init__(self, hass, coordinates, forecast, devices):
diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py
index db66419e54a..b2279e107da 100644
--- a/homeassistant/components/sensor/yweather.py
+++ b/homeassistant/components/sensor/yweather.py
@@ -174,7 +174,7 @@ class YahooWeatherSensor(Entity):
float(self._data.yahoo.Atmosphere['visibility'])/1.61, 2)
-class YahooWeatherData(object):
+class YahooWeatherData:
"""Handle Yahoo! API object and limit updates."""
def __init__(self, woeid, temp_unit):
diff --git a/homeassistant/components/sensor/zabbix.py b/homeassistant/components/sensor/zabbix.py
index baeed391557..21a3030b79b 100644
--- a/homeassistant/components/sensor/zabbix.py
+++ b/homeassistant/components/sensor/zabbix.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.zabbix as zabbix
+from homeassistant.components import zabbix
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
diff --git a/homeassistant/components/sensor/zamg.py b/homeassistant/components/sensor/zamg.py
index df5ff5e8d37..e8e5fdec4d8 100644
--- a/homeassistant/components/sensor/zamg.py
+++ b/homeassistant/components/sensor/zamg.py
@@ -133,7 +133,7 @@ class ZamgSensor(Entity):
self.probe.update()
-class ZamgData(object):
+class ZamgData:
"""The class for handling the data retrieval."""
API_URL = 'http://www.zamg.ac.at/ogd/'
diff --git a/homeassistant/components/sensor/zoneminder.py b/homeassistant/components/sensor/zoneminder.py
index 1189a53bb09..60b6a018fc2 100644
--- a/homeassistant/components/sensor/zoneminder.py
+++ b/homeassistant/components/sensor/zoneminder.py
@@ -12,7 +12,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import STATE_UNKNOWN
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.helpers.entity import Entity
-import homeassistant.components.zoneminder as zoneminder
+from homeassistant.components import zoneminder
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py
index b2a913c2af8..c6356efe157 100644
--- a/homeassistant/components/sensor/zwave.py
+++ b/homeassistant/components/sensor/zwave.py
@@ -8,7 +8,7 @@ import logging
from homeassistant.components.sensor import DOMAIN
from homeassistant.components import zwave
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
-from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
@@ -64,7 +64,7 @@ class ZWaveMultilevelSensor(ZWaveSensor):
"""Return the state of the sensor."""
if self._units in ('C', 'F'):
return round(self._state, 1)
- elif isinstance(self._state, float):
+ if isinstance(self._state, float):
return round(self._state, 2)
return self._state
@@ -74,7 +74,7 @@ class ZWaveMultilevelSensor(ZWaveSensor):
"""Return the unit the value is expressed in."""
if self._units == 'C':
return TEMP_CELSIUS
- elif self._units == 'F':
+ if self._units == 'F':
return TEMP_FAHRENHEIT
return self._units
diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py
new file mode 100644
index 00000000000..dc9f9cc4c25
--- /dev/null
+++ b/homeassistant/components/sisyphus.py
@@ -0,0 +1,84 @@
+"""
+Support for controlling Sisyphus Kinetic Art Tables.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/sisyphus/
+"""
+import asyncio
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_HOST,
+ CONF_NAME,
+ EVENT_HOMEASSISTANT_STOP
+)
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.discovery import async_load_platform
+
+REQUIREMENTS = ['sisyphus-control==2.1']
+
+_LOGGER = logging.getLogger(__name__)
+
+DATA_SISYPHUS = 'sisyphus'
+DOMAIN = 'sisyphus'
+
+AUTODETECT_SCHEMA = vol.Schema({})
+
+TABLE_SCHEMA = vol.Schema({
+ vol.Required(CONF_NAME): cv.string,
+ vol.Required(CONF_HOST): cv.string,
+})
+
+TABLES_SCHEMA = vol.Schema([TABLE_SCHEMA])
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA),
+}, extra=vol.ALLOW_EXTRA)
+
+
+async def async_setup(hass, config):
+ """Set up the sisyphus component."""
+ from sisyphus_control import Table
+ tables = hass.data.setdefault(DATA_SISYPHUS, {})
+ table_configs = config.get(DOMAIN)
+ session = async_get_clientsession(hass)
+
+ async def add_table(host, name=None):
+ """Add platforms for a single table with the given hostname."""
+ table = await Table.connect(host, session)
+ if name is None:
+ name = table.name
+ tables[name] = table
+ _LOGGER.debug("Connected to %s at %s", name, host)
+
+ hass.async_add_job(async_load_platform(
+ hass, 'light', DOMAIN, {
+ CONF_NAME: name,
+ }, config
+ ))
+ hass.async_add_job(async_load_platform(
+ hass, 'media_player', DOMAIN, {
+ CONF_NAME: name,
+ CONF_HOST: host,
+ }, config
+ ))
+
+ if isinstance(table_configs, dict): # AUTODETECT_SCHEMA
+ for ip_address in await Table.find_table_ips(session):
+ await add_table(ip_address)
+ else: # TABLES_SCHEMA
+ for conf in table_configs:
+ await add_table(conf[CONF_HOST], conf[CONF_NAME])
+
+ async def close_tables(*args):
+ """Close all table objects."""
+ tasks = [table.close() for table in tables.values()]
+ if tasks:
+ await asyncio.wait(tasks)
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables)
+
+ return True
diff --git a/homeassistant/components/sleepiq.py b/homeassistant/components/sleepiq.py
index df36eef2f9e..4d4ecf0160b 100644
--- a/homeassistant/components/sleepiq.py
+++ b/homeassistant/components/sleepiq.py
@@ -73,7 +73,7 @@ def setup(hass, config):
return True
-class SleepIQData(object):
+class SleepIQData:
"""Get the latest data from SleepIQ."""
def __init__(self, client):
diff --git a/homeassistant/components/smappee.py b/homeassistant/components/smappee.py
index b35cd8cf5a8..7904f0a6cce 100644
--- a/homeassistant/components/smappee.py
+++ b/homeassistant/components/smappee.py
@@ -16,7 +16,7 @@ from homeassistant.util import Throttle
from homeassistant.helpers.discovery import load_platform
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['smappy==0.2.15']
+REQUIREMENTS = ['smappy==0.2.16']
_LOGGER = logging.getLogger(__name__)
@@ -71,7 +71,7 @@ def setup(hass, config):
return True
-class Smappee(object):
+class Smappee:
"""Stores data retrieved from Smappee sensor."""
def __init__(self, client_id, client_secret, username,
diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips.py
index 4f50c6beaaa..34290819106 100644
--- a/homeassistant/components/snips.py
+++ b/homeassistant/components/snips.py
@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers import intent, config_validation as cv
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
DOMAIN = 'snips'
DEPENDENCIES = ['mqtt']
diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py
index a5a45417de2..4c5592c02c2 100644
--- a/homeassistant/components/sonos/__init__.py
+++ b/homeassistant/components/sonos/__init__.py
@@ -22,7 +22,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
"""Set up Sonos from a config entry."""
- hass.async_add_job(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'media_player'))
return True
@@ -31,7 +31,7 @@ async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
import soco
- return await hass.async_add_job(soco.discover)
+ return await hass.async_add_executor_job(soco.discover)
config_entry_flow.register_discovery_flow(DOMAIN, 'Sonos', _async_has_devices)
diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py
index 9742bc25c63..bf7db87f06b 100644
--- a/homeassistant/components/spc.py
+++ b/homeassistant/components/spc.py
@@ -56,14 +56,14 @@ def async_setup(hass, config):
# add sensor devices for each zone (typically motion/fire/door sensors)
zones = yield from api.get_zones()
if zones:
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN,
{ATTR_DISCOVER_DEVICES: zones}, config))
# create a separate alarm panel for each area
areas = yield from api.get_areas()
if areas:
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'alarm_control_panel', DOMAIN,
{ATTR_DISCOVER_AREAS: areas}, config))
@@ -151,7 +151,7 @@ def _ws_process_message(message, async_callback, *args):
"Unsuccessful websocket message delivered, ignoring: %s", message)
try:
yield from async_callback(message['data']['sia'], *args)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_LOGGER.exception("Exception in callback, ignoring")
diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider.py
new file mode 100644
index 00000000000..48632be6bad
--- /dev/null
+++ b/homeassistant/components/spider.py
@@ -0,0 +1,65 @@
+"""
+Support for Spider Smart devices.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/spider/
+"""
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.discovery import load_platform
+
+REQUIREMENTS = ['spiderpy==1.2.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = 'spider'
+
+SPIDER_COMPONENTS = [
+ 'climate',
+ 'switch'
+]
+
+SCAN_INTERVAL = timedelta(seconds=120)
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
+ cv.time_period,
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up Spider Component."""
+ from spiderpy.spiderapi import SpiderApi
+ from spiderpy.spiderapi import UnauthorizedException
+
+ username = config[DOMAIN][CONF_USERNAME]
+ password = config[DOMAIN][CONF_PASSWORD]
+ refresh_rate = config[DOMAIN][CONF_SCAN_INTERVAL]
+
+ try:
+ api = SpiderApi(username, password, refresh_rate.total_seconds())
+
+ hass.data[DOMAIN] = {
+ 'controller': api,
+ 'thermostats': api.get_thermostats(),
+ 'power_plugs': api.get_power_plugs()
+ }
+
+ for component in SPIDER_COMPONENTS:
+ load_platform(hass, component, DOMAIN, {})
+
+ _LOGGER.debug("Connection with Spider API succeeded")
+ return True
+ except UnauthorizedException:
+ _LOGGER.error("Can't connect to the Spider API")
+ return False
diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py
index b9ee8126ed3..cb69240ee73 100644
--- a/homeassistant/components/switch/__init__.py
+++ b/homeassistant/components/switch/__init__.py
@@ -114,7 +114,8 @@ async def async_setup(hass, config):
if not switch.should_poll:
continue
- update_tasks.append(switch.async_update_ha_state(True))
+ update_tasks.append(
+ switch.async_update_ha_state(True, service.context))
if update_tasks:
await asyncio.wait(update_tasks, loop=hass.loop)
diff --git a/homeassistant/components/switch/anel_pwrctrl.py b/homeassistant/components/switch/anel_pwrctrl.py
index 4e62b711979..01d27b8abcd 100644
--- a/homeassistant/components/switch/anel_pwrctrl.py
+++ b/homeassistant/components/switch/anel_pwrctrl.py
@@ -107,7 +107,7 @@ class PwrCtrlSwitch(SwitchDevice):
self._port.off()
-class PwrCtrlDevice(object):
+class PwrCtrlDevice:
"""Device representation for per device throttling."""
def __init__(self, device):
diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py
index 1547f4f1dee..2bcb04c566e 100644
--- a/homeassistant/components/switch/arduino.py
+++ b/homeassistant/components/switch/arduino.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.arduino as arduino
+from homeassistant.components import arduino
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/switch/bbb_gpio.py b/homeassistant/components/switch/bbb_gpio.py
index 5412f559b73..94952ac736b 100644
--- a/homeassistant/components/switch/bbb_gpio.py
+++ b/homeassistant/components/switch/bbb_gpio.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
-import homeassistant.components.bbb_gpio as bbb_gpio
+from homeassistant.components import bbb_gpio
from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME)
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py
index 46002112177..6b754effaf1 100644
--- a/homeassistant/components/switch/broadlink.py
+++ b/homeassistant/components/switch/broadlink.py
@@ -348,7 +348,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch):
self._state = self._parent_device.get_outlet_status(self._slot)
-class BroadlinkMP1Switch(object):
+class BroadlinkMP1Switch:
"""Representation of a Broadlink switch - To fetch states of all slots."""
def __init__(self, device):
diff --git a/homeassistant/components/switch/digitalloggers.py b/homeassistant/components/switch/digitalloggers.py
index f3af70c6222..29e6771d1d5 100644
--- a/homeassistant/components/switch/digitalloggers.py
+++ b/homeassistant/components/switch/digitalloggers.py
@@ -122,7 +122,7 @@ class DINRelay(SwitchDevice):
self._state = outlet_status[2] == 'ON'
-class DINRelayDevice(object):
+class DINRelayDevice:
"""Device representation for per device throttling."""
def __init__(self, power_switch):
diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/switch/dlink.py
index 1c7253c4ec3..9ce324ef6bb 100644
--- a/homeassistant/components/switch/dlink.py
+++ b/homeassistant/components/switch/dlink.py
@@ -125,7 +125,7 @@ class SmartPlugSwitch(SwitchDevice):
self.data.update()
-class SmartPlugData(object):
+class SmartPlugData:
"""Get the latest data from smart plug."""
def __init__(self, smartplug):
diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/switch/fritzdect.py
index 9968f631260..9c0f852846a 100644
--- a/homeassistant/components/switch/fritzdect.py
+++ b/homeassistant/components/switch/fritzdect.py
@@ -163,7 +163,7 @@ class FritzDectSwitch(SwitchDevice):
self.data.is_online = False
-class FritzDectSwitchData(object):
+class FritzDectSwitchData:
"""Get the latest data from the fritz box."""
def __init__(self, fritz, ain):
diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/switch/ihc.py
index 499a4ca53a7..3f461784693 100644
--- a/homeassistant/components/switch/ihc.py
+++ b/homeassistant/components/switch/ihc.py
@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.ihc/
"""
-from xml.etree.ElementTree import Element
-
import voluptuous as vol
from homeassistant.components.ihc import (
@@ -53,7 +51,7 @@ class IHCSwitch(IHCDevice, SwitchDevice):
"""IHC Switch."""
def __init__(self, ihc_controller, name: str, ihc_id: int,
- info: bool, product: Element = None) -> None:
+ info: bool, product=None) -> None:
"""Initialize the IHC switch."""
super().__init__(ihc_controller, name, ihc_id, product)
self._state = False
diff --git a/homeassistant/components/switch/insteon_local.py b/homeassistant/components/switch/insteon_local.py
index 4456436ea61..c4c8a854670 100644
--- a/homeassistant/components/switch/insteon_local.py
+++ b/homeassistant/components/switch/insteon_local.py
@@ -8,7 +8,7 @@ import logging
from datetime import timedelta
from homeassistant.components.switch import SwitchDevice
-import homeassistant.util as util
+from homeassistant import util
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/switch/litejet.py b/homeassistant/components/switch/litejet.py
index 1e7c46733ad..79ef4a5fd7f 100644
--- a/homeassistant/components/switch/litejet.py
+++ b/homeassistant/components/switch/litejet.py
@@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.litejet/
"""
import logging
-import homeassistant.components.litejet as litejet
+from homeassistant.components import litejet
from homeassistant.components.switch import SwitchDevice
DEPENDENCIES = ['litejet']
diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py
index ca70c212774..94e1d7ea6d6 100644
--- a/homeassistant/components/switch/modbus.py
+++ b/homeassistant/components/switch/modbus.py
@@ -7,7 +7,7 @@ https://home-assistant.io/components/switch.modbus/
import logging
import voluptuous as vol
-import homeassistant.components.modbus as modbus
+from homeassistant.components import modbus
from homeassistant.const import (
CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF)
from homeassistant.helpers.entity import ToggleEntity
diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py
index 1075888e199..eb91f8d846a 100644
--- a/homeassistant/components/switch/mqtt.py
+++ b/homeassistant/components/switch/mqtt.py
@@ -18,7 +18,7 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_ICON, STATE_ON)
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import async_get_last_state
@@ -31,12 +31,16 @@ DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False
CONF_UNIQUE_ID = 'unique_id'
+CONF_STATE_ON = "state_on"
+CONF_STATE_OFF = "state_off"
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_STATE_ON): cv.string,
+ vol.Optional(CONF_STATE_OFF): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -62,6 +66,8 @@ async def async_setup_platform(hass, config, async_add_devices,
config.get(CONF_RETAIN),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
+ config.get(CONF_STATE_ON),
+ config.get(CONF_STATE_OFF),
config.get(CONF_OPTIMISTIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
@@ -75,9 +81,10 @@ class MqttSwitch(MqttAvailability, SwitchDevice):
def __init__(self, name, icon,
state_topic, command_topic, availability_topic,
- qos, retain, payload_on, payload_off, optimistic,
- payload_available, payload_not_available,
- unique_id: Optional[str], value_template):
+ qos, retain, payload_on, payload_off, state_on,
+ state_off, optimistic, payload_available,
+ payload_not_available, unique_id: Optional[str],
+ value_template):
"""Initialize the MQTT switch."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -90,6 +97,8 @@ class MqttSwitch(MqttAvailability, SwitchDevice):
self._retain = retain
self._payload_on = payload_on
self._payload_off = payload_off
+ self._state_on = state_on if state_on else self._payload_on
+ self._state_off = state_off if state_off else self._payload_off
self._optimistic = optimistic
self._template = value_template
self._unique_id = unique_id
@@ -104,9 +113,9 @@ class MqttSwitch(MqttAvailability, SwitchDevice):
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
- if payload == self._payload_on:
+ if payload == self._state_on:
self._state = True
- elif payload == self._payload_off:
+ elif payload == self._state_off:
self._state = False
self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py
index 57fa4b00c98..7ffce13ff6a 100644
--- a/homeassistant/components/switch/pilight.py
+++ b/homeassistant/components/switch/pilight.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
-import homeassistant.components.pilight as pilight
+from homeassistant.components import pilight
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_SWITCHES, CONF_STATE,
CONF_PROTOCOL, STATE_ON)
@@ -80,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
-class _ReceiveHandle(object):
+class _ReceiveHandle:
def __init__(self, config, echo):
"""Initialize the handle."""
self.config_items = config.items()
diff --git a/homeassistant/components/switch/pulseaudio_loopback.py b/homeassistant/components/switch/pulseaudio_loopback.py
index e25368f3c5c..06f2ee5f550 100644
--- a/homeassistant/components/switch/pulseaudio_loopback.py
+++ b/homeassistant/components/switch/pulseaudio_loopback.py
@@ -11,7 +11,7 @@ from datetime import timedelta
import voluptuous as vol
-import homeassistant.util as util
+from homeassistant import util
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py
index 68e91612008..17b5c8e40d5 100644
--- a/homeassistant/components/switch/rfxtrx.py
+++ b/homeassistant/components/switch/rfxtrx.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.rfxtrx as rfxtrx
+from homeassistant.components import rfxtrx
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.components.rfxtrx import (
CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS,
diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py
index 26de2a78e18..300af4be61d 100644
--- a/homeassistant/components/switch/rpi_gpio.py
+++ b/homeassistant/components/switch/rpi_gpio.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
-import homeassistant.components.rpi_gpio as rpi_gpio
+from homeassistant.components import rpi_gpio
from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/switch/rpi_pfio.py b/homeassistant/components/switch/rpi_pfio.py
index 3031b1e0290..dad0c7c59ba 100644
--- a/homeassistant/components/switch/rpi_pfio.py
+++ b/homeassistant/components/switch/rpi_pfio.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.rpi_pfio as rpi_pfio
+from homeassistant.components import rpi_pfio
from homeassistant.components.switch import PLATFORM_SCHEMA
from homeassistant.const import ATTR_NAME, DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py
index 8b2734612de..b549f351afc 100644
--- a/homeassistant/components/switch/scsgate.py
+++ b/homeassistant/components/switch/scsgate.py
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.scsgate as scsgate
+from homeassistant.components import scsgate
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_STATE, CONF_NAME, CONF_DEVICES)
@@ -152,7 +152,7 @@ class SCSGateSwitch(SwitchDevice):
)
-class SCSGateScenarioSwitch(object):
+class SCSGateScenarioSwitch:
"""Provides a SCSGate scenario switch.
This switch is always in an 'off" state, when toggled it's used to trigger
diff --git a/homeassistant/components/switch/spider.py b/homeassistant/components/switch/spider.py
new file mode 100644
index 00000000000..94b7db8f1e5
--- /dev/null
+++ b/homeassistant/components/switch/spider.py
@@ -0,0 +1,77 @@
+"""
+Support for Spider switches.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/switch.spider/
+"""
+
+import logging
+
+from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN
+from homeassistant.components.switch import SwitchDevice
+
+DEPENDENCIES = ['spider']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the Spider thermostat."""
+ if discovery_info is None:
+ return
+
+ devices = [SpiderPowerPlug(hass.data[SPIDER_DOMAIN]['controller'], device)
+ for device in hass.data[SPIDER_DOMAIN]['power_plugs']]
+
+ add_devices(devices, True)
+
+
+class SpiderPowerPlug(SwitchDevice):
+ """Representation of a Spider Power Plug."""
+
+ def __init__(self, api, power_plug):
+ """Initialize the Vera device."""
+ self.api = api
+ self.power_plug = power_plug
+
+ @property
+ def unique_id(self):
+ """Return the ID of this switch."""
+ return self.power_plug.id
+
+ @property
+ def name(self):
+ """Return the name of the switch if any."""
+ return self.power_plug.name
+
+ @property
+ def current_power_w(self):
+ """Return the current power usage in W."""
+ return round(self.power_plug.current_energy_consumption)
+
+ @property
+ def today_energy_kwh(self):
+ """Return the current power usage in Kwh."""
+ return round(self.power_plug.today_energy_consumption / 1000, 2)
+
+ @property
+ def is_on(self):
+ """Return true if switch is on. Standby is on."""
+ return self.power_plug.is_on
+
+ @property
+ def available(self):
+ """Return true if switch is available."""
+ return self.power_plug.is_available
+
+ def turn_on(self, **kwargs):
+ """Turn device on."""
+ self.power_plug.turn_on()
+
+ def turn_off(self, **kwargs):
+ """Turn device off."""
+ self.power_plug.turn_off()
+
+ def update(self):
+ """Get the latest data."""
+ self.power_plug = self.api.get_power_plug(self.unique_id)
diff --git a/homeassistant/components/switch/thinkingcleaner.py b/homeassistant/components/switch/thinkingcleaner.py
index 37c2f52e228..0753435cfba 100644
--- a/homeassistant/components/switch/thinkingcleaner.py
+++ b/homeassistant/components/switch/thinkingcleaner.py
@@ -8,8 +8,7 @@ import time
import logging
from datetime import timedelta
-import homeassistant.util as util
-
+from homeassistant import util
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.helpers.entity import ToggleEntity
diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py
index eb54e7982a7..0cacdfe1539 100644
--- a/homeassistant/components/switch/tplink.py
+++ b/homeassistant/components/switch/tplink.py
@@ -87,8 +87,6 @@ class SmartPlugSwitch(SwitchDevice):
"""Update the TP-Link switch's state."""
from pyHS100 import SmartDeviceException
try:
- self._available = True
-
self._state = self.smartplug.state == \
self.smartplug.SWITCH_STATE_ON
@@ -121,6 +119,10 @@ class SmartPlugSwitch(SwitchDevice):
# Device returned no daily history
pass
+ self._available = True
+
except (SmartDeviceException, OSError) as ex:
- _LOGGER.warning("Could not read state for %s: %s", self.name, ex)
- self._available = False
+ if self._available:
+ _LOGGER.warning(
+ "Could not read state for %s: %s", self.name, ex)
+ self._available = False
diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py
index c18ad492d40..35ea435bf48 100644
--- a/homeassistant/components/switch/wemo.py
+++ b/homeassistant/components/switch/wemo.py
@@ -35,7 +35,7 @@ WEMO_STANDBY = 8
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up discovered WeMo switches."""
- import pywemo.discovery as discovery
+ from pywemo import discovery
if discovery_info is not None:
location = discovery_info['ssdp_description']
@@ -166,9 +166,9 @@ class WemoSwitch(SwitchDevice):
standby_state = int(self.insight_params['state'])
if standby_state == WEMO_ON:
return STATE_ON
- elif standby_state == WEMO_OFF:
+ if standby_state == WEMO_OFF:
return STATE_OFF
- elif standby_state == WEMO_STANDBY:
+ if standby_state == WEMO_STANDBY:
return STATE_STANDBY
return STATE_UNKNOWN
diff --git a/homeassistant/components/switch/zoneminder.py b/homeassistant/components/switch/zoneminder.py
index adf3bf2d9bd..fa32843eb4b 100644
--- a/homeassistant/components/switch/zoneminder.py
+++ b/homeassistant/components/switch/zoneminder.py
@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_COMMAND_ON, CONF_COMMAND_OFF)
-import homeassistant.components.zoneminder as zoneminder
+from homeassistant.components import zoneminder
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py
index 8a0a1683aa4..31f942bd3af 100644
--- a/homeassistant/components/switch/zwave.py
+++ b/homeassistant/components/switch/zwave.py
@@ -8,7 +8,7 @@ import logging
import time
from homeassistant.components.switch import DOMAIN, SwitchDevice
from homeassistant.components import zwave
-from homeassistant.components.zwave import workaround, async_setup_platform # noqa # pylint: disable=unused-import
+from homeassistant.components.zwave import workaround, async_setup_platform # noqa pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/tahoma.py b/homeassistant/components/tahoma.py
index ba91dd7c1fc..1cbc81709c4 100644
--- a/homeassistant/components/tahoma.py
+++ b/homeassistant/components/tahoma.py
@@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
TAHOMA_COMPONENTS = [
- 'scene', 'sensor', 'cover', 'switch'
+ 'scene', 'sensor', 'cover', 'switch', 'binary_sensor'
]
TAHOMA_TYPES = {
@@ -50,6 +50,7 @@ TAHOMA_TYPES = {
'io:WindowOpenerVeluxIOComponent': 'cover',
'io:LightIOSystemSensor': 'sensor',
'rts:GarageDoor4TRTSComponent': 'switch',
+ 'rtds:RTDSSmokeSensor': 'smoke',
}
diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py
index b9329a46b72..53695102601 100644
--- a/homeassistant/components/telegram_bot/__init__.py
+++ b/homeassistant/components/telegram_bot/__init__.py
@@ -502,7 +502,7 @@ class TelegramNotificationService:
text, chat_id=chat_id, message_id=message_id,
inline_message_id=inline_message_id,
**params)
- elif type_edit == SERVICE_EDIT_CAPTION:
+ if type_edit == SERVICE_EDIT_CAPTION:
func_send = self.bot.editMessageCaption
params[ATTR_CAPTION] = kwargs.get(ATTR_CAPTION)
else:
diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py
index ba8dc54b264..6ee42b32504 100644
--- a/homeassistant/components/telegram_bot/polling.py
+++ b/homeassistant/components/telegram_bot/polling.py
@@ -92,8 +92,7 @@ class TelegramPoll(BaseTelegramBotEntity):
if resp.status == 200:
_json = yield from resp.json()
return _json
- else:
- raise WrongHttpStatus('wrong status {}'.format(resp.status))
+ raise WrongHttpStatus('wrong status {}'.format(resp.status))
finally:
if resp is not None:
yield from resp.release()
diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py
index dfb4b1e5fa9..c2b7ba9ba0f 100644
--- a/homeassistant/components/tellduslive.py
+++ b/homeassistant/components/tellduslive.py
@@ -206,7 +206,7 @@ def setup(hass, config, session=None):
return True
-class TelldusLiveClient(object):
+class TelldusLiveClient:
"""Get the latest data and update the states."""
def __init__(self, hass, config, session):
@@ -240,11 +240,11 @@ class TelldusLiveClient(object):
from tellduslive import (DIM, UP, TURNON)
if device.methods & DIM:
return 'light'
- elif device.methods & UP:
+ if device.methods & UP:
return 'cover'
- elif device.methods & TURNON:
+ if device.methods & TURNON:
return 'switch'
- elif device.methods == 0:
+ if device.methods == 0:
return 'binary_sensor'
_LOGGER.warning(
"Unidentified device type (methods: %d)", device.methods)
@@ -349,9 +349,9 @@ class TelldusLiveEntity(Entity):
BATTERY_OK)
if self.device.battery == BATTERY_LOW:
return 1
- elif self.device.battery == BATTERY_UNKNOWN:
+ if self.device.battery == BATTERY_UNKNOWN:
return None
- elif self.device.battery == BATTERY_OK:
+ if self.device.battery == BATTERY_OK:
return 100
return self.device.battery # Percentage
diff --git a/homeassistant/components/thingspeak.py b/homeassistant/components/thingspeak.py
index a21d44527a1..9a876a87683 100644
--- a/homeassistant/components/thingspeak.py
+++ b/homeassistant/components/thingspeak.py
@@ -11,9 +11,8 @@ import voluptuous as vol
from homeassistant.const import (
CONF_API_KEY, CONF_ID, CONF_WHITELIST, STATE_UNAVAILABLE, STATE_UNKNOWN)
-from homeassistant.helpers import state as state_helper
+from homeassistant.helpers import event, state as state_helper
import homeassistant.helpers.config_validation as cv
-import homeassistant.helpers.event as event
REQUIREMENTS = ['thingspeak==0.4.1']
diff --git a/homeassistant/components/toon.py b/homeassistant/components/toon.py
index ffb820e8148..cfd0d297d54 100644
--- a/homeassistant/components/toon.py
+++ b/homeassistant/components/toon.py
@@ -59,7 +59,7 @@ def setup(hass, config):
return True
-class ToonDataStore(object):
+class ToonDataStore:
"""An object to store the Toon data."""
def __init__(self, username, password, gas=DEFAULT_GAS,
diff --git a/homeassistant/components/tradfri.py b/homeassistant/components/tradfri.py
index 9ed613abde0..b2e41902552 100644
--- a/homeassistant/components/tradfri.py
+++ b/homeassistant/components/tradfri.py
@@ -166,8 +166,8 @@ async def _setup_gateway(hass, hass_config, host, identity, key,
return True
gateways[gateway_id] = gateway
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'light', DOMAIN, {'gateway': gateway_id}, hass_config))
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {'gateway': gateway_id}, hass_config))
return True
diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py
index 999b584360c..f060c9f353a 100644
--- a/homeassistant/components/tts/__init__.py
+++ b/homeassistant/components/tts/__init__.py
@@ -29,7 +29,7 @@ from homeassistant.helpers import config_per_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.setup import async_prepare_setup_platform
-REQUIREMENTS = ['mutagen==1.40.0']
+REQUIREMENTS = ['mutagen==1.41.0']
_LOGGER = logging.getLogger(__name__)
@@ -169,7 +169,7 @@ async def async_setup(hass, config):
return True
-class SpeechManager(object):
+class SpeechManager:
"""Representation of a speech store."""
def __init__(self, hass):
@@ -440,7 +440,7 @@ class SpeechManager(object):
return data_bytes.getvalue()
-class Provider(object):
+class Provider:
"""Represent a single TTS provider."""
hass = None
diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py
index c557774b5f1..490c11baad7 100644
--- a/homeassistant/components/tuya.py
+++ b/homeassistant/components/tuya.py
@@ -33,7 +33,11 @@ SERVICE_FORCE_UPDATE = 'force_update'
SERVICE_PULL_DEVICES = 'pull_devices'
TUYA_TYPE_TO_HA = {
+ 'climate': 'climate',
+ 'cover': 'cover',
+ 'fan': 'fan',
'light': 'light',
+ 'scene': 'scene',
'switch': 'switch',
}
@@ -140,11 +144,6 @@ class TuyaDevice(Entity):
"""Return Tuya device name."""
return self.tuya.name()
- @property
- def entity_picture(self):
- """Return the entity picture to use in the frontend, if any."""
- return self.tuya.iconurl()
-
@property
def available(self):
"""Return if the device is available."""
diff --git a/homeassistant/components/upcloud.py b/homeassistant/components/upcloud.py
index 9de7f6c4444..0f503dcdc39 100644
--- a/homeassistant/components/upcloud.py
+++ b/homeassistant/components/upcloud.py
@@ -92,7 +92,7 @@ def setup(hass, config):
return True
-class UpCloud(object):
+class UpCloud:
"""Handle all communication with the UpCloud API."""
def __init__(self, manager):
diff --git a/homeassistant/components/upnp.py b/homeassistant/components/upnp.py
index 8aeb93fed25..b4fe9d3fce9 100644
--- a/homeassistant/components/upnp.py
+++ b/homeassistant/components/upnp.py
@@ -88,7 +88,7 @@ async def async_setup(hass, config):
service = device.find_first_service(IP_SERVICE)
if _service['serviceType'] == CIC_SERVICE:
unit = config.get(CONF_UNITS)
- hass.async_add_job(discovery.async_load_platform(
+ hass.async_create_task(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {'unit': unit}, config))
except UpnpSoapError as error:
_LOGGER.error(error)
diff --git a/homeassistant/components/usps.py b/homeassistant/components/usps.py
index 364562f1119..41aa240492b 100644
--- a/homeassistant/components/usps.py
+++ b/homeassistant/components/usps.py
@@ -65,7 +65,7 @@ def setup(hass, config):
return True
-class USPSData(object):
+class USPSData:
"""Stores the data retrieved from USPS.
For each entity to use, acts as the single point responsible for fetching
diff --git a/homeassistant/components/vacuum/mqtt.py b/homeassistant/components/vacuum/mqtt.py
index 8c2f110257f..fd80f4cdbfb 100644
--- a/homeassistant/components/vacuum/mqtt.py
+++ b/homeassistant/components/vacuum/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
-import homeassistant.components.mqtt as mqtt
+from homeassistant.components import mqtt
from homeassistant.components.mqtt import MqttAvailability
from homeassistant.components.vacuum import (
SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED,
diff --git a/homeassistant/components/vacuum/neato.py b/homeassistant/components/vacuum/neato.py
index 6289fed265d..224e763a097 100644
--- a/homeassistant/components/vacuum/neato.py
+++ b/homeassistant/components/vacuum/neato.py
@@ -12,7 +12,8 @@ from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.components.vacuum import (
VacuumDevice, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME,
SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
- SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON)
+ SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON,
+ SUPPORT_LOCATE)
from homeassistant.components.neato import (
NEATO_ROBOTS, NEATO_LOGIN, NEATO_MAP_DATA, ACTION, ERRORS, MODE, ALERTS)
@@ -24,7 +25,7 @@ SCAN_INTERVAL = timedelta(minutes=5)
SUPPORT_NEATO = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \
SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \
- SUPPORT_STATUS | SUPPORT_MAP
+ SUPPORT_STATUS | SUPPORT_MAP | SUPPORT_LOCATE
ATTR_CLEAN_START = 'clean_start'
ATTR_CLEAN_STOP = 'clean_stop'
@@ -211,3 +212,7 @@ class NeatoConnectedVacuum(VacuumDevice):
self.robot.pause_cleaning()
if self._state['state'] == 3:
self.robot.resume_cleaning()
+
+ def locate(self, **kwargs):
+ """Locate the robot by making it emit a sound."""
+ self.robot.locate()
diff --git a/homeassistant/components/velux.py b/homeassistant/components/velux.py
index 47daf17f2a9..c3c6c1e2114 100644
--- a/homeassistant/components/velux.py
+++ b/homeassistant/components/velux.py
@@ -39,7 +39,7 @@ async def async_setup(hass, config):
return False
for component in SUPPORTED_DOMAINS:
- hass.async_add_job(
+ hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, {}, config))
return True
diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py
index 0ab5e7ce39a..5bc6260c0a7 100644
--- a/homeassistant/components/vera.py
+++ b/homeassistant/components/vera.py
@@ -19,7 +19,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_LIGHTS, CONF_EXCLUDE)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyvera==0.2.43']
+REQUIREMENTS = ['pyvera==0.2.44']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py
index b367752c247..1f26ab639d6 100644
--- a/homeassistant/components/verisure.py
+++ b/homeassistant/components/verisure.py
@@ -89,7 +89,7 @@ def setup(hass, config):
return True
-class VerisureHub(object):
+class VerisureHub:
"""A Verisure hub wrapper class."""
def __init__(self, domain_config, verisure):
diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall.py
index 6557be2fb1b..0ce8870bedf 100644
--- a/homeassistant/components/volvooncall.py
+++ b/homeassistant/components/volvooncall.py
@@ -136,12 +136,11 @@ class VolvoData:
if (vehicle.registration_number and
vehicle.registration_number.lower()) in self.names:
return self.names[vehicle.registration_number.lower()]
- elif (vehicle.vin and
- vehicle.vin.lower() in self.names):
+ if vehicle.vin and vehicle.vin.lower() in self.names:
return self.names[vehicle.vin.lower()]
- elif vehicle.registration_number:
+ if vehicle.registration_number:
return vehicle.registration_number
- elif vehicle.vin:
+ if vehicle.vin:
return vehicle.vin
return ''
diff --git a/homeassistant/components/vultr.py b/homeassistant/components/vultr.py
index 59fc707bb28..b28189444ee 100644
--- a/homeassistant/components/vultr.py
+++ b/homeassistant/components/vultr.py
@@ -74,7 +74,7 @@ def setup(hass, config):
return True
-class Vultr(object):
+class Vultr:
"""Handle all communication with the Vultr API."""
def __init__(self, api_key):
diff --git a/homeassistant/components/weather/darksky.py b/homeassistant/components/weather/darksky.py
index 7afa97fd4f6..6dac22bc941 100644
--- a/homeassistant/components/weather/darksky.py
+++ b/homeassistant/components/weather/darksky.py
@@ -149,7 +149,7 @@ class DarkSkyWeather(WeatherEntity):
self._ds_daily = self._dark_sky.daily
-class DarkSkyData(object):
+class DarkSkyData:
"""Get the latest data from Dark Sky."""
def __init__(self, api_key, latitude, longitude, units):
diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py
index 65fa7c8cb0f..334948b67fb 100644
--- a/homeassistant/components/weather/openweathermap.py
+++ b/homeassistant/components/weather/openweathermap.py
@@ -18,7 +18,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyowm==2.8.0']
+REQUIREMENTS = ['pyowm==2.9.0']
_LOGGER = logging.getLogger(__name__)
@@ -195,7 +195,7 @@ class OpenWeatherMapWeather(WeatherEntity):
self.forecast_data = self._owm.forecast_data
-class WeatherData(object):
+class WeatherData:
"""Get the latest data from OpenWeatherMap."""
def __init__(self, owm, latitude, longitude, mode):
diff --git a/homeassistant/components/weather/yweather.py b/homeassistant/components/weather/yweather.py
index f9befece5a4..3f12195d6bf 100644
--- a/homeassistant/components/weather/yweather.py
+++ b/homeassistant/components/weather/yweather.py
@@ -175,7 +175,7 @@ class YahooWeatherWeather(WeatherEntity):
return
-class YahooWeatherData(object):
+class YahooWeatherData:
"""Handle the Yahoo! API object and limit updates."""
def __init__(self, woeid, temp_unit):
diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py
index 98e3057338a..ed478550c7a 100644
--- a/homeassistant/components/websocket_api.py
+++ b/homeassistant/components/websocket_api.py
@@ -18,7 +18,7 @@ from voluptuous.humanize import humanize_error
from homeassistant.const import (
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP,
__version__)
-from homeassistant.core import callback
+from homeassistant.core import Context, callback
from homeassistant.loader import bind_hass
from homeassistant.remote import JSONEncoder
from homeassistant.helpers import config_validation as cv
@@ -262,6 +262,18 @@ class ActiveConnection:
self._handle_task = None
self._writer_task = None
+ @property
+ def user(self):
+ """Return the user associated with the connection."""
+ return self.request.get('hass_user')
+
+ def context(self, msg):
+ """Return a context."""
+ user = self.user
+ if user is None:
+ return Context()
+ return Context(user_id=user.id)
+
def debug(self, message1, message2=''):
"""Print a debug message."""
_LOGGER.debug("WS %s: %s %s", id(self.wsock), message1, message2)
@@ -287,7 +299,7 @@ class ActiveConnection:
@callback
def send_message_outside(self, message):
- """Send a message to the client outside of the main task.
+ """Send a message to the client.
Closes connection if the client is not reading the messages.
@@ -508,7 +520,8 @@ def handle_call_service(hass, connection, msg):
async def call_service_helper(msg):
"""Call a service and fire complete message."""
await hass.services.async_call(
- msg['domain'], msg['service'], msg.get('service_data'), True)
+ msg['domain'], msg['service'], msg.get('service_data'), True,
+ connection.context(msg))
connection.send_message_outside(result_message(msg['id']))
hass.async_add_job(call_service_helper(msg))
diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py
index e8c7db5efe1..27027cc9eb4 100644
--- a/homeassistant/components/wemo.py
+++ b/homeassistant/components/wemo.py
@@ -26,6 +26,7 @@ WEMO_MODEL_DISPATCH = {
'Insight': 'switch',
'LightSwitch': 'switch',
'Maker': 'switch',
+ 'Motion': 'binary_sensor',
'Sensor': 'binary_sensor',
'Socket': 'switch'
}
diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py
index 7c171d74967..c996572bf51 100644
--- a/homeassistant/components/wink/__init__.py
+++ b/homeassistant/components/wink/__init__.py
@@ -173,10 +173,9 @@ def _request_app_setup(hass, config):
ATTR_CLIENT_SECRET: client_secret})
setup(hass, config)
return
- else:
- error_msg = "Your input was invalid. Please try again."
- _configurator = hass.data[DOMAIN]['configuring'][DOMAIN]
- configurator.notify_errors(_configurator, error_msg)
+ error_msg = "Your input was invalid. Please try again."
+ _configurator = hass.data[DOMAIN]['configuring'][DOMAIN]
+ configurator.notify_errors(_configurator, error_msg)
start_url = "{}{}".format(hass.config.api.base_url,
WINK_AUTH_CALLBACK_PATH)
@@ -452,7 +451,7 @@ def setup(hass, config):
_man = siren.wink.device_manufacturer()
if (service.service != SERVICE_SET_AUTO_SHUTOFF and
service.service != SERVICE_ENABLE_SIREN and
- (_man != 'dome' and _man != 'wink')):
+ _man not in ('dome', 'wink')):
_LOGGER.error("Service only valid for Dome or Wink sirens")
return
@@ -487,7 +486,7 @@ def setup(hass, config):
has_dome_or_wink_siren = False
for siren in pywink.get_sirens():
_man = siren.device_manufacturer()
- if _man == "dome" or _man == "wink":
+ if _man in ("dome", "wink"):
has_dome_or_wink_siren = True
_id = siren.object_id() + siren.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py
index 3a84e963841..67bdf744251 100644
--- a/homeassistant/components/zigbee.py
+++ b/homeassistant/components/zigbee.py
@@ -124,7 +124,7 @@ def frame_is_relevant(entity, frame):
return True
-class ZigBeeConfig(object):
+class ZigBeeConfig:
"""Handle the fetching of configuration from the config file."""
def __init__(self, config):
diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py
index fc2e7fc912d..f88b911a6a5 100644
--- a/homeassistant/components/zwave/discovery_schemas.py
+++ b/homeassistant/components/zwave/discovery_schemas.py
@@ -37,7 +37,9 @@ DISCOVERY_SCHEMAS = [
const.DISC_OPTIONAL: True,
}})},
{const.DISC_COMPONENT: 'climate',
- const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_THERMOSTAT],
+ const.DISC_GENERIC_DEVICE_CLASS: [
+ const.GENERIC_TYPE_THERMOSTAT,
+ const.GENERIC_TYPE_SENSOR_MULTILEVEL],
const.DISC_VALUES: dict(DEFAULT_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [
diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py
index b62eeb67d32..312d72575a9 100644
--- a/homeassistant/components/zwave/util.py
+++ b/homeassistant/components/zwave/util.py
@@ -82,7 +82,7 @@ async def check_has_unique_id(entity, ready_callback, timeout_callback, loop):
if entity.unique_id:
ready_callback(waited)
return
- elif waited >= const.NODE_READY_WAIT_SECS:
+ if waited >= const.NODE_READY_WAIT_SECS:
# Wait up to NODE_READY_WAIT_SECS seconds for unique_id to appear.
timeout_callback(waited)
return
diff --git a/homeassistant/config.py b/homeassistant/config.py
index d9206d62250..6120a20fd63 100644
--- a/homeassistant/config.py
+++ b/homeassistant/config.py
@@ -7,8 +7,9 @@ import os
import re
import shutil
# pylint: disable=unused-import
-from typing import Any, Tuple, Optional # noqa: F401
-
+from typing import ( # noqa: F401
+ Any, Tuple, Optional, Dict, List, Union, Callable)
+from types import ModuleType
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -21,7 +22,7 @@ from homeassistant.const import (
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
__version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB,
CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_TYPE)
-from homeassistant.core import callback, DOMAIN as CONF_CORE
+from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import get_component, get_platform
from homeassistant.util.yaml import load_yaml, SECRET_YAML
@@ -193,7 +194,7 @@ def ensure_config_exists(config_dir: str, detect_location: bool = True)\
return config_path
-def create_default_config(config_dir: str, detect_location=True)\
+def create_default_config(config_dir: str, detect_location: bool = True)\
-> Optional[str]:
"""Create a default configuration file in given configuration directory.
@@ -276,7 +277,7 @@ def create_default_config(config_dir: str, detect_location=True)\
return None
-async def async_hass_config_yaml(hass):
+async def async_hass_config_yaml(hass: HomeAssistant) -> Dict:
"""Load YAML from a Home Assistant configuration file.
This function allow a component inside the asyncio loop to reload its
@@ -284,23 +285,26 @@ async def async_hass_config_yaml(hass):
This method is a coroutine.
"""
- def _load_hass_yaml_config():
+ def _load_hass_yaml_config() -> Dict:
path = find_config_file(hass.config.config_dir)
- conf = load_yaml_config_file(path)
- return conf
+ if path is None:
+ raise HomeAssistantError(
+ "Config file not found in: {}".format(hass.config.config_dir))
+ return load_yaml_config_file(path)
- conf = await hass.async_add_job(_load_hass_yaml_config)
- return conf
+ return await hass.async_add_executor_job(_load_hass_yaml_config)
-def find_config_file(config_dir: str) -> Optional[str]:
+def find_config_file(config_dir: Optional[str]) -> Optional[str]:
"""Look in given directory for supported configuration files."""
+ if config_dir is None:
+ return None
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
return config_path if os.path.isfile(config_path) else None
-def load_yaml_config_file(config_path):
+def load_yaml_config_file(config_path: str) -> Dict[Any, Any]:
"""Parse a YAML configuration file.
This method needs to run in an executor.
@@ -323,7 +327,7 @@ def load_yaml_config_file(config_path):
return conf_dict
-def process_ha_config_upgrade(hass):
+def process_ha_config_upgrade(hass: HomeAssistant) -> None:
"""Upgrade configuration if necessary.
This method needs to run in an executor.
@@ -360,7 +364,8 @@ def process_ha_config_upgrade(hass):
@callback
-def async_log_exception(ex, domain, config, hass):
+def async_log_exception(ex: vol.Invalid, domain: str, config: Dict,
+ hass: HomeAssistant) -> None:
"""Log an error for configuration validation.
This method must be run in the event loop.
@@ -371,7 +376,7 @@ def async_log_exception(ex, domain, config, hass):
@callback
-def _format_config_error(ex, domain, config):
+def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str:
"""Generate log exception for configuration validation.
This method must be run in the event loop.
@@ -396,7 +401,8 @@ def _format_config_error(ex, domain, config):
return message
-async def async_process_ha_core_config(hass, config):
+async def async_process_ha_core_config(
+ hass: HomeAssistant, config: Dict) -> None:
"""Process the [homeassistant] section from the configuration.
This method is a coroutine.
@@ -405,12 +411,12 @@ async def async_process_ha_core_config(hass, config):
# Only load auth during startup.
if not hasattr(hass, 'auth'):
- hass.auth = await auth.auth_manager_from_config(
- hass, config.get(CONF_AUTH_PROVIDERS, []))
+ setattr(hass, 'auth', await auth.auth_manager_from_config(
+ hass, config.get(CONF_AUTH_PROVIDERS, [])))
hac = hass.config
- def set_time_zone(time_zone_str):
+ def set_time_zone(time_zone_str: Optional[str]) -> None:
"""Help to set the time zone."""
if time_zone_str is None:
return
@@ -430,11 +436,10 @@ async def async_process_ha_core_config(hass, config):
if key in config:
setattr(hac, attr, config[key])
- if CONF_TIME_ZONE in config:
- set_time_zone(config.get(CONF_TIME_ZONE))
+ set_time_zone(config.get(CONF_TIME_ZONE))
# Init whitelist external dir
- hac.whitelist_external_dirs = set((hass.config.path('www'),))
+ hac.whitelist_external_dirs = {hass.config.path('www')}
if CONF_WHITELIST_EXTERNAL_DIRS in config:
hac.whitelist_external_dirs.update(
set(config[CONF_WHITELIST_EXTERNAL_DIRS]))
@@ -484,12 +489,12 @@ async def async_process_ha_core_config(hass, config):
hac.time_zone, hac.elevation):
return
- discovered = []
+ discovered = [] # type: List[Tuple[str, Any]]
# If we miss some of the needed values, auto detect them
if None in (hac.latitude, hac.longitude, hac.units,
hac.time_zone):
- info = await hass.async_add_job(
+ info = await hass.async_add_executor_job(
loc_util.detect_location_info)
if info is None:
@@ -515,7 +520,7 @@ async def async_process_ha_core_config(hass, config):
if hac.elevation is None and hac.latitude is not None and \
hac.longitude is not None:
- elevation = await hass.async_add_job(
+ elevation = await hass.async_add_executor_job(
loc_util.elevation, hac.latitude, hac.longitude)
hac.elevation = elevation
discovered.append(('elevation', elevation))
@@ -526,7 +531,8 @@ async def async_process_ha_core_config(hass, config):
", ".join('{}: {}'.format(key, val) for key, val in discovered))
-def _log_pkg_error(package, component, config, message):
+def _log_pkg_error(
+ package: str, component: str, config: Dict, message: str) -> None:
"""Log an error while merging packages."""
message = "Package {} setup failed. Component {} {}".format(
package, component, message)
@@ -539,12 +545,13 @@ def _log_pkg_error(package, component, config, message):
_LOGGER.error(message)
-def _identify_config_schema(module):
+def _identify_config_schema(module: ModuleType) -> \
+ Tuple[Optional[str], Optional[Dict]]:
"""Extract the schema and identify list or dict based."""
try:
- schema = module.CONFIG_SCHEMA.schema[module.DOMAIN]
+ schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore
except (AttributeError, KeyError):
- return (None, None)
+ return None, None
t_schema = str(schema)
if t_schema.startswith('{'):
return ('dict', schema)
@@ -553,9 +560,10 @@ def _identify_config_schema(module):
return '', schema
-def _recursive_merge(conf, package):
+def _recursive_merge(
+ conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]:
"""Merge package into conf, recursively."""
- error = False
+ error = False # type: Union[bool, str]
for key, pack_conf in package.items():
if isinstance(pack_conf, dict):
if not pack_conf:
@@ -572,13 +580,12 @@ def _recursive_merge(conf, package):
else:
if conf.get(key) is not None:
return key
- else:
- conf[key] = pack_conf
+ conf[key] = pack_conf
return error
-def merge_packages_config(hass, config, packages,
- _log_pkg_error=_log_pkg_error):
+def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict,
+ _log_pkg_error: Callable = _log_pkg_error) -> Dict:
"""Merge packages into the top-level configuration. Mutate config."""
# pylint: disable=too-many-nested-blocks
PACKAGES_CONFIG_SCHEMA(packages)
@@ -642,7 +649,8 @@ def merge_packages_config(hass, config, packages,
@callback
-def async_process_component_config(hass, config, domain):
+def async_process_component_config(
+ hass: HomeAssistant, config: Dict, domain: str) -> Optional[Dict]:
"""Check component configuration and return processed configuration.
Returns None on error.
@@ -653,7 +661,7 @@ def async_process_component_config(hass, config, domain):
if hasattr(component, 'CONFIG_SCHEMA'):
try:
- config = component.CONFIG_SCHEMA(config)
+ config = component.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
return None
@@ -663,7 +671,8 @@ def async_process_component_config(hass, config, domain):
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
- p_validated = component.PLATFORM_SCHEMA(p_config)
+ p_validated = component.PLATFORM_SCHEMA( # type: ignore
+ p_config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
continue
@@ -684,7 +693,8 @@ def async_process_component_config(hass, config, domain):
if hasattr(platform, 'PLATFORM_SCHEMA'):
# pylint: disable=no-member
try:
- p_validated = platform.PLATFORM_SCHEMA(p_validated)
+ p_validated = platform.PLATFORM_SCHEMA( # type: ignore
+ p_validated)
except vol.Invalid as ex:
async_log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
@@ -702,14 +712,14 @@ def async_process_component_config(hass, config, domain):
return config
-async def async_check_ha_config_file(hass):
+async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]:
"""Check if Home Assistant configuration file is valid.
This method is a coroutine.
"""
from homeassistant.scripts.check_config import check_ha_config_file
- res = await hass.async_add_job(
+ res = await hass.async_add_executor_job(
check_ha_config_file, hass)
if not res.errors:
@@ -718,7 +728,9 @@ async def async_check_ha_config_file(hass):
@callback
-def async_notify_setup_error(hass, component, display_link=False):
+def async_notify_setup_error(
+ hass: HomeAssistant, component: str,
+ display_link: bool = False) -> None:
"""Print a persistent notification.
This method must be run in the event loop.
diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py
index 2e5613057f1..8e2bb3fa5df 100644
--- a/homeassistant/config_entries.py
+++ b/homeassistant/config_entries.py
@@ -113,9 +113,10 @@ the flow from the config panel.
import logging
import uuid
+from typing import Set, Optional # noqa pylint: disable=unused-import
from homeassistant import data_entry_flow
-from homeassistant.core import callback
+from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component, async_process_deps_reqs
from homeassistant.util.decorator import Registry
@@ -163,8 +164,9 @@ class ConfigEntry:
__slots__ = ('entry_id', 'version', 'domain', 'title', 'data', 'source',
'state')
- def __init__(self, version, domain, title, data, source, entry_id=None,
- state=ENTRY_STATE_NOT_LOADED):
+ def __init__(self, version: str, domain: str, title: str, data: dict,
+ source: str, entry_id: Optional[str] = None,
+ state: str = ENTRY_STATE_NOT_LOADED) -> None:
"""Initialize a config entry."""
# Unique id of the config entry
self.entry_id = entry_id or uuid.uuid4().hex
@@ -187,7 +189,8 @@ class ConfigEntry:
# State of the entry (LOADED, NOT_LOADED)
self.state = state
- async def async_setup(self, hass, *, component=None):
+ async def async_setup(
+ self, hass: HomeAssistant, *, component=None) -> None:
"""Set up an entry."""
if component is None:
component = getattr(hass.components, self.domain)
@@ -279,7 +282,7 @@ class ConfigEntries:
@callback
def async_domains(self):
"""Return domains for which we have entries."""
- seen = set()
+ seen = set() # type: Set[ConfigEntry]
result = []
for entry in self._entries:
diff --git a/homeassistant/const.py b/homeassistant/const.py
index 34cc3329a56..33a00b65533 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -1,8 +1,8 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
-MINOR_VERSION = 74
-PATCH_VERSION = '2'
+MINOR_VERSION = 75
+PATCH_VERSION = '0.dev0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)
@@ -224,9 +224,6 @@ ATTR_ID = 'id'
# Name
ATTR_NAME = 'name'
-# Data for a SERVICE_EXECUTED event
-ATTR_SERVICE_CALL_ID = 'service_call_id'
-
# Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id'
diff --git a/homeassistant/core.py b/homeassistant/core.py
index 8b534bf1731..b17df2c11fe 100644
--- a/homeassistant/core.py
+++ b/homeassistant/core.py
@@ -4,9 +4,9 @@ Core components of Home Assistant.
Home Assistant is a Home Automation framework for observing the state
of entities and react to changes.
"""
-# pylint: disable=unused-import
import asyncio
from concurrent.futures import ThreadPoolExecutor
+import datetime
import enum
import logging
import os
@@ -15,18 +15,22 @@ import re
import sys
import threading
from time import monotonic
+import uuid
from types import MappingProxyType
+# pylint: disable=unused-import
from typing import ( # NOQA
- Optional, Any, Callable, List, TypeVar, Dict, Coroutine)
+ Optional, Any, Callable, List, TypeVar, Dict, Coroutine, Set,
+ TYPE_CHECKING, Awaitable, Iterator)
from async_timeout import timeout
+import attr
import voluptuous as vol
from voluptuous.humanize import humanize_error
from homeassistant.const import (
ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE,
- ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
+ ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE,
@@ -37,12 +41,21 @@ from homeassistant.exceptions import (
from homeassistant.util.async_ import (
run_coroutine_threadsafe, run_callback_threadsafe,
fire_coroutine_threadsafe)
-import homeassistant.util as util
+from homeassistant import util
import homeassistant.util.dt as dt_util
-import homeassistant.util.location as location
+from homeassistant.util import location
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
+# Typing imports that create a circular dependency
+# pylint: disable=using-constant-test
+if TYPE_CHECKING:
+ from homeassistant.config_entries import ConfigEntries # noqa
+
+# pylint: disable=invalid-name
T = TypeVar('T')
+CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable)
+CALLBACK_TYPE = Callable[[], None]
+# pylint: enable=invalid-name
DOMAIN = 'homeassistant'
@@ -73,7 +86,7 @@ def valid_state(state: str) -> bool:
return len(state) < 256
-def callback(func: Callable[..., T]) -> Callable[..., T]:
+def callback(func: CALLABLE_T) -> CALLABLE_T:
"""Annotation to mark method as safe to call from within the event loop."""
setattr(func, '_hass_callback', True)
return func
@@ -85,7 +98,7 @@ def is_callback(func: Callable[..., Any]) -> bool:
@callback
-def async_loop_exception_handler(loop, context):
+def async_loop_exception_handler(_: Any, context: Dict) -> None:
"""Handle all exception inside the core loop."""
kwargs = {}
exception = context.get('exception')
@@ -93,7 +106,8 @@ def async_loop_exception_handler(loop, context):
kwargs['exc_info'] = (type(exception), exception,
exception.__traceback__)
- _LOGGER.error("Error doing job: %s", context['message'], **kwargs)
+ _LOGGER.error( # type: ignore
+ "Error doing job: %s", context['message'], **kwargs)
class CoreState(enum.Enum):
@@ -109,24 +123,26 @@ class CoreState(enum.Enum):
return self.value # type: ignore
-class HomeAssistant(object):
+class HomeAssistant:
"""Root object of the Home Assistant home automation."""
- def __init__(self, loop=None):
+ def __init__(
+ self,
+ loop: Optional[asyncio.events.AbstractEventLoop] = None) -> None:
"""Initialize new Home Assistant object."""
if sys.platform == 'win32':
self.loop = loop or asyncio.ProactorEventLoop()
else:
self.loop = loop or asyncio.get_event_loop()
- executor_opts = {'max_workers': None}
+ executor_opts = {'max_workers': None} # type: Dict[str, Any]
if sys.version_info[:2] >= (3, 6):
executor_opts['thread_name_prefix'] = 'SyncWorker'
self.executor = ThreadPoolExecutor(**executor_opts)
self.loop.set_default_executor(self.executor)
self.loop.set_exception_handler(async_loop_exception_handler)
- self._pending_tasks = []
+ self._pending_tasks = [] # type: list
self._track_task = True
self.bus = EventBus(self)
self.services = ServiceRegistry(self)
@@ -135,10 +151,10 @@ class HomeAssistant(object):
self.components = loader.Components(self)
self.helpers = loader.Helpers(self)
# This is a dictionary that any component can store any data on.
- self.data = {}
+ self.data = {} # type: dict
self.state = CoreState.not_running
self.exit_code = 0 # type: int
- self.config_entries = None
+ self.config_entries = None # type: Optional[ConfigEntries]
@property
def is_running(self) -> bool:
@@ -163,7 +179,7 @@ class HomeAssistant(object):
self.loop.close()
return self.exit_code
- async def async_start(self):
+ async def async_start(self) -> None:
"""Finalize startup from inside the event loop.
This method is a coroutine.
@@ -171,14 +187,13 @@ class HomeAssistant(object):
_LOGGER.info("Starting Home Assistant")
self.state = CoreState.starting
- # pylint: disable=protected-access
- self.loop._thread_ident = threading.get_ident()
+ setattr(self.loop, '_thread_ident', threading.get_ident())
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
try:
# Only block for EVENT_HOMEASSISTANT_START listener
self.async_stop_track_tasks()
- with timeout(TIMEOUT_EVENT_START, loop=self.loop):
+ with timeout(TIMEOUT_EVENT_START):
await self.async_block_till_done()
except asyncio.TimeoutError:
_LOGGER.warning(
@@ -188,7 +203,7 @@ class HomeAssistant(object):
', '.join(self.config.components))
# Allow automations to set up the start triggers before changing state
- await asyncio.sleep(0, loop=self.loop)
+ await asyncio.sleep(0)
self.state = CoreState.running
_async_create_timer(self)
@@ -217,13 +232,14 @@ class HomeAssistant(object):
task = None
if asyncio.iscoroutine(target):
- task = self.loop.create_task(target)
+ task = self.loop.create_task(target) # type: ignore
elif is_callback(target):
self.loop.call_soon(target, *args)
elif asyncio.iscoroutinefunction(target):
task = self.loop.create_task(target(*args))
else:
- task = self.loop.run_in_executor(None, target, *args)
+ task = self.loop.run_in_executor( # type: ignore
+ None, target, *args)
# If a task is scheduled
if self._track_task and task is not None:
@@ -249,11 +265,11 @@ class HomeAssistant(object):
@callback
def async_add_executor_job(
self,
- target: Callable[..., Any],
- *args: Any) -> asyncio.Future:
+ target: Callable[..., T],
+ *args: Any) -> Awaitable[T]:
"""Add an executor job from within the event loop."""
task = self.loop.run_in_executor(
- None, target, *args) # type: asyncio.Future
+ None, target, *args)
# If a task is scheduled
if self._track_task:
@@ -262,12 +278,12 @@ class HomeAssistant(object):
return task
@callback
- def async_track_tasks(self):
+ def async_track_tasks(self) -> None:
"""Track tasks so you can wait for all tasks to be done."""
self._track_task = True
@callback
- def async_stop_track_tasks(self):
+ def async_stop_track_tasks(self) -> None:
"""Stop track tasks so you can't wait for all tasks to be done."""
self._track_task = False
@@ -290,19 +306,19 @@ class HomeAssistant(object):
run_coroutine_threadsafe(
self.async_block_till_done(), loop=self.loop).result()
- async def async_block_till_done(self):
+ async def async_block_till_done(self) -> None:
"""Block till all pending work is done."""
# To flush out any call_soon_threadsafe
- await asyncio.sleep(0, loop=self.loop)
+ await asyncio.sleep(0)
while self._pending_tasks:
pending = [task for task in self._pending_tasks
if not task.done()]
self._pending_tasks.clear()
if pending:
- await asyncio.wait(pending, loop=self.loop)
+ await asyncio.wait(pending)
else:
- await asyncio.sleep(0, loop=self.loop)
+ await asyncio.sleep(0)
def stop(self) -> None:
"""Stop Home Assistant and shuts down all threads."""
@@ -329,31 +345,55 @@ class HomeAssistant(object):
self.loop.stop()
+@attr.s(slots=True, frozen=True)
+class Context:
+ """The context that triggered something."""
+
+ user_id = attr.ib(
+ type=str,
+ default=None,
+ )
+ id = attr.ib(
+ type=str,
+ default=attr.Factory(lambda: uuid.uuid4().hex),
+ )
+
+ def as_dict(self) -> dict:
+ """Return a dictionary representation of the context."""
+ return {
+ 'id': self.id,
+ 'user_id': self.user_id,
+ }
+
+
class EventOrigin(enum.Enum):
"""Represent the origin of an event."""
local = 'LOCAL'
remote = 'REMOTE'
- def __str__(self):
+ def __str__(self) -> str:
"""Return the event."""
- return self.value
+ return self.value # type: ignore
-class Event(object):
+class Event:
"""Representation of an event within the bus."""
- __slots__ = ['event_type', 'data', 'origin', 'time_fired']
+ __slots__ = ['event_type', 'data', 'origin', 'time_fired', 'context']
- def __init__(self, event_type, data=None, origin=EventOrigin.local,
- time_fired=None):
+ def __init__(self, event_type: str, data: Optional[Dict] = None,
+ origin: EventOrigin = EventOrigin.local,
+ time_fired: Optional[int] = None,
+ context: Optional[Context] = None) -> None:
"""Initialize a new event."""
self.event_type = event_type
self.data = data or {}
self.origin = origin
self.time_fired = time_fired or dt_util.utcnow()
+ self.context = context or Context()
- def as_dict(self):
+ def as_dict(self) -> Dict:
"""Create a dict representation of this Event.
Async friendly.
@@ -363,9 +403,10 @@ class Event(object):
'data': dict(self.data),
'origin': str(self.origin),
'time_fired': self.time_fired,
+ 'context': self.context.as_dict()
}
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return the representation."""
# pylint: disable=maybe-no-member
if self.data:
@@ -376,16 +417,16 @@ class Event(object):
return "".format(self.event_type,
str(self.origin)[0])
- def __eq__(self, other):
+ def __eq__(self, other: Any) -> bool:
"""Return the comparison."""
- return (self.__class__ == other.__class__ and
+ return (self.__class__ == other.__class__ and # type: ignore
self.event_type == other.event_type and
self.data == other.data and
self.origin == other.origin and
self.time_fired == other.time_fired)
-class EventBus(object):
+class EventBus:
"""Allow the firing of and listening for events."""
def __init__(self, hass: HomeAssistant) -> None:
@@ -394,7 +435,7 @@ class EventBus(object):
self._hass = hass
@callback
- def async_listeners(self):
+ def async_listeners(self) -> Dict[str, int]:
"""Return dictionary with events and the number of listeners.
This method must be run in the event loop.
@@ -403,20 +444,23 @@ class EventBus(object):
for key in self._listeners}
@property
- def listeners(self):
+ def listeners(self) -> Dict[str, int]:
"""Return dictionary with events and the number of listeners."""
- return run_callback_threadsafe(
+ return run_callback_threadsafe( # type: ignore
self._hass.loop, self.async_listeners
).result()
- def fire(self, event_type: str, event_data=None, origin=EventOrigin.local):
+ def fire(self, event_type: str, event_data: Optional[Dict] = None,
+ origin: EventOrigin = EventOrigin.local,
+ context: Optional[Context] = None) -> None:
"""Fire an event."""
self._hass.loop.call_soon_threadsafe(
- self.async_fire, event_type, event_data, origin)
+ self.async_fire, event_type, event_data, origin, context)
@callback
- def async_fire(self, event_type: str, event_data=None,
- origin=EventOrigin.local):
+ def async_fire(self, event_type: str, event_data: Optional[Dict] = None,
+ origin: EventOrigin = EventOrigin.local,
+ context: Optional[Context] = None) -> None:
"""Fire an event.
This method must be run in the event loop.
@@ -429,7 +473,7 @@ class EventBus(object):
event_type != EVENT_HOMEASSISTANT_CLOSE):
listeners = match_all_listeners + listeners
- event = Event(event_type, event_data, origin)
+ event = Event(event_type, event_data, origin, None, context)
if event_type != EVENT_TIME_CHANGED:
_LOGGER.info("Bus:Handling %s", event)
@@ -440,7 +484,8 @@ class EventBus(object):
for func in listeners:
self._hass.async_add_job(func, event)
- def listen(self, event_type, listener):
+ def listen(
+ self, event_type: str, listener: Callable) -> CALLBACK_TYPE:
"""Listen for all events or events of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
@@ -449,7 +494,7 @@ class EventBus(object):
async_remove_listener = run_callback_threadsafe(
self._hass.loop, self.async_listen, event_type, listener).result()
- def remove_listener():
+ def remove_listener() -> None:
"""Remove the listener."""
run_callback_threadsafe(
self._hass.loop, async_remove_listener).result()
@@ -457,7 +502,8 @@ class EventBus(object):
return remove_listener
@callback
- def async_listen(self, event_type, listener):
+ def async_listen(
+ self, event_type: str, listener: Callable) -> CALLBACK_TYPE:
"""Listen for all events or events of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
@@ -470,13 +516,14 @@ class EventBus(object):
else:
self._listeners[event_type] = [listener]
- def remove_listener():
+ def remove_listener() -> None:
"""Remove the listener."""
self._async_remove_listener(event_type, listener)
return remove_listener
- def listen_once(self, event_type, listener):
+ def listen_once(
+ self, event_type: str, listener: Callable) -> CALLBACK_TYPE:
"""Listen once for event of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
@@ -488,7 +535,7 @@ class EventBus(object):
self._hass.loop, self.async_listen_once, event_type, listener,
).result()
- def remove_listener():
+ def remove_listener() -> None:
"""Remove the listener."""
run_callback_threadsafe(
self._hass.loop, async_remove_listener).result()
@@ -496,7 +543,8 @@ class EventBus(object):
return remove_listener
@callback
- def async_listen_once(self, event_type, listener):
+ def async_listen_once(
+ self, event_type: str, listener: Callable) -> CALLBACK_TYPE:
"""Listen once for event of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
@@ -507,8 +555,8 @@ class EventBus(object):
This method must be run in the event loop.
"""
@callback
- def onetime_listener(event):
- """Remove listener from eventbus and then fire listener."""
+ def onetime_listener(event: Event) -> None:
+ """Remove listener from event bus and then fire listener."""
if hasattr(onetime_listener, 'run'):
return
# Set variable so that we will never run twice.
@@ -523,7 +571,8 @@ class EventBus(object):
return self.async_listen(event_type, onetime_listener)
@callback
- def _async_remove_listener(self, event_type, listener):
+ def _async_remove_listener(
+ self, event_type: str, listener: Callable) -> None:
"""Remove a listener of a specific event_type.
This method must be run in the event loop.
@@ -540,7 +589,7 @@ class EventBus(object):
_LOGGER.warning("Unable to remove unknown listener %s", listener)
-class State(object):
+class State:
"""Object to represent a state within the state machine.
entity_id: the entity that is represented.
@@ -548,13 +597,17 @@ class State(object):
attributes: extra information on entity and state
last_changed: last time the state was changed, not the attributes.
last_updated: last time this object was updated.
+ context: Context in which it was created
"""
__slots__ = ['entity_id', 'state', 'attributes',
- 'last_changed', 'last_updated']
+ 'last_changed', 'last_updated', 'context']
- def __init__(self, entity_id, state, attributes=None, last_changed=None,
- last_updated=None):
+ def __init__(self, entity_id: str, state: Any,
+ attributes: Optional[Dict] = None,
+ last_changed: Optional[datetime.datetime] = None,
+ last_updated: Optional[datetime.datetime] = None,
+ context: Optional[Context] = None) -> None:
"""Initialize a new state."""
state = str(state)
@@ -573,25 +626,26 @@ class State(object):
self.attributes = MappingProxyType(attributes or {})
self.last_updated = last_updated or dt_util.utcnow()
self.last_changed = last_changed or self.last_updated
+ self.context = context or Context()
@property
- def domain(self):
+ def domain(self) -> str:
"""Domain of this state."""
return split_entity_id(self.entity_id)[0]
@property
- def object_id(self):
+ def object_id(self) -> str:
"""Object id of this state."""
return split_entity_id(self.entity_id)[1]
@property
- def name(self):
+ def name(self) -> str:
"""Name of this state."""
return (
self.attributes.get(ATTR_FRIENDLY_NAME) or
self.object_id.replace('_', ' '))
- def as_dict(self):
+ def as_dict(self) -> Dict:
"""Return a dict representation of the State.
Async friendly.
@@ -603,10 +657,11 @@ class State(object):
'state': self.state,
'attributes': dict(self.attributes),
'last_changed': self.last_changed,
- 'last_updated': self.last_updated}
+ 'last_updated': self.last_updated,
+ 'context': self.context.as_dict()}
@classmethod
- def from_dict(cls, json_dict):
+ def from_dict(cls, json_dict: Dict) -> Any:
"""Initialize a state from a dict.
Async friendly.
@@ -627,44 +682,51 @@ class State(object):
if isinstance(last_updated, str):
last_updated = dt_util.parse_datetime(last_updated)
- return cls(json_dict['entity_id'], json_dict['state'],
- json_dict.get('attributes'), last_changed, last_updated)
+ context = json_dict.get('context')
+ if context:
+ context = Context(**context)
- def __eq__(self, other):
+ return cls(json_dict['entity_id'], json_dict['state'],
+ json_dict.get('attributes'), last_changed, last_updated,
+ context)
+
+ def __eq__(self, other: Any) -> bool:
"""Return the comparison of the state."""
- return (self.__class__ == other.__class__ and
+ return (self.__class__ == other.__class__ and # type: ignore
self.entity_id == other.entity_id and
self.state == other.state and
self.attributes == other.attributes)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return the representation of the states."""
- attr = "; {}".format(util.repr_helper(self.attributes)) \
- if self.attributes else ""
+ attrs = "; {}".format(util.repr_helper(self.attributes)) \
+ if self.attributes else ""
return "".format(
- self.entity_id, self.state, attr,
+ self.entity_id, self.state, attrs,
dt_util.as_local(self.last_changed).isoformat())
-class StateMachine(object):
+class StateMachine:
"""Helper class that tracks the state of different entities."""
- def __init__(self, bus, loop):
+ def __init__(self, bus: EventBus,
+ loop: asyncio.events.AbstractEventLoop) -> None:
"""Initialize state machine."""
- self._states = {}
+ self._states = {} # type: Dict[str, State]
self._bus = bus
self._loop = loop
- def entity_ids(self, domain_filter=None):
+ def entity_ids(self, domain_filter: Optional[str] = None)-> List[str]:
"""List of entity ids that are being tracked."""
future = run_callback_threadsafe(
self._loop, self.async_entity_ids, domain_filter
)
- return future.result()
+ return future.result() # type: ignore
@callback
- def async_entity_ids(self, domain_filter=None):
+ def async_entity_ids(
+ self, domain_filter: Optional[str] = None) -> List[str]:
"""List of entity ids that are being tracked.
This method must be run in the event loop.
@@ -677,26 +739,27 @@ class StateMachine(object):
return [state.entity_id for state in self._states.values()
if state.domain == domain_filter]
- def all(self):
+ def all(self)-> List[State]:
"""Create a list of all states."""
- return run_callback_threadsafe(self._loop, self.async_all).result()
+ return run_callback_threadsafe( # type: ignore
+ self._loop, self.async_all).result()
@callback
- def async_all(self):
+ def async_all(self)-> List[State]:
"""Create a list of all states.
This method must be run in the event loop.
"""
return list(self._states.values())
- def get(self, entity_id):
+ def get(self, entity_id: str) -> Optional[State]:
"""Retrieve state of entity_id or None if not found.
Async friendly.
"""
return self._states.get(entity_id.lower())
- def is_state(self, entity_id, state):
+ def is_state(self, entity_id: str, state: State) -> bool:
"""Test if entity exists and is specified state.
Async friendly.
@@ -704,16 +767,16 @@ class StateMachine(object):
state_obj = self.get(entity_id)
return state_obj is not None and state_obj.state == state
- def remove(self, entity_id):
+ def remove(self, entity_id: str) -> bool:
"""Remove the state of an entity.
Returns boolean to indicate if an entity was removed.
"""
- return run_callback_threadsafe(
+ return run_callback_threadsafe( # type: ignore
self._loop, self.async_remove, entity_id).result()
@callback
- def async_remove(self, entity_id):
+ def async_remove(self, entity_id: str) -> bool:
"""Remove the state of an entity.
Returns boolean to indicate if an entity was removed.
@@ -733,7 +796,10 @@ class StateMachine(object):
})
return True
- def set(self, entity_id, new_state, attributes=None, force_update=False):
+ def set(self, entity_id: str, new_state: Any,
+ attributes: Optional[Dict] = None,
+ force_update: bool = False,
+ context: Optional[Context] = None) -> None:
"""Set the state of an entity, add entity if it does not exist.
Attributes is an optional dict to specify attributes of this state.
@@ -744,11 +810,14 @@ class StateMachine(object):
run_callback_threadsafe(
self._loop,
self.async_set, entity_id, new_state, attributes, force_update,
+ context,
).result()
@callback
- def async_set(self, entity_id, new_state, attributes=None,
- force_update=False):
+ def async_set(self, entity_id: str, new_state: Any,
+ attributes: Optional[Dict] = None,
+ force_update: bool = False,
+ context: Optional[Context] = None) -> None:
"""Set the state of an entity, add entity if it does not exist.
Attributes is an optional dict to specify attributes of this state.
@@ -762,30 +831,39 @@ class StateMachine(object):
new_state = str(new_state)
attributes = attributes or {}
old_state = self._states.get(entity_id)
- is_existing = old_state is not None
- same_state = (is_existing and old_state.state == new_state and
- not force_update)
- same_attr = is_existing and old_state.attributes == attributes
+ if old_state is None:
+ same_state = False
+ same_attr = False
+ last_changed = None
+ else:
+ same_state = (old_state.state == new_state and
+ not force_update)
+ same_attr = old_state.attributes == attributes
+ last_changed = old_state.last_changed if same_state else None
if same_state and same_attr:
return
- last_changed = old_state.last_changed if same_state else None
- state = State(entity_id, new_state, attributes, last_changed)
+ if context is None:
+ context = Context()
+
+ state = State(entity_id, new_state, attributes, last_changed, None,
+ context)
self._states[entity_id] = state
self._bus.async_fire(EVENT_STATE_CHANGED, {
'entity_id': entity_id,
'old_state': old_state,
'new_state': state,
- })
+ }, EventOrigin.local, context)
-class Service(object):
+class Service:
"""Representation of a callable service."""
__slots__ = ['func', 'schema', 'is_callback', 'is_coroutinefunction']
- def __init__(self, func, schema):
+ def __init__(self, func: Callable, schema: Optional[vol.Schema],
+ context: Optional[Context] = None) -> None:
"""Initialize a service."""
self.func = func
self.schema = schema
@@ -793,54 +871,48 @@ class Service(object):
self.is_coroutinefunction = asyncio.iscoroutinefunction(func)
-class ServiceCall(object):
+class ServiceCall:
"""Representation of a call to a service."""
- __slots__ = ['domain', 'service', 'data', 'call_id']
+ __slots__ = ['domain', 'service', 'data', 'context']
- def __init__(self, domain, service, data=None, call_id=None):
+ def __init__(self, domain: str, service: str, data: Optional[Dict] = None,
+ context: Optional[Context] = None) -> None:
"""Initialize a service call."""
self.domain = domain.lower()
self.service = service.lower()
self.data = MappingProxyType(data or {})
- self.call_id = call_id
+ self.context = context or Context()
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return the representation of the service."""
if self.data:
- return "".format(
- self.domain, self.service, util.repr_helper(self.data))
+ return "".format(
+ self.domain, self.service, self.context.id,
+ util.repr_helper(self.data))
- return "".format(self.domain, self.service)
+ return "".format(
+ self.domain, self.service, self.context.id)
-class ServiceRegistry(object):
+class ServiceRegistry:
"""Offer the services over the eventbus."""
- def __init__(self, hass):
+ def __init__(self, hass: HomeAssistant) -> None:
"""Initialize a service registry."""
- self._services = {}
+ self._services = {} # type: Dict[str, Dict[str, Service]]
self._hass = hass
- self._async_unsub_call_event = None
-
- def _gen_unique_id():
- cur_id = 1
- while True:
- yield '{}-{}'.format(id(self), cur_id)
- cur_id += 1
-
- gen = _gen_unique_id()
- self._generate_unique_id = lambda: next(gen)
+ self._async_unsub_call_event = None # type: Optional[CALLBACK_TYPE]
@property
- def services(self):
+ def services(self) -> Dict[str, Dict[str, Service]]:
"""Return dictionary with per domain a list of available services."""
- return run_callback_threadsafe(
+ return run_callback_threadsafe( # type: ignore
self._hass.loop, self.async_services,
).result()
@callback
- def async_services(self):
+ def async_services(self) -> Dict[str, Dict[str, Service]]:
"""Return dictionary with per domain a list of available services.
This method must be run in the event loop.
@@ -848,14 +920,15 @@ class ServiceRegistry(object):
return {domain: self._services[domain].copy()
for domain in self._services}
- def has_service(self, domain, service):
+ def has_service(self, domain: str, service: str) -> bool:
"""Test if specified service exists.
Async friendly.
"""
return service.lower() in self._services.get(domain.lower(), [])
- def register(self, domain, service, service_func, schema=None):
+ def register(self, domain: str, service: str, service_func: Callable,
+ schema: Optional[vol.Schema] = None) -> None:
"""
Register a service.
@@ -867,7 +940,8 @@ class ServiceRegistry(object):
).result()
@callback
- def async_register(self, domain, service, service_func, schema=None):
+ def async_register(self, domain: str, service: str, service_func: Callable,
+ schema: Optional[vol.Schema] = None) -> None:
"""
Register a service.
@@ -893,13 +967,13 @@ class ServiceRegistry(object):
{ATTR_DOMAIN: domain, ATTR_SERVICE: service}
)
- def remove(self, domain, service):
+ def remove(self, domain: str, service: str) -> None:
"""Remove a registered service from service handler."""
run_callback_threadsafe(
self._hass.loop, self.async_remove, domain, service).result()
@callback
- def async_remove(self, domain, service):
+ def async_remove(self, domain: str, service: str) -> None:
"""Remove a registered service from service handler.
This method must be run in the event loop.
@@ -919,7 +993,10 @@ class ServiceRegistry(object):
{ATTR_DOMAIN: domain, ATTR_SERVICE: service}
)
- def call(self, domain, service, service_data=None, blocking=False):
+ def call(self, domain: str, service: str,
+ service_data: Optional[Dict] = None,
+ blocking: bool = False,
+ context: Optional[Context] = None) -> Optional[bool]:
"""
Call a service.
@@ -936,13 +1013,15 @@ class ServiceRegistry(object):
Because the service is sent as an event you are not allowed to use
the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data.
"""
- return run_coroutine_threadsafe(
- self.async_call(domain, service, service_data, blocking),
+ return run_coroutine_threadsafe( # type: ignore
+ self.async_call(domain, service, service_data, blocking, context),
self._hass.loop
).result()
- async def async_call(self, domain, service, service_data=None,
- blocking=False):
+ async def async_call(self, domain: str, service: str,
+ service_data: Optional[Dict] = None,
+ blocking: bool = False,
+ context: Optional[Context] = None) -> Optional[bool]:
"""
Call a service.
@@ -961,42 +1040,42 @@ class ServiceRegistry(object):
This method is a coroutine.
"""
- call_id = self._generate_unique_id()
-
+ context = context or Context()
event_data = {
ATTR_DOMAIN: domain.lower(),
ATTR_SERVICE: service.lower(),
ATTR_SERVICE_DATA: service_data,
- ATTR_SERVICE_CALL_ID: call_id,
}
- if blocking:
- fut = asyncio.Future(loop=self._hass.loop)
+ if not blocking:
+ self._hass.bus.async_fire(
+ EVENT_CALL_SERVICE, event_data, EventOrigin.local, context)
+ return None
- @callback
- def service_executed(event):
- """Handle an executed service."""
- if event.data[ATTR_SERVICE_CALL_ID] == call_id:
- fut.set_result(True)
+ fut = asyncio.Future() # type: asyncio.Future
- unsub = self._hass.bus.async_listen(
- EVENT_SERVICE_EXECUTED, service_executed)
+ @callback
+ def service_executed(event: Event) -> None:
+ """Handle an executed service."""
+ if event.context == context:
+ fut.set_result(True)
- self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data)
+ unsub = self._hass.bus.async_listen(
+ EVENT_SERVICE_EXECUTED, service_executed)
- if blocking:
- done, _ = await asyncio.wait(
- [fut], loop=self._hass.loop, timeout=SERVICE_CALL_LIMIT)
- success = bool(done)
- unsub()
- return success
+ self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data,
+ EventOrigin.local, context)
- async def _event_to_service_call(self, event):
+ done, _ = await asyncio.wait([fut], timeout=SERVICE_CALL_LIMIT)
+ success = bool(done)
+ unsub()
+ return success
+
+ async def _event_to_service_call(self, event: Event) -> None:
"""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()
- call_id = event.data.get(ATTR_SERVICE_CALL_ID)
+ domain = event.data.get(ATTR_DOMAIN).lower() # type: ignore
+ service = event.data.get(ATTR_SERVICE).lower() # type: ignore
if not self.has_service(domain, service):
if event.origin == EventOrigin.local:
@@ -1006,18 +1085,15 @@ class ServiceRegistry(object):
service_handler = self._services[domain][service]
- def fire_service_executed():
+ def fire_service_executed() -> None:
"""Fire service executed event."""
- if not call_id:
- return
-
- data = {ATTR_SERVICE_CALL_ID: call_id}
-
if (service_handler.is_coroutinefunction or
service_handler.is_callback):
- self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, data)
+ self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, {},
+ EventOrigin.local, event.context)
else:
- self._hass.bus.fire(EVENT_SERVICE_EXECUTED, data)
+ self._hass.bus.fire(EVENT_SERVICE_EXECUTED, {},
+ EventOrigin.local, event.context)
try:
if service_handler.schema:
@@ -1028,7 +1104,8 @@ class ServiceRegistry(object):
fire_service_executed()
return
- service_call = ServiceCall(domain, service, service_data, call_id)
+ service_call = ServiceCall(
+ domain, service, service_data, event.context)
try:
if service_handler.is_callback:
@@ -1038,44 +1115,44 @@ class ServiceRegistry(object):
await service_handler.func(service_call)
fire_service_executed()
else:
- def execute_service():
+ def execute_service() -> None:
"""Execute a service and fires a SERVICE_EXECUTED event."""
service_handler.func(service_call)
fire_service_executed()
- await self._hass.async_add_job(execute_service)
+ await self._hass.async_add_executor_job(execute_service)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error executing service %s', service_call)
-class Config(object):
+class Config:
"""Configuration settings for Home Assistant."""
- def __init__(self):
+ def __init__(self) -> None:
"""Initialize a new config object."""
self.latitude = None # type: Optional[float]
self.longitude = None # type: Optional[float]
self.elevation = None # type: Optional[int]
self.location_name = None # type: Optional[str]
- self.time_zone = None # type: Optional[str]
+ self.time_zone = None # type: Optional[datetime.tzinfo]
self.units = METRIC_SYSTEM # type: UnitSystem
# If True, pip install is skipped for requirements on startup
self.skip_pip = False # type: bool
# List of loaded components
- self.components = set()
+ self.components = set() # type: set
# Remote.API object pointing at local API
self.api = None
# Directory that holds the configuration
- self.config_dir = None
+ self.config_dir = None # type: Optional[str]
# List of allowed external dirs to access
- self.whitelist_external_dirs = set()
+ self.whitelist_external_dirs = set() # type: Set[str]
- def distance(self, lat: float, lon: float) -> float:
+ def distance(self, lat: float, lon: float) -> Optional[float]:
"""Calculate distance from Home Assistant.
Async friendly.
@@ -1083,7 +1160,7 @@ class Config(object):
return self.units.length(
location.distance(self.latitude, self.longitude, lat, lon), 'm')
- def path(self, *path):
+ def path(self, *path: str) -> str:
"""Generate path to the file within the configuration directory.
Async friendly.
@@ -1115,12 +1192,14 @@ class Config(object):
return False
- def as_dict(self):
+ def as_dict(self) -> Dict:
"""Create a dictionary representation of this dict.
Async friendly.
"""
- time_zone = self.time_zone or dt_util.UTC
+ time_zone = dt_util.UTC.zone
+ if self.time_zone and getattr(self.time_zone, 'zone'):
+ time_zone = getattr(self.time_zone, 'zone')
return {
'latitude': self.latitude,
@@ -1128,7 +1207,7 @@ class Config(object):
'elevation': self.elevation,
'unit_system': self.units.as_dict(),
'location_name': self.location_name,
- 'time_zone': time_zone.zone,
+ 'time_zone': time_zone,
'components': self.components,
'config_dir': self.config_dir,
'whitelist_external_dirs': self.whitelist_external_dirs,
@@ -1136,12 +1215,12 @@ class Config(object):
}
-def _async_create_timer(hass):
+def _async_create_timer(hass: HomeAssistant) -> None:
"""Create a timer that will start on HOMEASSISTANT_START."""
handle = None
@callback
- def fire_time_event(nxt):
+ def fire_time_event(nxt: float) -> None:
"""Fire next time event."""
nonlocal handle
@@ -1158,7 +1237,7 @@ def _async_create_timer(hass):
handle = hass.loop.call_later(slp_seconds, fire_time_event, nxt)
@callback
- def stop_timer(event):
+ def stop_timer(_: Event) -> None:
"""Stop the timer."""
if handle is not None:
handle.cancel()
diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py
index e51ba4d9718..f010ada02f3 100644
--- a/homeassistant/data_entry_flow.py
+++ b/homeassistant/data_entry_flow.py
@@ -1,8 +1,9 @@
"""Classes to help gather user submissions."""
import logging
import uuid
-
-from .core import callback
+import voluptuous as vol
+from typing import Dict, Any, Callable, List, Optional # noqa pylint: disable=unused-import
+from .core import callback, HomeAssistant
from .exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
@@ -35,15 +36,16 @@ class UnknownStep(FlowError):
class FlowManager:
"""Manage all the flows that are in progress."""
- def __init__(self, hass, async_create_flow, async_finish_flow):
+ def __init__(self, hass: HomeAssistant, async_create_flow: Callable,
+ async_finish_flow: Callable) -> None:
"""Initialize the flow manager."""
self.hass = hass
- self._progress = {}
+ self._progress = {} # type: Dict[str, Any]
self._async_create_flow = async_create_flow
self._async_finish_flow = async_finish_flow
@callback
- def async_progress(self):
+ def async_progress(self) -> List[Dict]:
"""Return the flows in progress."""
return [{
'flow_id': flow.flow_id,
@@ -51,7 +53,8 @@ class FlowManager:
'source': flow.source,
} for flow in self._progress.values()]
- async def async_init(self, handler, *, source=SOURCE_USER, data=None):
+ async def async_init(self, handler: Callable, *, source: str = SOURCE_USER,
+ data: str = None) -> Any:
"""Start a configuration flow."""
flow = await self._async_create_flow(handler, source=source, data=data)
flow.hass = self.hass
@@ -67,7 +70,8 @@ class FlowManager:
return await self._async_handle_step(flow, step, data)
- async def async_configure(self, flow_id, user_input=None):
+ async def async_configure(
+ self, flow_id: str, user_input: str = None) -> Any:
"""Continue a configuration flow."""
flow = self._progress.get(flow_id)
@@ -83,12 +87,13 @@ class FlowManager:
flow, step_id, user_input)
@callback
- def async_abort(self, flow_id):
+ def async_abort(self, flow_id: str) -> None:
"""Abort a flow."""
if self._progress.pop(flow_id, None) is None:
raise UnknownFlow
- async def _async_handle_step(self, flow, step_id, user_input):
+ async def _async_handle_step(self, flow: Any, step_id: str,
+ user_input: Optional[str]) -> Dict:
"""Handle a step of a flow."""
method = "async_step_{}".format(step_id)
@@ -97,7 +102,7 @@ class FlowManager:
raise UnknownStep("Handler {} doesn't support step {}".format(
flow.__class__.__name__, step_id))
- result = await getattr(flow, method)(user_input)
+ result = await getattr(flow, method)(user_input) # type: Dict
if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_ABORT):
@@ -133,8 +138,9 @@ class FlowHandler:
VERSION = 1
@callback
- def async_show_form(self, *, step_id, data_schema=None, errors=None,
- description_placeholders=None):
+ def async_show_form(self, *, step_id: str, data_schema: vol.Schema = None,
+ errors: Dict = None,
+ description_placeholders: Dict = None) -> Dict:
"""Return the definition of a form to gather user input."""
return {
'type': RESULT_TYPE_FORM,
@@ -147,7 +153,7 @@ class FlowHandler:
}
@callback
- def async_create_entry(self, *, title, data):
+ def async_create_entry(self, *, title: str, data: Dict) -> Dict:
"""Finish config flow and create a config entry."""
return {
'version': self.VERSION,
@@ -160,7 +166,7 @@ class FlowHandler:
}
@callback
- def async_abort(self, *, reason):
+ def async_abort(self, *, reason: str) -> Dict:
"""Abort the config flow."""
return {
'type': RESULT_TYPE_ABORT,
diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py
index 54cd569aceb..ed489ed858b 100644
--- a/homeassistant/helpers/__init__.py
+++ b/homeassistant/helpers/__init__.py
@@ -5,11 +5,6 @@ from typing import Any, Iterable, Tuple, Sequence, Dict
from homeassistant.const import CONF_PLATFORM
-# Typing Imports and TypeAlias
-# pylint: disable=using-constant-test,unused-import
-if False:
- from logging import Logger # NOQA
-
# pylint: disable=invalid-name
ConfigType = Dict[str, Any]
diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py
index 71f3374f0c0..53b246c700d 100644
--- a/homeassistant/helpers/aiohttp_client.py
+++ b/homeassistant/helpers/aiohttp_client.py
@@ -66,14 +66,13 @@ def async_create_clientsession(hass, verify_ssl=True, auto_cleanup=True,
return clientsession
-@asyncio.coroutine
@bind_hass
-def async_aiohttp_proxy_web(hass, request, web_coro, buffer_size=102400,
- timeout=10):
+async def async_aiohttp_proxy_web(hass, request, web_coro,
+ buffer_size=102400, timeout=10):
"""Stream websession request to aiohttp web response."""
try:
with async_timeout.timeout(timeout, loop=hass.loop):
- req = yield from web_coro
+ req = await web_coro
except asyncio.CancelledError:
# The user cancelled the request
@@ -88,7 +87,7 @@ def async_aiohttp_proxy_web(hass, request, web_coro, buffer_size=102400,
raise HTTPBadGateway() from err
try:
- yield from async_aiohttp_proxy_stream(
+ return await async_aiohttp_proxy_stream(
hass,
request,
req.content,
@@ -112,19 +111,15 @@ async def async_aiohttp_proxy_stream(hass, request, stream, content_type,
data = await stream.read(buffer_size)
if not data:
- await response.write_eof()
break
-
await response.write(data)
except (asyncio.TimeoutError, aiohttp.ClientError):
- # Something went wrong fetching data, close connection gracefully
- await response.write_eof()
-
- except asyncio.CancelledError:
- # The user closed the connection
+ # Something went wrong fetching data, closed connection
pass
+ return response
+
@callback
def _async_register_clientsession_shutdown(hass, clientsession):
diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py
index 921b3bcf06b..930f68c3da4 100644
--- a/homeassistant/helpers/condition.py
+++ b/homeassistant/helpers/condition.py
@@ -246,26 +246,24 @@ def sun(hass, before=None, after=None, before_offset=None, after_offset=None):
sunrise = get_astral_event_date(hass, 'sunrise', today)
sunset = get_astral_event_date(hass, 'sunset', today)
- if sunrise is None and (before == SUN_EVENT_SUNRISE or
- after == SUN_EVENT_SUNRISE):
+ if sunrise is None and SUN_EVENT_SUNRISE in (before, after):
# There is no sunrise today
return False
- if sunset is None and (before == SUN_EVENT_SUNSET or
- after == SUN_EVENT_SUNSET):
+ if sunset is None and SUN_EVENT_SUNSET in (before, after):
# There is no sunset today
return False
if before == SUN_EVENT_SUNRISE and utcnow > sunrise + before_offset:
return False
- elif before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset:
+ if before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset:
return False
if after == SUN_EVENT_SUNRISE and utcnow < sunrise + after_offset:
return False
- elif after == SUN_EVENT_SUNSET and utcnow < sunset + after_offset:
+ if after == SUN_EVENT_SUNSET and utcnow < sunset + after_offset:
return False
return True
diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py
index 0bd490940a9..056d45ad656 100644
--- a/homeassistant/helpers/config_validation.py
+++ b/homeassistant/helpers/config_validation.py
@@ -361,7 +361,7 @@ def temperature_unit(value) -> str:
value = str(value).upper()
if value == 'C':
return TEMP_CELSIUS
- elif value == 'F':
+ if value == 'F':
return TEMP_FAHRENHEIT
raise vol.Invalid('invalid temperature unit (expected C or F)')
@@ -435,15 +435,14 @@ def socket_timeout(value):
"""
if value is None:
return _GLOBAL_DEFAULT_TIMEOUT
- else:
- try:
- float_value = float(value)
- if float_value > 0.0:
- return float_value
- raise vol.Invalid('Invalid socket timeout value.'
- ' float > 0.0 required.')
- except Exception as _:
- raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_))
+ try:
+ float_value = float(value)
+ if float_value > 0.0:
+ return float_value
+ raise vol.Invalid('Invalid socket timeout value.'
+ ' float > 0.0 required.')
+ except Exception as _:
+ raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_))
# pylint: disable=no-value-for-parameter
diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py
index 5a0b2ca56ea..4f412eb58e7 100644
--- a/homeassistant/helpers/data_entry_flow.py
+++ b/homeassistant/helpers/data_entry_flow.py
@@ -23,7 +23,7 @@ class _BaseFlowManagerView(HomeAssistantView):
data.pop('data')
return data
- elif result['type'] != data_entry_flow.RESULT_TYPE_FORM:
+ if result['type'] != data_entry_flow.RESULT_TYPE_FORM:
return result
import voluptuous_serialize
diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py
index 73a09464439..8b621b2f01c 100644
--- a/homeassistant/helpers/deprecation.py
+++ b/homeassistant/helpers/deprecation.py
@@ -33,8 +33,7 @@ def deprecated_substitute(substitute_name):
# Return the old property
return getattr(self, substitute_name)
- else:
- return func(self)
+ return func(self)
return func_wrapper
return decorator
diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py
index cb587c432c1..7d0730a969c 100644
--- a/homeassistant/helpers/discovery.py
+++ b/homeassistant/helpers/discovery.py
@@ -145,7 +145,7 @@ async def async_load_platform(hass, component, platform, discovered=None,
Use `listen_platform` to register a callback for these events.
Warning: Do not await this inside a setup method to avoid a dead lock.
- Use `hass.async_add_job(async_load_platform(..))` instead.
+ Use `hass.async_create_task(async_load_platform(..))` instead.
This method is a coroutine.
"""
diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py
index 7dc5d2524ec..c356c266db6 100644
--- a/homeassistant/helpers/entity.py
+++ b/homeassistant/helpers/entity.py
@@ -56,7 +56,7 @@ def async_generate_entity_id(entity_id_format: str, name: Optional[str],
entity_id_format.format(slugify(name)), current_ids)
-class Entity(object):
+class Entity:
"""An abstract class for Home Assistant entities."""
# SAFE TO OVERWRITE
@@ -82,6 +82,9 @@ class Entity(object):
# Name in the entity registry
registry_name = None
+ # Hold list for functions to call on remove.
+ _on_remove = None
+
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
@@ -176,7 +179,7 @@ class Entity(object):
# produce undesirable effects in the entity's operation.
@asyncio.coroutine
- def async_update_ha_state(self, force_refresh=False):
+ def async_update_ha_state(self, force_refresh=False, context=None):
"""Update Home Assistant with current state of entity.
If force_refresh == True will update entity before setting state.
@@ -276,7 +279,7 @@ class Entity(object):
pass
self.hass.states.async_set(
- self.entity_id, state, attr, self.force_update)
+ self.entity_id, state, attr, self.force_update, context)
def schedule_update_ha_state(self, force_refresh=False):
"""Schedule an update ha state change task.
@@ -324,8 +327,19 @@ class Entity(object):
if self.parallel_updates:
self.parallel_updates.release()
+ @callback
+ def async_on_remove(self, func):
+ """Add a function to call when entity removed."""
+ if self._on_remove is None:
+ self._on_remove = []
+ self._on_remove.append(func)
+
async def async_remove(self):
"""Remove entity from Home Assistant."""
+ if self._on_remove is not None:
+ while self._on_remove:
+ self._on_remove.pop()()
+
if self.platform is not None:
await self.platform.async_remove_entity(self.entity_id)
else:
@@ -335,7 +349,17 @@ class Entity(object):
def async_registry_updated(self, old, new):
"""Called when the entity registry has been updated."""
self.registry_name = new.name
- self.async_schedule_update_ha_state()
+
+ if new.entity_id == self.entity_id:
+ self.async_schedule_update_ha_state()
+ return
+
+ async def readd():
+ """Remove and add entity again."""
+ await self.async_remove()
+ await self.platform.async_add_entities([self])
+
+ self.hass.async_create_task(readd())
def __eq__(self, other):
"""Return the comparison."""
diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py
index 4ac3a147296..72b6ceecbfd 100644
--- a/homeassistant/helpers/entity_component.py
+++ b/homeassistant/helpers/entity_component.py
@@ -17,7 +17,7 @@ from .entity_platform import EntityPlatform
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
-class EntityComponent(object):
+class EntityComponent:
"""The EntityComponent manages platforms that manages entities.
This class has the following responsibilities:
diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py
index 472a88888d8..dc1e376f471 100644
--- a/homeassistant/helpers/entity_platform.py
+++ b/homeassistant/helpers/entity_platform.py
@@ -15,7 +15,7 @@ SLOW_SETUP_MAX_WAIT = 60
PLATFORM_NOT_READY_RETRIES = 10
-class EntityPlatform(object):
+class EntityPlatform:
"""Manage the entities for a single platform."""
def __init__(self, *, hass, logger, domain, platform_name, platform,
@@ -283,7 +283,7 @@ class EntityPlatform(object):
entity.entity_id = entry.entity_id
entity.registry_name = entry.name
- entry.add_update_listener(entity)
+ entity.async_on_remove(entry.add_update_listener(entity))
# We won't generate an entity ID if the platform has already set one
# We will however make sure that platform cannot pick a registered ID
diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py
index 04d9cc450ba..2fa64ff8680 100644
--- a/homeassistant/helpers/entity_registry.py
+++ b/homeassistant/helpers/entity_registry.py
@@ -19,10 +19,10 @@ import weakref
import attr
-from ..core import callback, split_entity_id
-from ..loader import bind_hass
-from ..util import ensure_unique_string, slugify
-from ..util.yaml import load_yaml, save_yaml
+from homeassistant.core import callback, split_entity_id, valid_entity_id
+from homeassistant.loader import bind_hass
+from homeassistant.util import ensure_unique_string, slugify
+from homeassistant.util.yaml import load_yaml, save_yaml
PATH_REGISTRY = 'entity_registry.yaml'
DATA_REGISTRY = 'entity_registry'
@@ -63,8 +63,13 @@ class RegistryEntry:
"""Listen for when entry is updated.
Listener: Callback function(old_entry, new_entry)
+
+ Returns function to unlisten.
"""
- self.update_listeners.append(weakref.ref(listener))
+ weak_listener = weakref.ref(listener)
+ self.update_listeners.append(weak_listener)
+
+ return lambda: self.update_listeners.remove(weak_listener)
class EntityRegistry:
@@ -109,6 +114,12 @@ class EntityRegistry:
"""Get entity. Create if it doesn't exist."""
entity_id = self.async_get_entity_id(domain, platform, unique_id)
if entity_id:
+ entry = self.entities[entity_id]
+ if entry.config_entry_id == config_entry_id:
+ return entry
+
+ self._async_update_entity(
+ entity_id, config_entry_id=config_entry_id)
return self.entities[entity_id]
entity_id = self.async_generate_entity_id(
@@ -127,8 +138,19 @@ class EntityRegistry:
return entity
@callback
- def async_update_entity(self, entity_id, *, name=_UNDEF):
+ def async_update_entity(self, entity_id, *, name=_UNDEF,
+ new_entity_id=_UNDEF):
"""Update properties of an entity."""
+ return self._async_update_entity(
+ entity_id,
+ name=name,
+ new_entity_id=new_entity_id
+ )
+
+ @callback
+ def _async_update_entity(self, entity_id, *, name=_UNDEF,
+ config_entry_id=_UNDEF, new_entity_id=_UNDEF):
+ """Private facing update properties method."""
old = self.entities[entity_id]
changes = {}
@@ -136,6 +158,24 @@ class EntityRegistry:
if name is not _UNDEF and name != old.name:
changes['name'] = name
+ if (config_entry_id is not _UNDEF and
+ config_entry_id != old.config_entry_id):
+ changes['config_entry_id'] = config_entry_id
+
+ if new_entity_id is not _UNDEF and new_entity_id != old.entity_id:
+ if self.async_is_registered(new_entity_id):
+ raise ValueError('Entity is already registered')
+
+ if not valid_entity_id(new_entity_id):
+ raise ValueError('Invalid entity ID')
+
+ if (split_entity_id(new_entity_id)[0] !=
+ split_entity_id(entity_id)[0]):
+ raise ValueError('New entity ID should be same domain')
+
+ self.entities.pop(entity_id)
+ entity_id = changes['entity_id'] = new_entity_id
+
if not changes:
return old
diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py
index 19980394d26..5caa6b93131 100644
--- a/homeassistant/helpers/entity_values.py
+++ b/homeassistant/helpers/entity_values.py
@@ -6,7 +6,7 @@ import re
from homeassistant.core import split_entity_id
-class EntityValues(object):
+class EntityValues:
"""Class to store entity id based values."""
def __init__(self, exact=None, domain=None, glob=None):
diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py
index 712b48da0d7..c8488fa3334 100644
--- a/homeassistant/helpers/event.py
+++ b/homeassistant/helpers/event.py
@@ -374,7 +374,7 @@ def _process_state_match(parameter):
if parameter is None or parameter == MATCH_ALL:
return lambda _: True
- elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
+ if isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
return lambda state: state == parameter
parameter = tuple(parameter)
@@ -386,11 +386,11 @@ def _process_time_match(parameter):
if parameter is None or parameter == MATCH_ALL:
return lambda _: True
- elif isinstance(parameter, str) and parameter.startswith('/'):
+ if isinstance(parameter, str) and parameter.startswith('/'):
parameter = float(parameter[1:])
return lambda time: time % parameter == 0
- elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
+ if isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
return lambda time: time == parameter
parameter = tuple(parameter)
diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py
index 4357c4109eb..8f26d4fe0ee 100644
--- a/homeassistant/helpers/intent.py
+++ b/homeassistant/helpers/intent.py
@@ -63,7 +63,8 @@ async def async_handle(hass, platform, intent_type, slots=None,
intent_type, err)
raise InvalidSlotInfo(
'Received invalid slot info for {}'.format(intent_type)) from err
- except IntentHandleError:
+ # https://github.com/PyCQA/pylint/issues/2284
+ except IntentHandleError: # pylint: disable=try-except-raise
raise
except Exception as err:
raise IntentUnexpectedError(
diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py
index f2ae36e7fd0..a139be4b260 100644
--- a/homeassistant/helpers/script.py
+++ b/homeassistant/helpers/script.py
@@ -119,7 +119,7 @@ class Script():
self.hass.async_add_job(self._change_listener)
return
- elif CONF_WAIT_TEMPLATE in action:
+ if CONF_WAIT_TEMPLATE in action:
# Call ourselves in the future to continue work
wait_template = action[CONF_WAIT_TEMPLATE]
wait_template.hass = self.hass
@@ -147,7 +147,7 @@ class Script():
return
- elif CONF_CONDITION in action:
+ if CONF_CONDITION in action:
if not self._async_check_condition(action, variables):
break
diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py
index 7ab90b7a048..8aa3b553f3a 100644
--- a/homeassistant/helpers/service.py
+++ b/homeassistant/helpers/service.py
@@ -103,12 +103,10 @@ def extract_entity_ids(hass, service_call, expand_group=True):
return [ent_id for ent_id in
group.expand_entity_ids(service_ent_id)]
- else:
+ if isinstance(service_ent_id, str):
+ return [service_ent_id]
- if isinstance(service_ent_id, str):
- return [service_ent_id]
-
- return service_ent_id
+ return service_ent_id
@bind_hass
@@ -123,7 +121,7 @@ async def async_get_all_descriptions(hass):
def domain_yaml_file(domain):
"""Return the services.yaml location for a domain."""
if domain == ha.DOMAIN:
- import homeassistant.components as components
+ from homeassistant import components
component_path = path.dirname(components.__file__)
else:
component_path = path.dirname(get_component(hass, domain).__file__)
diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py
index 72deabaae28..4a3f915e810 100644
--- a/homeassistant/helpers/state.py
+++ b/homeassistant/helpers/state.py
@@ -92,7 +92,7 @@ SERVICE_TO_STATE = {
}
-class AsyncTrackStates(object):
+class AsyncTrackStates:
"""
Record the time when the with-block is entered.
@@ -214,9 +214,9 @@ def state_as_number(state):
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL):
return 1
- elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
- STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME,
- STATE_IDLE):
+ if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
+ STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME,
+ STATE_IDLE):
return 0
return float(state.state)
diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py
index f523726c388..ea620c9bccd 100644
--- a/homeassistant/helpers/template.py
+++ b/homeassistant/helpers/template.py
@@ -51,7 +51,7 @@ def render_complex(value, variables=None):
if isinstance(value, list):
return [render_complex(item, variables)
for item in value]
- elif isinstance(value, dict):
+ if isinstance(value, dict):
return {key: render_complex(item, variables)
for key, item in value.items()}
return value.async_render(variables)
@@ -82,7 +82,7 @@ def extract_entities(template, variables=None):
return MATCH_ALL
-class Template(object):
+class Template:
"""Class to hold a template and manage caching and rendering."""
def __init__(self, template, hass=None):
@@ -198,7 +198,7 @@ class Template(object):
self.hass == other.hass)
-class AllStates(object):
+class AllStates:
"""Class to expose all HA states as attributes."""
def __init__(self, hass):
@@ -226,7 +226,7 @@ class AllStates(object):
return STATE_UNKNOWN if state is None else state.state
-class DomainStates(object):
+class DomainStates:
"""Class to expose a specific HA domain as attributes."""
def __init__(self, hass, domain):
@@ -286,7 +286,7 @@ def _wrap_state(state):
return None if state is None else TemplateState(state)
-class TemplateMethods(object):
+class TemplateMethods:
"""Class to expose helpers to templates."""
def __init__(self, hass):
@@ -318,7 +318,7 @@ class TemplateMethods(object):
if point_state is None:
_LOGGER.warning("Closest:Unable to find state %s", args[0])
return None
- elif not loc_helper.has_location(point_state):
+ if not loc_helper.has_location(point_state):
_LOGGER.warning(
"Closest:State does not contain valid location: %s",
point_state)
@@ -420,7 +420,7 @@ class TemplateMethods(object):
"""Return state or entity_id if given."""
if isinstance(entity_id_or_state, State):
return entity_id_or_state
- elif isinstance(entity_id_or_state, str):
+ if isinstance(entity_id_or_state, str):
return self._hass.states.get(entity_id_or_state)
return None
diff --git a/homeassistant/loader.py b/homeassistant/loader.py
index 52e6b1e7703..3ac49e354b5 100644
--- a/homeassistant/loader.py
+++ b/homeassistant/loader.py
@@ -17,7 +17,7 @@ import sys
from types import ModuleType
# pylint: disable=unused-import
-from typing import Dict, List, Optional, Sequence, Set, TYPE_CHECKING # NOQA
+from typing import Optional, Set, TYPE_CHECKING, Callable, Any, TypeVar # NOQA
from homeassistant.const import PLATFORM_FORMAT
from homeassistant.util import OrderedSet
@@ -27,6 +27,8 @@ from homeassistant.util import OrderedSet
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant # NOQA
+CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) # noqa pylint: disable=invalid-name
+
PREPARED = False
DEPENDENCY_BLACKLIST = {'config'}
@@ -51,7 +53,8 @@ def set_component(hass, # type: HomeAssistant
cache[comp_name] = component
-def get_platform(hass, domain: str, platform: str) -> Optional[ModuleType]:
+def get_platform(hass, # type: HomeAssistant
+ domain: str, platform: str) -> Optional[ModuleType]:
"""Try to load specified platform.
Async friendly.
@@ -59,7 +62,8 @@ def get_platform(hass, domain: str, platform: str) -> Optional[ModuleType]:
return get_component(hass, PLATFORM_FORMAT.format(domain, platform))
-def get_component(hass, comp_or_platform) -> Optional[ModuleType]:
+def get_component(hass, # type: HomeAssistant
+ comp_or_platform: str) -> Optional[ModuleType]:
"""Try to load specified component.
Looks in config dir first, then built-in components.
@@ -73,6 +77,9 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]:
cache = hass.data.get(DATA_KEY)
if cache is None:
+ if hass.config.config_dir is None:
+ _LOGGER.error("Can't load components - config dir is not set")
+ return None
# Only insert if it's not there (happens during tests)
if sys.path[0] != hass.config.config_dir:
sys.path.insert(0, hass.config.config_dir)
@@ -134,14 +141,38 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]:
return None
+class ModuleWrapper:
+ """Class to wrap a Python module and auto fill in hass argument."""
+
+ def __init__(self,
+ hass, # type: HomeAssistant
+ module: ModuleType) -> None:
+ """Initialize the module wrapper."""
+ self._hass = hass
+ self._module = module
+
+ def __getattr__(self, attr: str) -> Any:
+ """Fetch an attribute."""
+ value = getattr(self._module, attr)
+
+ if hasattr(value, '__bind_hass'):
+ value = ft.partial(value, self._hass)
+
+ setattr(self, attr, value)
+ return value
+
+
class Components:
"""Helper to load components."""
- def __init__(self, hass):
+ def __init__(
+ self,
+ hass # type: HomeAssistant
+ ) -> None:
"""Initialize the Components class."""
self._hass = hass
- def __getattr__(self, comp_name):
+ def __getattr__(self, comp_name: str) -> ModuleWrapper:
"""Fetch a component."""
component = get_component(self._hass, comp_name)
if component is None:
@@ -154,11 +185,14 @@ class Components:
class Helpers:
"""Helper to load helpers."""
- def __init__(self, hass):
+ def __init__(
+ self,
+ hass # type: HomeAssistant
+ ) -> None:
"""Initialize the Helpers class."""
self._hass = hass
- def __getattr__(self, helper_name):
+ def __getattr__(self, helper_name: str) -> ModuleWrapper:
"""Fetch a helper."""
helper = importlib.import_module(
'homeassistant.helpers.{}'.format(helper_name))
@@ -167,33 +201,14 @@ class Helpers:
return wrapped
-class ModuleWrapper:
- """Class to wrap a Python module and auto fill in hass argument."""
-
- def __init__(self, hass, module):
- """Initialize the module wrapper."""
- self._hass = hass
- self._module = module
-
- def __getattr__(self, attr):
- """Fetch an attribute."""
- value = getattr(self._module, attr)
-
- if hasattr(value, '__bind_hass'):
- value = ft.partial(value, self._hass)
-
- setattr(self, attr, value)
- return value
-
-
-def bind_hass(func):
+def bind_hass(func: CALLABLE_T) -> CALLABLE_T:
"""Decorate function to indicate that first argument is hass."""
- # pylint: disable=protected-access
- func.__bind_hass = True
+ setattr(func, '__bind_hass', True)
return func
-def load_order_component(hass, comp_name: str) -> OrderedSet:
+def load_order_component(hass, # type: HomeAssistant
+ comp_name: str) -> OrderedSet:
"""Return an OrderedSet of components in the correct order of loading.
Raises HomeAssistantError if a circular dependency is detected.
@@ -204,7 +219,8 @@ def load_order_component(hass, comp_name: str) -> OrderedSet:
return _load_order_component(hass, comp_name, OrderedSet(), set())
-def _load_order_component(hass, comp_name: str, load_order: OrderedSet,
+def _load_order_component(hass, # type: HomeAssistant
+ comp_name: str, load_order: OrderedSet,
loading: Set) -> OrderedSet:
"""Recursive function to get load order of components.
diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py
index d5c629c9d34..edd25817f5a 100644
--- a/homeassistant/monkey_patch.py
+++ b/homeassistant/monkey_patch.py
@@ -20,9 +20,10 @@ Related Python bugs:
- https://bugs.python.org/issue26617
"""
import sys
+from typing import Any
-def patch_weakref_tasks():
+def patch_weakref_tasks() -> None:
"""Replace weakref.WeakSet to address Python 3 bug."""
# pylint: disable=no-self-use, protected-access, bare-except
import asyncio.tasks
@@ -30,18 +31,18 @@ def patch_weakref_tasks():
class IgnoreCalls:
"""Ignore add calls."""
- def add(self, other):
+ def add(self, other: Any) -> None:
"""No-op add."""
return
- asyncio.tasks.Task._all_tasks = IgnoreCalls()
+ asyncio.tasks.Task._all_tasks = IgnoreCalls() # type: ignore
try:
del asyncio.tasks.Task.__del__
except: # noqa: E722
pass
-def disable_c_asyncio():
+def disable_c_asyncio() -> None:
"""Disable using C implementation of asyncio.
Required to be able to apply the weakref monkey patch.
@@ -53,18 +54,16 @@ def disable_c_asyncio():
PATH_TRIGGER = '_asyncio'
- def __init__(self, path_entry):
+ def __init__(self, path_entry: str) -> None:
if path_entry != self.PATH_TRIGGER:
raise ImportError()
- return
- def find_module(self, fullname, path=None):
+ def find_module(self, fullname: str, path: Any = None) -> None:
"""Find a module."""
if fullname == self.PATH_TRIGGER:
# We lint in Py35, exception is introduced in Py36
# pylint: disable=undefined-variable
- raise ModuleNotFoundError() # noqa
- return None
+ raise ModuleNotFoundError() # type: ignore # noqa
sys.path_hooks.append(AsyncioImportFinder)
sys.path.insert(0, AsyncioImportFinder.PATH_TRIGGER)
diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt
index 66b17cf9bd9..e832314cf17 100644
--- a/homeassistant/package_constraints.txt
+++ b/homeassistant/package_constraints.txt
@@ -8,7 +8,7 @@ pip>=8.0.3
pytz>=2018.04
pyyaml>=3.13,<4
requests==2.19.1
-voluptuous==0.11.1
+voluptuous==0.11.3
# Breaks Python 3.6 and is not needed for our supported Python versions
enum34==1000000000.0.0
diff --git a/homeassistant/remote.py b/homeassistant/remote.py
index ae932b7d955..313f98a890c 100644
--- a/homeassistant/remote.py
+++ b/homeassistant/remote.py
@@ -13,7 +13,7 @@ import json
import logging
import urllib.parse
-from typing import Optional
+from typing import Optional, Dict, Any, List
from aiohttp.hdrs import METH_GET, METH_POST, METH_DELETE, CONTENT_TYPE
import requests
@@ -41,7 +41,7 @@ class APIStatus(enum.Enum):
return self.value # type: ignore
-class API(object):
+class API:
"""Object to pass around Home Assistant API location and credentials."""
def __init__(self, host: str, api_password: Optional[str] = None,
@@ -62,7 +62,7 @@ class API(object):
if port is not None:
self.base_url += ':{}'.format(port)
- self.status = None
+ self.status = None # type: Optional[APIStatus]
self._headers = {CONTENT_TYPE: CONTENT_TYPE_JSON}
if api_password is not None:
@@ -75,20 +75,24 @@ class API(object):
return self.status == APIStatus.OK
- def __call__(self, method, path, data=None, timeout=5):
+ def __call__(self, method: str, path: str, data: Dict = None,
+ timeout: int = 5) -> requests.Response:
"""Make a call to the Home Assistant API."""
- if data is not None:
- data = json.dumps(data, cls=JSONEncoder)
+ if data is None:
+ data_str = None
+ else:
+ data_str = json.dumps(data, cls=JSONEncoder)
url = urllib.parse.urljoin(self.base_url, path)
try:
if method == METH_GET:
return requests.get(
- url, params=data, timeout=timeout, headers=self._headers)
+ url, params=data_str, timeout=timeout,
+ headers=self._headers)
return requests.request(
- method, url, data=data, timeout=timeout,
+ method, url, data=data_str, timeout=timeout,
headers=self._headers)
except requests.exceptions.ConnectionError:
@@ -110,22 +114,22 @@ class JSONEncoder(json.JSONEncoder):
"""JSONEncoder that supports Home Assistant objects."""
# pylint: disable=method-hidden
- def default(self, o):
+ def default(self, o: Any) -> Any:
"""Convert Home Assistant objects.
Hand other objects to the original method.
"""
if isinstance(o, datetime):
return o.isoformat()
- elif isinstance(o, set):
+ if isinstance(o, set):
return list(o)
- elif hasattr(o, 'as_dict'):
+ if hasattr(o, 'as_dict'):
return o.as_dict()
return json.JSONEncoder.default(self, o)
-def validate_api(api):
+def validate_api(api: API) -> APIStatus:
"""Make a call to validate API."""
try:
req = api(METH_GET, URL_API)
@@ -133,7 +137,7 @@ def validate_api(api):
if req.status_code == 200:
return APIStatus.OK
- elif req.status_code == 401:
+ if req.status_code == 401:
return APIStatus.INVALID_PASSWORD
return APIStatus.UNKNOWN
@@ -142,12 +146,12 @@ def validate_api(api):
return APIStatus.CANNOT_CONNECT
-def get_event_listeners(api):
+def get_event_listeners(api: API) -> Dict:
"""List of events that is being listened for."""
try:
req = api(METH_GET, URL_API_EVENTS)
- return req.json() if req.status_code == 200 else {}
+ return req.json() if req.status_code == 200 else {} # type: ignore
except (HomeAssistantError, ValueError):
# ValueError if req.json() can't parse the json
@@ -156,7 +160,7 @@ def get_event_listeners(api):
return {}
-def fire_event(api, event_type, data=None):
+def fire_event(api: API, event_type: str, data: Dict = None) -> None:
"""Fire an event at remote API."""
try:
req = api(METH_POST, URL_API_EVENTS_EVENT.format(event_type), data)
@@ -169,7 +173,7 @@ def fire_event(api, event_type, data=None):
_LOGGER.exception("Error firing event")
-def get_state(api, entity_id):
+def get_state(api: API, entity_id: str) -> Optional[ha.State]:
"""Query given API for state of entity_id."""
try:
req = api(METH_GET, URL_API_STATES_ENTITY.format(entity_id))
@@ -186,7 +190,7 @@ def get_state(api, entity_id):
return None
-def get_states(api):
+def get_states(api: API) -> List[ha.State]:
"""Query given API for all states."""
try:
req = api(METH_GET,
@@ -202,7 +206,7 @@ def get_states(api):
return []
-def remove_state(api, entity_id):
+def remove_state(api: API, entity_id: str) -> bool:
"""Call API to remove state for entity_id.
Return True if entity is gone (removed/never existed).
@@ -222,7 +226,8 @@ def remove_state(api, entity_id):
return False
-def set_state(api, entity_id, new_state, attributes=None, force_update=False):
+def set_state(api: API, entity_id: str, new_state: str,
+ attributes: Dict = None, force_update: bool = False) -> bool:
"""Tell API to update state for entity_id.
Return True if success.
@@ -249,14 +254,14 @@ def set_state(api, entity_id, new_state, attributes=None, force_update=False):
return False
-def is_state(api, entity_id, state):
+def is_state(api: API, entity_id: str, state: str) -> bool:
"""Query API to see if entity_id is specified state."""
cur_state = get_state(api, entity_id)
- return cur_state and cur_state.state == state
+ return bool(cur_state and cur_state.state == state)
-def get_services(api):
+def get_services(api: API) -> Dict:
"""Return a list of dicts.
Each dict has a string "domain" and a list of strings "services".
@@ -264,7 +269,7 @@ def get_services(api):
try:
req = api(METH_GET, URL_API_SERVICES)
- return req.json() if req.status_code == 200 else {}
+ return req.json() if req.status_code == 200 else {} # type: ignore
except (HomeAssistantError, ValueError):
# ValueError if req.json() can't parse the json
@@ -273,7 +278,9 @@ def get_services(api):
return {}
-def call_service(api, domain, service, service_data=None, timeout=5):
+def call_service(api: API, domain: str, service: str,
+ service_data: Dict = None,
+ timeout: int = 5) -> None:
"""Call a service at the remote API."""
try:
req = api(METH_POST,
@@ -288,7 +295,7 @@ def call_service(api, domain, service, service_data=None, timeout=5):
_LOGGER.exception("Error calling service")
-def get_config(api):
+def get_config(api: API) -> Dict:
"""Return configuration."""
try:
req = api(METH_GET, URL_API_CONFIG)
@@ -299,7 +306,7 @@ def get_config(api):
result = req.json()
if 'components' in result:
result['components'] = set(result['components'])
- return result
+ return result # type: ignore
except (HomeAssistantError, ValueError):
# ValueError if req.json() can't parse the JSON
diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py
index 753947a2c12..b73ec4e184e 100644
--- a/homeassistant/requirements.py
+++ b/homeassistant/requirements.py
@@ -3,15 +3,18 @@ import asyncio
from functools import partial
import logging
import os
+from typing import List, Dict, Optional
import homeassistant.util.package as pkg_util
+from homeassistant.core import HomeAssistant
DATA_PIP_LOCK = 'pip_lock'
CONSTRAINT_FILE = 'package_constraints.txt'
_LOGGER = logging.getLogger(__name__)
-async def async_process_requirements(hass, name, requirements):
+async def async_process_requirements(hass: HomeAssistant, name: str,
+ requirements: List[str]) -> bool:
"""Install the requirements for a component or platform.
This method is a coroutine.
@@ -25,7 +28,7 @@ async def async_process_requirements(hass, name, requirements):
async with pip_lock:
for req in requirements:
- ret = await hass.async_add_job(pip_install, req)
+ ret = await hass.async_add_executor_job(pip_install, req)
if not ret:
_LOGGER.error("Not initializing %s because could not install "
"requirement %s", name, req)
@@ -34,11 +37,11 @@ async def async_process_requirements(hass, name, requirements):
return True
-def pip_kwargs(config_dir):
+def pip_kwargs(config_dir: Optional[str]) -> Dict[str, str]:
"""Return keyword arguments for PIP install."""
kwargs = {
'constraints': os.path.join(os.path.dirname(__file__), CONSTRAINT_FILE)
}
- if not pkg_util.is_virtual_env():
+ if not (config_dir is None or pkg_util.is_virtual_env()):
kwargs['target'] = os.path.join(config_dir, 'deps')
return kwargs
diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py
index 69b1bf21c08..d7be5b1a91c 100644
--- a/homeassistant/scripts/check_config.py
+++ b/homeassistant/scripts/check_config.py
@@ -18,7 +18,7 @@ from homeassistant.config import (
CONF_PACKAGES, merge_packages_config, _format_config_error,
find_config_file, load_yaml_config_file,
extract_domain_configs, config_per_platform)
-import homeassistant.util.yaml as yaml
+from homeassistant.util import yaml
from homeassistant.exceptions import HomeAssistantError
REQUIREMENTS = ('colorlog==3.1.4',)
@@ -163,13 +163,13 @@ def check(config_dir, secrets=False):
'secret_cache': None,
}
- # pylint: disable=unused-variable
+ # pylint: disable=possibly-unused-variable
def mock_load(filename):
"""Mock hass.util.load_yaml to save config file names."""
res['yaml_files'][filename] = True
return MOCKS['load'][1](filename)
- # pylint: disable=unused-variable
+ # pylint: disable=possibly-unused-variable
def mock_secrets(ldr, node):
"""Mock _get_secrets."""
try:
diff --git a/homeassistant/scripts/influxdb_import.py b/homeassistant/scripts/influxdb_import.py
index 421e84d503a..031df1d3a72 100644
--- a/homeassistant/scripts/influxdb_import.py
+++ b/homeassistant/scripts/influxdb_import.py
@@ -137,6 +137,7 @@ def run(script_args: List) -> int:
override_measurement = args.override_measurement
default_measurement = args.default_measurement
+ # pylint: disable=assignment-from-no-return
query = session.query(func.count(models.Events.event_type)).filter(
models.Events.event_type == 'state_changed')
diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py
index 275a33627a9..6c6557897ee 100644
--- a/homeassistant/scripts/macos/__init__.py
+++ b/homeassistant/scripts/macos/__init__.py
@@ -52,10 +52,10 @@ def run(args):
if args[0] == 'install':
install_osx()
return 0
- elif args[0] == 'uninstall':
+ if args[0] == 'uninstall':
uninstall_osx()
return 0
- elif args[0] == 'restart':
+ if args[0] == 'restart':
uninstall_osx()
# A small delay is needed on some systems to let the unload finish.
time.sleep(0.5)
diff --git a/homeassistant/setup.py b/homeassistant/setup.py
index 478320dca27..31404b978eb 100644
--- a/homeassistant/setup.py
+++ b/homeassistant/setup.py
@@ -4,7 +4,7 @@ import logging.handlers
from timeit import default_timer as timer
from types import ModuleType
-from typing import Optional, Dict
+from typing import Optional, Dict, List
from homeassistant import requirements, core, loader, config as conf_util
from homeassistant.config import async_notify_setup_error
@@ -56,7 +56,9 @@ async def async_setup_component(hass: core.HomeAssistant, domain: str,
return await task # type: ignore
-async def _async_process_dependencies(hass, config, name, dependencies):
+async def _async_process_dependencies(
+ hass: core.HomeAssistant, config: Dict, name: str,
+ dependencies: List[str]) -> bool:
"""Ensure all dependencies are set up."""
blacklisted = [dep for dep in dependencies
if dep in loader.DEPENDENCY_BLACKLIST]
@@ -88,12 +90,12 @@ async def _async_process_dependencies(hass, config, name, dependencies):
async def _async_setup_component(hass: core.HomeAssistant,
- domain: str, config) -> bool:
+ domain: str, config: Dict) -> bool:
"""Set up a component for Home Assistant.
This method is a coroutine.
"""
- def log_error(msg, link=True):
+ def log_error(msg: str, link: bool = True) -> None:
"""Log helper."""
_LOGGER.error("Setup failed for %s: %s", domain, msg)
async_notify_setup_error(hass, domain, link)
@@ -157,14 +159,15 @@ async def _async_setup_component(hass: core.HomeAssistant,
if result is False:
log_error("Component failed to initialize.")
return False
- elif result is not True:
+ if result is not True:
log_error("Component did not return boolean if setup was successful. "
"Disabling component.")
loader.set_component(hass, domain, None)
return False
- for entry in hass.config_entries.async_entries(domain):
- await entry.async_setup(hass, component=component)
+ if hass.config_entries:
+ for entry in hass.config_entries.async_entries(domain):
+ await entry.async_setup(hass, component=component)
hass.config.components.add(component.DOMAIN) # type: ignore
@@ -180,7 +183,7 @@ async def _async_setup_component(hass: core.HomeAssistant,
return True
-async def async_prepare_setup_platform(hass: core.HomeAssistant, config,
+async def async_prepare_setup_platform(hass: core.HomeAssistant, config: Dict,
domain: str, platform_name: str) \
-> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup.
@@ -189,7 +192,7 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config,
"""
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
- def log_error(msg):
+ def log_error(msg: str) -> None:
"""Log helper."""
_LOGGER.error("Unable to prepare setup for platform %s: %s",
platform_path, msg)
@@ -203,7 +206,7 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config,
return None
# Already loaded
- elif platform_path in hass.config.components:
+ if platform_path in hass.config.components:
return platform
try:
@@ -216,7 +219,9 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config,
return platform
-async def async_process_deps_reqs(hass, config, name, module):
+async def async_process_deps_reqs(
+ hass: core.HomeAssistant, config: Dict, name: str,
+ module: ModuleType) -> None:
"""Process all dependencies and requirements for a module.
Module is a Python module of either a component or platform.
@@ -230,14 +235,14 @@ async def async_process_deps_reqs(hass, config, name, module):
if hasattr(module, 'DEPENDENCIES'):
dep_success = await _async_process_dependencies(
- hass, config, name, module.DEPENDENCIES)
+ hass, config, name, module.DEPENDENCIES) # type: ignore
if not dep_success:
raise HomeAssistantError("Could not setup all dependencies.")
if not hass.config.skip_pip and hasattr(module, 'REQUIREMENTS'):
req_success = await requirements.async_process_requirements(
- hass, name, module.REQUIREMENTS)
+ hass, name, module.REQUIREMENTS) # type: ignore
if not req_success:
raise HomeAssistantError("Could not install all requirements.")
diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py
index bbf0f7e11e2..64c9f4f02c9 100644
--- a/homeassistant/util/__init__.py
+++ b/homeassistant/util/__init__.py
@@ -1,9 +1,8 @@
"""Helper methods for various modules."""
import asyncio
-from collections.abc import MutableSet
+from datetime import datetime, timedelta
from itertools import chain
import threading
-from datetime import datetime
import re
import enum
import socket
@@ -13,12 +12,16 @@ from functools import wraps
from types import MappingProxyType
from unicodedata import normalize
-from typing import Any, Optional, TypeVar, Callable, KeysView, Union, Iterable
+from typing import (Any, Optional, TypeVar, Callable, KeysView, Union, # noqa
+ Iterable, List, Dict, Iterator, Coroutine, MutableSet)
from .dt import as_local, utcnow
+# pylint: disable=invalid-name
T = TypeVar('T')
U = TypeVar('U')
+ENUM_T = TypeVar('ENUM_T', bound=enum.Enum)
+# pylint: enable=invalid-name
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
@@ -55,7 +58,7 @@ def repr_helper(inp: Any) -> str:
return ", ".join(
repr_helper(key)+"="+repr_helper(item) for key, item
in inp.items())
- elif isinstance(inp, datetime):
+ if isinstance(inp, datetime):
return as_local(inp).isoformat()
return str(inp)
@@ -90,7 +93,7 @@ def ensure_unique_string(preferred_string: str, current_strings:
# Taken from: http://stackoverflow.com/a/11735897
-def get_local_ip():
+def get_local_ip() -> str:
"""Try to determine the local IP address of the machine."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@@ -98,7 +101,7 @@ def get_local_ip():
# Use Google Public DNS server to determine own IP
sock.connect(('8.8.8.8', 80))
- return sock.getsockname()[0]
+ return sock.getsockname()[0] # type: ignore
except socket.error:
try:
return socket.gethostbyname(socket.gethostname())
@@ -109,7 +112,7 @@ def get_local_ip():
# Taken from http://stackoverflow.com/a/23728630
-def get_random_string(length=10):
+def get_random_string(length: int = 10) -> str:
"""Return a random string with letters and digits."""
generator = random.SystemRandom()
source_chars = string.ascii_letters + string.digits
@@ -120,59 +123,62 @@ def get_random_string(length=10):
class OrderedEnum(enum.Enum):
"""Taken from Python 3.4.0 docs."""
- def __ge__(self, other):
+ # https://github.com/PyCQA/pylint/issues/2306
+ # pylint: disable=comparison-with-callable
+
+ def __ge__(self, other: ENUM_T) -> bool:
"""Return the greater than element."""
if self.__class__ is other.__class__:
- return self.value >= other.value
+ return bool(self.value >= other.value)
return NotImplemented
- def __gt__(self, other):
+ def __gt__(self, other: ENUM_T) -> bool:
"""Return the greater element."""
if self.__class__ is other.__class__:
- return self.value > other.value
+ return bool(self.value > other.value)
return NotImplemented
- def __le__(self, other):
+ def __le__(self, other: ENUM_T) -> bool:
"""Return the lower than element."""
if self.__class__ is other.__class__:
- return self.value <= other.value
+ return bool(self.value <= other.value)
return NotImplemented
- def __lt__(self, other):
+ def __lt__(self, other: ENUM_T) -> bool:
"""Return the lower element."""
if self.__class__ is other.__class__:
- return self.value < other.value
+ return bool(self.value < other.value)
return NotImplemented
-class OrderedSet(MutableSet):
+class OrderedSet(MutableSet[T]):
"""Ordered set taken from http://code.activestate.com/recipes/576694/."""
- def __init__(self, iterable=None):
+ def __init__(self, iterable: Iterable[T] = None) -> None:
"""Initialize the set."""
- self.end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.map = {} # key --> [key, prev, next]
+ self.end = end = [] # type: List[Any]
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.map = {} # type: Dict[T, List] # key --> [key, prev, next]
if iterable is not None:
- self |= iterable
+ self |= iterable # type: ignore
- def __len__(self):
+ def __len__(self) -> int:
"""Return the length of the set."""
return len(self.map)
- def __contains__(self, key):
+ def __contains__(self, key: T) -> bool: # type: ignore
"""Check if key is in set."""
return key in self.map
# pylint: disable=arguments-differ
- def add(self, key):
+ def add(self, key: T) -> None:
"""Add an element to the end of the set."""
if key not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end]
- def promote(self, key):
+ def promote(self, key: T) -> None:
"""Promote element to beginning of the set, add if not there."""
if key in self.map:
self.discard(key)
@@ -182,14 +188,14 @@ class OrderedSet(MutableSet):
curr[2] = begin[1] = self.map[key] = [key, curr, begin]
# pylint: disable=arguments-differ
- def discard(self, key):
+ def discard(self, key: T) -> None:
"""Discard an element from the set."""
if key in self.map:
key, prev_item, next_item = self.map.pop(key)
prev_item[2] = next_item
next_item[1] = prev_item
- def __iter__(self):
+ def __iter__(self) -> Iterator[T]:
"""Iterate of the set."""
end = self.end
curr = end[2]
@@ -197,7 +203,7 @@ class OrderedSet(MutableSet):
yield curr[0]
curr = curr[2]
- def __reversed__(self):
+ def __reversed__(self) -> Iterator[T]:
"""Reverse the ordering."""
end = self.end
curr = end[1]
@@ -206,7 +212,7 @@ class OrderedSet(MutableSet):
curr = curr[1]
# pylint: disable=arguments-differ
- def pop(self, last=True):
+ def pop(self, last: bool = True) -> T:
"""Pop element of the end of the set.
Set last=False to pop from the beginning.
@@ -215,27 +221,27 @@ class OrderedSet(MutableSet):
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
- return key
+ return key # type: ignore
- def update(self, *args):
+ def update(self, *args: Any) -> None:
"""Add elements from args to the set."""
for item in chain(*args):
self.add(item)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return the representation."""
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
- def __eq__(self, other):
+ def __eq__(self, other: Any) -> bool:
"""Return the comparison."""
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
-class Throttle(object):
+class Throttle:
"""A class for throttling the execution of tasks.
This method decorator adds a cooldown to a method to prevent it from being
@@ -253,20 +259,21 @@ class Throttle(object):
Adds a datetime attribute `last_call` to the method.
"""
- def __init__(self, min_time, limit_no_throttle=None):
+ def __init__(self, min_time: timedelta,
+ limit_no_throttle: timedelta = None) -> None:
"""Initialize the throttle."""
self.min_time = min_time
self.limit_no_throttle = limit_no_throttle
- def __call__(self, method):
+ def __call__(self, method: Callable) -> Callable:
"""Caller for the throttle."""
# Make sure we return a coroutine if the method is async.
if asyncio.iscoroutinefunction(method):
- async def throttled_value():
+ async def throttled_value() -> None:
"""Stand-in function for when real func is being throttled."""
return None
else:
- def throttled_value():
+ def throttled_value() -> None: # type: ignore
"""Stand-in function for when real func is being throttled."""
return None
@@ -287,14 +294,14 @@ class Throttle(object):
'.' not in method.__qualname__.split('..')[-1])
@wraps(method)
- def wrapper(*args, **kwargs):
+ def wrapper(*args: Any, **kwargs: Any) -> Union[Callable, Coroutine]:
"""Wrap that allows wrapped to be called only once per min_time.
If we cannot acquire the lock, it is running so return None.
"""
# pylint: disable=protected-access
if hasattr(method, '__self__'):
- host = method.__self__
+ host = getattr(method, '__self__')
elif is_func:
host = wrapper
else:
@@ -317,7 +324,7 @@ class Throttle(object):
if force or utcnow() - throttle[1] > self.min_time:
result = method(*args, **kwargs)
throttle[1] = utcnow()
- return result
+ return result # type: ignore
return throttled_value()
finally:
diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py
index b3aa370da2e..aa030bf13c7 100644
--- a/homeassistant/util/async_.py
+++ b/homeassistant/util/async_.py
@@ -3,22 +3,25 @@ import concurrent.futures
import threading
import logging
from asyncio import coroutines
+from asyncio.events import AbstractEventLoop
from asyncio.futures import Future
from asyncio import ensure_future
-
+from typing import Any, Union, Coroutine, Callable, Generator
_LOGGER = logging.getLogger(__name__)
-def _set_result_unless_cancelled(fut, result):
+def _set_result_unless_cancelled(fut: Future, result: Any) -> None:
"""Set the result only if the Future was not cancelled."""
if fut.cancelled():
return
fut.set_result(result)
-def _set_concurrent_future_state(concurr, source):
+def _set_concurrent_future_state(
+ concurr: concurrent.futures.Future,
+ source: Union[concurrent.futures.Future, Future]) -> None:
"""Copy state from a future to a concurrent.futures.Future."""
assert source.done()
if source.cancelled():
@@ -33,7 +36,8 @@ def _set_concurrent_future_state(concurr, source):
concurr.set_result(result)
-def _copy_future_state(source, dest):
+def _copy_future_state(source: Union[concurrent.futures.Future, Future],
+ dest: Union[concurrent.futures.Future, Future]) -> None:
"""Copy state from another Future.
The other Future may be a concurrent.futures.Future.
@@ -53,7 +57,9 @@ def _copy_future_state(source, dest):
dest.set_result(result)
-def _chain_future(source, destination):
+def _chain_future(
+ source: Union[concurrent.futures.Future, Future],
+ destination: Union[concurrent.futures.Future, Future]) -> None:
"""Chain two futures so that when one completes, so does the other.
The result (or exception) of source will be copied to destination.
@@ -65,23 +71,32 @@ def _chain_future(source, destination):
if not isinstance(destination, (Future, concurrent.futures.Future)):
raise TypeError('A future is required for destination argument')
# pylint: disable=protected-access
- source_loop = source._loop if isinstance(source, Future) else None
- dest_loop = destination._loop if isinstance(destination, Future) else None
+ if isinstance(source, Future):
+ source_loop = source._loop # type: ignore
+ else:
+ source_loop = None
+ if isinstance(destination, Future):
+ dest_loop = destination._loop # type: ignore
+ else:
+ dest_loop = None
- def _set_state(future, other):
+ def _set_state(future: Union[concurrent.futures.Future, Future],
+ other: Union[concurrent.futures.Future, Future]) -> None:
if isinstance(future, Future):
_copy_future_state(other, future)
else:
_set_concurrent_future_state(future, other)
- def _call_check_cancel(destination):
+ def _call_check_cancel(
+ destination: Union[concurrent.futures.Future, Future]) -> None:
if destination.cancelled():
if source_loop is None or source_loop is dest_loop:
source.cancel()
else:
source_loop.call_soon_threadsafe(source.cancel)
- def _call_set_state(source):
+ def _call_set_state(
+ source: Union[concurrent.futures.Future, Future]) -> None:
if dest_loop is None or dest_loop is source_loop:
_set_state(destination, source)
else:
@@ -91,7 +106,9 @@ def _chain_future(source, destination):
source.add_done_callback(_call_set_state)
-def run_coroutine_threadsafe(coro, loop):
+def run_coroutine_threadsafe(
+ coro: Union[Coroutine, Generator],
+ loop: AbstractEventLoop) -> concurrent.futures.Future:
"""Submit a coroutine object to a given event loop.
Return a concurrent.futures.Future to access the result.
@@ -102,9 +119,9 @@ def run_coroutine_threadsafe(coro, loop):
if not coroutines.iscoroutine(coro):
raise TypeError('A coroutine object is required')
- future = concurrent.futures.Future()
+ future = concurrent.futures.Future() # type: concurrent.futures.Future
- def callback():
+ def callback() -> None:
"""Handle the call to the coroutine."""
try:
_chain_future(ensure_future(coro, loop=loop), future)
@@ -119,7 +136,8 @@ def run_coroutine_threadsafe(coro, loop):
return future
-def fire_coroutine_threadsafe(coro, loop):
+def fire_coroutine_threadsafe(coro: Coroutine,
+ loop: AbstractEventLoop) -> None:
"""Submit a coroutine object to a given event loop.
This method does not provide a way to retrieve the result and
@@ -133,15 +151,15 @@ def fire_coroutine_threadsafe(coro, loop):
if not coroutines.iscoroutine(coro):
raise TypeError('A coroutine object is required: %s' % coro)
- def callback():
+ def callback() -> None:
"""Handle the firing of a coroutine."""
ensure_future(coro, loop=loop)
loop.call_soon_threadsafe(callback)
- return
-def run_callback_threadsafe(loop, callback, *args):
+def run_callback_threadsafe(loop: AbstractEventLoop, callback: Callable,
+ *args: Any) -> concurrent.futures.Future:
"""Submit a callback object to a given event loop.
Return a concurrent.futures.Future to access the result.
@@ -150,9 +168,9 @@ def run_callback_threadsafe(loop, callback, *args):
if ident is not None and ident == threading.get_ident():
raise RuntimeError('Cannot be called from within the event loop')
- future = concurrent.futures.Future()
+ future = concurrent.futures.Future() # type: concurrent.futures.Future
- def run_callback():
+ def run_callback() -> None:
"""Run callback and store result."""
try:
future.set_result(callback(*args))
diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py
index a26f7014444..0538bfbf369 100644
--- a/homeassistant/util/color.py
+++ b/homeassistant/util/color.py
@@ -2,7 +2,7 @@
import math
import colorsys
-from typing import Tuple
+from typing import Tuple, List
# Official CSS3 colors from w3.org:
# https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4
@@ -162,7 +162,7 @@ COLORS = {
}
-def color_name_to_rgb(color_name):
+def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]:
"""Convert color name to RGB hex value."""
# COLORS map has no spaces in it, so make the color_name have no
# spaces in it as well for matching purposes
@@ -305,7 +305,8 @@ def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]:
return (r, g, b)
-def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[float, float, float]:
+def color_RGB_to_hsv(
+ iR: float, iG: float, iB: float) -> Tuple[float, float, float]:
"""Convert an rgb color to its hsv representation.
Hue is scaled 0-360
@@ -316,7 +317,7 @@ def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[float, float, float]:
return round(fHSV[0]*360, 3), round(fHSV[1]*100, 3), round(fHSV[2]*100, 3)
-def color_RGB_to_hs(iR: int, iG: int, iB: int) -> Tuple[float, float]:
+def color_RGB_to_hs(iR: float, iG: float, iB: float) -> Tuple[float, float]:
"""Convert an rgb color to its hs representation."""
return color_RGB_to_hsv(iR, iG, iB)[:2]
@@ -340,7 +341,7 @@ def color_hs_to_RGB(iH: float, iS: float) -> Tuple[int, int, int]:
def color_xy_to_hs(vX: float, vY: float) -> Tuple[float, float]:
"""Convert an xy color to its hs representation."""
h, s, _ = color_RGB_to_hsv(*color_xy_to_RGB(vX, vY))
- return (h, s)
+ return h, s
def color_hs_to_xy(iH: float, iS: float) -> Tuple[float, float]:
@@ -348,8 +349,7 @@ def color_hs_to_xy(iH: float, iS: float) -> Tuple[float, float]:
return color_RGB_to_xy(*color_hs_to_RGB(iH, iS))
-def _match_max_scale(input_colors: Tuple[int, ...],
- output_colors: Tuple[int, ...]) -> Tuple[int, ...]:
+def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple:
"""Match the maximum value of the output to the input."""
max_in = max(input_colors)
max_out = max(output_colors)
@@ -360,7 +360,7 @@ def _match_max_scale(input_colors: Tuple[int, ...],
return tuple(int(round(i * factor)) for i in output_colors)
-def color_rgb_to_rgbw(r, g, b):
+def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]:
"""Convert an rgb color to an rgbw representation."""
# Calculate the white channel as the minimum of input rgb channels.
# Subtract the white portion from the remaining rgb channels.
@@ -369,25 +369,25 @@ def color_rgb_to_rgbw(r, g, b):
# Match the output maximum value to the input. This ensures the full
# channel range is used.
- return _match_max_scale((r, g, b), rgbw)
+ return _match_max_scale((r, g, b), rgbw) # type: ignore
-def color_rgbw_to_rgb(r, g, b, w):
+def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> Tuple[int, int, int]:
"""Convert an rgbw color to an rgb representation."""
# Add the white channel back into the rgb channels.
rgb = (r + w, g + w, b + w)
# Match the output maximum value to the input. This ensures the
# output doesn't overflow.
- return _match_max_scale((r, g, b, w), rgb)
+ return _match_max_scale((r, g, b, w), rgb) # type: ignore
-def color_rgb_to_hex(r, g, b):
+def color_rgb_to_hex(r: int, g: int, b: int) -> str:
"""Return a RGB color from a hex color string."""
return '{0:02x}{1:02x}{2:02x}'.format(round(r), round(g), round(b))
-def rgb_hex_to_rgb_list(hex_string):
+def rgb_hex_to_rgb_list(hex_string: str) -> List[int]:
"""Return an RGB color value list from a hex color string."""
return [int(hex_string[i:i + len(hex_string) // 3], 16)
for i in range(0,
@@ -395,12 +395,14 @@ def rgb_hex_to_rgb_list(hex_string):
len(hex_string) // 3)]
-def color_temperature_to_hs(color_temperature_kelvin):
+def color_temperature_to_hs(
+ color_temperature_kelvin: float) -> Tuple[float, float]:
"""Return an hs color from a color temperature in Kelvin."""
return color_RGB_to_hs(*color_temperature_to_rgb(color_temperature_kelvin))
-def color_temperature_to_rgb(color_temperature_kelvin):
+def color_temperature_to_rgb(
+ color_temperature_kelvin: float) -> Tuple[float, float, float]:
"""
Return an RGB color from a color temperature in Kelvin.
@@ -421,7 +423,7 @@ def color_temperature_to_rgb(color_temperature_kelvin):
blue = _get_blue(tmp_internal)
- return (red, green, blue)
+ return red, green, blue
def _bound(color_component: float, minimum: float = 0,
@@ -464,11 +466,11 @@ def _get_blue(temperature: float) -> float:
return _bound(blue)
-def color_temperature_mired_to_kelvin(mired_temperature):
+def color_temperature_mired_to_kelvin(mired_temperature: float) -> float:
"""Convert absolute mired shift to degrees kelvin."""
return math.floor(1000000 / mired_temperature)
-def color_temperature_kelvin_to_mired(kelvin_temperature):
+def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> float:
"""Convert degrees kelvin to mired shift."""
return math.floor(1000000 / kelvin_temperature)
diff --git a/homeassistant/util/decorator.py b/homeassistant/util/decorator.py
index c26606d52cf..22ed1a4dae6 100644
--- a/homeassistant/util/decorator.py
+++ b/homeassistant/util/decorator.py
@@ -1,12 +1,15 @@
"""Decorator utility functions."""
+from typing import Callable, TypeVar
+
+CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) # noqa pylint: disable=invalid-name
class Registry(dict):
"""Registry of items."""
- def register(self, name):
+ def register(self, name: str) -> Callable[[CALLABLE_T], CALLABLE_T]:
"""Return decorator to register item with a specific name."""
- def decorator(func):
+ def decorator(func: CALLABLE_T) -> CALLABLE_T:
"""Register decorated function."""
self[name] = func
return func
diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py
index 0f07a90e9bb..ce6775b9ea7 100644
--- a/homeassistant/util/dt.py
+++ b/homeassistant/util/dt.py
@@ -65,20 +65,20 @@ def as_utc(dattim: dt.datetime) -> dt.datetime:
"""
if dattim.tzinfo == UTC:
return dattim
- elif dattim.tzinfo is None:
+ if dattim.tzinfo is None:
dattim = DEFAULT_TIME_ZONE.localize(dattim) # type: ignore
return dattim.astimezone(UTC)
-def as_timestamp(dt_value):
+def as_timestamp(dt_value: dt.datetime) -> float:
"""Convert a date/time into a unix time (seconds since 1970)."""
if hasattr(dt_value, "timestamp"):
- parsed_dt = dt_value
+ parsed_dt = dt_value # type: Optional[dt.datetime]
else:
parsed_dt = parse_datetime(str(dt_value))
- if not parsed_dt:
- raise ValueError("not a valid date/time.")
+ if parsed_dt is None:
+ raise ValueError("not a valid date/time.")
return parsed_dt.timestamp()
@@ -86,7 +86,7 @@ def as_local(dattim: dt.datetime) -> dt.datetime:
"""Convert a UTC datetime object to local time zone."""
if dattim.tzinfo == DEFAULT_TIME_ZONE:
return dattim
- elif dattim.tzinfo is None:
+ if dattim.tzinfo is None:
dattim = UTC.localize(dattim)
return dattim.astimezone(DEFAULT_TIME_ZONE)
@@ -98,7 +98,7 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime:
def start_of_local_day(dt_or_d:
- Union[dt.date, dt.datetime]=None) -> dt.datetime:
+ Union[dt.date, dt.datetime] = None) -> dt.datetime:
"""Return local datetime object of start of day from date or datetime."""
if dt_or_d is None:
date = now().date() # type: dt.date
@@ -150,7 +150,7 @@ def parse_date(dt_str: str) -> Optional[dt.date]:
return None
-def parse_time(time_str):
+def parse_time(time_str: str) -> Optional[dt.time]:
"""Parse a time string (00:20:00) into Time object.
Return None if invalid.
diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py
index 1029e58c118..8ecfebd5b33 100644
--- a/homeassistant/util/json.py
+++ b/homeassistant/util/json.py
@@ -38,7 +38,7 @@ def load_json(filename: str, default: Union[List, Dict, None] = None) \
return {} if default is None else default
-def save_json(filename: str, data: Union[List, Dict]):
+def save_json(filename: str, data: Union[List, Dict]) -> None:
"""Save JSON data to a file.
Returns True on success.
diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py
index e390b537d34..16aec2ec617 100644
--- a/homeassistant/util/location.py
+++ b/homeassistant/util/location.py
@@ -33,7 +33,7 @@ LocationInfo = collections.namedtuple(
'use_metric'])
-def detect_location_info():
+def detect_location_info() -> Optional[LocationInfo]:
"""Detect location information."""
data = _get_freegeoip()
@@ -49,15 +49,21 @@ def detect_location_info():
return LocationInfo(**data)
-def distance(lat1, lon1, lat2, lon2):
+def distance(lat1: Optional[float], lon1: Optional[float],
+ lat2: float, lon2: float) -> Optional[float]:
"""Calculate the distance in meters between two points.
Async friendly.
"""
- return vincenty((lat1, lon1), (lat2, lon2)) * 1000
+ if lat1 is None or lon1 is None:
+ return None
+ result = vincenty((lat1, lon1), (lat2, lon2))
+ if result is None:
+ return None
+ return result * 1000
-def elevation(latitude, longitude):
+def elevation(latitude: float, longitude: float) -> int:
"""Return elevation for given latitude and longitude."""
try:
req = requests.get(
diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py
index 10b43445184..f2bf15d8a03 100644
--- a/homeassistant/util/logging.py
+++ b/homeassistant/util/logging.py
@@ -1,7 +1,9 @@
"""Logging utilities."""
import asyncio
+from asyncio.events import AbstractEventLoop
import logging
import threading
+from typing import Optional
from .async_ import run_coroutine_threadsafe
@@ -9,12 +11,12 @@ from .async_ import run_coroutine_threadsafe
class HideSensitiveDataFilter(logging.Filter):
"""Filter API password calls."""
- def __init__(self, text):
+ def __init__(self, text: str) -> None:
"""Initialize sensitive data filter."""
super().__init__()
self.text = text
- def filter(self, record):
+ def filter(self, record: logging.LogRecord) -> bool:
"""Hide sensitive data in messages."""
record.msg = record.msg.replace(self.text, '*******')
@@ -22,14 +24,15 @@ class HideSensitiveDataFilter(logging.Filter):
# pylint: disable=invalid-name
-class AsyncHandler(object):
+class AsyncHandler:
"""Logging handler wrapper to add an async layer."""
- def __init__(self, loop, handler):
+ def __init__(
+ self, loop: AbstractEventLoop, handler: logging.Handler) -> None:
"""Initialize async logging handler wrapper."""
self.handler = handler
self.loop = loop
- self._queue = asyncio.Queue(loop=loop)
+ self._queue = asyncio.Queue(loop=loop) # type: asyncio.Queue
self._thread = threading.Thread(target=self._process)
# Delegate from handler
@@ -45,11 +48,11 @@ class AsyncHandler(object):
self._thread.start()
- def close(self):
+ def close(self) -> None:
"""Wrap close to handler."""
self.emit(None)
- async def async_close(self, blocking=False):
+ async def async_close(self, blocking: bool = False) -> None:
"""Close the handler.
When blocking=True, will wait till closed.
@@ -60,7 +63,7 @@ class AsyncHandler(object):
while self._thread.is_alive():
await asyncio.sleep(0, loop=self.loop)
- def emit(self, record):
+ def emit(self, record: Optional[logging.LogRecord]) -> None:
"""Process a record."""
ident = self.loop.__dict__.get("_thread_ident")
@@ -71,11 +74,11 @@ class AsyncHandler(object):
else:
self.loop.call_soon_threadsafe(self._queue.put_nowait, record)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return the string names."""
return str(self.handler)
- def _process(self):
+ def _process(self) -> None:
"""Process log in a thread."""
while True:
record = run_coroutine_threadsafe(
@@ -87,34 +90,34 @@ class AsyncHandler(object):
self.handler.emit(record)
- def createLock(self):
+ def createLock(self) -> None:
"""Ignore lock stuff."""
pass
- def acquire(self):
+ def acquire(self) -> None:
"""Ignore lock stuff."""
pass
- def release(self):
+ def release(self) -> None:
"""Ignore lock stuff."""
pass
@property
- def level(self):
+ def level(self) -> int:
"""Wrap property level to handler."""
return self.handler.level
@property
- def formatter(self):
+ def formatter(self) -> Optional[logging.Formatter]:
"""Wrap property formatter to handler."""
return self.handler.formatter
@property
- def name(self):
+ def name(self) -> str:
"""Wrap property set_name to handler."""
- return self.handler.get_name()
+ return self.handler.get_name() # type: ignore
@name.setter
- def name(self, name):
+ def name(self, name: str) -> None:
"""Wrap property get_name to handler."""
- self.handler.name = name
+ self.handler.set_name(name) # type: ignore
diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py
index d1d398020de..9433046e688 100644
--- a/homeassistant/util/package.py
+++ b/homeassistant/util/package.py
@@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
INSTALL_LOCK = threading.Lock()
-def is_virtual_env():
+def is_virtual_env() -> bool:
"""Return if we run in a virtual environtment."""
# Check supports venv && virtualenv
return (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py
index 4f528cfcb51..392c5986c89 100644
--- a/homeassistant/util/ssl.py
+++ b/homeassistant/util/ssl.py
@@ -4,7 +4,7 @@ import ssl
import certifi
-def client_context():
+def client_context() -> ssl.SSLContext:
"""Return an SSL context for making requests."""
context = ssl.create_default_context(
purpose=ssl.Purpose.SERVER_AUTH,
@@ -13,7 +13,7 @@ def client_context():
return context
-def server_context():
+def server_context() -> ssl.SSLContext:
"""Return an SSL context following the Mozilla recommendations.
TLS configuration follows the best-practice guidelines specified here:
diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py
index 913d6456906..6e2b378b218 100644
--- a/homeassistant/util/temperature.py
+++ b/homeassistant/util/temperature.py
@@ -29,6 +29,6 @@ def convert(temperature: float, from_unit: str, to_unit: str,
if from_unit == to_unit:
return temperature
- elif from_unit == TEMP_CELSIUS:
+ if from_unit == TEMP_CELSIUS:
return celsius_to_fahrenheit(temperature, interval)
return fahrenheit_to_celsius(temperature, interval)
diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py
index 4cc0fff96b9..5a8f515c3ad 100644
--- a/homeassistant/util/unit_system.py
+++ b/homeassistant/util/unit_system.py
@@ -1,6 +1,7 @@
"""Unit system helper class and methods."""
import logging
+from typing import Optional
from numbers import Number
from homeassistant.const import (
@@ -61,10 +62,10 @@ def is_valid_unit(unit: str, unit_type: str) -> bool:
return unit in units
-class UnitSystem(object):
+class UnitSystem:
"""A container for units of measure."""
- def __init__(self: object, name: str, temperature: str, length: str,
+ def __init__(self, name: str, temperature: str, length: str,
volume: str, mass: str) -> None:
"""Initialize the unit system object."""
errors = \
@@ -99,7 +100,7 @@ class UnitSystem(object):
return temperature_util.convert(temperature,
from_unit, self.temperature_unit)
- def length(self, length: float, from_unit: str) -> float:
+ def length(self, length: Optional[float], from_unit: str) -> float:
"""Convert the given length to this unit system."""
if not isinstance(length, Number):
raise TypeError('{} is not a numeric value.'.format(str(length)))
diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py
index 298d52722a5..69f83aefad7 100644
--- a/homeassistant/util/yaml.py
+++ b/homeassistant/util/yaml.py
@@ -4,7 +4,7 @@ import os
import sys
import fnmatch
from collections import OrderedDict
-from typing import Union, List, Dict
+from typing import Union, List, Dict, Iterator, overload, TypeVar
import yaml
try:
@@ -22,7 +22,10 @@ from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
_SECRET_NAMESPACE = 'homeassistant'
SECRET_YAML = 'secrets.yaml'
-__SECRET_CACHE = {} # type: Dict
+__SECRET_CACHE = {} # type: Dict[str, JSON_TYPE]
+
+JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name
+DICT_T = TypeVar('DICT_T', bound=Dict) # pylint: disable=invalid-name
class NodeListClass(list):
@@ -37,7 +40,42 @@ class NodeStrClass(str):
pass
-def _add_reference(obj, loader, node):
+# pylint: disable=too-many-ancestors
+class SafeLineLoader(yaml.SafeLoader):
+ """Loader class that keeps track of line numbers."""
+
+ def compose_node(self, parent: yaml.nodes.Node,
+ index: int) -> yaml.nodes.Node:
+ """Annotate a node with the first line it was seen."""
+ last_line = self.line # type: int
+ node = super(SafeLineLoader,
+ self).compose_node(parent, index) # type: yaml.nodes.Node
+ node.__line__ = last_line + 1 # type: ignore
+ return node
+
+
+# pylint: disable=pointless-statement
+@overload
+def _add_reference(obj: Union[list, NodeListClass],
+ loader: yaml.SafeLoader,
+ node: yaml.nodes.Node) -> NodeListClass: ...
+
+
+@overload # noqa: F811
+def _add_reference(obj: Union[str, NodeStrClass],
+ loader: yaml.SafeLoader,
+ node: yaml.nodes.Node) -> NodeStrClass: ...
+
+
+@overload # noqa: F811
+def _add_reference(obj: DICT_T,
+ loader: yaml.SafeLoader,
+ node: yaml.nodes.Node) -> DICT_T: ...
+# pylint: enable=pointless-statement
+
+
+def _add_reference(obj, loader: SafeLineLoader, # type: ignore # noqa: F811
+ node: yaml.nodes.Node):
"""Add file reference information to an object."""
if isinstance(obj, list):
obj = NodeListClass(obj)
@@ -48,20 +86,7 @@ def _add_reference(obj, loader, node):
return obj
-# pylint: disable=too-many-ancestors
-class SafeLineLoader(yaml.SafeLoader):
- """Loader class that keeps track of line numbers."""
-
- def compose_node(self, parent: yaml.nodes.Node, index) -> yaml.nodes.Node:
- """Annotate a node with the first line it was seen."""
- last_line = self.line # type: int
- node = super(SafeLineLoader,
- self).compose_node(parent, index) # type: yaml.nodes.Node
- node.__line__ = last_line + 1 # type: ignore
- return node
-
-
-def load_yaml(fname: str) -> Union[List, Dict]:
+def load_yaml(fname: str) -> JSON_TYPE:
"""Load a YAML file."""
try:
with open(fname, encoding='utf-8') as conf_file:
@@ -83,12 +108,12 @@ def dump(_dict: dict) -> str:
.replace(': null\n', ':\n')
-def save_yaml(path, data):
+def save_yaml(path: str, data: dict) -> None:
"""Save YAML to a file."""
# Dump before writing to not truncate the file if dumping fails
- data = dump(data)
+ str_data = dump(data)
with open(path, 'w', encoding='utf-8') as outfile:
- outfile.write(data)
+ outfile.write(str_data)
def clear_secret_cache() -> None:
@@ -100,7 +125,7 @@ def clear_secret_cache() -> None:
def _include_yaml(loader: SafeLineLoader,
- node: yaml.nodes.Node) -> Union[List, Dict]:
+ node: yaml.nodes.Node) -> JSON_TYPE:
"""Load another YAML file and embeds it using the !include tag.
Example:
@@ -115,7 +140,7 @@ def _is_file_valid(name: str) -> bool:
return not name.startswith('.')
-def _find_files(directory: str, pattern: str):
+def _find_files(directory: str, pattern: str) -> Iterator[str]:
"""Recursively load files in a directory."""
for root, dirs, files in os.walk(directory, topdown=True):
dirs[:] = [d for d in dirs if _is_file_valid(d)]
@@ -151,7 +176,7 @@ def _include_dir_merge_named_yaml(loader: SafeLineLoader,
def _include_dir_list_yaml(loader: SafeLineLoader,
- node: yaml.nodes.Node):
+ node: yaml.nodes.Node) -> List[JSON_TYPE]:
"""Load multiple files from directory as a list."""
loc = os.path.join(os.path.dirname(loader.name), node.value)
return [load_yaml(f) for f in _find_files(loc, '*.yaml')
@@ -159,11 +184,11 @@ def _include_dir_list_yaml(loader: SafeLineLoader,
def _include_dir_merge_list_yaml(loader: SafeLineLoader,
- node: yaml.nodes.Node):
+ node: yaml.nodes.Node) -> JSON_TYPE:
"""Load multiple files from directory as a merged list."""
loc = os.path.join(os.path.dirname(loader.name),
node.value) # type: str
- merged_list = [] # type: List
+ merged_list = [] # type: List[JSON_TYPE]
for fname in _find_files(loc, '*.yaml'):
if os.path.basename(fname) == SECRET_YAML:
continue
@@ -202,28 +227,27 @@ def _ordered_dict(loader: SafeLineLoader,
return _add_reference(OrderedDict(nodes), loader, node)
-def _construct_seq(loader: SafeLineLoader, node: yaml.nodes.Node):
+def _construct_seq(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE:
"""Add line number and file name to Load YAML sequence."""
obj, = loader.construct_yaml_seq(node)
return _add_reference(obj, loader, node)
def _env_var_yaml(loader: SafeLineLoader,
- node: yaml.nodes.Node):
+ node: yaml.nodes.Node) -> str:
"""Load environment variables and embed it into the configuration YAML."""
args = node.value.split()
# Check for a default value
if len(args) > 1:
return os.getenv(args[0], ' '.join(args[1:]))
- elif args[0] in os.environ:
+ if args[0] in os.environ:
return os.environ[args[0]]
- else:
- _LOGGER.error("Environment variable %s not defined.", node.value)
- raise HomeAssistantError(node.value)
+ _LOGGER.error("Environment variable %s not defined.", node.value)
+ raise HomeAssistantError(node.value)
-def _load_secret_yaml(secret_path: str) -> Dict:
+def _load_secret_yaml(secret_path: str) -> JSON_TYPE:
"""Load the secrets yaml from path."""
secret_path = os.path.join(secret_path, SECRET_YAML)
if secret_path in __SECRET_CACHE:
@@ -249,7 +273,7 @@ def _load_secret_yaml(secret_path: str) -> Dict:
def _secret_yaml(loader: SafeLineLoader,
- node: yaml.nodes.Node):
+ node: yaml.nodes.Node) -> JSON_TYPE:
"""Load secrets and embed it into the configuration YAML."""
secret_path = os.path.dirname(loader.name)
while True:
@@ -309,9 +333,10 @@ yaml.SafeLoader.add_constructor('!include_dir_merge_named',
# From: https://gist.github.com/miracle2k/3184458
# pylint: disable=redefined-outer-name
-def represent_odict(dump, tag, mapping, flow_style=None):
+def represent_odict(dump, tag, mapping, # type: ignore
+ flow_style=None) -> yaml.MappingNode:
"""Like BaseRepresenter.represent_mapping but does not issue the sort()."""
- value = []
+ value = [] # type: list
node = yaml.MappingNode(tag, value, flow_style=flow_style)
if dump.alias_key is not None:
dump.represented_objects[dump.alias_key] = node
diff --git a/mypy.ini b/mypy.ini
index 3970ea72d47..c92786e643f 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,10 +1,18 @@
[mypy]
-warn_redundant_casts = true
-warn_unused_configs = true
-ignore_missing_imports = true
+check_untyped_defs = true
follow_imports = silent
-warn_unused_ignores = true
+ignore_missing_imports = true
+warn_incomplete_stub = true
+warn_redundant_casts = true
warn_return_any = true
+warn_unused_configs = true
+warn_unused_ignores = true
+
+[mypy-homeassistant.*]
+disallow_untyped_defs = true
+
+[mypy-homeassistant.config_entries]
+disallow_untyped_defs = false
[mypy-homeassistant.util.yaml]
warn_return_any = false
diff --git a/pylintrc b/pylintrc
index d47437cb121..00bc6582f3a 100644
--- a/pylintrc
+++ b/pylintrc
@@ -4,7 +4,6 @@
# duplicate-code - unavoidable
# cyclic-import - doesn't test if both import on load
# abstract-class-little-used - prevents from setting right foundation
-# abstract-class-not-used - is flaky, should not show up but does
# unused-argument - generic callbacks and setup methods create a lot of warnings
# global-statement - used for the on-demand requirement installation
# redefined-variable-type - this is Python, we're duck typing!
@@ -12,15 +11,17 @@
# too-few-* - same as too-many-*
# abstract-method - with intro of async there are always methods missing
# inconsistent-return-statements - doesn't handle raise
+# useless-return - https://github.com/PyCQA/pylint/issues/2300
+# not-an-iterable - https://github.com/PyCQA/pylint/issues/2311
disable=
abstract-class-little-used,
- abstract-class-not-used,
abstract-method,
cyclic-import,
duplicate-code,
global-statement,
inconsistent-return-statements,
locally-disabled,
+ not-an-iterable,
not-context-manager,
redefined-variable-type,
too-few-public-methods,
@@ -32,7 +33,8 @@ disable=
too-many-public-methods,
too-many-return-statements,
too-many-statements,
- unused-argument
+ unused-argument,
+ useless-return
[REPORTS]
reports=no
diff --git a/requirements_all.txt b/requirements_all.txt
index 72dc74b0f66..9be2d66f327 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -9,7 +9,7 @@ pip>=8.0.3
pytz>=2018.04
pyyaml>=3.13,<4
requests==2.19.1
-voluptuous==0.11.1
+voluptuous==0.11.3
# homeassistant.components.nuimo_controller
--only-binary=all nuimo==0.1.0
@@ -108,6 +108,9 @@ aiolifx_effects==0.1.2
# homeassistant.components.scene.hunterdouglas_powerview
aiopvapi==1.5.4
+# homeassistant.components.cover.aladdin_connect
+aladdin_connect==0.1
+
# homeassistant.components.alarmdecoder
alarmdecoder==1.13.2
@@ -154,7 +157,7 @@ batinfo==0.4.2
# homeassistant.components.sensor.geizhals
# homeassistant.components.sensor.scrape
# homeassistant.components.sensor.sytadin
-beautifulsoup4==4.6.0
+beautifulsoup4==4.6.1
# homeassistant.components.zha
bellows==0.6.0
@@ -196,6 +199,9 @@ braviarc-homeassistant==0.3.7.dev0
# homeassistant.components.switch.broadlink
broadlink==0.9.0
+# homeassistant.components.cover.brunt
+brunt==0.1.2
+
# homeassistant.components.device_tracker.bluetooth_tracker
bt_proximity==0.1.2
@@ -260,7 +266,7 @@ defusedxml==0.5.0
deluge-client==1.4.0
# homeassistant.components.media_player.denonavr
-denonavr==0.7.4
+denonavr==0.7.5
# homeassistant.components.media_player.directv
directpy==0.5
@@ -309,7 +315,7 @@ ephem==3.7.6.0
epson-projector==0.1.3
# homeassistant.components.netgear_lte
-eternalegypt==0.0.2
+eternalegypt==0.0.3
# homeassistant.components.keyboard_remote
# evdev==0.6.1
@@ -415,7 +421,7 @@ hole==0.3.0
holidays==0.9.5
# homeassistant.components.frontend
-home-assistant-frontend==20180720.0
+home-assistant-frontend==20180726.0
# homeassistant.components.homekit_controller
# homekit==0.10
@@ -452,7 +458,7 @@ influxdb==5.0.0
insteonlocal==0.53
# homeassistant.components.insteon_plm
-insteonplm==0.11.3
+insteonplm==0.11.7
# homeassistant.components.sensor.iperf3
iperf3==0.1.10
@@ -529,6 +535,9 @@ lw12==0.9.2
# homeassistant.components.sensor.lyft
lyft_rides==0.2
+# homeassistant.components.sensor.magicseaweed
+magicseaweed==1.0.0
+
# homeassistant.components.matrix
matrix-client==0.2.0
@@ -552,7 +561,7 @@ mitemp_bt==0.0.1
motorparts==1.0.2
# homeassistant.components.tts
-mutagen==1.40.0
+mutagen==1.41.0
# homeassistant.components.mychevy
mychevy==0.4.0
@@ -590,7 +599,7 @@ nuheat==0.3.0
# homeassistant.components.binary_sensor.trend
# homeassistant.components.image_processing.opencv
-numpy==1.14.5
+numpy==1.15.0
# homeassistant.components.google
oauth2client==4.0.0
@@ -643,7 +652,7 @@ piglow==1.2.4
pilight==0.1.1
# homeassistant.components.camera.proxy
-pillow==5.0.0
+pillow==5.2.0
# homeassistant.components.dominos
pizzapi==0.0.3
@@ -728,7 +737,7 @@ pyairvisual==2.0.1
pyalarmdotcom==0.3.2
# homeassistant.components.arlo
-pyarlo==0.1.9
+pyarlo==0.2.0
# homeassistant.components.notify.xmpp
pyasn1-modules==0.1.5
@@ -737,7 +746,7 @@ pyasn1-modules==0.1.5
pyasn1==0.3.7
# homeassistant.components.netatmo
-pyatmo==1.0.0
+pyatmo==1.1.1
# homeassistant.components.apple_tv
pyatv==0.3.10
@@ -753,7 +762,7 @@ pyblackbird==0.5
# pybluez==0.22
# homeassistant.components.neato
-pybotvac==0.0.7
+pybotvac==0.0.9
# homeassistant.components.cloudflare
pycfdns==0.0.1
@@ -822,6 +831,9 @@ pyflexit==0.3
# homeassistant.components.binary_sensor.flic
pyflic-homeassistant==0.4.dev0
+# homeassistant.components.light.futurenow
+pyfnip==0.2
+
# homeassistant.components.fritzbox
pyfritzhome==0.3.7
@@ -847,7 +859,7 @@ pyhik==0.1.8
pyhiveapi==0.2.14
# homeassistant.components.homematic
-pyhomematic==0.1.45
+pyhomematic==0.1.46
# homeassistant.components.sensor.hydroquebec
pyhydroquebec==2.2.2
@@ -955,7 +967,7 @@ pyotp==2.2.6
# homeassistant.components.sensor.openweathermap
# homeassistant.components.weather.openweathermap
-pyowm==2.8.0
+pyowm==2.9.0
# homeassistant.components.sensor.pollen
pypollencom==2.1.0
@@ -1141,7 +1153,7 @@ pyuptimerobot==0.0.5
# pyuserinput==0.1.11
# homeassistant.components.vera
-pyvera==0.2.43
+pyvera==0.2.44
# homeassistant.components.switch.vesync
pyvesync==0.1.1
@@ -1241,7 +1253,10 @@ shodan==1.8.1
simplepush==1.1.4
# homeassistant.components.alarm_control_panel.simplisafe
-simplisafe-python==1.0.5
+simplisafe-python==2.0.2
+
+# homeassistant.components.sisyphus
+sisyphus-control==2.1
# homeassistant.components.skybell
skybellpy==0.1.2
@@ -1256,7 +1271,7 @@ sleekxmpp==1.3.2
sleepyq==0.6
# homeassistant.components.smappee
-smappy==0.2.15
+smappy==0.2.16
# homeassistant.components.raspihats
# homeassistant.components.sensor.bh1750
@@ -1278,6 +1293,9 @@ somecomfort==0.5.2
# homeassistant.components.sensor.speedtest
speedtest-cli==2.0.2
+# homeassistant.components.spider
+spiderpy==1.2.0
+
# homeassistant.components.sensor.spotcrime
spotcrime==1.0.3
@@ -1287,7 +1305,7 @@ spotipy-homeassistant==2.4.4.dev1
# homeassistant.components.recorder
# homeassistant.scripts.db_migrator
# homeassistant.components.sensor.sql
-sqlalchemy==1.2.9
+sqlalchemy==1.2.10
# homeassistant.components.statsd
statsd==3.2.1
@@ -1340,6 +1358,9 @@ toonlib==1.0.2
# homeassistant.components.alarm_control_panel.totalconnect
total_connect_client==0.18
+# homeassistant.components.device_tracker.tplink
+tplink==0.2.1
+
# homeassistant.components.sensor.transmission
# homeassistant.components.switch.transmission
transmissionrpc==0.11
@@ -1442,7 +1463,7 @@ yeelight==0.4.0
yeelightsunflower==0.0.10
# homeassistant.components.media_extractor
-youtube_dl==2018.07.04
+youtube_dl==2018.07.29
# homeassistant.components.light.zengge
zengge==0.2
diff --git a/requirements_test.txt b/requirements_test.txt
index db53699379c..225958a722c 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -6,9 +6,9 @@ coveralls==1.2.0
flake8-docstrings==1.0.3
flake8==3.5
mock-open==1.3.1
-mypy==0.610
+mypy==0.620
pydocstyle==1.1.1
-pylint==1.9.2
+pylint==2.0.1
pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 3de2285eae9..ff95cd3be25 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -7,9 +7,9 @@ coveralls==1.2.0
flake8-docstrings==1.0.3
flake8==3.5
mock-open==1.3.1
-mypy==0.610
+mypy==0.620
pydocstyle==1.1.1
-pylint==1.9.2
+pylint==2.0.1
pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
@@ -81,7 +81,7 @@ hbmqtt==0.9.2
holidays==0.9.5
# homeassistant.components.frontend
-home-assistant-frontend==20180720.0
+home-assistant-frontend==20180726.0
# homeassistant.components.homematicip_cloud
homematicip==0.9.8
@@ -102,7 +102,7 @@ mficlient==0.3.0
# homeassistant.components.binary_sensor.trend
# homeassistant.components.image_processing.opencv
-numpy==1.14.5
+numpy==1.15.0
# homeassistant.components.mqtt
# homeassistant.components.shiftr
@@ -197,7 +197,7 @@ somecomfort==0.5.2
# homeassistant.components.recorder
# homeassistant.scripts.db_migrator
# homeassistant.components.sensor.sql
-sqlalchemy==1.2.9
+sqlalchemy==1.2.10
# homeassistant.components.statsd
statsd==3.2.1
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index 9a5b4dd1a43..d92502de078 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -182,6 +182,10 @@ def gather_modules():
for req in module.REQUIREMENTS:
if req in IGNORE_REQ:
continue
+ if '://' in req:
+ errors.append(
+ "{}[Only pypi dependencies are allowed: {}]".format(
+ package, req))
if req.partition('==')[1] == '' and req not in IGNORE_PIN:
errors.append(
"{}[Please pin requirement {}, see {}]".format(
@@ -257,7 +261,7 @@ def write_requirements_file(data):
def write_test_requirements_file(data):
- """Write the modules to the requirements_all.txt."""
+ """Write the modules to the requirements_test_all.txt."""
with open('requirements_test_all.txt', 'w+', newline="\n") as req_file:
req_file.write(data)
@@ -275,7 +279,7 @@ def validate_requirements_file(data):
def validate_requirements_test_file(data):
- """Validate if requirements_all.txt is up to date."""
+ """Validate if requirements_test_all.txt is up to date."""
with open('requirements_test_all.txt', 'r') as req_file:
return data == req_file.read()
diff --git a/setup.py b/setup.py
index bbf10dd309d..7519fc6a873 100755
--- a/setup.py
+++ b/setup.py
@@ -42,7 +42,7 @@ REQUIRES = [
'pytz>=2018.04',
'pyyaml>=3.13,<4',
'requests==2.19.1',
- 'voluptuous==0.11.1',
+ 'voluptuous==0.11.3',
]
MIN_PY_VERSION = '.'.join(map(str, hass_const.REQUIRED_PYTHON_VER))
diff --git a/tests/common.py b/tests/common.py
index b03d473e6f3..5567a431e58 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -187,7 +187,7 @@ def async_mock_service(hass, domain, service, schema=None):
"""Set up a fake service & return a calls log list to this service."""
calls = []
- @asyncio.coroutine
+ @ha.callback
def mock_service_log(call): # pylint: disable=unnecessary-lambda
"""Mock service call."""
calls.append(call)
@@ -355,7 +355,7 @@ def ensure_auth_manager_loaded(auth_mgr):
store._users = OrderedDict()
-class MockModule(object):
+class MockModule:
"""Representation of a fake module."""
# pylint: disable=invalid-name
@@ -391,7 +391,7 @@ class MockModule(object):
self.async_unload_entry = async_unload_entry
-class MockPlatform(object):
+class MockPlatform:
"""Provide a fake platform."""
# pylint: disable=invalid-name
@@ -496,14 +496,13 @@ class MockToggleDevice(entity.ToggleEntity):
"""Return the last call."""
if not self.calls:
return None
- elif method is None:
+ if method is None:
return self.calls[-1]
- else:
- try:
- return next(call for call in reversed(self.calls)
- if call[0] == method)
- except StopIteration:
- return None
+ try:
+ return next(call for call in reversed(self.calls)
+ if call[0] == method)
+ except StopIteration:
+ return None
class MockConfigEntry(config_entries.ConfigEntry):
diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py
index afa4d19b5d9..cf8535653a9 100644
--- a/tests/components/alexa/test_smart_home.py
+++ b/tests/components/alexa/test_smart_home.py
@@ -124,7 +124,7 @@ def discovery_test(device, hass, expected_endpoints=1):
if expected_endpoints == 1:
return endpoints[0]
- elif expected_endpoints > 1:
+ if expected_endpoints > 1:
return endpoints
return None
@@ -1225,7 +1225,7 @@ def reported_properties(hass, endpoint):
return _ReportedProperties(msg['context']['properties'])
-class _ReportedProperties(object):
+class _ReportedProperties:
def __init__(self, properties):
self.properties = properties
diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py
index 807bf15854b..eea768c96a0 100644
--- a/tests/components/auth/test_init.py
+++ b/tests/components/auth/test_init.py
@@ -2,6 +2,7 @@
from datetime import timedelta
from unittest.mock import patch
+from homeassistant.auth.models import Credentials
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from homeassistant.components import auth
@@ -90,12 +91,20 @@ def test_credential_store_expiration():
async def test_ws_current_user(hass, hass_ws_client, hass_access_token):
- """Test the current user command."""
+ """Test the current user command with homeassistant creds."""
assert await async_setup_component(hass, 'auth', {
'http': {
'api_password': 'bla'
}
})
+
+ user = hass_access_token.refresh_token.user
+ credential = Credentials(auth_provider_type='homeassistant',
+ auth_provider_id=None,
+ data={}, id='test-id')
+ user.credentials.append(credential)
+ assert len(user.credentials) == 1
+
with patch('homeassistant.auth.AuthManager.active', return_value=True):
client = await hass_ws_client(hass, hass_access_token)
@@ -107,12 +116,17 @@ async def test_ws_current_user(hass, hass_ws_client, hass_access_token):
result = await client.receive_json()
assert result['success'], result
- user = hass_access_token.refresh_token.user
user_dict = result['result']
assert user_dict['name'] == user.name
assert user_dict['id'] == user.id
assert user_dict['is_owner'] == user.is_owner
+ assert len(user_dict['credentials']) == 1
+
+ hass_cred = user_dict['credentials'][0]
+ assert hass_cred['auth_provider_type'] == 'homeassistant'
+ assert hass_cred['auth_provider_id'] is None
+ assert 'data' not in hass_cred
async def test_cors_on_token(hass, aiohttp_client):
@@ -130,3 +144,68 @@ async def test_cors_on_token(hass, aiohttp_client):
'origin': 'http://example.com'
})
assert resp.headers['Access-Control-Allow-Origin'] == 'http://example.com'
+
+
+async def test_refresh_token_system_generated(hass, aiohttp_client):
+ """Test that we can get access tokens for system generated user."""
+ client = await async_setup_auth(hass, aiohttp_client)
+ user = await hass.auth.async_create_system_user('Test System')
+ refresh_token = await hass.auth.async_create_refresh_token(user, None)
+
+ resp = await client.post('/auth/token', data={
+ 'client_id': 'https://this-is-not-allowed-for-system-users.com/',
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 400
+ result = await resp.json()
+ assert result['error'] == 'invalid_request'
+
+ resp = await client.post('/auth/token', data={
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 200
+ tokens = await resp.json()
+ assert hass.auth.async_get_access_token(tokens['access_token']) is not None
+
+
+async def test_refresh_token_different_client_id(hass, aiohttp_client):
+ """Test that we verify client ID."""
+ client = await async_setup_auth(hass, aiohttp_client)
+ user = await hass.auth.async_create_user('Test User')
+ refresh_token = await hass.auth.async_create_refresh_token(user, CLIENT_ID)
+
+ # No client ID
+ resp = await client.post('/auth/token', data={
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 400
+ result = await resp.json()
+ assert result['error'] == 'invalid_request'
+
+ # Different client ID
+ resp = await client.post('/auth/token', data={
+ 'client_id': 'http://example-different.com',
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 400
+ result = await resp.json()
+ assert result['error'] == 'invalid_request'
+
+ # Correct
+ resp = await client.post('/auth/token', data={
+ 'client_id': CLIENT_ID,
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 200
+ tokens = await resp.json()
+ assert hass.auth.async_get_access_token(tokens['access_token']) is not None
diff --git a/tests/components/auth/test_init_login_flow.py b/tests/components/auth/test_login_flow.py
similarity index 66%
rename from tests/components/auth/test_init_login_flow.py
rename to tests/components/auth/test_login_flow.py
index 50bd03d6ced..8b6108067c5 100644
--- a/tests/components/auth/test_init_login_flow.py
+++ b/tests/components/auth/test_login_flow.py
@@ -8,6 +8,7 @@ async def test_fetch_auth_providers(hass, aiohttp_client):
"""Test fetching auth providers."""
client = await async_setup_auth(hass, aiohttp_client)
resp = await client.get('/auth/providers')
+ assert resp.status == 200
assert await resp.json() == [{
'name': 'Example',
'type': 'insecure_example',
@@ -60,3 +61,31 @@ async def test_invalid_username_password(hass, aiohttp_client):
assert step['step_id'] == 'init'
assert step['errors']['base'] == 'invalid_auth'
+
+
+async def test_login_exist_user(hass, aiohttp_client):
+ """Test logging in with exist user."""
+ client = await async_setup_auth(hass, aiohttp_client, setup_api=True)
+ cred = await hass.auth.auth_providers[0].async_get_or_create_credentials(
+ {'username': 'test-user'})
+ await hass.auth.async_get_or_create_user(cred)
+
+ resp = await client.post('/auth/login_flow', json={
+ 'client_id': CLIENT_ID,
+ 'handler': ['insecure_example', None],
+ 'redirect_uri': CLIENT_REDIRECT_URI,
+ })
+ assert resp.status == 200
+ step = await resp.json()
+
+ resp = await client.post(
+ '/auth/login_flow/{}'.format(step['flow_id']), json={
+ 'client_id': CLIENT_ID,
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+
+ assert resp.status == 200
+ step = await resp.json()
+ assert step['type'] == 'create_entry'
+ assert len(step['result']) > 1
diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py
index 33f1a7aa704..b1990fb80aa 100644
--- a/tests/components/automation/test_init.py
+++ b/tests/components/automation/test_init.py
@@ -437,10 +437,12 @@ class TestAutomation(unittest.TestCase):
}
}
}}):
- automation.reload(self.hass)
- self.hass.block_till_done()
- # De-flake ?!
- self.hass.block_till_done()
+ with patch('homeassistant.config.find_config_file',
+ return_value=''):
+ automation.reload(self.hass)
+ self.hass.block_till_done()
+ # De-flake ?!
+ self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is None
assert self.hass.states.get('automation.bye') is not None
@@ -485,8 +487,10 @@ class TestAutomation(unittest.TestCase):
with patch('homeassistant.config.load_yaml_config_file', autospec=True,
return_value={automation.DOMAIN: 'not valid'}):
- automation.reload(self.hass)
- self.hass.block_till_done()
+ with patch('homeassistant.config.find_config_file',
+ return_value=''):
+ automation.reload(self.hass)
+ self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is None
@@ -521,8 +525,10 @@ class TestAutomation(unittest.TestCase):
with patch('homeassistant.config.load_yaml_config_file',
side_effect=HomeAssistantError('bla')):
- automation.reload(self.hass)
- self.hass.block_till_done()
+ with patch('homeassistant.config.find_config_file',
+ return_value=''):
+ automation.reload(self.hass)
+ self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is not None
diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py
index d01b62e4c12..07389c7c8a9 100644
--- a/tests/components/binary_sensor/test_command_line.py
+++ b/tests/components/binary_sensor/test_command_line.py
@@ -24,7 +24,9 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
config = {'name': 'Test',
'command': 'echo 1',
'payload_on': '1',
- 'payload_off': '0'}
+ 'payload_off': '0',
+ 'command_timeout': 15
+ }
devices = []
@@ -43,7 +45,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
def test_template(self):
"""Test setting the state with a template."""
- data = command_line.CommandSensorData(self.hass, 'echo 10')
+ data = command_line.CommandSensorData(self.hass, 'echo 10', 15)
entity = command_line.CommandBinarySensor(
self.hass, data, 'test', None, '1.0', '0',
@@ -53,7 +55,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
def test_sensor_off(self):
"""Test setting the state with a template."""
- data = command_line.CommandSensorData(self.hass, 'echo 0')
+ data = command_line.CommandSensorData(self.hass, 'echo 0', 15)
entity = command_line.CommandBinarySensor(
self.hass, data, 'test', None, '1', '0', None)
diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/binary_sensor/test_ffmpeg.py
index aadafadd4a6..da9350008d8 100644
--- a/tests/components/binary_sensor/test_ffmpeg.py
+++ b/tests/components/binary_sensor/test_ffmpeg.py
@@ -7,7 +7,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component, mock_coro)
-class TestFFmpegNoiseSetup(object):
+class TestFFmpegNoiseSetup:
"""Test class for ffmpeg."""
def setup_method(self):
@@ -72,7 +72,7 @@ class TestFFmpegNoiseSetup(object):
assert entity.state == 'on'
-class TestFFmpegMotionSetup(object):
+class TestFFmpegMotionSetup:
"""Test class for ffmpeg."""
def setup_method(self):
diff --git a/tests/components/binary_sensor/test_workday.py b/tests/components/binary_sensor/test_workday.py
index af7e856e417..893745ce3de 100644
--- a/tests/components/binary_sensor/test_workday.py
+++ b/tests/components/binary_sensor/test_workday.py
@@ -12,7 +12,7 @@ from tests.common import (
FUNCTION_PATH = 'homeassistant.components.binary_sensor.workday.get_date'
-class TestWorkdaySetup(object):
+class TestWorkdaySetup:
"""Test class for workday sensor."""
def setup_method(self):
diff --git a/tests/components/camera/test_demo.py b/tests/components/camera/test_demo.py
index 51e04fca351..b901b723c0b 100644
--- a/tests/components/camera/test_demo.py
+++ b/tests/components/camera/test_demo.py
@@ -1,14 +1,89 @@
"""The tests for local file camera component."""
-import asyncio
+from unittest.mock import mock_open, patch, PropertyMock
+
+import pytest
+
from homeassistant.components import camera
+from homeassistant.components.camera import STATE_STREAMING, STATE_IDLE
+from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
-@asyncio.coroutine
-def test_motion_detection(hass):
+@pytest.fixture
+def demo_camera(hass):
+ """Initialize a demo camera platform."""
+ hass.loop.run_until_complete(async_setup_component(hass, 'camera', {
+ camera.DOMAIN: {
+ 'platform': 'demo'
+ }
+ }))
+ return hass.data['camera'].get_entity('camera.demo_camera')
+
+
+async def test_init_state_is_streaming(hass, demo_camera):
+ """Demo camera initialize as streaming."""
+ assert demo_camera.state == STATE_STREAMING
+
+ mock_on_img = mock_open(read_data=b'ON')
+ with patch('homeassistant.components.camera.demo.open', mock_on_img,
+ create=True):
+ image = await camera.async_get_image(hass, demo_camera.entity_id)
+ assert mock_on_img.called
+ assert mock_on_img.call_args_list[0][0][0][-6:] \
+ in ['_0.jpg', '_1.jpg', '_2.jpg', '_3.jpg']
+ assert image.content == b'ON'
+
+
+async def test_turn_on_state_back_to_streaming(hass, demo_camera):
+ """After turn on state back to streaming."""
+ assert demo_camera.state == STATE_STREAMING
+ await camera.async_turn_off(hass, demo_camera.entity_id)
+ await hass.async_block_till_done()
+
+ assert demo_camera.state == STATE_IDLE
+
+ await camera.async_turn_on(hass, demo_camera.entity_id)
+ await hass.async_block_till_done()
+
+ assert demo_camera.state == STATE_STREAMING
+
+
+async def test_turn_off_image(hass, demo_camera):
+ """After turn off, Demo camera raise error."""
+ await camera.async_turn_off(hass, demo_camera.entity_id)
+ await hass.async_block_till_done()
+
+ with pytest.raises(HomeAssistantError) as error:
+ await camera.async_get_image(hass, demo_camera.entity_id)
+ assert error.args[0] == 'Camera is off'
+
+
+async def test_turn_off_invalid_camera(hass, demo_camera):
+ """Turn off non-exist camera should quietly fail."""
+ assert demo_camera.state == STATE_STREAMING
+ await camera.async_turn_off(hass, 'camera.invalid_camera')
+ await hass.async_block_till_done()
+
+ assert demo_camera.state == STATE_STREAMING
+
+
+async def test_turn_off_unsupport_camera(hass, demo_camera):
+ """Turn off unsupported camera should quietly fail."""
+ assert demo_camera.state == STATE_STREAMING
+ with patch('homeassistant.components.camera.demo.DemoCamera'
+ '.supported_features', new_callable=PropertyMock) as m:
+ m.return_value = 0
+
+ await camera.async_turn_off(hass, demo_camera.entity_id)
+ await hass.async_block_till_done()
+
+ assert demo_camera.state == STATE_STREAMING
+
+
+async def test_motion_detection(hass):
"""Test motion detection services."""
# Setup platform
- yield from async_setup_component(hass, 'camera', {
+ await async_setup_component(hass, 'camera', {
'camera': {
'platform': 'demo'
}
@@ -20,7 +95,7 @@ def test_motion_detection(hass):
# Call service to turn on motion detection
camera.enable_motion_detection(hass, 'camera.demo_camera')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
# Check if state has been updated.
state = hass.states.get('camera.demo_camera')
diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py
index d0f1425a595..cf902ca1779 100644
--- a/tests/components/camera/test_init.py
+++ b/tests/components/camera/test_init.py
@@ -30,7 +30,7 @@ def mock_camera(hass):
yield
-class TestSetupCamera(object):
+class TestSetupCamera:
"""Test class for setup camera."""
def setup_method(self):
@@ -53,7 +53,7 @@ class TestSetupCamera(object):
setup_component(self.hass, camera.DOMAIN, config)
-class TestGetImage(object):
+class TestGetImage:
"""Test class for camera."""
def setup_method(self):
diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py
index 2de0782fd91..18292d32a02 100644
--- a/tests/components/camera/test_uvc.py
+++ b/tests/components/camera/test_uvc.py
@@ -45,8 +45,7 @@ class TestUVCSetup(unittest.TestCase):
"""Create a mock camera."""
if uuid == 'id3':
return {'model': 'airCam'}
- else:
- return {'model': 'UVC'}
+ return {'model': 'UVC'}
mock_remote.return_value.index.return_value = mock_cameras
mock_remote.return_value.get_camera.side_effect = mock_get_camera
diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py
index b12c0c38f3a..69df11715e9 100644
--- a/tests/components/climate/test_honeywell.py
+++ b/tests/components/climate/test_honeywell.py
@@ -320,7 +320,7 @@ class TestHoneywellRound(unittest.TestCase):
self.device.set_temperature.call_args, mock.call('House', 25)
)
- def test_set_operation_mode(self: unittest.TestCase) -> None:
+ def test_set_operation_mode(self) -> None:
"""Test setting the system operation."""
self.round1.set_operation_mode('cool')
self.assertEqual('cool', self.round1.current_operation)
@@ -384,7 +384,7 @@ class TestHoneywellUS(unittest.TestCase):
self.assertEqual(74, self.device.setpoint_cool)
self.assertEqual(74, self.honeywell.target_temperature)
- def test_set_operation_mode(self: unittest.TestCase) -> None:
+ def test_set_operation_mode(self) -> None:
"""Test setting the operation mode."""
self.honeywell.set_operation_mode('cool')
self.assertEqual('cool', self.device.system_mode)
diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py
index 1591b8da1d2..559f29372de 100644
--- a/tests/components/config/test_entity_registry.py
+++ b/tests/components/config/test_entity_registry.py
@@ -54,8 +54,8 @@ async def test_get_entity(hass, client):
}
-async def test_update_entity(hass, client):
- """Test get entry."""
+async def test_update_entity_name(hass, client):
+ """Test updating entity name."""
mock_registry(hass, {
'test_domain.world': RegistryEntry(
entity_id='test_domain.world',
@@ -92,7 +92,7 @@ async def test_update_entity(hass, client):
async def test_update_entity_no_changes(hass, client):
- """Test get entry."""
+ """Test update entity with no changes."""
mock_registry(hass, {
'test_domain.world': RegistryEntry(
entity_id='test_domain.world',
@@ -129,7 +129,7 @@ async def test_update_entity_no_changes(hass, client):
async def test_get_nonexisting_entity(client):
- """Test get entry."""
+ """Test get entry with nonexisting entity."""
await client.send_json({
'id': 6,
'type': 'config/entity_registry/get',
@@ -141,7 +141,7 @@ async def test_get_nonexisting_entity(client):
async def test_update_nonexisting_entity(client):
- """Test get entry."""
+ """Test update a nonexisting entity."""
await client.send_json({
'id': 6,
'type': 'config/entity_registry/update',
@@ -151,3 +151,37 @@ async def test_update_nonexisting_entity(client):
msg = await client.receive_json()
assert not msg['success']
+
+
+async def test_update_entity_id(hass, client):
+ """Test update entity id."""
+ mock_registry(hass, {
+ 'test_domain.world': RegistryEntry(
+ entity_id='test_domain.world',
+ unique_id='1234',
+ # Using component.async_add_entities is equal to platform "domain"
+ platform='test_platform',
+ )
+ })
+ platform = MockEntityPlatform(hass)
+ entity = MockEntity(unique_id='1234')
+ await platform.async_add_entities([entity])
+
+ assert hass.states.get('test_domain.world') is not None
+
+ await client.send_json({
+ 'id': 6,
+ 'type': 'config/entity_registry/update',
+ 'entity_id': 'test_domain.world',
+ 'new_entity_id': 'test_domain.planet',
+ })
+
+ msg = await client.receive_json()
+
+ assert msg['result'] == {
+ 'entity_id': 'test_domain.planet',
+ 'name': None
+ }
+
+ assert hass.states.get('test_domain.world') is None
+ assert hass.states.get('test_domain.planet') is not None
diff --git a/tests/components/config/test_zwave.py b/tests/components/config/test_zwave.py
index 672bafeaf28..8aae5c0a28b 100644
--- a/tests/components/config/test_zwave.py
+++ b/tests/components/config/test_zwave.py
@@ -367,3 +367,192 @@ def test_save_config(hass, client):
result = yield from resp.json()
assert network.write_config.called
assert result == {'message': 'Z-Wave configuration saved to file.'}
+
+
+async def test_get_protection_values(hass, client):
+ """Test getting protection values on node."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=18,
+ command_classes=[const.COMMAND_CLASS_PROTECTION])
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1,
+ command_class=const.COMMAND_CLASS_PROTECTION)
+ value.label = 'Protection Test'
+ value.data_items = ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ value.data = 'Unprotected'
+ network.nodes = {18: node}
+ node.value = value
+
+ node.get_protection_item.return_value = "Unprotected"
+ node.get_protection_items.return_value = value.data_items
+ node.get_protections.return_value = {value.value_id: 'Object'}
+
+ resp = await client.get('/api/zwave/protection/18')
+
+ assert resp.status == 200
+ result = await resp.json()
+ assert node.get_protections.called
+ assert node.get_protection_item.called
+ assert node.get_protection_items.called
+ assert result == {
+ 'value_id': '123456',
+ 'selected': 'Unprotected',
+ 'options': ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ }
+
+
+async def test_get_protection_values_nonexisting_node(hass, client):
+ """Test getting protection values on node with wrong nodeid."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=18,
+ command_classes=[const.COMMAND_CLASS_PROTECTION])
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1,
+ command_class=const.COMMAND_CLASS_PROTECTION)
+ value.label = 'Protection Test'
+ value.data_items = ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ value.data = 'Unprotected'
+ network.nodes = {17: node}
+ node.value = value
+
+ resp = await client.get('/api/zwave/protection/18')
+
+ assert resp.status == 404
+ result = await resp.json()
+ assert not node.get_protections.called
+ assert not node.get_protection_item.called
+ assert not node.get_protection_items.called
+ assert result == {'message': 'Node not found'}
+
+
+async def test_get_protection_values_without_protectionclass(hass, client):
+ """Test getting protection values on node without protectionclass."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=18)
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1)
+ network.nodes = {18: node}
+ node.value = value
+
+ resp = await client.get('/api/zwave/protection/18')
+
+ assert resp.status == 200
+ result = await resp.json()
+ assert not node.get_protections.called
+ assert not node.get_protection_item.called
+ assert not node.get_protection_items.called
+ assert result == {}
+
+
+async def test_set_protection_value(hass, client):
+ """Test setting protection value on node."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=18,
+ command_classes=[const.COMMAND_CLASS_PROTECTION])
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1,
+ command_class=const.COMMAND_CLASS_PROTECTION)
+ value.label = 'Protection Test'
+ value.data_items = ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ value.data = 'Unprotected'
+ network.nodes = {18: node}
+ node.value = value
+
+ resp = await client.post(
+ '/api/zwave/protection/18', data=json.dumps({
+ 'value_id': '123456', 'selection': 'Protection by Sequence'}))
+
+ assert resp.status == 200
+ result = await resp.json()
+ assert node.set_protection.called
+ assert result == {'message': 'Protection setting succsessfully set'}
+
+
+async def test_set_protection_value_failed(hass, client):
+ """Test setting protection value failed on node."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=18,
+ command_classes=[const.COMMAND_CLASS_PROTECTION])
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1,
+ command_class=const.COMMAND_CLASS_PROTECTION)
+ value.label = 'Protection Test'
+ value.data_items = ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ value.data = 'Unprotected'
+ network.nodes = {18: node}
+ node.value = value
+ node.set_protection.return_value = False
+
+ resp = await client.post(
+ '/api/zwave/protection/18', data=json.dumps({
+ 'value_id': '123456', 'selection': 'Protecton by Seuence'}))
+
+ assert resp.status == 202
+ result = await resp.json()
+ assert node.set_protection.called
+ assert result == {'message': 'Protection setting did not complete'}
+
+
+async def test_set_protection_value_nonexisting_node(hass, client):
+ """Test setting protection value on nonexisting node."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=17,
+ command_classes=[const.COMMAND_CLASS_PROTECTION])
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1,
+ command_class=const.COMMAND_CLASS_PROTECTION)
+ value.label = 'Protection Test'
+ value.data_items = ['Unprotected', 'Protection by Sequence',
+ 'No Operation Possible']
+ value.data = 'Unprotected'
+ network.nodes = {17: node}
+ node.value = value
+ node.set_protection.return_value = False
+
+ resp = await client.post(
+ '/api/zwave/protection/18', data=json.dumps({
+ 'value_id': '123456', 'selection': 'Protecton by Seuence'}))
+
+ assert resp.status == 404
+ result = await resp.json()
+ assert not node.set_protection.called
+ assert result == {'message': 'Node not found'}
+
+
+async def test_set_protection_value_missing_class(hass, client):
+ """Test setting protection value on node without protectionclass."""
+ network = hass.data[DATA_NETWORK] = MagicMock()
+ node = MockNode(node_id=17)
+ value = MockValue(
+ value_id=123456,
+ index=0,
+ instance=1)
+ network.nodes = {17: node}
+ node.value = value
+ node.set_protection.return_value = False
+
+ resp = await client.post(
+ '/api/zwave/protection/17', data=json.dumps({
+ 'value_id': '123456', 'selection': 'Protecton by Seuence'}))
+
+ assert resp.status == 404
+ result = await resp.json()
+ assert not node.set_protection.called
+ assert result == {'message': 'No protection commandclass on this node'}
diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py
index 1cee08feb0a..8f5342de1e3 100644
--- a/tests/components/deconz/test_init.py
+++ b/tests/components/deconz/test_init.py
@@ -91,7 +91,7 @@ async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
- with patch.object(hass, 'async_add_job') as mock_add_job, \
+ with patch.object(hass, 'async_create_task') as mock_add_job, \
patch.object(hass, 'config_entries') as mock_config_entries, \
patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(True)):
diff --git a/tests/components/device_tracker/test_tomato.py b/tests/components/device_tracker/test_tomato.py
index cce39ce43a7..0c20350a845 100644
--- a/tests/components/device_tracker/test_tomato.py
+++ b/tests/components/device_tracker/test_tomato.py
@@ -22,9 +22,9 @@ def mock_session_response(*args, **kwargs):
# Password: bar
if args[0].headers['Authorization'] != 'Basic Zm9vOmJhcg==':
return MockSessionResponse(None, 401)
- elif "gimmie_bad_data" in args[0].body:
+ if "gimmie_bad_data" in args[0].body:
return MockSessionResponse('This shouldn\'t (wldev = be here.;', 200)
- elif "gimmie_good_data" in args[0].body:
+ if "gimmie_good_data" in args[0].body:
return MockSessionResponse(
"wldev = [ ['eth1','F4:F5:D8:AA:AA:AA',"
"-42,5500,1000,7043,0],['eth1','58:EF:68:00:00:00',"
diff --git a/tests/components/device_tracker/test_upc_connect.py b/tests/components/device_tracker/test_upc_connect.py
index e45d70bc172..6294ba3467a 100644
--- a/tests/components/device_tracker/test_upc_connect.py
+++ b/tests/components/device_tracker/test_upc_connect.py
@@ -33,7 +33,7 @@ def mock_load_config():
yield
-class TestUPCConnect(object):
+class TestUPCConnect:
"""Tests for the Ddwrt device tracker platform."""
def setup_method(self):
diff --git a/tests/components/device_tracker/test_xiaomi.py b/tests/components/device_tracker/test_xiaomi.py
index bdd921f395f..0705fb2c399 100644
--- a/tests/components/device_tracker/test_xiaomi.py
+++ b/tests/components/device_tracker/test_xiaomi.py
@@ -55,21 +55,21 @@ def mocked_requests(*args, **kwargs):
"code": "401",
"msg": "Invalid token"
}, 200)
- elif data and data.get('username', None) == TOKEN_TIMEOUT_USERNAME:
+ if data and data.get('username', None) == TOKEN_TIMEOUT_USERNAME:
# deliver an expired token
return MockResponse({
"url": "/cgi-bin/luci/;stok=ef5860/web/home",
"token": "timedOut",
"code": "0"
}, 200)
- elif str(args[0]).startswith(URL_AUTHORIZE):
+ if str(args[0]).startswith(URL_AUTHORIZE):
# deliver an authorized token
return MockResponse({
"url": "/cgi-bin/luci/;stok=ef5860/web/home",
"token": "ef5860",
"code": "0"
}, 200)
- elif str(args[0]).endswith("timedOut/" + URL_LIST_END) \
+ if str(args[0]).endswith("timedOut/" + URL_LIST_END) \
and FIRST_CALL is True:
FIRST_CALL = False
# deliver an error when called with expired token
@@ -77,7 +77,7 @@ def mocked_requests(*args, **kwargs):
"code": "401",
"msg": "Invalid token"
}, 200)
- elif str(args[0]).endswith(URL_LIST_END):
+ if str(args[0]).endswith(URL_LIST_END):
# deliver the device list
return MockResponse({
"mac": "1C:98:EC:0E:D5:A4",
@@ -149,8 +149,7 @@ def mocked_requests(*args, **kwargs):
],
"code": 0
}, 200)
- else:
- _LOGGER.debug('UNKNOWN ROUTE')
+ _LOGGER.debug('UNKNOWN ROUTE')
class TestXiaomiDeviceScanner(unittest.TestCase):
diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py
index 555802f9a2c..8315de34e06 100644
--- a/tests/components/emulated_hue/test_upnp.py
+++ b/tests/components/emulated_hue/test_upnp.py
@@ -89,7 +89,7 @@ class TestEmulatedHue(unittest.TestCase):
# Make sure the XML is parsable
try:
ET.fromstring(result.text)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
self.fail('description.xml is not valid XML!')
def test_create_username(self):
diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py
index 4a950910809..dfa67f48614 100644
--- a/tests/components/frontend/test_init.py
+++ b/tests/components/frontend/test_init.py
@@ -71,7 +71,7 @@ def test_frontend_and_static(mock_http_client):
# Test we can retrieve frontend.js
frontendjs = re.search(
- r'(?P\/frontend_es5\/app-[A-Za-z0-9]{32}.js)', text)
+ r'(?P\/frontend_es5\/app-[A-Za-z0-9]{8}.js)', text)
assert frontendjs is not None
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
@@ -226,7 +226,7 @@ def test_extra_urls(mock_http_client_with_urls):
resp = yield from mock_http_client_with_urls.get('/states?latest')
assert resp.status == 200
text = yield from resp.text()
- assert text.find('href="https://domain.com/my_extra_url.html"') >= 0
+ assert text.find("href='https://domain.com/my_extra_url.html'") >= 0
@asyncio.coroutine
@@ -235,7 +235,7 @@ def test_extra_urls_es5(mock_http_client_with_urls):
resp = yield from mock_http_client_with_urls.get('/states?es5')
assert resp.status == 200
text = yield from resp.text()
- assert text.find('href="https://domain.com/my_extra_url_es5.html"') >= 0
+ assert text.find("href='https://domain.com/my_extra_url_es5.html'") >= 0
async def test_get_panels(hass, hass_ws_client):
diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py
index 31ad70e8aba..a5e9bbc0b82 100644
--- a/tests/components/group/test_init.py
+++ b/tests/components/group/test_init.py
@@ -365,8 +365,10 @@ class TestComponentsGroup(unittest.TestCase):
'icon': 'mdi:work',
'view': True,
}}}):
- group.reload(self.hass)
- self.hass.block_till_done()
+ with patch('homeassistant.config.find_config_file',
+ return_value=''):
+ group.reload(self.hass)
+ self.hass.block_till_done()
assert sorted(self.hass.states.entity_ids()) == \
['group.all_tests', 'group.hello']
diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py
index f67a6cbccec..b1975669731 100644
--- a/tests/components/hassio/test_init.py
+++ b/tests/components/hassio/test_init.py
@@ -3,8 +3,11 @@ import asyncio
import os
from unittest.mock import patch, Mock
+import pytest
+
from homeassistant.setup import async_setup_component
-from homeassistant.components.hassio import async_check_config
+from homeassistant.components.hassio import (
+ STORAGE_KEY, async_check_config)
from tests.common import mock_coro
@@ -15,20 +18,28 @@ MOCK_ENVIRON = {
}
-@asyncio.coroutine
-def test_setup_api_ping(hass, aioclient_mock):
- """Test setup with API ping."""
+@pytest.fixture(autouse=True)
+def mock_all(aioclient_mock):
+ """Mock all setup requests."""
+ aioclient_mock.post(
+ "http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
+ aioclient_mock.post(
+ "http://127.0.0.1/supervisor/options", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
+
+@asyncio.coroutine
+def test_setup_api_ping(hass, aioclient_mock):
+ """Test setup with API ping."""
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {})
assert result
- assert aioclient_mock.call_count == 2
+ assert aioclient_mock.call_count == 3
assert hass.components.hassio.get_homeassistant_version() == "10.0"
assert hass.components.hassio.is_hassio()
@@ -36,14 +47,6 @@ def test_setup_api_ping(hass, aioclient_mock):
@asyncio.coroutine
def test_setup_api_push_api_data(hass, aioclient_mock):
"""Test setup with API push."""
- aioclient_mock.get(
- "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={
- 'result': 'ok', 'data': {'last_version': '10.0'}})
- aioclient_mock.post(
- "http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
-
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'http': {
@@ -64,14 +67,6 @@ def test_setup_api_push_api_data(hass, aioclient_mock):
@asyncio.coroutine
def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
"""Test setup with API push with active server host."""
- aioclient_mock.get(
- "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={
- 'result': 'ok', 'data': {'last_version': '10.0'}})
- aioclient_mock.post(
- "http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
-
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'http': {
@@ -90,19 +85,12 @@ def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
assert not aioclient_mock.mock_calls[1][2]['watchdog']
-@asyncio.coroutine
-def test_setup_api_push_api_data_default(hass, aioclient_mock):
+async def test_setup_api_push_api_data_default(hass, aioclient_mock,
+ hass_storage):
"""Test setup with API push default data."""
- aioclient_mock.get(
- "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={
- 'result': 'ok', 'data': {'last_version': '10.0'}})
- aioclient_mock.post(
- "http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
-
- with patch.dict(os.environ, MOCK_ENVIRON):
- result = yield from async_setup_component(hass, 'hassio', {
+ with patch.dict(os.environ, MOCK_ENVIRON), \
+ patch('homeassistant.auth.AuthManager.active', return_value=True):
+ result = await async_setup_component(hass, 'hassio', {
'http': {},
'hassio': {}
})
@@ -112,19 +100,61 @@ def test_setup_api_push_api_data_default(hass, aioclient_mock):
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['password'] is None
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
+ refresh_token = aioclient_mock.mock_calls[1][2]['refresh_token']
+ hassio_user = await hass.auth.async_get_user(
+ hass_storage[STORAGE_KEY]['data']['hassio_user']
+ )
+ assert hassio_user is not None
+ assert hassio_user.system_generated
+ assert refresh_token in hassio_user.refresh_tokens
+
+
+async def test_setup_api_push_api_data_no_auth(hass, aioclient_mock,
+ hass_storage):
+ """Test setup with API push default data."""
+ with patch.dict(os.environ, MOCK_ENVIRON):
+ result = await async_setup_component(hass, 'hassio', {
+ 'http': {},
+ 'hassio': {}
+ })
+ assert result
+
+ assert aioclient_mock.call_count == 3
+ assert not aioclient_mock.mock_calls[1][2]['ssl']
+ assert aioclient_mock.mock_calls[1][2]['password'] is None
+ assert aioclient_mock.mock_calls[1][2]['port'] == 8123
+ assert aioclient_mock.mock_calls[1][2]['refresh_token'] is None
+
+
+async def test_setup_api_existing_hassio_user(hass, aioclient_mock,
+ hass_storage):
+ """Test setup with API push default data."""
+ user = await hass.auth.async_create_system_user('Hass.io test')
+ token = await hass.auth.async_create_refresh_token(user)
+ hass_storage[STORAGE_KEY] = {
+ 'version': 1,
+ 'data': {
+ 'hassio_user': user.id
+ }
+ }
+ with patch.dict(os.environ, MOCK_ENVIRON), \
+ patch('homeassistant.auth.AuthManager.active', return_value=True):
+ result = await async_setup_component(hass, 'hassio', {
+ 'http': {},
+ 'hassio': {}
+ })
+ assert result
+
+ assert aioclient_mock.call_count == 3
+ assert not aioclient_mock.mock_calls[1][2]['ssl']
+ assert aioclient_mock.mock_calls[1][2]['password'] is None
+ assert aioclient_mock.mock_calls[1][2]['port'] == 8123
+ assert aioclient_mock.mock_calls[1][2]['refresh_token'] == token.token
@asyncio.coroutine
def test_setup_core_push_timezone(hass, aioclient_mock):
"""Test setup with API push default data."""
- aioclient_mock.get(
- "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={
- 'result': 'ok', 'data': {'last_version': '10.0'}})
- aioclient_mock.post(
- "http://127.0.0.1/supervisor/options", json={'result': 'ok'})
-
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'hassio': {},
@@ -134,21 +164,13 @@ def test_setup_core_push_timezone(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 3
- assert aioclient_mock.mock_calls[1][2]['timezone'] == "testzone"
+ assert aioclient_mock.call_count == 4
+ assert aioclient_mock.mock_calls[2][2]['timezone'] == "testzone"
@asyncio.coroutine
def test_setup_hassio_no_additional_data(hass, aioclient_mock):
"""Test setup with API push default data."""
- aioclient_mock.get(
- "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={
- 'result': 'ok', 'data': {'last_version': '10.0'}})
- aioclient_mock.get(
- "http://127.0.0.1/homeassistant/info", json={'result': 'ok'})
-
with patch.dict(os.environ, MOCK_ENVIRON), \
patch.dict(os.environ, {'HASSIO_TOKEN': "123456"}):
result = yield from async_setup_component(hass, 'hassio', {
@@ -156,7 +178,7 @@ def test_setup_hassio_no_additional_data(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 2
+ assert aioclient_mock.call_count == 3
assert aioclient_mock.mock_calls[-1][3]['X-HASSIO-KEY'] == "123456"
@@ -234,14 +256,14 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
'hassio', 'addon_stdin', {'addon': 'test', 'input': 'test'})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 4
+ assert aioclient_mock.call_count == 5
assert aioclient_mock.mock_calls[-1][2] == 'test'
yield from hass.services.async_call('hassio', 'host_shutdown', {})
yield from hass.services.async_call('hassio', 'host_reboot', {})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 6
+ assert aioclient_mock.call_count == 7
yield from hass.services.async_call('hassio', 'snapshot_full', {})
yield from hass.services.async_call('hassio', 'snapshot_partial', {
@@ -251,7 +273,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 8
+ assert aioclient_mock.call_count == 9
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'password': "123456"}
@@ -267,7 +289,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 10
+ assert aioclient_mock.call_count == 11
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False,
'password': "123456"
@@ -289,17 +311,17 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
yield from hass.services.async_call('homeassistant', 'stop')
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 1
+ assert aioclient_mock.call_count == 2
yield from hass.services.async_call('homeassistant', 'check_config')
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 2
+ assert aioclient_mock.call_count == 3
yield from hass.services.async_call('homeassistant', 'restart')
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 4
+ assert aioclient_mock.call_count == 5
@asyncio.coroutine
diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py
index 628c5405eaa..ab2e3be11d6 100644
--- a/tests/components/image_processing/test_init.py
+++ b/tests/components/image_processing/test_init.py
@@ -12,7 +12,7 @@ from tests.common import (
get_test_home_assistant, get_test_instance_port, assert_setup_component)
-class TestSetupImageProcessing(object):
+class TestSetupImageProcessing:
"""Test class for setup image processing."""
def setup_method(self):
@@ -48,7 +48,7 @@ class TestSetupImageProcessing(object):
assert self.hass.services.has_service(ip.DOMAIN, 'scan')
-class TestImageProcessing(object):
+class TestImageProcessing:
"""Test class for image processing."""
def setup_method(self):
@@ -109,7 +109,7 @@ class TestImageProcessing(object):
assert state.state == '0'
-class TestImageProcessingAlpr(object):
+class TestImageProcessingAlpr:
"""Test class for alpr image processing."""
def setup_method(self):
@@ -211,7 +211,7 @@ class TestImageProcessingAlpr(object):
assert event_data[0]['entity_id'] == 'image_processing.demo_alpr'
-class TestImageProcessingFace(object):
+class TestImageProcessingFace:
"""Test class for face image processing."""
def setup_method(self):
diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/image_processing/test_microsoft_face_detect.py
index b743dee9704..acc2519c9b7 100644
--- a/tests/components/image_processing/test_microsoft_face_detect.py
+++ b/tests/components/image_processing/test_microsoft_face_detect.py
@@ -11,7 +11,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
-class TestMicrosoftFaceDetectSetup(object):
+class TestMicrosoftFaceDetectSetup:
"""Test class for image processing."""
def setup_method(self):
@@ -74,7 +74,7 @@ class TestMicrosoftFaceDetectSetup(object):
assert self.hass.states.get('image_processing.test_local')
-class TestMicrosoftFaceDetect(object):
+class TestMicrosoftFaceDetect:
"""Test class for image processing."""
def setup_method(self):
diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/image_processing/test_microsoft_face_identify.py
index c2ab5684ed0..8797f661767 100644
--- a/tests/components/image_processing/test_microsoft_face_identify.py
+++ b/tests/components/image_processing/test_microsoft_face_identify.py
@@ -11,7 +11,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
-class TestMicrosoftFaceIdentifySetup(object):
+class TestMicrosoftFaceIdentifySetup:
"""Test class for image processing."""
def setup_method(self):
@@ -75,7 +75,7 @@ class TestMicrosoftFaceIdentifySetup(object):
assert self.hass.states.get('image_processing.test_local')
-class TestMicrosoftFaceIdentify(object):
+class TestMicrosoftFaceIdentify:
"""Test class for image processing."""
def setup_method(self):
diff --git a/tests/components/image_processing/test_openalpr_cloud.py b/tests/components/image_processing/test_openalpr_cloud.py
index 50060e08a4b..65e735a6f7e 100644
--- a/tests/components/image_processing/test_openalpr_cloud.py
+++ b/tests/components/image_processing/test_openalpr_cloud.py
@@ -12,7 +12,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
-class TestOpenAlprCloudSetup(object):
+class TestOpenAlprCloudSetup:
"""Test class for image processing."""
def setup_method(self):
@@ -103,7 +103,7 @@ class TestOpenAlprCloudSetup(object):
setup_component(self.hass, ip.DOMAIN, config)
-class TestOpenAlprCloud(object):
+class TestOpenAlprCloud:
"""Test class for image processing."""
def setup_method(self):
diff --git a/tests/components/image_processing/test_openalpr_local.py b/tests/components/image_processing/test_openalpr_local.py
index fc40f8e17fb..38e94166c5a 100644
--- a/tests/components/image_processing/test_openalpr_local.py
+++ b/tests/components/image_processing/test_openalpr_local.py
@@ -26,7 +26,7 @@ def mock_async_subprocess():
return async_popen
-class TestOpenAlprLocalSetup(object):
+class TestOpenAlprLocalSetup:
"""Test class for image processing."""
def setup_method(self):
@@ -96,7 +96,7 @@ class TestOpenAlprLocalSetup(object):
setup_component(self.hass, ip.DOMAIN, config)
-class TestOpenAlprLocal(object):
+class TestOpenAlprLocal:
"""Test class for image processing."""
def setup_method(self):
diff --git a/tests/components/light/test_group.py b/tests/components/light/test_group.py
index 26b949720d9..901535c5465 100644
--- a/tests/components/light/test_group.py
+++ b/tests/components/light/test_group.py
@@ -200,21 +200,24 @@ async def test_effect_list(hass):
}})
hass.states.async_set('light.test1', 'on',
- {'effect_list': ['None', 'Random', 'Colorloop']})
+ {'effect_list': ['None', 'Random', 'Colorloop'],
+ 'supported_features': 4})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert set(state.attributes['effect_list']) == {
'None', 'Random', 'Colorloop'}
hass.states.async_set('light.test2', 'on',
- {'effect_list': ['None', 'Random', 'Rainbow']})
+ {'effect_list': ['None', 'Random', 'Rainbow'],
+ 'supported_features': 4})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert set(state.attributes['effect_list']) == {
'None', 'Random', 'Colorloop', 'Rainbow'}
hass.states.async_set('light.test1', 'off',
- {'effect_list': ['None', 'Colorloop', 'Seven']})
+ {'effect_list': ['None', 'Colorloop', 'Seven'],
+ 'supported_features': 4})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert set(state.attributes['effect_list']) == {
@@ -229,27 +232,27 @@ async def test_effect(hass):
}})
hass.states.async_set('light.test1', 'on',
- {'effect': 'None', 'supported_features': 2})
+ {'effect': 'None', 'supported_features': 6})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert state.attributes['effect'] == 'None'
hass.states.async_set('light.test2', 'on',
- {'effect': 'None', 'supported_features': 2})
+ {'effect': 'None', 'supported_features': 6})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert state.attributes['effect'] == 'None'
hass.states.async_set('light.test3', 'on',
- {'effect': 'Random', 'supported_features': 2})
+ {'effect': 'Random', 'supported_features': 6})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert state.attributes['effect'] == 'None'
hass.states.async_set('light.test1', 'off',
- {'effect': 'None', 'supported_features': 2})
+ {'effect': 'None', 'supported_features': 6})
hass.states.async_set('light.test2', 'off',
- {'effect': 'None', 'supported_features': 2})
+ {'effect': 'None', 'supported_features': 6})
await hass.async_block_till_done()
state = hass.states.get('light.light_group')
assert state.attributes['effect'] == 'Random'
diff --git a/tests/components/light/test_hue.py b/tests/components/light/test_hue.py
index a1e3867f9c3..db8d7e5f1e1 100644
--- a/tests/components/light/test_hue.py
+++ b/tests/components/light/test_hue.py
@@ -182,7 +182,7 @@ def mock_bridge(hass):
if path == 'lights':
return bridge.mock_light_responses.popleft()
- elif path == 'groups':
+ if path == 'groups':
return bridge.mock_group_responses.popleft()
return None
diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py
index 634e3774b8a..4d779eef461 100644
--- a/tests/components/light/test_init.py
+++ b/tests/components/light/test_init.py
@@ -1,14 +1,16 @@
"""The tests for the Light component."""
# pylint: disable=protected-access
import unittest
+import unittest.mock as mock
import os
+from io import StringIO
-from homeassistant.setup import setup_component
-import homeassistant.loader as loader
+from homeassistant import core, loader
+from homeassistant.setup import setup_component, async_setup_component
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_SUPPORTED_FEATURES)
-import homeassistant.components.light as light
+from homeassistant.components import light
from homeassistant.helpers.intent import IntentHandleError
from tests.common import (
@@ -308,6 +310,82 @@ class TestLight(unittest.TestCase):
light.ATTR_BRIGHTNESS: 100
}, data)
+ def test_default_profiles_group(self):
+ """Test default turn-on light profile for all lights."""
+ platform = loader.get_component(self.hass, 'light.test')
+ platform.init()
+
+ user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
+ real_isfile = os.path.isfile
+ real_open = open
+
+ def _mock_isfile(path):
+ if path == user_light_file:
+ return True
+ return real_isfile(path)
+
+ def _mock_open(path):
+ if path == user_light_file:
+ return StringIO(profile_data)
+ return real_open(path)
+
+ profile_data = "id,x,y,brightness\n" +\
+ "group.all_lights.default,.4,.6,99\n"
+ with mock.patch('os.path.isfile', side_effect=_mock_isfile):
+ with mock.patch('builtins.open', side_effect=_mock_open):
+ self.assertTrue(setup_component(
+ self.hass, light.DOMAIN,
+ {light.DOMAIN: {CONF_PLATFORM: 'test'}}
+ ))
+
+ dev, _, _ = platform.DEVICES
+ light.turn_on(self.hass, dev.entity_id)
+ self.hass.block_till_done()
+ _, data = dev.last_call('turn_on')
+ self.assertEqual({
+ light.ATTR_HS_COLOR: (71.059, 100),
+ light.ATTR_BRIGHTNESS: 99
+ }, data)
+
+ def test_default_profiles_light(self):
+ """Test default turn-on light profile for a specific light."""
+ platform = loader.get_component(self.hass, 'light.test')
+ platform.init()
+
+ user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
+ real_isfile = os.path.isfile
+ real_open = open
+
+ def _mock_isfile(path):
+ if path == user_light_file:
+ return True
+ return real_isfile(path)
+
+ def _mock_open(path):
+ if path == user_light_file:
+ return StringIO(profile_data)
+ return real_open(path)
+
+ profile_data = "id,x,y,brightness\n" +\
+ "group.all_lights.default,.3,.5,200\n" +\
+ "light.ceiling_2.default,.6,.6,100\n"
+ with mock.patch('os.path.isfile', side_effect=_mock_isfile):
+ with mock.patch('builtins.open', side_effect=_mock_open):
+ self.assertTrue(setup_component(
+ self.hass, light.DOMAIN,
+ {light.DOMAIN: {CONF_PLATFORM: 'test'}}
+ ))
+
+ dev = next(filter(lambda x: x.entity_id == 'light.ceiling_2',
+ platform.DEVICES))
+ light.turn_on(self.hass, dev.entity_id)
+ self.hass.block_till_done()
+ _, data = dev.last_call('turn_on')
+ self.assertEqual({
+ light.ATTR_HS_COLOR: (50.353, 100),
+ light.ATTR_BRIGHTNESS: 100
+ }, data)
+
async def test_intent_set_color(hass):
"""Test the set color intent."""
@@ -397,3 +475,24 @@ async def test_intent_set_color_and_brightness(hass):
assert call.data.get(ATTR_ENTITY_ID) == 'light.hello_2'
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20
+
+
+async def test_light_context(hass):
+ """Test that light context works."""
+ assert await async_setup_component(hass, 'light', {
+ 'light': {
+ 'platform': 'test'
+ }
+ })
+
+ state = hass.states.get('light.ceiling')
+ assert state is not None
+
+ await hass.services.async_call('light', 'toggle', {
+ 'entity_id': state.entity_id,
+ }, True, core.Context(user_id='abcd'))
+
+ state2 = hass.states.get('light.ceiling')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py
index 7d6dd65e90a..404d60c0a2e 100644
--- a/tests/components/light/test_mqtt.py
+++ b/tests/components/light/test_mqtt.py
@@ -415,6 +415,12 @@ class TestLightMQTT(unittest.TestCase):
'name': 'test',
'state_topic': 'test_light_rgb/status',
'command_topic': 'test_light_rgb/set',
+ 'brightness_command_topic': 'test_light_rgb/brightness/set',
+ 'rgb_command_topic': 'test_light_rgb/rgb/set',
+ 'color_temp_command_topic': 'test_light_rgb/color_temp/set',
+ 'effect_command_topic': 'test_light_rgb/effect/set',
+ 'white_value_command_topic': 'test_light_rgb/white_value/set',
+ 'xy_command_topic': 'test_light_rgb/xy/set',
'brightness_state_topic': 'test_light_rgb/brightness/status',
'color_temp_state_topic': 'test_light_rgb/color_temp/status',
'effect_state_topic': 'test_light_rgb/effect/status',
@@ -475,6 +481,7 @@ class TestLightMQTT(unittest.TestCase):
'effect_command_topic': 'test_light_rgb/effect/set',
'white_value_command_topic': 'test_light_rgb/white_value/set',
'xy_command_topic': 'test_light_rgb/xy/set',
+ 'effect_list': ['colorloop', 'random'],
'qos': 2,
'payload_on': 'on',
'payload_off': 'off'
diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py
index e1c3da50e7e..1cf09f2ccb5 100644
--- a/tests/components/light/test_mqtt_template.py
+++ b/tests/components/light/test_mqtt_template.py
@@ -228,6 +228,8 @@ class TestLightMQTTTemplate(unittest.TestCase):
'{{ green|d }}-'
'{{ blue|d }}',
'command_off_template': 'off',
+ 'effect_list': ['colorloop', 'random'],
+ 'effect_command_topic': 'test_light_rgb/effect/set',
'qos': 2
}
})
diff --git a/tests/components/media_player/test_blackbird.py b/tests/components/media_player/test_blackbird.py
index 7c85775949c..550bfe88a61 100644
--- a/tests/components/media_player/test_blackbird.py
+++ b/tests/components/media_player/test_blackbird.py
@@ -25,7 +25,7 @@ class AttrDict(dict):
return self[item]
-class MockBlackbird(object):
+class MockBlackbird:
"""Mock for pyblackbird object."""
def __init__(self):
diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/media_player/test_monoprice.py
index 399cdc67ca6..14e1769047a 100644
--- a/tests/components/media_player/test_monoprice.py
+++ b/tests/components/media_player/test_monoprice.py
@@ -27,7 +27,7 @@ class AttrDict(dict):
return self[item]
-class MockMonoprice(object):
+class MockMonoprice:
"""Mock for pymonoprice object."""
def __init__(self):
diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/media_player/test_yamaha.py
index e17241485db..980284737a2 100644
--- a/tests/components/media_player/test_yamaha.py
+++ b/tests/components/media_player/test_yamaha.py
@@ -15,7 +15,7 @@ def _create_zone_mock(name, url):
return zone
-class FakeYamahaDevice(object):
+class FakeYamahaDevice:
"""A fake Yamaha device."""
def __init__(self, ctrl_url, name, zones=None):
diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py
index ed6c77f676c..9e0ef14a3fa 100644
--- a/tests/components/mqtt/test_discovery.py
+++ b/tests/components/mqtt/test_discovery.py
@@ -124,6 +124,28 @@ def test_discover_climate(hass, mqtt_mock, caplog):
assert ('climate', 'bla') in hass.data[ALREADY_DISCOVERED]
+@asyncio.coroutine
+def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
+ """Test discovering an MQTT alarm control panel component."""
+ yield from async_start(hass, 'homeassistant', {})
+
+ data = (
+ '{ "name": "AlarmControlPanelTest",'
+ ' "state_topic": "test_topic",'
+ ' "command_topic": "test_topic" }'
+ )
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/alarm_control_panel/bla/config', data)
+ yield from hass.async_block_till_done()
+
+ state = hass.states.get('alarm_control_panel.AlarmControlPanelTest')
+
+ assert state is not None
+ assert state.name == 'AlarmControlPanelTest'
+ assert ('alarm_control_panel', 'bla') in hass.data[ALREADY_DISCOVERED]
+
+
@asyncio.coroutine
def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
"""Test sending in correct JSON with optional node_id included."""
diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py
index a847de51142..8e7ef4348f7 100644
--- a/tests/components/notify/test_group.py
+++ b/tests/components/notify/test_group.py
@@ -26,8 +26,7 @@ class TestNotifyGroup(unittest.TestCase):
def mock_get_service(hass, config, discovery_info=None):
if config['name'] == 'demo1':
return self.service1
- else:
- return self.service2
+ return self.service2
with assert_setup_component(2), \
patch.object(demo, 'get_service', mock_get_service):
diff --git a/tests/components/notify/test_html5.py b/tests/components/notify/test_html5.py
index 318f3c7512c..486300679b7 100644
--- a/tests/components/notify/test_html5.py
+++ b/tests/components/notify/test_html5.py
@@ -65,7 +65,7 @@ async def mock_client(hass, aiohttp_client, registrations=None):
return await aiohttp_client(hass.http.app)
-class TestHtml5Notify(object):
+class TestHtml5Notify:
"""Tests for HTML5 notify platform."""
def test_get_service_with_no_json(self):
diff --git a/tests/components/recorder/models_original.py b/tests/components/recorder/models_original.py
index 31ec5ee7ed7..990414d7713 100644
--- a/tests/components/recorder/models_original.py
+++ b/tests/components/recorder/models_original.py
@@ -157,7 +157,6 @@ def _process_timestamp(ts):
"""Process a timestamp into datetime object."""
if ts is None:
return None
- elif ts.tzinfo is None:
+ if ts.tzinfo is None:
return dt_util.UTC.localize(ts)
- else:
- return dt_util.as_utc(ts)
+ return dt_util.as_utc(ts)
diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py
index bc073a04c47..808f8cff6a1 100644
--- a/tests/components/sensor/test_command_line.py
+++ b/tests/components/sensor/test_command_line.py
@@ -1,5 +1,6 @@
"""The tests for the Command line sensor platform."""
import unittest
+from unittest.mock import patch
from homeassistant.helpers.template import Template
from homeassistant.components.sensor import command_line
@@ -17,11 +18,16 @@ class TestCommandSensorSensor(unittest.TestCase):
"""Stop everything that was started."""
self.hass.stop()
+ def update_side_effect(self, data):
+ """Side effect function for mocking CommandSensorData.update()."""
+ self.commandline.data = data
+
def test_setup(self):
"""Test sensor setup."""
config = {'name': 'Test',
'unit_of_measurement': 'in',
- 'command': 'echo 5'
+ 'command': 'echo 5',
+ 'command_timeout': 15
}
devices = []
@@ -41,11 +47,11 @@ class TestCommandSensorSensor(unittest.TestCase):
def test_template(self):
"""Test command sensor with template."""
- data = command_line.CommandSensorData(self.hass, 'echo 50')
+ data = command_line.CommandSensorData(self.hass, 'echo 50', 15)
entity = command_line.CommandSensor(
self.hass, data, 'test', 'in',
- Template('{{ value | multiply(0.1) }}', self.hass))
+ Template('{{ value | multiply(0.1) }}', self.hass), [])
entity.update()
self.assertEqual(5, float(entity.state))
@@ -55,7 +61,7 @@ class TestCommandSensorSensor(unittest.TestCase):
self.hass.states.set('sensor.test_state', 'Works')
data = command_line.CommandSensorData(
self.hass,
- 'echo {{ states.sensor.test_state.state }}'
+ 'echo {{ states.sensor.test_state.state }}', 15
)
data.update()
@@ -63,7 +69,109 @@ class TestCommandSensorSensor(unittest.TestCase):
def test_bad_command(self):
"""Test bad command."""
- data = command_line.CommandSensorData(self.hass, 'asdfasdf')
+ data = command_line.CommandSensorData(self.hass, 'asdfasdf', 15)
data.update()
self.assertEqual(None, data.value)
+
+ def test_update_with_json_attrs(self):
+ """Test attributes get extracted from a JSON result."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\
+ \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'),
+ 15
+ )
+
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key',
+ 'another_key',
+ 'key_three'])
+ self.sensor.update()
+ self.assertEqual('some_json_value',
+ self.sensor.device_state_attributes['key'])
+ self.assertEqual('another_json_value',
+ self.sensor.device_state_attributes['another_key'])
+ self.assertEqual('value_three',
+ self.sensor.device_state_attributes['key_three'])
+
+ @patch('homeassistant.components.sensor.command_line._LOGGER')
+ def test_update_with_json_attrs_no_data(self, mock_logger):
+ """Test attributes when no JSON result fetched."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ 'echo ', 15
+ )
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key'])
+ self.sensor.update()
+ self.assertEqual({}, self.sensor.device_state_attributes)
+ self.assertTrue(mock_logger.warning.called)
+
+ @patch('homeassistant.components.sensor.command_line._LOGGER')
+ def test_update_with_json_attrs_not_dict(self, mock_logger):
+ """Test attributes get extracted from a JSON result."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ 'echo [1, 2, 3]', 15
+ )
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key'])
+ self.sensor.update()
+ self.assertEqual({}, self.sensor.device_state_attributes)
+ self.assertTrue(mock_logger.warning.called)
+
+ @patch('homeassistant.components.sensor.command_line._LOGGER')
+ def test_update_with_json_attrs_bad_JSON(self, mock_logger):
+ """Test attributes get extracted from a JSON result."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ 'echo This is text rather than JSON data.', 15
+ )
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key'])
+ self.sensor.update()
+ self.assertEqual({}, self.sensor.device_state_attributes)
+ self.assertTrue(mock_logger.warning.called)
+
+ def test_update_with_missing_json_attrs(self):
+ """Test attributes get extracted from a JSON result."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\
+ \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'),
+ 15
+ )
+
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key',
+ 'another_key',
+ 'key_three',
+ 'special_key'])
+ self.sensor.update()
+ self.assertEqual('some_json_value',
+ self.sensor.device_state_attributes['key'])
+ self.assertEqual('another_json_value',
+ self.sensor.device_state_attributes['another_key'])
+ self.assertEqual('value_three',
+ self.sensor.device_state_attributes['key_three'])
+ self.assertFalse('special_key' in self.sensor.device_state_attributes)
+
+ def test_update_with_unnecessary_json_attrs(self):
+ """Test attributes get extracted from a JSON result."""
+ data = command_line.CommandSensorData(
+ self.hass,
+ ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\
+ \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'),
+ 15
+ )
+
+ self.sensor = command_line.CommandSensor(self.hass, data, 'test',
+ None, None, ['key',
+ 'another_key'])
+ self.sensor.update()
+ self.assertEqual('some_json_value',
+ self.sensor.device_state_attributes['key'])
+ self.assertEqual('another_json_value',
+ self.sensor.device_state_attributes['another_key'])
+ self.assertFalse('key_three' in self.sensor.device_state_attributes)
diff --git a/tests/components/sensor/test_moon.py b/tests/components/sensor/test_moon.py
index 334dd9a0bec..9086df6e79b 100644
--- a/tests/components/sensor/test_moon.py
+++ b/tests/components/sensor/test_moon.py
@@ -37,7 +37,7 @@ class TestMoonSensor(unittest.TestCase):
assert setup_component(self.hass, 'sensor', config)
state = self.hass.states.get('sensor.moon_day1')
- self.assertEqual(state.state, 'Waxing crescent')
+ self.assertEqual(state.state, 'waxing_crescent')
@patch('homeassistant.components.sensor.moon.dt_util.utcnow',
return_value=DAY2)
@@ -53,4 +53,4 @@ class TestMoonSensor(unittest.TestCase):
assert setup_component(self.hass, 'sensor', config)
state = self.hass.states.get('sensor.moon_day2')
- self.assertEqual(state.state, 'Waning gibbous')
+ self.assertEqual(state.state, 'waning_gibbous')
diff --git a/tests/components/sensor/test_radarr.py b/tests/components/sensor/test_radarr.py
index 94eeafad7b1..0d6aca9d0b7 100644
--- a/tests/components/sensor/test_radarr.py
+++ b/tests/components/sensor/test_radarr.py
@@ -83,7 +83,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 12
}
], 200)
- elif 'api/command' in url:
+ if 'api/command' in url:
return MockResponse([
{
"name": "RescanMovie",
@@ -94,7 +94,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 24
}
], 200)
- elif 'api/movie' in url:
+ if 'api/movie' in url:
return MockResponse([
{
"title": "Assassin's Creed",
@@ -149,7 +149,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 1
}
], 200)
- elif 'api/diskspace' in url:
+ if 'api/diskspace' in url:
return MockResponse([
{
"path": "/data",
@@ -158,7 +158,7 @@ def mocked_requests_get(*args, **kwargs):
"totalSpace": 499738734592
}
], 200)
- elif 'api/system/status' in url:
+ if 'api/system/status' in url:
return MockResponse({
"version": "0.2.0.210",
"buildTime": "2017-01-22T23:12:49Z",
@@ -182,10 +182,9 @@ def mocked_requests_get(*args, **kwargs):
"(Stable 4.6.1.3/abb06f1 "
"Mon Oct 3 07:57:59 UTC 2016)")
}, 200)
- else:
- return MockResponse({
- "error": "Unauthorized"
- }, 401)
+ return MockResponse({
+ "error": "Unauthorized"
+ }, 401)
class TestRadarrSetup(unittest.TestCase):
diff --git a/tests/components/sensor/test_sonarr.py b/tests/components/sensor/test_sonarr.py
index 9e2050e850c..275bb4a1e8b 100644
--- a/tests/components/sensor/test_sonarr.py
+++ b/tests/components/sensor/test_sonarr.py
@@ -139,7 +139,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 14402
}
], 200)
- elif 'api/command' in url:
+ if 'api/command' in url:
return MockResponse([
{
"name": "RescanSeries",
@@ -150,7 +150,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 24
}
], 200)
- elif 'api/wanted/missing' in url or 'totalRecords' in url:
+ if 'api/wanted/missing' in url or 'totalRecords' in url:
return MockResponse(
{
"page": 1,
@@ -325,7 +325,7 @@ def mocked_requests_get(*args, **kwargs):
}
]
}, 200)
- elif 'api/queue' in url:
+ if 'api/queue' in url:
return MockResponse([
{
"series": {
@@ -449,7 +449,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 1503378561
}
], 200)
- elif 'api/series' in url:
+ if 'api/series' in url:
return MockResponse([
{
"title": "Marvel's Daredevil",
@@ -540,7 +540,7 @@ def mocked_requests_get(*args, **kwargs):
"id": 7
}
], 200)
- elif 'api/diskspace' in url:
+ if 'api/diskspace' in url:
return MockResponse([
{
"path": "/data",
@@ -549,7 +549,7 @@ def mocked_requests_get(*args, **kwargs):
"totalSpace": 499738734592
}
], 200)
- elif 'api/system/status' in url:
+ if 'api/system/status' in url:
return MockResponse({
"version": "2.0.0.1121",
"buildTime": "2014-02-08T20:49:36.5560392Z",
@@ -568,10 +568,9 @@ def mocked_requests_get(*args, **kwargs):
"startOfWeek": 0,
"urlBase": ""
}, 200)
- else:
- return MockResponse({
- "error": "Unauthorized"
- }, 401)
+ return MockResponse({
+ "error": "Unauthorized"
+ }, 401)
class TestSonarrSetup(unittest.TestCase):
diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py
index 61e665f265c..155ed85dac2 100644
--- a/tests/components/switch/test_flux.py
+++ b/tests/components/switch/test_flux.py
@@ -92,8 +92,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -134,8 +133,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -181,8 +179,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -228,8 +225,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -276,8 +272,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -323,8 +318,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -374,8 +368,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -426,8 +419,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -477,8 +469,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -528,8 +519,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -579,8 +569,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -627,8 +616,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -677,8 +665,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -739,9 +726,8 @@ class TestSwitchFlux(unittest.TestCase):
if event == 'sunrise':
print('sunrise {}'.format(sunrise_time))
return sunrise_time
- else:
- print('sunset {}'.format(sunset_time))
- return sunset_time
+ print('sunset {}'.format(sunset_time))
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -793,8 +779,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
@@ -838,8 +823,7 @@ class TestSwitchFlux(unittest.TestCase):
def event_date(hass, event, now=None):
if event == 'sunrise':
return sunrise_time
- else:
- return sunset_time
+ return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.helpers.sun.get_astral_event_date',
diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py
index d679aa2c827..55e44299294 100644
--- a/tests/components/switch/test_init.py
+++ b/tests/components/switch/test_init.py
@@ -2,8 +2,8 @@
# pylint: disable=protected-access
import unittest
-from homeassistant.setup import setup_component
-from homeassistant import loader
+from homeassistant.setup import setup_component, async_setup_component
+from homeassistant import core, loader
from homeassistant.components import switch
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
@@ -91,3 +91,24 @@ class TestSwitch(unittest.TestCase):
'{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'},
}
))
+
+
+async def test_switch_context(hass):
+ """Test that switch context works."""
+ assert await async_setup_component(hass, 'switch', {
+ 'switch': {
+ 'platform': 'test'
+ }
+ })
+
+ state = hass.states.get('switch.ac')
+ assert state is not None
+
+ await hass.services.async_call('switch', 'toggle', {
+ 'entity_id': state.entity_id,
+ }, True, core.Context(user_id='abcd'))
+
+ state2 = hass.states.get('switch.ac')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py
index 31f9a729c53..7cd5a42b4a3 100644
--- a/tests/components/switch/test_mqtt.py
+++ b/tests/components/switch/test_mqtt.py
@@ -249,6 +249,37 @@ class TestSwitchMQTT(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
+ def test_custom_state_payload(self):
+ """Test the state payload."""
+ assert setup_component(self.hass, switch.DOMAIN, {
+ switch.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'state_topic': 'state-topic',
+ 'command_topic': 'command-topic',
+ 'payload_on': 1,
+ 'payload_off': 0,
+ 'state_on': "HIGH",
+ 'state_off': "LOW",
+ }
+ })
+
+ state = self.hass.states.get('switch.test')
+ self.assertEqual(STATE_OFF, state.state)
+ self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE))
+
+ fire_mqtt_message(self.hass, 'state-topic', 'HIGH')
+ self.hass.block_till_done()
+
+ state = self.hass.states.get('switch.test')
+ self.assertEqual(STATE_ON, state.state)
+
+ fire_mqtt_message(self.hass, 'state-topic', 'LOW')
+ self.hass.block_till_done()
+
+ state = self.hass.states.get('switch.test')
+ self.assertEqual(STATE_OFF, state.state)
+
def test_unique_id(self):
"""Test unique id option only creates one switch per unique_id."""
assert setup_component(self.hass, switch.DOMAIN, {
diff --git a/tests/components/test_api.py b/tests/components/test_api.py
index f53010ef27f..09dc27e97c1 100644
--- a/tests/components/test_api.py
+++ b/tests/components/test_api.py
@@ -12,6 +12,8 @@ from homeassistant.bootstrap import DATA_LOGGING
import homeassistant.core as ha
from homeassistant.setup import async_setup_component
+from tests.common import async_mock_service
+
@pytest.fixture
def mock_api_client(hass, aiohttp_client):
@@ -429,3 +431,58 @@ async def test_api_error_log(hass, aiohttp_client):
assert mock_file.mock_calls[0][1][0] == hass.data[DATA_LOGGING]
assert resp.status == 200
assert await resp.text() == 'Hello'
+
+
+async def test_api_fire_event_context(hass, mock_api_client,
+ hass_access_token):
+ """Test if the API sets right context if we fire an event."""
+ test_value = []
+
+ @ha.callback
+ def listener(event):
+ """Helper method that will verify our event got called."""
+ test_value.append(event)
+
+ hass.bus.async_listen("test.event", listener)
+
+ await mock_api_client.post(
+ const.URL_API_EVENTS_EVENT.format("test.event"),
+ headers={
+ 'authorization': 'Bearer {}'.format(hass_access_token.token)
+ })
+ await hass.async_block_till_done()
+
+ assert len(test_value) == 1
+ assert test_value[0].context.user_id == \
+ hass_access_token.refresh_token.user.id
+
+
+async def test_api_call_service_context(hass, mock_api_client,
+ hass_access_token):
+ """Test if the API sets right context if we call a service."""
+ calls = async_mock_service(hass, 'test_domain', 'test_service')
+
+ await mock_api_client.post(
+ '/api/services/test_domain/test_service',
+ headers={
+ 'authorization': 'Bearer {}'.format(hass_access_token.token)
+ })
+ await hass.async_block_till_done()
+
+ assert len(calls) == 1
+ assert calls[0].context.user_id == hass_access_token.refresh_token.user.id
+
+
+async def test_api_set_state_context(hass, mock_api_client, hass_access_token):
+ """Test if the API sets right context if we set state."""
+ await mock_api_client.post(
+ '/api/states/light.kitchen',
+ json={
+ 'state': 'on'
+ },
+ headers={
+ 'authorization': 'Bearer {}'.format(hass_access_token.token)
+ })
+
+ state = hass.states.get('light.kitchen')
+ assert state.context.user_id == hass_access_token.refresh_token.user.id
diff --git a/tests/components/test_ffmpeg.py b/tests/components/test_ffmpeg.py
index 5a5fdffd5a3..44c3a1dd695 100644
--- a/tests/components/test_ffmpeg.py
+++ b/tests/components/test_ffmpeg.py
@@ -38,7 +38,7 @@ class MockFFmpegDev(ffmpeg.FFmpegBase):
self.called_entities = entity_ids
-class TestFFmpegSetup(object):
+class TestFFmpegSetup:
"""Test class for ffmpeg."""
def setup_method(self):
diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py
index 280704fdc31..892fe5b5f4d 100644
--- a/tests/components/test_graphite.py
+++ b/tests/components/test_graphite.py
@@ -224,13 +224,12 @@ class TestGraphite(unittest.TestCase):
def fake_get():
if len(runs) >= 2:
return self.gf._quit_object
- elif runs:
+ if runs:
runs.append(1)
return mock.MagicMock(event_type='somethingelse',
data={'new_event': None})
- else:
- runs.append(1)
- return event
+ runs.append(1)
+ return event
with mock.patch.object(self.gf, '_queue') as mock_queue:
with mock.patch.object(self.gf, '_report_attributes') as mock_r:
diff --git a/tests/components/test_microsoft_face.py b/tests/components/test_microsoft_face.py
index 370059a0a09..92f840b8033 100644
--- a/tests/components/test_microsoft_face.py
+++ b/tests/components/test_microsoft_face.py
@@ -9,7 +9,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component, mock_coro, load_fixture)
-class TestMicrosoftFaceSetup(object):
+class TestMicrosoftFaceSetup:
"""Test the microsoft face component."""
def setup_method(self):
diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py
index 48bc04d46ed..8da1311c87d 100644
--- a/tests/components/test_mqtt_eventstream.py
+++ b/tests/components/test_mqtt_eventstream.py
@@ -18,7 +18,7 @@ from tests.common import (
)
-class TestMqttEventStream(object):
+class TestMqttEventStream:
"""Test the MQTT eventstream module."""
def setup_method(self):
@@ -104,12 +104,14 @@ class TestMqttEventStream(object):
"state": "on",
"entity_id": e_id,
"attributes": {},
- "last_changed": now.isoformat()
+ "last_changed": now.isoformat(),
}
event['event_data'] = {"new_state": new_state, "entity_id": e_id}
# Verify that the message received was that expected
- assert json.loads(msg) == event
+ result = json.loads(msg)
+ result['event_data']['new_state'].pop('context')
+ assert result == event
@patch('homeassistant.components.mqtt.async_publish')
def test_time_event_does_not_send_message(self, mock_pub):
diff --git a/tests/components/test_mqtt_statestream.py b/tests/components/test_mqtt_statestream.py
index 2ed2f4487ea..4cf79e679cd 100644
--- a/tests/components/test_mqtt_statestream.py
+++ b/tests/components/test_mqtt_statestream.py
@@ -12,7 +12,7 @@ from tests.common import (
)
-class TestMqttStateStream(object):
+class TestMqttStateStream:
"""Test the MQTT statestream module."""
def setup_method(self):
diff --git a/tests/components/test_plant.py b/tests/components/test_plant.py
index ee1372509d9..95167dd181b 100644
--- a/tests/components/test_plant.py
+++ b/tests/components/test_plant.py
@@ -41,7 +41,7 @@ GOOD_CONFIG = {
}
-class _MockState(object):
+class _MockState:
def __init__(self, state=None):
self.state = state
diff --git a/tests/components/test_rest_command.py b/tests/components/test_rest_command.py
index 3ddcfae8c01..097fb799d40 100644
--- a/tests/components/test_rest_command.py
+++ b/tests/components/test_rest_command.py
@@ -10,7 +10,7 @@ from tests.common import (
get_test_home_assistant, assert_setup_component)
-class TestRestCommandSetup(object):
+class TestRestCommandSetup:
"""Test the rest command component."""
def setup_method(self):
@@ -47,7 +47,7 @@ class TestRestCommandSetup(object):
assert self.hass.services.has_service(rc.DOMAIN, 'test_get')
-class TestRestCommandComponent(object):
+class TestRestCommandComponent:
"""Test the rest command component."""
def setup_method(self):
diff --git a/tests/components/test_script.py b/tests/components/test_script.py
index fcb0047c135..c4282cdfbaf 100644
--- a/tests/components/test_script.py
+++ b/tests/components/test_script.py
@@ -199,8 +199,10 @@ class TestScriptComponent(unittest.TestCase):
}
}]
}}}):
- script.reload(self.hass)
- self.hass.block_till_done()
+ with patch('homeassistant.config.find_config_file',
+ return_value=''):
+ script.reload(self.hass)
+ self.hass.block_till_done()
assert self.hass.states.get(ENTITY_ID) is None
assert not self.hass.services.has_service(script.DOMAIN, 'test')
diff --git a/tests/components/test_system_log.py b/tests/components/test_system_log.py
index 59e99e5c1b5..5d48fd88127 100644
--- a/tests/components/test_system_log.py
+++ b/tests/components/test_system_log.py
@@ -28,7 +28,7 @@ async def get_error_log(hass, aiohttp_client, expected_count):
def _generate_and_log_exception(exception, log):
try:
raise Exception(exception)
- except: # noqa: E722 # pylint: disable=bare-except
+ except: # noqa: E722 pylint: disable=bare-except
_LOGGER.exception(log)
diff --git a/tests/components/test_websocket_api.py b/tests/components/test_websocket_api.py
index dc1688bae16..1fac1af9f64 100644
--- a/tests/components/test_websocket_api.py
+++ b/tests/components/test_websocket_api.py
@@ -10,7 +10,7 @@ from homeassistant.core import callback
from homeassistant.components import websocket_api as wapi
from homeassistant.setup import async_setup_component
-from tests.common import mock_coro
+from tests.common import mock_coro, async_mock_service
API_PASSWORD = 'test1234'
@@ -443,3 +443,94 @@ async def test_auth_with_invalid_token(hass, aiohttp_client):
auth_msg = await ws.receive_json()
assert auth_msg['type'] == wapi.TYPE_AUTH_INVALID
+
+
+async def test_call_service_context_with_user(hass, aiohttp_client,
+ hass_access_token):
+ """Test that the user is set in the service call context."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ calls = async_mock_service(hass, 'domain_test', 'test_service')
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(wapi.URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active') as auth_active:
+ auth_active.return_value = True
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': wapi.TYPE_AUTH,
+ 'access_token': hass_access_token.token
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == wapi.TYPE_AUTH_OK
+
+ await ws.send_json({
+ 'id': 5,
+ 'type': wapi.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await ws.receive_json()
+ assert msg['success']
+
+ assert len(calls) == 1
+ call = calls[0]
+ assert call.domain == 'domain_test'
+ assert call.service == 'test_service'
+ assert call.data == {'hello': 'world'}
+ assert call.context.user_id == hass_access_token.refresh_token.user.id
+
+
+async def test_call_service_context_no_user(hass, aiohttp_client):
+ """Test that connection without user sets context."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ calls = async_mock_service(hass, 'domain_test', 'test_service')
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(wapi.URL) as ws:
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': wapi.TYPE_AUTH,
+ 'api_password': API_PASSWORD
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == wapi.TYPE_AUTH_OK
+
+ await ws.send_json({
+ 'id': 5,
+ 'type': wapi.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await ws.receive_json()
+ assert msg['success']
+
+ assert len(calls) == 1
+ call = calls[0]
+ assert call.domain == 'domain_test'
+ assert call.service == 'test_service'
+ assert call.data == {'hello': 'world'}
+ assert call.context.user_id is None
diff --git a/tests/components/tts/test_google.py b/tests/components/tts/test_google.py
index 6a2d2c65035..cf9a7b2db29 100644
--- a/tests/components/tts/test_google.py
+++ b/tests/components/tts/test_google.py
@@ -15,7 +15,7 @@ from tests.common import (
from .test_init import mutagen_mock # noqa
-class TestTTSGooglePlatform(object):
+class TestTTSGooglePlatform:
"""Test the Google speech component."""
def setup_method(self):
diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py
index b6bfa430fd2..e8746ee762f 100644
--- a/tests/components/tts/test_init.py
+++ b/tests/components/tts/test_init.py
@@ -29,7 +29,7 @@ def mutagen_mock():
yield
-class TestTTS(object):
+class TestTTS:
"""Test the Google speech component."""
def setup_method(self):
diff --git a/tests/components/tts/test_marytts.py b/tests/components/tts/test_marytts.py
index b55236c5e8e..7ec2ae39cd6 100644
--- a/tests/components/tts/test_marytts.py
+++ b/tests/components/tts/test_marytts.py
@@ -14,7 +14,7 @@ from tests.common import (
from .test_init import mutagen_mock # noqa
-class TestTTSMaryTTSPlatform(object):
+class TestTTSMaryTTSPlatform:
"""Test the speech component."""
def setup_method(self):
diff --git a/tests/components/tts/test_voicerss.py b/tests/components/tts/test_voicerss.py
index 2abdc0e69ff..365cf1ff73b 100644
--- a/tests/components/tts/test_voicerss.py
+++ b/tests/components/tts/test_voicerss.py
@@ -14,7 +14,7 @@ from tests.common import (
from .test_init import mutagen_mock # noqa
-class TestTTSVoiceRSSPlatform(object):
+class TestTTSVoiceRSSPlatform:
"""Test the voicerss speech component."""
def setup_method(self):
diff --git a/tests/components/tts/test_yandextts.py b/tests/components/tts/test_yandextts.py
index 5b4ef4dcf53..82d20318928 100644
--- a/tests/components/tts/test_yandextts.py
+++ b/tests/components/tts/test_yandextts.py
@@ -13,7 +13,7 @@ from tests.common import (
from .test_init import mutagen_mock # noqa
-class TestTTSYandexPlatform(object):
+class TestTTSYandexPlatform:
"""Test the speech component."""
def setup_method(self):
diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py
index e608dcccaba..39abf6f588f 100644
--- a/tests/components/zwave/test_init.py
+++ b/tests/components/zwave/test_init.py
@@ -163,10 +163,10 @@ def test_zwave_ready_wait(hass, mock_openzwave):
asyncio_sleep = asyncio.sleep
@asyncio.coroutine
- def sleep(duration, loop):
+ def sleep(duration, loop=None):
if duration > 0:
sleeps.append(duration)
- yield from asyncio_sleep(0, loop=loop)
+ yield from asyncio_sleep(0)
with patch('homeassistant.components.zwave.dt_util.utcnow', new=utcnow):
with patch('asyncio.sleep', new=sleep):
@@ -248,10 +248,10 @@ async def test_unparsed_node_discovery(hass, mock_openzwave):
asyncio_sleep = asyncio.sleep
- async def sleep(duration, loop):
+ async def sleep(duration, loop=None):
if duration > 0:
sleeps.append(duration)
- await asyncio_sleep(0, loop=loop)
+ await asyncio_sleep(0)
with patch('homeassistant.components.zwave.dt_util.utcnow', new=utcnow):
with patch('asyncio.sleep', new=sleep):
diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py
index 066e7386c6e..55e67def2bc 100644
--- a/tests/helpers/test_dispatcher.py
+++ b/tests/helpers/test_dispatcher.py
@@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import (
from tests.common import get_test_home_assistant
-class TestHelpersDispatcher(object):
+class TestHelpersDispatcher:
"""Tests for discovery helper methods."""
def setup_method(self, method):
diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py
index 4211e3da31b..e24bec489f4 100644
--- a/tests/helpers/test_entity.py
+++ b/tests/helpers/test_entity.py
@@ -71,7 +71,7 @@ def test_async_update_support(hass):
assert len(async_update) == 1
-class TestHelpersEntity(object):
+class TestHelpersEntity:
"""Test homeassistant.helpers.entity module."""
def setup_method(self, method):
@@ -400,3 +400,15 @@ def test_async_remove_no_platform(hass):
assert len(hass.states.async_entity_ids()) == 1
yield from ent.async_remove()
assert len(hass.states.async_entity_ids()) == 0
+
+
+async def test_async_remove_runs_callbacks(hass):
+ """Test async_remove method when no platform set."""
+ result = []
+
+ ent = entity.Entity()
+ ent.hass = hass
+ ent.entity_id = 'test.test'
+ ent.async_on_remove(lambda: result.append(1))
+ await ent.async_remove()
+ assert len(result) == 1
diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py
index 2d2f148189f..b52405aa8be 100644
--- a/tests/helpers/test_entity_platform.py
+++ b/tests/helpers/test_entity_platform.py
@@ -5,6 +5,8 @@ import unittest
from unittest.mock import patch, Mock, MagicMock
from datetime import timedelta
+import pytest
+
from homeassistant.exceptions import PlatformNotReady
import homeassistant.loader as loader
from homeassistant.helpers.entity import generate_entity_id
@@ -487,7 +489,7 @@ def test_registry_respect_entity_disabled(hass):
assert hass.states.async_entity_ids() == []
-async def test_entity_registry_updates(hass):
+async def test_entity_registry_updates_name(hass):
"""Test that updates on the entity registry update platform entities."""
registry = mock_registry(hass, {
'test_domain.world': entity_registry.RegistryEntry(
@@ -602,3 +604,75 @@ def test_not_fails_with_adding_empty_entities_(hass):
yield from component.async_add_entities([])
assert len(hass.states.async_entity_ids()) == 0
+
+
+async def test_entity_registry_updates_entity_id(hass):
+ """Test that updates on the entity registry update platform entities."""
+ registry = mock_registry(hass, {
+ 'test_domain.world': entity_registry.RegistryEntry(
+ entity_id='test_domain.world',
+ unique_id='1234',
+ # Using component.async_add_entities is equal to platform "domain"
+ platform='test_platform',
+ name='Some name'
+ )
+ })
+ platform = MockEntityPlatform(hass)
+ entity = MockEntity(unique_id='1234')
+ await platform.async_add_entities([entity])
+
+ state = hass.states.get('test_domain.world')
+ assert state is not None
+ assert state.name == 'Some name'
+
+ registry.async_update_entity('test_domain.world',
+ new_entity_id='test_domain.planet')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ assert hass.states.get('test_domain.world') is None
+ assert hass.states.get('test_domain.planet') is not None
+
+
+async def test_entity_registry_updates_invalid_entity_id(hass):
+ """Test that we can't update to an invalid entity id."""
+ registry = mock_registry(hass, {
+ 'test_domain.world': entity_registry.RegistryEntry(
+ entity_id='test_domain.world',
+ unique_id='1234',
+ # Using component.async_add_entities is equal to platform "domain"
+ platform='test_platform',
+ name='Some name'
+ ),
+ 'test_domain.existing': entity_registry.RegistryEntry(
+ entity_id='test_domain.existing',
+ unique_id='5678',
+ platform='test_platform',
+ ),
+ })
+ platform = MockEntityPlatform(hass)
+ entity = MockEntity(unique_id='1234')
+ await platform.async_add_entities([entity])
+
+ state = hass.states.get('test_domain.world')
+ assert state is not None
+ assert state.name == 'Some name'
+
+ with pytest.raises(ValueError):
+ registry.async_update_entity('test_domain.world',
+ new_entity_id='test_domain.existing')
+
+ with pytest.raises(ValueError):
+ registry.async_update_entity('test_domain.world',
+ new_entity_id='invalid_entity_id')
+
+ with pytest.raises(ValueError):
+ registry.async_update_entity('test_domain.world',
+ new_entity_id='diff_domain.world')
+
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ assert hass.states.get('test_domain.world') is not None
+ assert hass.states.get('invalid_entity_id') is None
+ assert hass.states.get('diff_domain.world') is None
diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py
index 6808206243f..5a9efd5c041 100644
--- a/tests/helpers/test_entity_registry.py
+++ b/tests/helpers/test_entity_registry.py
@@ -107,7 +107,8 @@ def test_loading_saving_data(hass, registry):
# Ensure same order
assert list(registry.entities) == list(registry2.entities)
new_entry1 = registry.async_get_or_create('light', 'hue', '1234')
- new_entry2 = registry.async_get_or_create('light', 'hue', '5678')
+ new_entry2 = registry.async_get_or_create('light', 'hue', '5678',
+ config_entry_id='mock-id')
assert orig_entry1 == new_entry1
assert orig_entry2 == new_entry2
@@ -191,3 +192,13 @@ def test_async_get_entity_id(registry):
assert registry.async_get_entity_id(
'light', 'hue', '1234') == 'light.hue_1234'
assert registry.async_get_entity_id('light', 'hue', '123') is None
+
+
+async def test_updating_config_entry_id(registry):
+ """Test that we update config entry id in registry."""
+ entry = registry.async_get_or_create(
+ 'light', 'hue', '5678', config_entry_id='mock-id-1')
+ entry2 = registry.async_get_or_create(
+ 'light', 'hue', '5678', config_entry_id='mock-id-2')
+ assert entry.entity_id == entry2.entity_id
+ assert entry2.config_entry_id == 'mock-id-2'
diff --git a/tests/test_core.py b/tests/test_core.py
index 7633c820d2d..9de801e0bb4 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -277,6 +277,10 @@ class TestEvent(unittest.TestCase):
'data': data,
'origin': 'LOCAL',
'time_fired': now,
+ 'context': {
+ 'id': event.context.id,
+ 'user_id': event.context.user_id,
+ },
}
self.assertEqual(expected, event.as_dict())
@@ -598,18 +602,16 @@ class TestStateMachine(unittest.TestCase):
self.assertEqual(1, len(events))
-class TestServiceCall(unittest.TestCase):
- """Test ServiceCall class."""
+def test_service_call_repr():
+ """Test ServiceCall repr."""
+ call = ha.ServiceCall('homeassistant', 'start')
+ assert str(call) == \
+ "".format(call.context.id)
- def test_repr(self):
- """Test repr method."""
- self.assertEqual(
- "",
- str(ha.ServiceCall('homeassistant', 'start')))
-
- self.assertEqual(
- "",
- str(ha.ServiceCall('homeassistant', 'start', {"fast": "yes"})))
+ call2 = ha.ServiceCall('homeassistant', 'start', {'fast': 'yes'})
+ assert str(call2) == \
+ "".format(
+ call2.context.id)
class TestServiceRegistry(unittest.TestCase):
diff --git a/tests/util/test_init.py b/tests/util/test_init.py
index 60b0e68ca59..1f43c5a4b49 100644
--- a/tests/util/test_init.py
+++ b/tests/util/test_init.py
@@ -221,7 +221,7 @@ class TestUtil(unittest.TestCase):
def test_throttle_per_instance(self):
"""Test that the throttle method is done per instance of a class."""
- class Tester(object):
+ class Tester:
"""A tester class for the throttle."""
@util.Throttle(timedelta(seconds=1))
@@ -234,7 +234,7 @@ class TestUtil(unittest.TestCase):
def test_throttle_on_method(self):
"""Test that throttle works when wrapping a method."""
- class Tester(object):
+ class Tester:
"""A tester class for the throttle."""
def hello(self):
@@ -249,7 +249,7 @@ class TestUtil(unittest.TestCase):
def test_throttle_on_two_method(self):
"""Test that throttle works when wrapping two methods."""
- class Tester(object):
+ class Tester:
"""A test class for the throttle."""
@util.Throttle(timedelta(seconds=1))