diff --git a/.coveragerc b/.coveragerc
index de1d8463477..bb0be2d9433 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -104,6 +104,9 @@ omit =
homeassistant/components/fritzbox.py
homeassistant/components/switch/fritzbox.py
+ homeassistant/components/ecovacs.py
+ homeassistant/components/*/ecovacs.py
+
homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
@@ -113,6 +116,12 @@ omit =
homeassistant/components/google.py
homeassistant/components/*/google.py
+ homeassistant/components/hangouts/__init__.py
+ homeassistant/components/hangouts/const.py
+ homeassistant/components/hangouts/hangouts_bot.py
+ homeassistant/components/hangouts/hangups_utils.py
+ homeassistant/components/*/hangouts.py
+
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
@@ -133,12 +142,13 @@ omit =
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
+
+ homeassistant/components/insteon/*
+ homeassistant/components/*/insteon.py
homeassistant/components/insteon_local.py
- homeassistant/components/*/insteon_local.py
-
- homeassistant/components/insteon_plm/*
- homeassistant/components/*/insteon_plm.py
+
+ homeassistant/components/insteon_plm.py
homeassistant/components/ios.py
homeassistant/components/*/ios.py
@@ -685,6 +695,7 @@ omit =
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/netdata_public.py
homeassistant/components/sensor/neurio_energy.py
+ homeassistant/components/sensor/noaa_tides.py
homeassistant/components/sensor/nsw_fuel_station.py
homeassistant/components/sensor/nut.py
homeassistant/components/sensor/nzbget.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 53f577d02eb..c756cb383d4 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -87,6 +87,8 @@ homeassistant/components/*/axis.py @kane610
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/*/deconz.py @kane610
+homeassistant/components/ecovacs.py @OverloadUT
+homeassistant/components/*/ecovacs.py @OverloadUT
homeassistant/components/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/hive.py @Rendili @KJonline
diff --git a/docs/source/api/homeassistant.rst b/docs/source/api/homeassistant.rst
index f5ff069451d..599f5fb8019 100644
--- a/docs/source/api/homeassistant.rst
+++ b/docs/source/api/homeassistant.rst
@@ -60,14 +60,6 @@ loader module
:undoc-members:
:show-inheritance:
-remote module
----------------------------
-
-.. automodule:: homeassistant.remote
- :members:
- :undoc-members:
- :show-inheritance:
-
Module contents
---------------
diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py
index 148f97702e3..b5ba869cdf1 100644
--- a/homeassistant/auth/__init__.py
+++ b/homeassistant/auth/__init__.py
@@ -2,23 +2,30 @@
import asyncio
import logging
from collections import OrderedDict
-from typing import List, Awaitable
+from typing import Any, Dict, List, Optional, Tuple, cast
import jwt
+import voluptuous as vol
+
from homeassistant import data_entry_flow
from homeassistant.core import callback, HomeAssistant
from homeassistant.util import dt as dt_util
-from . import auth_store
-from .providers import auth_provider_from_config
+from . import auth_store, models
+from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
+from .providers import auth_provider_from_config, AuthProvider, LoginFlow
_LOGGER = logging.getLogger(__name__)
+_MfaModuleDict = Dict[str, MultiFactorAuthModule]
+_ProviderKey = Tuple[str, Optional[str]]
+_ProviderDict = Dict[_ProviderKey, AuthProvider]
async def auth_manager_from_config(
hass: HomeAssistant,
- provider_configs: List[dict]) -> Awaitable['AuthManager']:
+ provider_configs: List[Dict[str, Any]],
+ module_configs: List[Dict[str, Any]]) -> 'AuthManager':
"""Initialize an auth manager from config."""
store = auth_store.AuthStore(hass)
if provider_configs:
@@ -26,9 +33,9 @@ async def auth_manager_from_config(
*[auth_provider_from_config(hass, store, config)
for config in provider_configs])
else:
- providers = []
+ providers = ()
# So returned auth providers are in same order as config
- provider_hash = OrderedDict()
+ provider_hash = OrderedDict() # type: _ProviderDict
for provider in providers:
if provider is None:
continue
@@ -42,28 +49,53 @@ async def auth_manager_from_config(
continue
provider_hash[key] = provider
- manager = AuthManager(hass, store, provider_hash)
+
+ if module_configs:
+ modules = await asyncio.gather(
+ *[auth_mfa_module_from_config(hass, config)
+ for config in module_configs])
+ else:
+ modules = ()
+ # So returned auth modules are in same order as config
+ module_hash = OrderedDict() # type: _MfaModuleDict
+ for module in modules:
+ if module is None:
+ continue
+
+ if module.id in module_hash:
+ _LOGGER.error(
+ 'Found duplicate multi-factor module: %s. Please add unique '
+ 'IDs if you want to have the same module twice.', module.id)
+ continue
+
+ module_hash[module.id] = module
+
+ manager = AuthManager(hass, store, provider_hash, module_hash)
return manager
class AuthManager:
"""Manage the authentication for Home Assistant."""
- def __init__(self, hass, store, providers):
+ def __init__(self, hass: HomeAssistant, store: auth_store.AuthStore,
+ providers: _ProviderDict, mfa_modules: _MfaModuleDict) \
+ -> None:
"""Initialize the auth manager."""
+ self.hass = hass
self._store = store
self._providers = providers
+ self._mfa_modules = mfa_modules
self.login_flow = data_entry_flow.FlowManager(
hass, self._async_create_login_flow,
self._async_finish_login_flow)
@property
- def active(self):
+ def active(self) -> bool:
"""Return if any auth providers are registered."""
return bool(self._providers)
@property
- def support_legacy(self):
+ def support_legacy(self) -> bool:
"""
Return if legacy_api_password auth providers are registered.
@@ -75,19 +107,39 @@ class AuthManager:
return False
@property
- def auth_providers(self):
+ def auth_providers(self) -> List[AuthProvider]:
"""Return a list of available auth providers."""
return list(self._providers.values())
- async def async_get_users(self):
+ @property
+ def auth_mfa_modules(self) -> List[MultiFactorAuthModule]:
+ """Return a list of available auth modules."""
+ return list(self._mfa_modules.values())
+
+ def get_auth_mfa_module(self, module_id: str) \
+ -> Optional[MultiFactorAuthModule]:
+ """Return an multi-factor auth module, None if not found."""
+ return self._mfa_modules.get(module_id)
+
+ async def async_get_users(self) -> List[models.User]:
"""Retrieve all users."""
return await self._store.async_get_users()
- async def async_get_user(self, user_id):
+ async def async_get_user(self, user_id: str) -> Optional[models.User]:
"""Retrieve a user."""
return await self._store.async_get_user(user_id)
- async def async_create_system_user(self, name):
+ async def async_get_user_by_credentials(
+ self, credentials: models.Credentials) -> Optional[models.User]:
+ """Get a user by credential, return None if not found."""
+ for user in await self.async_get_users():
+ for creds in user.credentials:
+ if creds.id == credentials.id:
+ return user
+
+ return None
+
+ async def async_create_system_user(self, name: str) -> models.User:
"""Create a system user."""
return await self._store.async_create_user(
name=name,
@@ -95,27 +147,27 @@ class AuthManager:
is_active=True,
)
- async def async_create_user(self, name):
+ async def async_create_user(self, name: str) -> models.User:
"""Create a user."""
kwargs = {
'name': name,
'is_active': True,
- }
+ } # type: Dict[str, Any]
if await self._user_should_be_owner():
kwargs['is_owner'] = True
return await self._store.async_create_user(**kwargs)
- async def async_get_or_create_user(self, credentials):
+ async def async_get_or_create_user(self, credentials: models.Credentials) \
+ -> models.User:
"""Get or create a user."""
if not credentials.is_new:
- for user in await self._store.async_get_users():
- for creds in user.credentials:
- if creds.id == credentials.id:
- return user
-
- raise ValueError('Unable to find the user.')
+ user = await self.async_get_user_by_credentials(credentials)
+ if user is None:
+ raise ValueError('Unable to find the user.')
+ else:
+ return user
auth_provider = self._async_get_auth_provider(credentials)
@@ -127,15 +179,16 @@ class AuthManager:
return await self._store.async_create_user(
credentials=credentials,
- name=info.get('name'),
- is_active=info.get('is_active', False)
+ name=info.name,
+ is_active=info.is_active,
)
- async def async_link_user(self, user, credentials):
+ async def async_link_user(self, user: models.User,
+ credentials: models.Credentials) -> None:
"""Link credentials to an existing user."""
await self._store.async_link_user(user, credentials)
- async def async_remove_user(self, user):
+ async def async_remove_user(self, user: models.User) -> None:
"""Remove a user."""
tasks = [
self.async_remove_credentials(credentials)
@@ -147,27 +200,75 @@ class AuthManager:
await self._store.async_remove_user(user)
- async def async_activate_user(self, user):
+ async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
await self._store.async_activate_user(user)
- async def async_deactivate_user(self, user):
+ async def async_deactivate_user(self, user: models.User) -> None:
"""Deactivate a user."""
if user.is_owner:
raise ValueError('Unable to deactive the owner')
await self._store.async_deactivate_user(user)
- async def async_remove_credentials(self, credentials):
+ async def async_remove_credentials(
+ self, credentials: models.Credentials) -> None:
"""Remove credentials."""
provider = self._async_get_auth_provider(credentials)
if (provider is not None and
hasattr(provider, 'async_will_remove_credentials')):
- await provider.async_will_remove_credentials(credentials)
+ # https://github.com/python/mypy/issues/1424
+ await provider.async_will_remove_credentials( # type: ignore
+ credentials)
await self._store.async_remove_credentials(credentials)
- async def async_create_refresh_token(self, user, client_id=None):
+ async def async_enable_user_mfa(self, user: models.User,
+ mfa_module_id: str, data: Any) -> None:
+ """Enable a multi-factor auth module for user."""
+ if user.system_generated:
+ raise ValueError('System generated users cannot enable '
+ 'multi-factor auth module.')
+
+ module = self.get_auth_mfa_module(mfa_module_id)
+ if module is None:
+ raise ValueError('Unable find multi-factor auth module: {}'
+ .format(mfa_module_id))
+
+ if module.setup_schema is not None:
+ try:
+ # pylint: disable=not-callable
+ data = module.setup_schema(data)
+ except vol.Invalid as err:
+ raise ValueError('Data does not match schema: {}'.format(err))
+
+ await module.async_setup_user(user.id, data)
+
+ async def async_disable_user_mfa(self, user: models.User,
+ mfa_module_id: str) -> None:
+ """Disable a multi-factor auth module for user."""
+ if user.system_generated:
+ raise ValueError('System generated users cannot disable '
+ 'multi-factor auth module.')
+
+ module = self.get_auth_mfa_module(mfa_module_id)
+ if module is None:
+ raise ValueError('Unable find multi-factor auth module: {}'
+ .format(mfa_module_id))
+
+ await module.async_depose_user(user.id)
+
+ async def async_get_enabled_mfa(self, user: models.User) -> List[str]:
+ """List enabled mfa modules for user."""
+ module_ids = []
+ for module_id, module in self._mfa_modules.items():
+ if await module.async_is_user_setup(user.id):
+ module_ids.append(module_id)
+ return module_ids
+
+ async def async_create_refresh_token(self, user: models.User,
+ client_id: Optional[str] = None) \
+ -> models.RefreshToken:
"""Create a new refresh token for a user."""
if not user.is_active:
raise ValueError('User is not active')
@@ -182,16 +283,25 @@ class AuthManager:
return await self._store.async_create_refresh_token(user, client_id)
- async def async_get_refresh_token(self, token_id):
+ async def async_get_refresh_token(
+ self, token_id: str) -> Optional[models.RefreshToken]:
"""Get refresh token by id."""
return await self._store.async_get_refresh_token(token_id)
- async def async_get_refresh_token_by_token(self, token):
+ async def async_get_refresh_token_by_token(
+ self, token: str) -> Optional[models.RefreshToken]:
"""Get refresh token by token."""
return await self._store.async_get_refresh_token_by_token(token)
+ async def async_remove_refresh_token(self,
+ refresh_token: models.RefreshToken) \
+ -> None:
+ """Delete a refresh token."""
+ await self._store.async_remove_refresh_token(refresh_token)
+
@callback
- def async_create_access_token(self, refresh_token):
+ def async_create_access_token(self,
+ refresh_token: models.RefreshToken) -> str:
"""Create a new access token."""
# pylint: disable=no-self-use
return jwt.encode({
@@ -200,15 +310,16 @@ class AuthManager:
'exp': dt_util.utcnow() + refresh_token.access_token_expiration,
}, refresh_token.jwt_key, algorithm='HS256').decode()
- async def async_validate_access_token(self, token):
- """Return if an access token is valid."""
+ async def async_validate_access_token(
+ self, token: str) -> Optional[models.RefreshToken]:
+ """Return refresh token if an access token is valid."""
try:
unverif_claims = jwt.decode(token, verify=False)
except jwt.InvalidTokenError:
return None
refresh_token = await self.async_get_refresh_token(
- unverif_claims.get('iss'))
+ cast(str, unverif_claims.get('iss')))
if refresh_token is None:
jwt_key = ''
@@ -228,34 +339,63 @@ class AuthManager:
except jwt.InvalidTokenError:
return None
- if not refresh_token.user.is_active:
+ if refresh_token is None or not refresh_token.user.is_active:
return None
return refresh_token
- async def _async_create_login_flow(self, handler, *, context, data):
+ async def _async_create_login_flow(
+ self, handler: _ProviderKey, *, context: Optional[Dict],
+ data: Optional[Any]) -> data_entry_flow.FlowHandler:
"""Create a login flow."""
auth_provider = self._providers[handler]
- return await auth_provider.async_credential_flow(context)
+ return await auth_provider.async_login_flow(context)
- async def _async_finish_login_flow(self, context, result):
- """Result of a credential login flow."""
+ async def _async_finish_login_flow(
+ self, flow: LoginFlow, result: Dict[str, Any]) \
+ -> Dict[str, Any]:
+ """Return a user as result of login flow."""
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
- return None
+ return result
+
+ # we got final result
+ if isinstance(result['data'], models.User):
+ result['result'] = result['data']
+ return result
auth_provider = self._providers[result['handler']]
- return await auth_provider.async_get_or_create_credentials(
+ credentials = await auth_provider.async_get_or_create_credentials(
result['data'])
+ if flow.context is not None and flow.context.get('credential_only'):
+ result['result'] = credentials
+ return result
+
+ # multi-factor module cannot enabled for new credential
+ # which has not linked to a user yet
+ if auth_provider.support_mfa and not credentials.is_new:
+ user = await self.async_get_user_by_credentials(credentials)
+ if user is not None:
+ modules = await self.async_get_enabled_mfa(user)
+
+ if modules:
+ flow.user = user
+ flow.available_mfa_modules = modules
+ return await flow.async_step_select_mfa_module()
+
+ result['result'] = await self.async_get_or_create_user(credentials)
+ return result
+
@callback
- def _async_get_auth_provider(self, credentials):
- """Helper to get auth provider from a set of credentials."""
+ def _async_get_auth_provider(
+ self, credentials: models.Credentials) -> Optional[AuthProvider]:
+ """Get auth provider from a set of credentials."""
auth_provider_key = (credentials.auth_provider_type,
credentials.auth_provider_id)
return self._providers.get(auth_provider_key)
- async def _user_should_be_owner(self):
+ async def _user_should_be_owner(self) -> bool:
"""Determine if user should be owner.
A user should be an owner if it is the first non-system user that is
diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py
index 806cd109d78..0f12d69211c 100644
--- a/homeassistant/auth/auth_store.py
+++ b/homeassistant/auth/auth_store.py
@@ -1,8 +1,11 @@
"""Storage for auth models."""
from collections import OrderedDict
from datetime import timedelta
+from logging import getLogger
+from typing import Any, Dict, List, Optional # noqa: F401
import hmac
+from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util
from . import models
@@ -20,35 +23,41 @@ class AuthStore:
called that needs it.
"""
- def __init__(self, hass):
+ def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the auth store."""
self.hass = hass
- self._users = None
+ self._users = None # type: Optional[Dict[str, models.User]]
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
- async def async_get_users(self):
+ async def async_get_users(self) -> List[models.User]:
"""Retrieve all users."""
if self._users is None:
- await self.async_load()
+ await self._async_load()
+ assert self._users is not None
return list(self._users.values())
- async def async_get_user(self, user_id):
+ async def async_get_user(self, user_id: str) -> Optional[models.User]:
"""Retrieve a user by id."""
if self._users is None:
- await self.async_load()
+ await self._async_load()
+ assert self._users is not None
return self._users.get(user_id)
- async def async_create_user(self, name, is_owner=None, is_active=None,
- system_generated=None, credentials=None):
+ async def async_create_user(
+ self, name: Optional[str], is_owner: Optional[bool] = None,
+ is_active: Optional[bool] = None,
+ system_generated: Optional[bool] = None,
+ credentials: Optional[models.Credentials] = None) -> models.User:
"""Create a new user."""
if self._users is None:
- await self.async_load()
+ await self._async_load()
+ assert self._users is not None
kwargs = {
'name': name
- }
+ } # type: Dict[str, Any]
if is_owner is not None:
kwargs['is_owner'] = is_owner
@@ -64,36 +73,46 @@ class AuthStore:
self._users[new_user.id] = new_user
if credentials is None:
- await self.async_save()
+ self._async_schedule_save()
return new_user
# Saving is done inside the link.
await self.async_link_user(new_user, credentials)
return new_user
- async def async_link_user(self, user, credentials):
+ async def async_link_user(self, user: models.User,
+ credentials: models.Credentials) -> None:
"""Add credentials to an existing user."""
user.credentials.append(credentials)
- await self.async_save()
+ self._async_schedule_save()
credentials.is_new = False
- async def async_remove_user(self, user):
+ async def async_remove_user(self, user: models.User) -> None:
"""Remove a user."""
- self._users.pop(user.id)
- await self.async_save()
+ if self._users is None:
+ await self._async_load()
+ assert self._users is not None
- async def async_activate_user(self, user):
+ self._users.pop(user.id)
+ self._async_schedule_save()
+
+ async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
user.is_active = True
- await self.async_save()
+ self._async_schedule_save()
- async def async_deactivate_user(self, user):
+ async def async_deactivate_user(self, user: models.User) -> None:
"""Activate a user."""
user.is_active = False
- await self.async_save()
+ self._async_schedule_save()
- async def async_remove_credentials(self, credentials):
+ async def async_remove_credentials(
+ self, credentials: models.Credentials) -> None:
"""Remove credentials."""
+ if self._users is None:
+ await self._async_load()
+ assert self._users is not None
+
for user in self._users.values():
found = None
@@ -106,19 +125,35 @@ class AuthStore:
user.credentials.pop(found)
break
- await self.async_save()
+ self._async_schedule_save()
- async def async_create_refresh_token(self, user, client_id=None):
+ async def async_create_refresh_token(
+ self, user: models.User, client_id: Optional[str] = None) \
+ -> models.RefreshToken:
"""Create a new token for a user."""
refresh_token = models.RefreshToken(user=user, client_id=client_id)
user.refresh_tokens[refresh_token.id] = refresh_token
- await self.async_save()
+ self._async_schedule_save()
return refresh_token
- async def async_get_refresh_token(self, token_id):
+ async def async_remove_refresh_token(
+ self, refresh_token: models.RefreshToken) -> None:
+ """Remove a refresh token."""
+ if self._users is None:
+ await self._async_load()
+ assert self._users is not None
+
+ for user in self._users.values():
+ if user.refresh_tokens.pop(refresh_token.id, None):
+ self._async_schedule_save()
+ break
+
+ async def async_get_refresh_token(
+ self, token_id: str) -> Optional[models.RefreshToken]:
"""Get refresh token by id."""
if self._users is None:
- await self.async_load()
+ await self._async_load()
+ assert self._users is not None
for user in self._users.values():
refresh_token = user.refresh_tokens.get(token_id)
@@ -127,10 +162,12 @@ class AuthStore:
return None
- async def async_get_refresh_token_by_token(self, token):
+ async def async_get_refresh_token_by_token(
+ self, token: str) -> Optional[models.RefreshToken]:
"""Get refresh token by token."""
if self._users is None:
- await self.async_load()
+ await self._async_load()
+ assert self._users is not None
found = None
@@ -141,7 +178,7 @@ class AuthStore:
return found
- async def async_load(self):
+ async def _async_load(self) -> None:
"""Load the users."""
data = await self._store.async_load()
@@ -150,7 +187,7 @@ class AuthStore:
if self._users is not None:
return
- users = OrderedDict()
+ users = OrderedDict() # type: Dict[str, models.User]
if data is None:
self._users = users
@@ -173,11 +210,17 @@ class AuthStore:
if 'jwt_key' not in rt_dict:
continue
+ created_at = dt_util.parse_datetime(rt_dict['created_at'])
+ if created_at is None:
+ getLogger(__name__).error(
+ 'Ignoring refresh token %(id)s with invalid created_at '
+ '%(created_at)s for user_id %(user_id)s', rt_dict)
+ continue
token = models.RefreshToken(
id=rt_dict['id'],
user=users[rt_dict['user_id']],
client_id=rt_dict['client_id'],
- created_at=dt_util.parse_datetime(rt_dict['created_at']),
+ created_at=created_at,
access_token_expiration=timedelta(
seconds=rt_dict['access_token_expiration']),
token=rt_dict['token'],
@@ -187,8 +230,19 @@ class AuthStore:
self._users = users
- async def async_save(self):
+ @callback
+ def _async_schedule_save(self) -> None:
"""Save users."""
+ if self._users is None:
+ return
+
+ self._store.async_delay_save(self._data_to_save, 1)
+
+ @callback
+ def _data_to_save(self) -> Dict:
+ """Return the data to store."""
+ assert self._users is not None
+
users = [
{
'id': user.id,
@@ -227,10 +281,8 @@ class AuthStore:
for refresh_token in user.refresh_tokens.values()
]
- data = {
+ return {
'users': users,
'credentials': credentials,
'refresh_tokens': refresh_tokens,
}
-
- await self._store.async_save(data, delay=1)
diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py
new file mode 100644
index 00000000000..d0707c4a745
--- /dev/null
+++ b/homeassistant/auth/mfa_modules/__init__.py
@@ -0,0 +1,141 @@
+"""Plugable auth modules for Home Assistant."""
+from datetime import timedelta
+import importlib
+import logging
+import types
+from typing import Any, Dict, Optional
+
+import voluptuous as vol
+from voluptuous.humanize import humanize_error
+
+from homeassistant import requirements
+from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
+from homeassistant.core import HomeAssistant
+from homeassistant.util.decorator import Registry
+
+MULTI_FACTOR_AUTH_MODULES = Registry()
+
+MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({
+ vol.Required(CONF_TYPE): str,
+ vol.Optional(CONF_NAME): str,
+ # Specify ID if you have two mfa auth module for same type.
+ vol.Optional(CONF_ID): str,
+}, extra=vol.ALLOW_EXTRA)
+
+SESSION_EXPIRATION = timedelta(minutes=5)
+
+DATA_REQS = 'mfa_auth_module_reqs_processed'
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class MultiFactorAuthModule:
+ """Multi-factor Auth Module of validation function."""
+
+ DEFAULT_TITLE = 'Unnamed auth module'
+
+ def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
+ """Initialize an auth module."""
+ self.hass = hass
+ self.config = config
+
+ @property
+ def id(self) -> str: # pylint: disable=invalid-name
+ """Return id of the auth module.
+
+ Default is same as type
+ """
+ return self.config.get(CONF_ID, self.type)
+
+ @property
+ def type(self) -> str:
+ """Return type of the module."""
+ return self.config[CONF_TYPE] # type: ignore
+
+ @property
+ def name(self) -> str:
+ """Return the name of the auth module."""
+ return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
+
+ # Implement by extending class
+
+ @property
+ def input_schema(self) -> vol.Schema:
+ """Return a voluptuous schema to define mfa auth module's input."""
+ raise NotImplementedError
+
+ @property
+ def setup_schema(self) -> Optional[vol.Schema]:
+ """Return a vol schema to validate mfa auth module's setup input.
+
+ Optional
+ """
+ return None
+
+ async def async_setup_user(self, user_id: str, setup_data: Any) -> None:
+ """Set up user for mfa auth module."""
+ raise NotImplementedError
+
+ async def async_depose_user(self, user_id: str) -> None:
+ """Remove user from mfa module."""
+ raise NotImplementedError
+
+ async def async_is_user_setup(self, user_id: str) -> bool:
+ """Return whether user is setup."""
+ raise NotImplementedError
+
+ async def async_validation(
+ self, user_id: str, user_input: Dict[str, Any]) -> bool:
+ """Return True if validation passed."""
+ raise NotImplementedError
+
+
+async def auth_mfa_module_from_config(
+ hass: HomeAssistant, config: Dict[str, Any]) \
+ -> Optional[MultiFactorAuthModule]:
+ """Initialize an auth module from a config."""
+ module_name = config[CONF_TYPE]
+ module = await _load_mfa_module(hass, module_name)
+
+ if module is None:
+ return None
+
+ try:
+ config = module.CONFIG_SCHEMA(config) # type: ignore
+ except vol.Invalid as err:
+ _LOGGER.error('Invalid configuration for multi-factor module %s: %s',
+ module_name, humanize_error(config, err))
+ return None
+
+ return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore
+
+
+async def _load_mfa_module(hass: HomeAssistant, module_name: str) \
+ -> Optional[types.ModuleType]:
+ """Load an mfa auth module."""
+ module_path = 'homeassistant.auth.mfa_modules.{}'.format(module_name)
+
+ try:
+ module = importlib.import_module(module_path)
+ except ImportError:
+ _LOGGER.warning('Unable to find %s', module_path)
+ return None
+
+ if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
+ return module
+
+ processed = hass.data.get(DATA_REQS)
+ if processed and module_name in processed:
+ return module
+
+ processed = hass.data[DATA_REQS] = set()
+
+ # https://github.com/python/mypy/issues/1424
+ req_success = await requirements.async_process_requirements(
+ hass, module_path, module.REQUIREMENTS) # type: ignore
+
+ if not req_success:
+ return None
+
+ processed.add(module_name)
+ return module
diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py
new file mode 100644
index 00000000000..59b3f64d2e0
--- /dev/null
+++ b/homeassistant/auth/mfa_modules/insecure_example.py
@@ -0,0 +1,82 @@
+"""Example auth module."""
+import logging
+from typing import Any, Dict, Optional
+
+import voluptuous as vol
+
+from homeassistant.core import HomeAssistant
+
+from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \
+ MULTI_FACTOR_AUTH_MODULE_SCHEMA
+
+CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({
+ vol.Required('data'): [vol.Schema({
+ vol.Required('user_id'): str,
+ vol.Required('pin'): str,
+ })]
+}, extra=vol.PREVENT_EXTRA)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@MULTI_FACTOR_AUTH_MODULES.register('insecure_example')
+class InsecureExampleModule(MultiFactorAuthModule):
+ """Example auth module validate pin."""
+
+ DEFAULT_TITLE = 'Insecure Personal Identify Number'
+
+ def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
+ """Initialize the user data store."""
+ super().__init__(hass, config)
+ self._data = config['data']
+
+ @property
+ def input_schema(self) -> vol.Schema:
+ """Validate login flow input data."""
+ return vol.Schema({'pin': str})
+
+ @property
+ def setup_schema(self) -> Optional[vol.Schema]:
+ """Validate async_setup_user input data."""
+ return vol.Schema({'pin': str})
+
+ async def async_setup_user(self, user_id: str, setup_data: Any) -> None:
+ """Set up user to use mfa module."""
+ # data shall has been validate in caller
+ pin = setup_data['pin']
+
+ for data in self._data:
+ if data['user_id'] == user_id:
+ # already setup, override
+ data['pin'] = pin
+ return
+
+ self._data.append({'user_id': user_id, 'pin': pin})
+
+ async def async_depose_user(self, user_id: str) -> None:
+ """Remove user from mfa module."""
+ found = None
+ for data in self._data:
+ if data['user_id'] == user_id:
+ found = data
+ break
+ if found:
+ self._data.remove(found)
+
+ async def async_is_user_setup(self, user_id: str) -> bool:
+ """Return whether user is setup."""
+ for data in self._data:
+ if data['user_id'] == user_id:
+ return True
+ return False
+
+ async def async_validation(
+ self, user_id: str, user_input: Dict[str, Any]) -> bool:
+ """Return True if validation passed."""
+ for data in self._data:
+ if data['user_id'] == user_id:
+ # user_input has been validate in caller
+ if data['pin'] == user_input['pin']:
+ return True
+
+ return False
diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py
index 3f49c56bce6..a6500510e0d 100644
--- a/homeassistant/auth/models.py
+++ b/homeassistant/auth/models.py
@@ -1,5 +1,6 @@
"""Auth models."""
from datetime import datetime, timedelta
+from typing import Dict, List, NamedTuple, Optional # noqa: F401
import uuid
import attr
@@ -14,17 +15,21 @@ from .util import generate_secret
class User:
"""A user."""
- name = attr.ib(type=str)
+ name = attr.ib(type=str) # type: Optional[str]
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
is_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False)
system_generated = attr.ib(type=bool, default=False)
# List of credentials of a user.
- credentials = attr.ib(type=list, default=attr.Factory(list), cmp=False)
+ credentials = attr.ib(
+ type=list, default=attr.Factory(list), cmp=False
+ ) # type: List[Credentials]
# Tokens associated with a user.
- refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)
+ refresh_tokens = attr.ib(
+ type=dict, default=attr.Factory(dict), cmp=False
+ ) # type: Dict[str, RefreshToken]
@attr.s(slots=True)
@@ -32,7 +37,7 @@ class RefreshToken:
"""RefreshToken for a user to grant new access tokens."""
user = attr.ib(type=User)
- client_id = attr.ib(type=str)
+ client_id = attr.ib(type=str) # type: Optional[str]
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
access_token_expiration = attr.ib(type=timedelta,
@@ -48,10 +53,14 @@ class Credentials:
"""Credentials for a user on an auth provider."""
auth_provider_type = attr.ib(type=str)
- auth_provider_id = attr.ib(type=str)
+ auth_provider_id = attr.ib(type=str) # type: Optional[str]
# Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict)
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
is_new = attr.ib(type=bool, default=True)
+
+
+UserMeta = NamedTuple("UserMeta",
+ [('name', Optional[str]), ('is_active', bool)])
diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py
index ac5b6107b8a..e8ef7cbf3d4 100644
--- a/homeassistant/auth/providers/__init__.py
+++ b/homeassistant/auth/providers/__init__.py
@@ -1,16 +1,21 @@
"""Auth providers for Home Assistant."""
import importlib
import logging
+import types
+from typing import Any, Dict, List, Optional
import voluptuous as vol
from voluptuous.humanize import humanize_error
-from homeassistant import requirements
-from homeassistant.core import callback
-from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
+from homeassistant import data_entry_flow, requirements
+from homeassistant.core import callback, HomeAssistant
+from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
+from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry
-from homeassistant.auth.models import Credentials
+from ..auth_store import AuthStore
+from ..models import Credentials, User, UserMeta # noqa: F401
+from ..mfa_modules import SESSION_EXPIRATION
_LOGGER = logging.getLogger(__name__)
DATA_REQS = 'auth_prov_reqs_processed'
@@ -25,7 +30,87 @@ AUTH_PROVIDER_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-async def auth_provider_from_config(hass, store, config):
+class AuthProvider:
+ """Provider of user authentication."""
+
+ DEFAULT_TITLE = 'Unnamed auth provider'
+
+ def __init__(self, hass: HomeAssistant, store: AuthStore,
+ config: Dict[str, Any]) -> None:
+ """Initialize an auth provider."""
+ self.hass = hass
+ self.store = store
+ self.config = config
+
+ @property
+ def id(self) -> Optional[str]: # pylint: disable=invalid-name
+ """Return id of the auth provider.
+
+ Optional, can be None.
+ """
+ return self.config.get(CONF_ID)
+
+ @property
+ def type(self) -> str:
+ """Return type of the provider."""
+ return self.config[CONF_TYPE] # type: ignore
+
+ @property
+ def name(self) -> str:
+ """Return the name of the auth provider."""
+ return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
+
+ @property
+ def support_mfa(self) -> bool:
+ """Return whether multi-factor auth supported by the auth provider."""
+ return True
+
+ async def async_credentials(self) -> List[Credentials]:
+ """Return all credentials of this provider."""
+ users = await self.store.async_get_users()
+ return [
+ credentials
+ for user in users
+ for credentials in user.credentials
+ if (credentials.auth_provider_type == self.type and
+ credentials.auth_provider_id == self.id)
+ ]
+
+ @callback
+ def async_create_credentials(self, data: Dict[str, str]) -> Credentials:
+ """Create credentials."""
+ return Credentials(
+ auth_provider_type=self.type,
+ auth_provider_id=self.id,
+ data=data,
+ )
+
+ # Implement by extending class
+
+ async def async_login_flow(self, context: Optional[Dict]) -> 'LoginFlow':
+ """Return the data flow for logging in with auth provider.
+
+ Auth provider should extend LoginFlow and return an instance.
+ """
+ raise NotImplementedError
+
+ async def async_get_or_create_credentials(
+ self, flow_result: Dict[str, str]) -> Credentials:
+ """Get credentials based on the flow result."""
+ raise NotImplementedError
+
+ async def async_user_meta_for_credentials(
+ self, credentials: Credentials) -> UserMeta:
+ """Return extra user metadata for credentials.
+
+ Will be used to populate info when creating a new user.
+ """
+ raise NotImplementedError
+
+
+async def auth_provider_from_config(
+ hass: HomeAssistant, store: AuthStore,
+ config: Dict[str, Any]) -> Optional[AuthProvider]:
"""Initialize an auth provider from a config."""
provider_name = config[CONF_TYPE]
module = await load_auth_provider_module(hass, provider_name)
@@ -34,16 +119,17 @@ async def auth_provider_from_config(hass, store, config):
return None
try:
- config = module.CONFIG_SCHEMA(config)
+ config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error('Invalid configuration for auth provider %s: %s',
provider_name, humanize_error(config, err))
return None
- return AUTH_PROVIDERS[provider_name](hass, store, config)
+ return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore
-async def load_auth_provider_module(hass, provider):
+async def load_auth_provider_module(
+ hass: HomeAssistant, provider: str) -> Optional[types.ModuleType]:
"""Load an auth provider."""
try:
module = importlib.import_module(
@@ -62,8 +148,10 @@ async def load_auth_provider_module(hass, provider):
elif provider in processed:
return module
+ # https://github.com/python/mypy/issues/1424
+ reqs = module.REQUIREMENTS # type: ignore
req_success = await requirements.async_process_requirements(
- hass, 'auth provider {}'.format(provider), module.REQUIREMENTS)
+ hass, 'auth provider {}'.format(provider), reqs)
if not req_success:
return None
@@ -72,72 +160,88 @@ async def load_auth_provider_module(hass, provider):
return module
-class AuthProvider:
- """Provider of user authentication."""
+class LoginFlow(data_entry_flow.FlowHandler):
+ """Handler for the login flow."""
- DEFAULT_TITLE = 'Unnamed auth provider'
+ def __init__(self, auth_provider: AuthProvider) -> None:
+ """Initialize the login flow."""
+ self._auth_provider = auth_provider
+ self._auth_module_id = None # type: Optional[str]
+ self._auth_manager = auth_provider.hass.auth # type: ignore
+ self.available_mfa_modules = [] # type: List
+ self.created_at = dt_util.utcnow()
+ self.user = None # type: Optional[User]
- def __init__(self, hass, store, config):
- """Initialize an auth provider."""
- self.hass = hass
- self.store = store
- self.config = config
+ async def async_step_init(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
+ """Handle the first step of login flow.
- @property
- def id(self): # pylint: disable=invalid-name
- """Return id of the auth provider.
-
- Optional, can be None.
+ Return self.async_show_form(step_id='init') if user_input == None.
+ Return await self.async_finish(flow_result) if login init step pass.
"""
- return self.config.get(CONF_ID)
+ raise NotImplementedError
- @property
- def type(self):
- """Return type of the provider."""
- return self.config[CONF_TYPE]
+ async def async_step_select_mfa_module(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
+ """Handle the step of select mfa module."""
+ errors = {}
- @property
- def name(self):
- """Return the name of the auth provider."""
- return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
+ if user_input is not None:
+ auth_module = user_input.get('multi_factor_auth_module')
+ if auth_module in self.available_mfa_modules:
+ self._auth_module_id = auth_module
+ return await self.async_step_mfa()
+ errors['base'] = 'invalid_auth_module'
- async def async_credentials(self):
- """Return all credentials of this provider."""
- users = await self.store.async_get_users()
- return [
- credentials
- for user in users
- for credentials in user.credentials
- if (credentials.auth_provider_type == self.type and
- credentials.auth_provider_id == self.id)
- ]
+ if len(self.available_mfa_modules) == 1:
+ self._auth_module_id = self.available_mfa_modules[0]
+ return await self.async_step_mfa()
- @callback
- def async_create_credentials(self, data):
- """Create credentials."""
- return Credentials(
- auth_provider_type=self.type,
- auth_provider_id=self.id,
- data=data,
+ return self.async_show_form(
+ step_id='select_mfa_module',
+ data_schema=vol.Schema({
+ 'multi_factor_auth_module': vol.In(self.available_mfa_modules)
+ }),
+ errors=errors,
)
- # Implement by extending class
+ async def async_step_mfa(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
+ """Handle the step of mfa validation."""
+ errors = {}
- async def async_credential_flow(self, context):
- """Return the data flow for logging in with auth provider."""
- raise NotImplementedError
+ auth_module = self._auth_manager.get_auth_mfa_module(
+ self._auth_module_id)
+ if auth_module is None:
+ # Given an invalid input to async_step_select_mfa_module
+ # will show invalid_auth_module error
+ return await self.async_step_select_mfa_module(user_input={})
- async def async_get_or_create_credentials(self, flow_result):
- """Get credentials based on the flow result."""
- raise NotImplementedError
+ if user_input is not None:
+ expires = self.created_at + SESSION_EXPIRATION
+ if dt_util.utcnow() > expires:
+ errors['base'] = 'login_expired'
+ else:
+ result = await auth_module.async_validation(
+ self.user.id, user_input) # type: ignore
+ if not result:
+ errors['base'] = 'invalid_auth'
- async def async_user_meta_for_credentials(self, credentials):
- """Return extra user metadata for credentials.
+ if not errors:
+ return await self.async_finish(self.user)
- Will be used to populate info when creating a new user.
+ return self.async_show_form(
+ step_id='mfa',
+ data_schema=auth_module.input_schema,
+ errors=errors,
+ )
- Values to populate:
- - name: string
- - is_active: boolean
- """
- return {}
+ async def async_finish(self, flow_result: Any) -> Dict:
+ """Handle the pass of login flow."""
+ return self.async_create_entry(
+ title=self._auth_provider.name,
+ data=flow_result
+ )
diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py
index 5a2355264ab..ce252497901 100644
--- a/homeassistant/auth/providers/homeassistant.py
+++ b/homeassistant/auth/providers/homeassistant.py
@@ -3,24 +3,24 @@ import base64
from collections import OrderedDict
import hashlib
import hmac
-from typing import Dict # noqa: F401 pylint: disable=unused-import
+from typing import Any, Dict, List, Optional, cast
import voluptuous as vol
-from homeassistant import data_entry_flow
from homeassistant.const import CONF_ID
-from homeassistant.core import callback
+from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
-from homeassistant.auth.util import generate_secret
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
+from ..models import Credentials, UserMeta
+from ..util import generate_secret
-from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
STORAGE_VERSION = 1
STORAGE_KEY = 'auth_provider.homeassistant'
-def _disallow_id(conf):
+def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]:
"""Disallow ID in config."""
if CONF_ID in conf:
raise vol.Invalid(
@@ -46,13 +46,13 @@ class InvalidUser(HomeAssistantError):
class Data:
"""Hold the user data."""
- def __init__(self, hass):
+ def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store."""
self.hass = hass
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
- self._data = None
+ self._data = None # type: Optional[Dict[str, Any]]
- async def async_load(self):
+ async def async_load(self) -> None:
"""Load stored data."""
data = await self._store.async_load()
@@ -65,9 +65,9 @@ class Data:
self._data = data
@property
- def users(self):
+ def users(self) -> List[Dict[str, str]]:
"""Return users."""
- return self._data['users']
+ return self._data['users'] # type: ignore
def validate_login(self, username: str, password: str) -> None:
"""Validate a username and password.
@@ -79,7 +79,7 @@ class Data:
found = None
# Compare all users to avoid timing attacks.
- for user in self._data['users']:
+ for user in self.users:
if username == user['username']:
found = user
@@ -94,8 +94,8 @@ class Data:
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)
+ salt = self._data['salt'].encode() # type: ignore
+ hashed = hashlib.pbkdf2_hmac('sha512', password.encode(), salt, 100000)
if for_storage:
hashed = base64.b64encode(hashed)
return hashed
@@ -137,7 +137,7 @@ class Data:
else:
raise InvalidUser
- async def async_save(self):
+ async def async_save(self) -> None:
"""Save data."""
await self._store.async_save(self._data)
@@ -150,7 +150,7 @@ class HassAuthProvider(AuthProvider):
data = None
- async def async_initialize(self):
+ async def async_initialize(self) -> None:
"""Initialize the auth provider."""
if self.data is not None:
return
@@ -158,19 +158,22 @@ class HassAuthProvider(AuthProvider):
self.data = Data(self.hass)
await self.data.async_load()
- async def async_credential_flow(self, context):
+ async def async_login_flow(
+ self, context: Optional[Dict]) -> LoginFlow:
"""Return a flow to login."""
- return LoginFlow(self)
+ return HassLoginFlow(self)
- async def async_validate_login(self, username: str, password: str):
- """Helper to validate a username and password."""
+ async def async_validate_login(self, username: str, password: str) -> None:
+ """Validate a username and password."""
if self.data is None:
await self.async_initialize()
+ assert self.data is not None
await self.hass.async_add_executor_job(
self.data.validate_login, username, password)
- async def async_get_or_create_credentials(self, flow_result):
+ async def async_get_or_create_credentials(
+ self, flow_result: Dict[str, str]) -> Credentials:
"""Get credentials based on the flow result."""
username = flow_result['username']
@@ -183,17 +186,17 @@ class HassAuthProvider(AuthProvider):
'username': username
})
- async def async_user_meta_for_credentials(self, credentials):
+ async def async_user_meta_for_credentials(
+ self, credentials: Credentials) -> UserMeta:
"""Get extra info for this credential."""
- return {
- 'name': credentials.data['username'],
- 'is_active': True,
- }
+ return UserMeta(name=credentials.data['username'], is_active=True)
- async def async_will_remove_credentials(self, credentials):
+ async def async_will_remove_credentials(
+ self, credentials: Credentials) -> None:
"""When credentials get removed, also remove the auth."""
if self.data is None:
await self.async_initialize()
+ assert self.data is not None
try:
self.data.async_remove_auth(credentials.data['username'])
@@ -203,29 +206,26 @@ class HassAuthProvider(AuthProvider):
pass
-class LoginFlow(data_entry_flow.FlowHandler):
+class HassLoginFlow(LoginFlow):
"""Handler for the login flow."""
- def __init__(self, auth_provider):
- """Initialize the login flow."""
- self._auth_provider = auth_provider
-
- async def async_step_init(self, user_input=None):
+ async def async_step_init(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
"""Handle the step of the form."""
errors = {}
if user_input is not None:
try:
- await self._auth_provider.async_validate_login(
- user_input['username'], user_input['password'])
+ await cast(HassAuthProvider, self._auth_provider)\
+ .async_validate_login(user_input['username'],
+ user_input['password'])
except InvalidAuth:
errors['base'] = 'invalid_auth'
if not errors:
- return self.async_create_entry(
- title=self._auth_provider.name,
- data=user_input
- )
+ user_input.pop('password')
+ return await self.async_finish(user_input)
schema = OrderedDict() # type: Dict[str, type]
schema['username'] = str
diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py
index 96f824140ed..72e3dfe140a 100644
--- a/homeassistant/auth/providers/insecure_example.py
+++ b/homeassistant/auth/providers/insecure_example.py
@@ -1,14 +1,15 @@
"""Example auth provider."""
from collections import OrderedDict
import hmac
+from typing import Any, Dict, Optional, cast
import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError
-from homeassistant import data_entry_flow
from homeassistant.core import callback
-from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
+from ..models import Credentials, UserMeta
USER_SCHEMA = vol.Schema({
@@ -31,13 +32,13 @@ class InvalidAuthError(HomeAssistantError):
class ExampleAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
- async def async_credential_flow(self, context):
+ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow:
"""Return a flow to login."""
- return LoginFlow(self)
+ return ExampleLoginFlow(self)
@callback
- def async_validate_login(self, username, password):
- """Helper to validate a username and password."""
+ def async_validate_login(self, username: str, password: str) -> None:
+ """Validate a username and password."""
user = None
# Compare all users to avoid timing attacks.
@@ -56,7 +57,8 @@ class ExampleAuthProvider(AuthProvider):
password.encode('utf-8')):
raise InvalidAuthError
- async def async_get_or_create_credentials(self, flow_result):
+ async def async_get_or_create_credentials(
+ self, flow_result: Dict[str, str]) -> Credentials:
"""Get credentials based on the flow result."""
username = flow_result['username']
@@ -69,49 +71,45 @@ class ExampleAuthProvider(AuthProvider):
'username': username
})
- async def async_user_meta_for_credentials(self, credentials):
+ async def async_user_meta_for_credentials(
+ self, credentials: Credentials) -> UserMeta:
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
"""
username = credentials.data['username']
- info = {
- 'is_active': True,
- }
+ name = None
for user in self.config['users']:
if user['username'] == username:
- info['name'] = user.get('name')
+ name = user.get('name')
break
- return info
+ return UserMeta(name=name, is_active=True)
-class LoginFlow(data_entry_flow.FlowHandler):
+class ExampleLoginFlow(LoginFlow):
"""Handler for the login flow."""
- def __init__(self, auth_provider):
- """Initialize the login flow."""
- self._auth_provider = auth_provider
-
- async def async_step_init(self, user_input=None):
+ async def async_step_init(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
"""Handle the step of the form."""
errors = {}
if user_input is not None:
try:
- self._auth_provider.async_validate_login(
- user_input['username'], user_input['password'])
+ cast(ExampleAuthProvider, self._auth_provider)\
+ .async_validate_login(user_input['username'],
+ user_input['password'])
except InvalidAuthError:
errors['base'] = 'invalid_auth'
if not errors:
- return self.async_create_entry(
- title=self._auth_provider.name,
- data=user_input
- )
+ user_input.pop('password')
+ return await self.async_finish(user_input)
- schema = OrderedDict()
+ schema = OrderedDict() # type: Dict[str, type]
schema['username'] = str
schema['password'] = str
diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py
index f2f467e07ec..f631f8e73cf 100644
--- a/homeassistant/auth/providers/legacy_api_password.py
+++ b/homeassistant/auth/providers/legacy_api_password.py
@@ -3,16 +3,17 @@ Support Legacy API password auth provider.
It will be removed when auth system production ready
"""
-from collections import OrderedDict
import hmac
+from typing import Any, Dict, Optional, cast
import voluptuous as vol
-from homeassistant.exceptions import HomeAssistantError
-from homeassistant import data_entry_flow
+from homeassistant.components.http import HomeAssistantHTTP # noqa: F401
from homeassistant.core import callback
+from homeassistant.exceptions import HomeAssistantError
-from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
+from ..models import Credentials, UserMeta
USER_SCHEMA = vol.Schema({
@@ -36,25 +37,21 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
DEFAULT_TITLE = 'Legacy API Password'
- async def async_credential_flow(self, context):
+ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow:
"""Return a flow to login."""
- return LoginFlow(self)
+ return LegacyLoginFlow(self)
@callback
- def async_validate_login(self, password):
- """Helper to validate a username and password."""
- if not hasattr(self.hass, 'http'):
- raise ValueError('http component is not loaded')
+ def async_validate_login(self, password: str) -> None:
+ """Validate a username and password."""
+ hass_http = getattr(self.hass, 'http', None) # type: HomeAssistantHTTP
- if self.hass.http.api_password is None:
- raise ValueError('http component is not configured using'
- ' api_password')
-
- if not hmac.compare_digest(self.hass.http.api_password.encode('utf-8'),
+ if not hmac.compare_digest(hass_http.api_password.encode('utf-8'),
password.encode('utf-8')):
raise InvalidAuthError
- async def async_get_or_create_credentials(self, flow_result):
+ async def async_get_or_create_credentials(
+ self, flow_result: Dict[str, str]) -> Credentials:
"""Return LEGACY_USER always."""
for credential in await self.async_credentials():
if credential.data['username'] == LEGACY_USER:
@@ -64,47 +61,43 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
'username': LEGACY_USER
})
- async def async_user_meta_for_credentials(self, credentials):
+ async def async_user_meta_for_credentials(
+ self, credentials: Credentials) -> UserMeta:
"""
Set name as LEGACY_USER always.
Will be used to populate info when creating a new user.
"""
- return {
- 'name': LEGACY_USER,
- 'is_active': True,
- }
+ return UserMeta(name=LEGACY_USER, is_active=True)
-class LoginFlow(data_entry_flow.FlowHandler):
+class LegacyLoginFlow(LoginFlow):
"""Handler for the login flow."""
- def __init__(self, auth_provider):
- """Initialize the login flow."""
- self._auth_provider = auth_provider
-
- async def async_step_init(self, user_input=None):
+ async def async_step_init(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
"""Handle the step of the form."""
errors = {}
+ hass_http = getattr(self.hass, 'http', None)
+ if hass_http is None or not hass_http.api_password:
+ return self.async_abort(
+ reason='no_api_password_set'
+ )
+
if user_input is not None:
try:
- self._auth_provider.async_validate_login(
- user_input['password'])
+ cast(LegacyApiPasswordAuthProvider, self._auth_provider)\
+ .async_validate_login(user_input['password'])
except InvalidAuthError:
errors['base'] = 'invalid_auth'
if not errors:
- return self.async_create_entry(
- title=self._auth_provider.name,
- data={}
- )
-
- schema = OrderedDict()
- schema['password'] = str
+ return await self.async_finish({})
return self.async_show_form(
step_id='init',
- data_schema=vol.Schema(schema),
+ data_schema=vol.Schema({'password': str}),
errors=errors,
)
diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py
index 7a4b0126505..37e032e58d7 100644
--- a/homeassistant/auth/providers/trusted_networks.py
+++ b/homeassistant/auth/providers/trusted_networks.py
@@ -3,12 +3,16 @@
It shows list of users if access from trusted network.
Abort login flow if not access from trusted network.
"""
+from typing import Any, Dict, Optional, cast
+
import voluptuous as vol
-from homeassistant import data_entry_flow
+from homeassistant.components.http import HomeAssistantHTTP # noqa: F401
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
-from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
+
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
+from ..models import Credentials, UserMeta
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
@@ -31,16 +35,24 @@ class TrustedNetworksAuthProvider(AuthProvider):
DEFAULT_TITLE = 'Trusted Networks'
- async def async_credential_flow(self, context):
+ @property
+ def support_mfa(self) -> bool:
+ """Trusted Networks auth provider does not support MFA."""
+ return False
+
+ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow:
"""Return a flow to login."""
+ assert context is not None
users = await self.store.async_get_users()
available_users = {user.id: user.name
for user in users
if not user.system_generated and user.is_active}
- return LoginFlow(self, context.get('ip_address'), available_users)
+ return TrustedNetworksLoginFlow(
+ self, cast(str, context.get('ip_address')), available_users)
- async def async_get_or_create_credentials(self, flow_result):
+ async def async_get_or_create_credentials(
+ self, flow_result: Dict[str, str]) -> Credentials:
"""Get credentials based on the flow result."""
user_id = flow_result['user']
@@ -59,7 +71,8 @@ class TrustedNetworksAuthProvider(AuthProvider):
# We only allow login as exist user
raise InvalidUserError
- async def async_user_meta_for_credentials(self, credentials):
+ async def async_user_meta_for_credentials(
+ self, credentials: Credentials) -> UserMeta:
"""Return extra user metadata for credentials.
Trusted network auth provider should never create new user.
@@ -67,35 +80,41 @@ class TrustedNetworksAuthProvider(AuthProvider):
raise NotImplementedError
@callback
- def async_validate_access(self, ip_address):
+ def async_validate_access(self, ip_address: str) -> None:
"""Make sure the access from trusted networks.
Raise InvalidAuthError if not.
- Raise InvalidAuthError if trusted_networks is not config
+ Raise InvalidAuthError if trusted_networks is not configured.
"""
- if (not hasattr(self.hass, 'http') or
- not self.hass.http or not self.hass.http.trusted_networks):
+ hass_http = getattr(self.hass, 'http', None) # type: HomeAssistantHTTP
+
+ if not hass_http or not hass_http.trusted_networks:
raise InvalidAuthError('trusted_networks is not configured')
if not any(ip_address in trusted_network for trusted_network
- in self.hass.http.trusted_networks):
+ in hass_http.trusted_networks):
raise InvalidAuthError('Not in trusted_networks')
-class LoginFlow(data_entry_flow.FlowHandler):
+class TrustedNetworksLoginFlow(LoginFlow):
"""Handler for the login flow."""
- def __init__(self, auth_provider, ip_address, available_users):
+ def __init__(self, auth_provider: TrustedNetworksAuthProvider,
+ ip_address: str, available_users: Dict[str, Optional[str]]) \
+ -> None:
"""Initialize the login flow."""
- self._auth_provider = auth_provider
+ super().__init__(auth_provider)
self._available_users = available_users
self._ip_address = ip_address
- async def async_step_init(self, user_input=None):
+ async def async_step_init(
+ self, user_input: Optional[Dict[str, str]] = None) \
+ -> Dict[str, Any]:
"""Handle the step of the form."""
errors = {}
try:
- self._auth_provider.async_validate_access(self._ip_address)
+ cast(TrustedNetworksAuthProvider, self._auth_provider)\
+ .async_validate_access(self._ip_address)
except InvalidAuthError:
errors['base'] = 'invalid_auth'
@@ -111,10 +130,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
errors['base'] = 'invalid_auth'
if not errors:
- return self.async_create_entry(
- title=self._auth_provider.name,
- data=user_input
- )
+ return await self.async_finish(user_input)
schema = {'user': vol.In(self._available_users)}
diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py
index 43c7168dd2e..41fa61964de 100644
--- a/homeassistant/bootstrap.py
+++ b/homeassistant/bootstrap.py
@@ -87,9 +87,11 @@ async def async_from_config_dict(config: Dict[str, Any],
log_no_color)
core_config = config.get(core.DOMAIN, {})
+ has_api_password = bool((config.get('http') or {}).get('api_password'))
try:
- await conf_util.async_process_ha_core_config(hass, core_config)
+ await conf_util.async_process_ha_core_config(
+ hass, core_config, has_api_password)
except vol.Invalid as ex:
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
return None
@@ -307,7 +309,7 @@ def async_enable_logging(hass: core.HomeAssistant,
hass.data[DATA_LOGGING] = err_log_path
else:
_LOGGER.error(
- "Unable to setup error log %s (access denied)", err_log_path)
+ "Unable to set up error log %s (access denied)", err_log_path)
async def async_mount_local_lib_path(config_dir: str) -> str:
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index f9f2a4e03be..63977ed88c7 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -141,7 +141,7 @@ def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/alarm_control_panel/homematicip_cloud.py b/homeassistant/components/alarm_control_panel/homematicip_cloud.py
index 79f872951db..9f5b52a31f4 100644
--- a/homeassistant/components/alarm_control_panel/homematicip_cloud.py
+++ b/homeassistant/components/alarm_control_panel/homematicip_cloud.py
@@ -1,32 +1,31 @@
"""
-Support for HomematicIP alarm control panel.
+Support for HomematicIP Cloud alarm control panel.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.homematicip_cloud/
"""
import logging
+from homeassistant.components.alarm_control_panel import AlarmControlPanel
+from homeassistant.components.homematicip_cloud import (
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED)
-from homeassistant.components.alarm_control_panel import AlarmControlPanel
-from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
-
-
-DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
+DEPENDENCIES = ['homematicip_cloud']
+
HMIP_ZONE_AWAY = 'EXTERNAL'
HMIP_ZONE_HOME = 'INTERNAL'
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Set up the HomematicIP alarm control devices."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Set up the HomematicIP Cloud alarm control devices."""
pass
@@ -45,7 +44,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
- """Representation of an HomematicIP security zone group."""
+ """Representation of an HomematicIP Cloud security zone group."""
def __init__(self, home, device):
"""Initialize the security zone group."""
diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py
index b400a927b5e..47b274875fc 100644
--- a/homeassistant/components/alarm_control_panel/simplisafe.py
+++ b/homeassistant/components/alarm_control_panel/simplisafe.py
@@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
simplisafe = SimpliSafeApiInterface(username, password)
except SimpliSafeAPIException:
- _LOGGER.error("Failed to setup SimpliSafe")
+ _LOGGER.error("Failed to set up SimpliSafe")
return
systems = []
diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py
index 80a02b3275d..3ec01fc6ab8 100644
--- a/homeassistant/components/alert.py
+++ b/homeassistant/components/alert.py
@@ -111,6 +111,7 @@ def async_setup(hass, config):
for alert_id in alert_ids:
alert = all_alerts[alert_id]
+ alert.async_set_context(service_call.context)
if service_call.service == SERVICE_TURN_ON:
yield from alert.async_turn_on()
elif service_call.service == SERVICE_TOGGLE:
diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py
index 042d878fceb..eab725c4653 100644
--- a/homeassistant/components/alexa/smart_home.py
+++ b/homeassistant/components/alexa/smart_home.py
@@ -13,12 +13,13 @@ import homeassistant.util.color as color_util
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.util.decorator import Registry
from homeassistant.const import (
- ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, CONF_NAME,
- SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
- SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
+ ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
+ ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK,
+ SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
+ SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
- CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
+ STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
@@ -53,6 +54,7 @@ CONF_DISPLAY_CATEGORIES = 'display_categories'
HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
+EVENT_ALEXA_SMART_HOME = 'alexa_smart_home'
class _DisplayCategory:
@@ -159,7 +161,8 @@ class _AlexaEntity:
The API handlers should manipulate entities only through this interface.
"""
- def __init__(self, config, entity):
+ def __init__(self, hass, config, entity):
+ self.hass = hass
self.config = config
self.entity = entity
self.entity_conf = config.entity_config.get(entity.entity_id, {})
@@ -383,6 +386,10 @@ class _AlexaInputController(_AlexaInterface):
class _AlexaTemperatureSensor(_AlexaInterface):
+ def __init__(self, hass, entity):
+ _AlexaInterface.__init__(self, entity)
+ self.hass = hass
+
def name(self):
return 'Alexa.TemperatureSensor'
@@ -396,9 +403,10 @@ class _AlexaTemperatureSensor(_AlexaInterface):
if name != 'temperature':
raise _UnsupportedProperty(name)
- unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
+ unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
temp = self.entity.state
if self.entity.domain == climate.DOMAIN:
+ unit = self.hass.config.units.temperature_unit
temp = self.entity.attributes.get(
climate.ATTR_CURRENT_TEMPERATURE)
return {
@@ -408,6 +416,10 @@ class _AlexaTemperatureSensor(_AlexaInterface):
class _AlexaThermostatController(_AlexaInterface):
+ def __init__(self, hass, entity):
+ _AlexaInterface.__init__(self, entity)
+ self.hass = hass
+
def name(self):
return 'Alexa.ThermostatController'
@@ -438,8 +450,7 @@ class _AlexaThermostatController(_AlexaInterface):
raise _UnsupportedProperty(name)
return mode
- unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
- temp = None
+ unit = self.hass.config.units.temperature_unit
if name == 'targetSetpoint':
temp = self.entity.attributes.get(climate.ATTR_TEMPERATURE)
elif name == 'lowerSetpoint':
@@ -490,8 +501,8 @@ class _ClimateCapabilities(_AlexaEntity):
return [_DisplayCategory.THERMOSTAT]
def interfaces(self):
- yield _AlexaThermostatController(self.entity)
- yield _AlexaTemperatureSensor(self.entity)
+ yield _AlexaThermostatController(self.hass, self.entity)
+ yield _AlexaTemperatureSensor(self.hass, self.entity)
@ENTITY_ADAPTERS.register(cover.DOMAIN)
@@ -608,11 +619,11 @@ class _SensorCapabilities(_AlexaEntity):
def interfaces(self):
attrs = self.entity.attributes
- if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
+ if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
- yield _AlexaTemperatureSensor(self.entity)
+ yield _AlexaTemperatureSensor(self.hass, self.entity)
class _Cause:
@@ -703,24 +714,47 @@ class SmartHomeView(http.HomeAssistantView):
return b'' if response is None else self.json(response)
-@asyncio.coroutine
-def async_handle_message(hass, config, message):
+async def async_handle_message(hass, config, request, context=None):
"""Handle incoming API messages."""
- assert message[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3'
+ assert request[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3'
+
+ if context is None:
+ context = ha.Context()
# Read head data
- message = message[API_DIRECTIVE]
- namespace = message[API_HEADER]['namespace']
- name = message[API_HEADER]['name']
+ request = request[API_DIRECTIVE]
+ namespace = request[API_HEADER]['namespace']
+ name = request[API_HEADER]['name']
# Do we support this API request?
funct_ref = HANDLERS.get((namespace, name))
- if not funct_ref:
+ if funct_ref:
+ response = await funct_ref(hass, config, request, context)
+ else:
_LOGGER.warning(
"Unsupported API request %s/%s", namespace, name)
- return api_error(message)
+ response = api_error(request)
- return (yield from funct_ref(hass, config, message))
+ request_info = {
+ 'namespace': namespace,
+ 'name': name,
+ }
+
+ if API_ENDPOINT in request and 'endpointId' in request[API_ENDPOINT]:
+ request_info['entity_id'] = \
+ request[API_ENDPOINT]['endpointId'].replace('#', '.')
+
+ response_header = response[API_EVENT][API_HEADER]
+
+ hass.bus.async_fire(EVENT_ALEXA_SMART_HOME, {
+ 'request': request_info,
+ 'response': {
+ 'namespace': response_header['namespace'],
+ 'name': response_header['name'],
+ }
+ }, context=context)
+
+ return response
def api_message(request,
@@ -784,8 +818,7 @@ def api_error(request,
@HANDLERS.register(('Alexa.Discovery', 'Discover'))
-@asyncio.coroutine
-def async_api_discovery(hass, config, request):
+async def async_api_discovery(hass, config, request, context):
"""Create a API formatted discovery response.
Async friendly.
@@ -800,7 +833,7 @@ def async_api_discovery(hass, config, request):
if entity.domain not in ENTITY_ADAPTERS:
continue
- alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
+ alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity)
endpoint = {
'displayCategories': alexa_entity.display_categories(),
@@ -827,8 +860,7 @@ def async_api_discovery(hass, config, request):
def extract_entity(funct):
"""Decorate for extract entity object from request."""
- @asyncio.coroutine
- def async_api_entity_wrapper(hass, config, request):
+ async def async_api_entity_wrapper(hass, config, request, context):
"""Process a turn on request."""
entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.')
@@ -839,15 +871,14 @@ def extract_entity(funct):
request[API_HEADER]['name'], entity_id)
return api_error(request, error_type='NO_SUCH_ENDPOINT')
- return (yield from funct(hass, config, request, entity))
+ return await funct(hass, config, request, context, entity)
return async_api_entity_wrapper
@HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
@extract_entity
-@asyncio.coroutine
-def async_api_turn_on(hass, config, request, entity):
+async def async_api_turn_on(hass, config, request, context, entity):
"""Process a turn on request."""
domain = entity.domain
if entity.domain == group.DOMAIN:
@@ -857,17 +888,16 @@ def async_api_turn_on(hass, config, request, entity):
if entity.domain == cover.DOMAIN:
service = cover.SERVICE_OPEN_COVER
- yield from hass.services.async_call(domain, service, {
+ await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PowerController', 'TurnOff'))
@extract_entity
-@asyncio.coroutine
-def async_api_turn_off(hass, config, request, entity):
+async def async_api_turn_off(hass, config, request, context, entity):
"""Process a turn off request."""
domain = entity.domain
if entity.domain == group.DOMAIN:
@@ -877,32 +907,30 @@ def async_api_turn_off(hass, config, request, entity):
if entity.domain == cover.DOMAIN:
service = cover.SERVICE_CLOSE_COVER
- yield from hass.services.async_call(domain, service, {
+ await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_brightness(hass, config, request, entity):
+async def async_api_set_brightness(hass, config, request, context, entity):
"""Process a set brightness request."""
brightness = int(request[API_PAYLOAD]['brightness'])
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness'))
@extract_entity
-@asyncio.coroutine
-def async_api_adjust_brightness(hass, config, request, entity):
+async def async_api_adjust_brightness(hass, config, request, context, entity):
"""Process an adjust brightness request."""
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
@@ -915,18 +943,17 @@ def async_api_adjust_brightness(hass, config, request, entity):
# set brightness
brightness = max(0, brightness_delta + current)
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.ColorController', 'SetColor'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_color(hass, config, request, entity):
+async def async_api_set_color(hass, config, request, context, entity):
"""Process a set color request."""
rgb = color_util.color_hsb_to_RGB(
float(request[API_PAYLOAD]['color']['hue']),
@@ -934,25 +961,25 @@ def async_api_set_color(hass, config, request, entity):
float(request[API_PAYLOAD]['color']['brightness'])
)
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_color_temperature(hass, config, request, entity):
+async def async_api_set_color_temperature(hass, config, request, context,
+ entity):
"""Process a set color temperature request."""
kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin'])
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_KELVIN: kelvin,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@@ -960,17 +987,17 @@ def async_api_set_color_temperature(hass, config, request, entity):
@HANDLERS.register(
('Alexa.ColorTemperatureController', 'DecreaseColorTemperature'))
@extract_entity
-@asyncio.coroutine
-def async_api_decrease_color_temp(hass, config, request, entity):
+async def async_api_decrease_color_temp(hass, config, request, context,
+ entity):
"""Process a decrease color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS))
value = min(max_mireds, current + 50)
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@@ -978,31 +1005,30 @@ def async_api_decrease_color_temp(hass, config, request, entity):
@HANDLERS.register(
('Alexa.ColorTemperatureController', 'IncreaseColorTemperature'))
@extract_entity
-@asyncio.coroutine
-def async_api_increase_color_temp(hass, config, request, entity):
+async def async_api_increase_color_temp(hass, config, request, context,
+ entity):
"""Process an increase color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
value = max(min_mireds, current - 50)
- yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.SceneController', 'Activate'))
@extract_entity
-@asyncio.coroutine
-def async_api_activate(hass, config, request, entity):
+async def async_api_activate(hass, config, request, context, entity):
"""Process an activate request."""
domain = entity.domain
- yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
+ await hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
@@ -1019,14 +1045,13 @@ def async_api_activate(hass, config, request, entity):
@HANDLERS.register(('Alexa.SceneController', 'Deactivate'))
@extract_entity
-@asyncio.coroutine
-def async_api_deactivate(hass, config, request, entity):
+async def async_api_deactivate(hass, config, request, context, entity):
"""Process a deactivate request."""
domain = entity.domain
- yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
+ await hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
@@ -1043,8 +1068,7 @@ def async_api_deactivate(hass, config, request, entity):
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_percentage(hass, config, request, entity):
+async def async_api_set_percentage(hass, config, request, context, entity):
"""Process a set percentage request."""
percentage = int(request[API_PAYLOAD]['percentage'])
service = None
@@ -1066,16 +1090,15 @@ def async_api_set_percentage(hass, config, request, entity):
service = SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = percentage
- yield from hass.services.async_call(
- entity.domain, service, data, blocking=False)
+ await hass.services.async_call(
+ entity.domain, service, data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage'))
@extract_entity
-@asyncio.coroutine
-def async_api_adjust_percentage(hass, config, request, entity):
+async def async_api_adjust_percentage(hass, config, request, context, entity):
"""Process an adjust percentage request."""
percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None
@@ -1114,20 +1137,19 @@ def async_api_adjust_percentage(hass, config, request, entity):
data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
- yield from hass.services.async_call(
- entity.domain, service, data, blocking=False)
+ await hass.services.async_call(
+ entity.domain, service, data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.LockController', 'Lock'))
@extract_entity
-@asyncio.coroutine
-def async_api_lock(hass, config, request, entity):
+async def async_api_lock(hass, config, request, context, entity):
"""Process a lock request."""
- yield from hass.services.async_call(entity.domain, SERVICE_LOCK, {
+ await hass.services.async_call(entity.domain, SERVICE_LOCK, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
# Alexa expects a lockState in the response, we don't know the actual
# lockState at this point but assume it is locked. It is reported
@@ -1144,20 +1166,18 @@ def async_api_lock(hass, config, request, entity):
# Not supported by Alexa yet
@HANDLERS.register(('Alexa.LockController', 'Unlock'))
@extract_entity
-@asyncio.coroutine
-def async_api_unlock(hass, config, request, entity):
+async def async_api_unlock(hass, config, request, context, entity):
"""Process an unlock request."""
- yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
+ await hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id
- }, blocking=False)
+ }, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'SetVolume'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_volume(hass, config, request, entity):
+async def async_api_set_volume(hass, config, request, context, entity):
"""Process a set volume request."""
volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2)
@@ -1166,17 +1186,16 @@ def async_api_set_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_VOLUME_SET,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.InputController', 'SelectInput'))
@extract_entity
-@asyncio.coroutine
-def async_api_select_input(hass, config, request, entity):
+async def async_api_select_input(hass, config, request, context, entity):
"""Process a set input request."""
media_input = request[API_PAYLOAD]['input']
@@ -1200,17 +1219,16 @@ def async_api_select_input(hass, config, request, entity):
media_player.ATTR_INPUT_SOURCE: media_input,
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, media_player.SERVICE_SELECT_SOURCE,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity
-@asyncio.coroutine
-def async_api_adjust_volume(hass, config, request, entity):
+async def async_api_adjust_volume(hass, config, request, context, entity):
"""Process an adjust volume request."""
volume_delta = int(request[API_PAYLOAD]['volume'])
@@ -1229,17 +1247,16 @@ def async_api_adjust_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_SET,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume'))
@extract_entity
-@asyncio.coroutine
-def async_api_adjust_volume_step(hass, config, request, entity):
+async def async_api_adjust_volume_step(hass, config, request, context, entity):
"""Process an adjust volume step request."""
# media_player volume up/down service does not support specifying steps
# each component handles it differently e.g. via config.
@@ -1252,13 +1269,13 @@ def async_api_adjust_volume_step(hass, config, request, entity):
}
if volume_step > 0:
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_UP,
- data, blocking=False)
+ data, blocking=False, context=context)
elif volume_step < 0:
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_DOWN,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@@ -1266,8 +1283,7 @@ def async_api_adjust_volume_step(hass, config, request, entity):
@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute'))
@HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity
-@asyncio.coroutine
-def async_api_set_mute(hass, config, request, entity):
+async def async_api_set_mute(hass, config, request, context, entity):
"""Process a set mute request."""
mute = bool(request[API_PAYLOAD]['mute'])
@@ -1276,98 +1292,94 @@ def async_api_set_mute(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_MUTE,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Play'))
@extract_entity
-@asyncio.coroutine
-def async_api_play(hass, config, request, entity):
+async def async_api_play(hass, config, request, context, entity):
"""Process a play request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PLAY,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Pause'))
@extract_entity
-@asyncio.coroutine
-def async_api_pause(hass, config, request, entity):
+async def async_api_pause(hass, config, request, context, entity):
"""Process a pause request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PAUSE,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Stop'))
@extract_entity
-@asyncio.coroutine
-def async_api_stop(hass, config, request, entity):
+async def async_api_stop(hass, config, request, context, entity):
"""Process a stop request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_MEDIA_STOP,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Next'))
@extract_entity
-@asyncio.coroutine
-def async_api_next(hass, config, request, entity):
+async def async_api_next(hass, config, request, context, entity):
"""Process a next request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_MEDIA_NEXT_TRACK,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Previous'))
@extract_entity
-@asyncio.coroutine
-def async_api_previous(hass, config, request, entity):
+async def async_api_previous(hass, config, request, context, entity):
"""Process a previous request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
- yield from hass.services.async_call(
+ await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK,
- data, blocking=False)
+ data, blocking=False, context=context)
return api_message(request)
-def api_error_temp_range(request, temp, min_temp, max_temp, unit):
+def api_error_temp_range(hass, request, temp, min_temp, max_temp):
"""Create temperature value out of range API error response.
Async friendly.
"""
+ unit = hass.config.units.temperature_unit
temp_range = {
'minimumValue': {
'value': min_temp,
@@ -1388,8 +1400,9 @@ def api_error_temp_range(request, temp, min_temp, max_temp, unit):
)
-def temperature_from_object(temp_obj, to_unit, interval=False):
+def temperature_from_object(hass, temp_obj, interval=False):
"""Get temperature from Temperature object in requested unit."""
+ to_unit = hass.config.units.temperature_unit
from_unit = TEMP_CELSIUS
temp = float(temp_obj['value'])
@@ -1405,9 +1418,8 @@ def temperature_from_object(temp_obj, to_unit, interval=False):
@HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature'))
@extract_entity
-async def async_api_set_target_temp(hass, config, request, entity):
+async def async_api_set_target_temp(hass, config, request, context, entity):
"""Process a set target temperature request."""
- unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP)
@@ -1417,48 +1429,45 @@ async def async_api_set_target_temp(hass, config, request, entity):
payload = request[API_PAYLOAD]
if 'targetSetpoint' in payload:
- temp = temperature_from_object(
- payload['targetSetpoint'], unit)
+ temp = temperature_from_object(hass, payload['targetSetpoint'])
if temp < min_temp or temp > max_temp:
return api_error_temp_range(
- request, temp, min_temp, max_temp, unit)
+ hass, request, temp, min_temp, max_temp)
data[ATTR_TEMPERATURE] = temp
if 'lowerSetpoint' in payload:
- temp_low = temperature_from_object(
- payload['lowerSetpoint'], unit)
+ temp_low = temperature_from_object(hass, payload['lowerSetpoint'])
if temp_low < min_temp or temp_low > max_temp:
return api_error_temp_range(
- request, temp_low, min_temp, max_temp, unit)
+ hass, request, temp_low, min_temp, max_temp)
data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
if 'upperSetpoint' in payload:
- temp_high = temperature_from_object(
- payload['upperSetpoint'], unit)
+ temp_high = temperature_from_object(hass, payload['upperSetpoint'])
if temp_high < min_temp or temp_high > max_temp:
return api_error_temp_range(
- request, temp_high, min_temp, max_temp, unit)
+ hass, request, temp_high, min_temp, max_temp)
data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
await hass.services.async_call(
- entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False)
+ entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False,
+ context=context)
return api_message(request)
@HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature'))
@extract_entity
-async def async_api_adjust_target_temp(hass, config, request, entity):
+async def async_api_adjust_target_temp(hass, config, request, context, entity):
"""Process an adjust target temperature request."""
- unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP)
temp_delta = temperature_from_object(
- request[API_PAYLOAD]['targetSetpointDelta'], unit, interval=True)
+ hass, request[API_PAYLOAD]['targetSetpointDelta'], interval=True)
target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta
if target_temp < min_temp or target_temp > max_temp:
return api_error_temp_range(
- request, target_temp, min_temp, max_temp, unit)
+ hass, request, target_temp, min_temp, max_temp)
data = {
ATTR_ENTITY_ID: entity.entity_id,
@@ -1466,14 +1475,16 @@ async def async_api_adjust_target_temp(hass, config, request, entity):
}
await hass.services.async_call(
- entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False)
+ entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False,
+ context=context)
return api_message(request)
@HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode'))
@extract_entity
-async def async_api_set_thermostat_mode(hass, config, request, entity):
+async def async_api_set_thermostat_mode(hass, config, request, context,
+ entity):
"""Process a set thermostat mode request."""
mode = request[API_PAYLOAD]['thermostatMode']
mode = mode if isinstance(mode, str) else mode['value']
@@ -1499,17 +1510,16 @@ async def async_api_set_thermostat_mode(hass, config, request, entity):
await hass.services.async_call(
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
- blocking=False)
+ blocking=False, context=context)
return api_message(request)
@HANDLERS.register(('Alexa', 'ReportState'))
@extract_entity
-@asyncio.coroutine
-def async_api_reportstate(hass, config, request, entity):
+async def async_api_reportstate(hass, config, request, context, entity):
"""Process a ReportState request."""
- alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
+ alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity)
properties = []
for interface in alexa_entity.interfaces():
properties.extend(interface.serialize_properties())
diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py
index de28eeff5ca..0fbb4de39f1 100644
--- a/homeassistant/components/api.py
+++ b/homeassistant/components/api.py
@@ -24,7 +24,7 @@ from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates
-import homeassistant.remote as rem
+from homeassistant.helpers.json import JSONEncoder
_LOGGER = logging.getLogger(__name__)
@@ -102,7 +102,7 @@ class APIEventStream(HomeAssistantView):
if event.event_type == EVENT_HOMEASSISTANT_STOP:
data = stop_obj
else:
- data = json.dumps(event, cls=rem.JSONEncoder)
+ data = json.dumps(event, cls=JSONEncoder)
await to_write.put(data)
diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py
index 102bfe58b55..4251b23e514 100644
--- a/homeassistant/components/auth/__init__.py
+++ b/homeassistant/components/auth/__init__.py
@@ -44,13 +44,26 @@ a limited expiration.
"expires_in": 1800,
"token_type": "Bearer"
}
+
+## Revoking a refresh token
+
+It is also possible to revoke a refresh token and all access tokens that have
+ever been granted by that refresh token. Response code will ALWAYS be 200.
+
+{
+ "token": "IJKLMNOPQRST",
+ "action": "revoke"
+}
+
"""
import logging
import uuid
from datetime import timedelta
+from aiohttp import web
import voluptuous as vol
+from homeassistant.auth.models import User, Credentials
from homeassistant.components import websocket_api
from homeassistant.components.http.ban import log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
@@ -68,37 +81,40 @@ SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_CURRENT_USER,
})
+RESULT_TYPE_CREDENTIALS = 'credentials'
+RESULT_TYPE_USER = 'user'
+
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass, config):
"""Component to allow users to login."""
- store_credentials, retrieve_credentials = _create_cred_store()
+ store_result, retrieve_result = _create_auth_code_store()
- hass.http.register_view(GrantTokenView(retrieve_credentials))
- hass.http.register_view(LinkUserView(retrieve_credentials))
+ hass.http.register_view(TokenView(retrieve_result))
+ hass.http.register_view(LinkUserView(retrieve_result))
hass.components.websocket_api.async_register_command(
WS_TYPE_CURRENT_USER, websocket_current_user,
SCHEMA_WS_CURRENT_USER
)
- await login_flow.async_setup(hass, store_credentials)
+ await login_flow.async_setup(hass, store_result)
return True
-class GrantTokenView(HomeAssistantView):
- """View to grant tokens."""
+class TokenView(HomeAssistantView):
+ """View to issue or revoke tokens."""
url = '/auth/token'
name = 'api:auth:token'
requires_auth = False
cors_allowed = True
- def __init__(self, retrieve_credentials):
- """Initialize the grant token view."""
- self._retrieve_credentials = retrieve_credentials
+ def __init__(self, retrieve_user):
+ """Initialize the token view."""
+ self._retrieve_user = retrieve_user
@log_invalid_auth
async def post(self, request):
@@ -108,6 +124,13 @@ class GrantTokenView(HomeAssistantView):
grant_type = data.get('grant_type')
+ # IndieAuth 6.3.5
+ # The revocation endpoint is the same as the token endpoint.
+ # The revocation request includes an additional parameter,
+ # action=revoke.
+ if data.get('action') == 'revoke':
+ return await self._async_handle_revoke_token(hass, data)
+
if grant_type == 'authorization_code':
return await self._async_handle_auth_code(hass, data)
@@ -118,6 +141,25 @@ class GrantTokenView(HomeAssistantView):
'error': 'unsupported_grant_type',
}, status_code=400)
+ async def _async_handle_revoke_token(self, hass, data):
+ """Handle revoke token request."""
+ # OAuth 2.0 Token Revocation [RFC7009]
+ # 2.2 The authorization server responds with HTTP status code 200
+ # if the token has been revoked successfully or if the client
+ # submitted an invalid token.
+ token = data.get('token')
+
+ if token is None:
+ return web.Response(status=200)
+
+ refresh_token = await hass.auth.async_get_refresh_token_by_token(token)
+
+ if refresh_token is None:
+ return web.Response(status=200)
+
+ await hass.auth.async_remove_refresh_token(refresh_token)
+ return web.Response(status=200)
+
async def _async_handle_auth_code(self, hass, data):
"""Handle authorization code request."""
client_id = data.get('client_id')
@@ -132,17 +174,19 @@ class GrantTokenView(HomeAssistantView):
if code is None:
return self.json({
'error': 'invalid_request',
+ 'error_description': 'Invalid code',
}, status_code=400)
- credentials = self._retrieve_credentials(client_id, code)
+ user = self._retrieve_user(client_id, RESULT_TYPE_USER, code)
- if credentials is None:
+ if user is None or not isinstance(user, User):
return self.json({
'error': 'invalid_request',
'error_description': 'Invalid code',
}, status_code=400)
- user = await hass.auth.async_get_or_create_user(credentials)
+ # refresh user
+ user = await hass.auth.async_get_user(user.id)
if not user.is_active:
return self.json({
@@ -220,7 +264,7 @@ class LinkUserView(HomeAssistantView):
user = request['hass_user']
credentials = self._retrieve_credentials(
- data['client_id'], data['code'])
+ data['client_id'], RESULT_TYPE_CREDENTIALS, data['code'])
if credentials is None:
return self.json_message('Invalid code', status_code=400)
@@ -230,37 +274,45 @@ class LinkUserView(HomeAssistantView):
@callback
-def _create_cred_store():
- """Create a credential store."""
- temp_credentials = {}
+def _create_auth_code_store():
+ """Create an in memory store."""
+ temp_results = {}
@callback
- def store_credentials(client_id, credentials):
- """Store credentials and return a code to retrieve it."""
+ def store_result(client_id, result):
+ """Store flow result and return a code to retrieve it."""
+ if isinstance(result, User):
+ result_type = RESULT_TYPE_USER
+ elif isinstance(result, Credentials):
+ result_type = RESULT_TYPE_CREDENTIALS
+ else:
+ raise ValueError('result has to be either User or Credentials')
+
code = uuid.uuid4().hex
- temp_credentials[(client_id, code)] = (dt_util.utcnow(), credentials)
+ temp_results[(client_id, result_type, code)] = \
+ (dt_util.utcnow(), result_type, result)
return code
@callback
- def retrieve_credentials(client_id, code):
- """Retrieve credentials."""
- key = (client_id, code)
+ def retrieve_result(client_id, result_type, code):
+ """Retrieve flow result."""
+ key = (client_id, result_type, code)
- if key not in temp_credentials:
+ if key not in temp_results:
return None
- created, credentials = temp_credentials.pop(key)
+ created, _, result = temp_results.pop(key)
# OAuth 4.2.1
# The authorization code MUST expire shortly after it is issued to
# mitigate the risk of leaks. A maximum authorization code lifetime of
# 10 minutes is RECOMMENDED.
if dt_util.utcnow() - created < timedelta(minutes=10):
- return credentials
+ return result
return None
- return store_credentials, retrieve_credentials
+ return store_result, retrieve_result
@callback
diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py
index 48f7ab06ab4..bcf73258ffa 100644
--- a/homeassistant/components/auth/indieauth.py
+++ b/homeassistant/components/auth/indieauth.py
@@ -4,6 +4,7 @@ from html.parser import HTMLParser
from ipaddress import ip_address, ip_network
from urllib.parse import urlparse, urljoin
+import aiohttp
from aiohttp.client_exceptions import ClientError
# IP addresses of loopback interfaces
@@ -76,18 +77,17 @@ async def fetch_redirect_uris(hass, url):
We do not implement extracting redirect uris from headers.
"""
- session = hass.helpers.aiohttp_client.async_get_clientsession()
parser = LinkTagParser('redirect_uri')
chunks = 0
try:
- resp = await session.get(url, timeout=5)
+ async with aiohttp.ClientSession() as session:
+ async with session.get(url, timeout=5) as resp:
+ async for data in resp.content.iter_chunked(1024):
+ parser.feed(data.decode())
+ chunks += 1
- async for data in resp.content.iter_chunked(1024):
- parser.feed(data.decode())
- chunks += 1
-
- if chunks == 10:
- break
+ if chunks == 10:
+ break
except (asyncio.TimeoutError, ClientError):
pass
diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py
index e1d21bbb632..a518bdde415 100644
--- a/homeassistant/components/auth/login_flow.py
+++ b/homeassistant/components/auth/login_flow.py
@@ -22,10 +22,14 @@ 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.
+And optional parameter 'type' has to set as 'link_user' if login flow used for
+link credential to exist user. Default 'type' is 'authorize'.
+
{
"client_id": "https://hassbian.local:8123/",
"handler": ["local_provider", null],
- "redirect_url": "https://hassbian.local:8123/"
+ "redirect_url": "https://hassbian.local:8123/",
+ "type': "authorize"
}
Return value will be a step in a data entry flow. See the docs for data entry
@@ -49,6 +53,9 @@ flow for details.
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.
+The authorization code associated with an authorized user by default, it will
+associate with an credential if "type" set to "link_user" in
+"/auth/login_flow"
{
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
@@ -71,12 +78,12 @@ from homeassistant.components.http.view import HomeAssistantView
from . import indieauth
-async def async_setup(hass, store_credentials):
+async def async_setup(hass, store_result):
"""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))
+ LoginFlowResourceView(hass.auth.login_flow, store_result))
class AuthProvidersView(HomeAssistantView):
@@ -138,6 +145,7 @@ class LoginFlowIndexView(HomeAssistantView):
vol.Required('client_id'): str,
vol.Required('handler'): vol.Any(str, list),
vol.Required('redirect_uri'): str,
+ vol.Optional('type', default='authorize'): str,
}))
@log_invalid_auth
async def post(self, request, data):
@@ -153,7 +161,10 @@ class LoginFlowIndexView(HomeAssistantView):
try:
result = await self._flow_mgr.async_init(
- handler, context={'ip_address': request[KEY_REAL_IP]})
+ handler, context={
+ 'ip_address': request[KEY_REAL_IP],
+ 'credential_only': data.get('type') == 'link_user',
+ })
except data_entry_flow.UnknownHandler:
return self.json_message('Invalid handler specified', 404)
except data_entry_flow.UnknownStep:
@@ -169,10 +180,10 @@ class LoginFlowResourceView(HomeAssistantView):
name = 'api:auth:login_flow:resource'
requires_auth = False
- def __init__(self, flow_mgr, store_credentials):
+ def __init__(self, flow_mgr, store_result):
"""Initialize the login flow resource view."""
self._flow_mgr = flow_mgr
- self._store_credentials = store_credentials
+ self._store_result = store_result
async def get(self, request):
"""Do not allow getting status of a flow in progress."""
@@ -212,7 +223,7 @@ class LoginFlowResourceView(HomeAssistantView):
return self.json(_prepare_result_json(result))
result.pop('data')
- result['result'] = self._store_credentials(client_id, result['result'])
+ result['result'] = self._store_result(client_id, result['result'])
return self.json(result)
diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py
index 8b1cd3cad84..c6c0af90d15 100644
--- a/homeassistant/components/automation/__init__.py
+++ b/homeassistant/components/automation/__init__.py
@@ -1,5 +1,5 @@
"""
-Allow to setup simple automation rules via the config file.
+Allow to set up simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py
index 26878044fe2..7b2da21ff6a 100644
--- a/homeassistant/components/binary_sensor/__init__.py
+++ b/homeassistant/components/binary_sensor/__init__.py
@@ -58,7 +58,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/binary_sensor/bmw_connected_drive.py b/homeassistant/components/binary_sensor/bmw_connected_drive.py
index 308298d1bcd..d19deec4884 100644
--- a/homeassistant/components/binary_sensor/bmw_connected_drive.py
+++ b/homeassistant/components/binary_sensor/bmw_connected_drive.py
@@ -71,7 +71,10 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
@property
def should_poll(self) -> bool:
- """Data update is triggered from BMWConnectedDriveEntity."""
+ """Return False.
+
+ Data update is triggered from BMWConnectedDriveEntity.
+ """
return False
@property
diff --git a/homeassistant/components/binary_sensor/egardia.py b/homeassistant/components/binary_sensor/egardia.py
index 76d90e78376..7d443dfafdf 100644
--- a/homeassistant/components/binary_sensor/egardia.py
+++ b/homeassistant/components/binary_sensor/egardia.py
@@ -58,7 +58,7 @@ class EgardiaBinarySensor(BinarySensorDevice):
@property
def name(self):
- """The name of the device."""
+ """Return the name of the device."""
return self._name
@property
@@ -74,5 +74,5 @@ class EgardiaBinarySensor(BinarySensorDevice):
@property
def device_class(self):
- """The device class."""
+ """Return the device class."""
return self._device_class
diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py
index de6ad8223d7..78e8f9a973e 100644
--- a/homeassistant/components/binary_sensor/hikvision.py
+++ b/homeassistant/components/binary_sensor/hikvision.py
@@ -90,7 +90,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data = HikvisionData(hass, url, port, name, username, password)
if data.sensors is None:
- _LOGGER.error("Hikvision event stream has no data, unable to setup")
+ _LOGGER.error("Hikvision event stream has no data, unable to set up")
return False
entities = []
diff --git a/homeassistant/components/binary_sensor/homematic.py b/homeassistant/components/binary_sensor/homematic.py
index d85c10f9a34..a0ec359e048 100644
--- a/homeassistant/components/binary_sensor/homematic.py
+++ b/homeassistant/components/binary_sensor/homematic.py
@@ -5,27 +5,28 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematic/
"""
import logging
-from homeassistant.const import STATE_UNKNOWN
+
from homeassistant.components.binary_sensor import BinarySensorDevice
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
+from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
- 'Remote': None,
- 'ShutterContact': 'opening',
- 'MaxShutterContact': 'opening',
'IPShutterContact': 'opening',
- 'Smoke': 'smoke',
- 'SmokeV2': 'smoke',
+ 'MaxShutterContact': 'opening',
'Motion': 'motion',
'MotionV2': 'motion',
- 'RemoteMotion': None,
- 'WeatherSensor': None,
- 'TiltSensor': None,
'PresenceIP': 'motion',
+ 'Remote': None,
+ 'RemoteMotion': None,
+ 'ShutterContact': 'opening',
+ 'Smoke': 'smoke',
+ 'SmokeV2': 'smoke',
+ 'TiltSensor': None,
+ 'WeatherSensor': None,
}
diff --git a/homeassistant/components/binary_sensor/homematicip_cloud.py b/homeassistant/components/binary_sensor/homematicip_cloud.py
index 962817827f0..72dc2c8b462 100644
--- a/homeassistant/components/binary_sensor/homematicip_cloud.py
+++ b/homeassistant/components/binary_sensor/homematicip_cloud.py
@@ -1,16 +1,15 @@
"""
-Support for HomematicIP binary sensor.
+Support for HomematicIP Cloud binary sensor.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematicip_cloud/
"""
-
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
DEPENDENCIES = ['homematicip_cloud']
@@ -19,14 +18,14 @@ _LOGGER = logging.getLogger(__name__)
STATE_SMOKE_OFF = 'IDLE_OFF'
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Set up the binary sensor devices."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Set up the HomematicIP Cloud binary sensor devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
- """Set up the HomematicIP binary sensor from a config entry."""
+ """Set up the HomematicIP Cloud binary sensor from a config entry."""
from homematicip.aio.device import (
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector)
@@ -45,7 +44,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
- """HomematicIP shutter contact."""
+ """Representation of a HomematicIP Cloud shutter contact."""
@property
def device_class(self):
@@ -65,7 +64,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
- """HomematicIP motion detector."""
+ """Representation of a HomematicIP Cloud motion detector."""
@property
def device_class(self):
@@ -81,7 +80,7 @@ class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
- """HomematicIP smoke detector."""
+ """Representation of a HomematicIP Cloud smoke detector."""
@property
def device_class(self):
diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon.py
similarity index 76%
rename from homeassistant/components/binary_sensor/insteon_plm.py
rename to homeassistant/components/binary_sensor/insteon.py
index 25fc3fb5d73..789ad894a41 100644
--- a/homeassistant/components/binary_sensor/insteon_plm.py
+++ b/homeassistant/components/binary_sensor/insteon.py
@@ -2,15 +2,15 @@
Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.insteon_plm/
+https://home-assistant.io/components/binary_sensor.insteon/
"""
import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
-from homeassistant.components.insteon_plm import InsteonPLMEntity
+from homeassistant.components.insteon import InsteonEntity
-DEPENDENCIES = ['insteon_plm']
+DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
@@ -24,27 +24,27 @@ SENSOR_TYPES = {'openClosedSensor': 'opening',
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
- """Set up the INSTEON PLM device class for the hass platform."""
- plm = hass.data['insteon_plm'].get('plm')
+ """Set up the INSTEON device class for the hass platform."""
+ insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
- device = plm.devices[address]
+ device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
name = device.states[state_key].name
if name != 'dryLeakSensor':
_LOGGER.debug('Adding device %s entity %s to Binary Sensor platform',
device.address.hex, device.states[state_key].name)
- new_entity = InsteonPLMBinarySensor(device, state_key)
+ new_entity = InsteonBinarySensor(device, state_key)
async_add_devices([new_entity])
-class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
+class InsteonBinarySensor(InsteonEntity, BinarySensorDevice):
"""A Class for an Insteon device entity."""
def __init__(self, device, state_key):
- """Initialize the INSTEON PLM binary sensor."""
+ """Initialize the INSTEON binary sensor."""
super().__init__(device, state_key)
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name)
diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/binary_sensor/knx.py
index e6b28047cb8..76142dc0af2 100644
--- a/homeassistant/components/binary_sensor/knx.py
+++ b/homeassistant/components/binary_sensor/knx.py
@@ -105,7 +105,7 @@ class KNXBinarySensor(BinarySensorDevice):
def __init__(self, hass, device):
"""Initialize of KNX binary sensor."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
self.automations = []
@@ -116,12 +116,12 @@ class KNXBinarySensor(BinarySensorDevice):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -136,9 +136,9 @@ class KNXBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this sensor."""
- return self.device.device_class
+ return self._device.device_class
@property
def is_on(self):
"""Return true if the binary sensor is on."""
- return self.device.is_on()
+ return self._device.is_on()
diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py
index 31460c1eedc..03bc484983b 100644
--- a/homeassistant/components/binary_sensor/nest.py
+++ b/homeassistant/components/binary_sensor/nest.py
@@ -130,7 +130,7 @@ class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
def update(self):
"""Retrieve latest state."""
- value = getattr(self.device, self.variable)
+ value = getattr(self._device, self.variable)
if self.variable in STRUCTURE_BINARY_TYPES:
self._state = bool(STRUCTURE_BINARY_STATE_MAP
[self.variable].get(value))
@@ -154,4 +154,5 @@ class NestActivityZoneSensor(NestBinarySensor):
def update(self):
"""Retrieve latest state."""
- self._state = self.device.has_ongoing_motion_in_zone(self.zone.zone_id)
+ self._state = self._device.has_ongoing_motion_in_zone(
+ self.zone.zone_id)
diff --git a/homeassistant/components/binary_sensor/tellduslive.py b/homeassistant/components/binary_sensor/tellduslive.py
index e5d2d83fe47..6746d532780 100644
--- a/homeassistant/components/binary_sensor/tellduslive.py
+++ b/homeassistant/components/binary_sensor/tellduslive.py
@@ -31,4 +31,4 @@ class TelldusLiveSensor(TelldusLiveEntity, BinarySensorDevice):
@property
def is_on(self):
"""Return true if switch is on."""
- return self.device.is_on
+ return self._device.is_on
diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py
index 78f471d125b..7165a0dbdf3 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.15.0']
+REQUIREMENTS = ['numpy==1.15.1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/binary_sensor/xiaomi_aqara.py
index f53d07f2995..c1641c5f1ec 100644
--- a/homeassistant/components/binary_sensor/xiaomi_aqara.py
+++ b/homeassistant/components/binary_sensor/xiaomi_aqara.py
@@ -28,11 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
- if 'proto' not in device or int(device['proto'][0:1]) == 1:
- data_key = 'status'
- else:
- data_key = 'window_status'
- devices.append(XiaomiDoorSensor(device, data_key, gateway))
+ devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'sensor_wleak.aq1':
devices.append(XiaomiWaterLeakSensor(device, gateway))
elif model in ['smoke', 'sensor_smoke']:
@@ -44,17 +40,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
- data_key = 'channel_0'
+ data_key = 'button_0'
devices.append(XiaomiButton(device, 'Switch', data_key,
hass, gateway))
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
- devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
+ if 'proto' not in device or int(device['proto'][0:1]) == 1:
+ data_key = 'channel_0'
+ else:
+ data_key = 'button_0'
+ devices.append(XiaomiButton(device, 'Wall Switch', data_key,
hass, gateway))
elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']:
+ if 'proto' not in device or int(device['proto'][0:1]) == 1:
+ data_key_left = 'channel_0'
+ data_key_right = 'channel_1'
+ else:
+ data_key_left = 'button_0'
+ data_key_right = 'button_1'
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
- 'channel_0', hass, gateway))
+ data_key_left, hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
- 'channel_1', hass, gateway))
+ data_key_right, hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
elif model in ['cube', 'sensor_cube', 'sensor_cube.aqgl01']:
@@ -119,7 +125,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
if value is None:
return False
- if value == '1':
+ if value in ('1', '2'):
if self._state:
return False
self._state = True
@@ -194,9 +200,13 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor."""
- def __init__(self, device, data_key, xiaomi_hub):
+ def __init__(self, device, xiaomi_hub):
"""Initialize the XiaomiDoorSensor."""
self._open_since = 0
+ if 'proto' not in device or int(device['proto'][0:1]) == 1:
+ data_key = 'status'
+ else:
+ data_key = 'window_status'
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
xiaomi_hub, data_key, 'opening')
@@ -237,8 +247,12 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
def __init__(self, device, xiaomi_hub):
"""Initialize the XiaomiWaterLeakSensor."""
+ if 'proto' not in device or int(device['proto'][0:1]) == 1:
+ data_key = 'status'
+ else:
+ data_key = 'wleak_status'
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
- xiaomi_hub, 'status', 'moisture')
+ xiaomi_hub, data_key, 'moisture')
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
@@ -285,7 +299,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
if value is None:
return False
- if value == '1':
+ if value in ('1', '2'):
if self._state:
return False
self._state = True
@@ -359,6 +373,10 @@ class XiaomiCube(XiaomiBinarySensor):
self._hass = hass
self._last_action = None
self._state = False
+ if 'proto' not in device or int(device['proto'][0:1]) == 1:
+ self._data_key = 'status'
+ else:
+ self._data_key = 'cube_status'
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
None, None)
@@ -371,19 +389,12 @@ class XiaomiCube(XiaomiBinarySensor):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
- if 'status' in data:
+ if self._data_key in data:
self._hass.bus.fire('cube_action', {
'entity_id': self.entity_id,
- 'action_type': data['status']
+ 'action_type': data[self._data_key]
})
- self._last_action = data['status']
-
- if 'cube_status' in data:
- self._hass.bus.fire('cube_action', {
- 'entity_id': self.entity_id,
- 'action_type': data['cube_status']
- })
- self._last_action = data['cube_status']
+ self._last_action = data[self._data_key]
if 'rotate' in data:
self._hass.bus.fire('cube_action', {
diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py
index 061b09c1b3b..b0ad1a867a8 100644
--- a/homeassistant/components/bmw_connected_drive/__init__.py
+++ b/homeassistant/components/bmw_connected_drive/__init__.py
@@ -20,6 +20,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'bmw_connected_drive'
CONF_REGION = 'region'
+CONF_READ_ONLY = 'read_only'
ATTR_VIN = 'vin'
ACCOUNT_SCHEMA = vol.Schema({
@@ -27,6 +28,7 @@ ACCOUNT_SCHEMA = vol.Schema({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_REGION): vol.Any('north_america', 'china',
'rest_of_world'),
+ vol.Optional(CONF_READ_ONLY, default=False): cv.boolean,
})
CONFIG_SCHEMA = vol.Schema({
@@ -82,8 +84,10 @@ def setup_account(account_config: dict, hass, name: str) \
username = account_config[CONF_USERNAME]
password = account_config[CONF_PASSWORD]
region = account_config[CONF_REGION]
+ read_only = account_config[CONF_READ_ONLY]
_LOGGER.debug('Adding new account %s', name)
- cd_account = BMWConnectedDriveAccount(username, password, region, name)
+ cd_account = BMWConnectedDriveAccount(username, password, region, name,
+ read_only)
def execute_service(call):
"""Execute a service for a vehicle.
@@ -99,13 +103,13 @@ def setup_account(account_config: dict, hass, name: str) \
function_name = _SERVICE_MAP[call.service]
function_call = getattr(vehicle.remote_services, function_name)
function_call()
-
- # register the remote services
- for service in _SERVICE_MAP:
- hass.services.register(
- DOMAIN, service,
- execute_service,
- schema=SERVICE_SCHEMA)
+ if not read_only:
+ # register the remote services
+ for service in _SERVICE_MAP:
+ hass.services.register(
+ DOMAIN, service,
+ execute_service,
+ schema=SERVICE_SCHEMA)
# update every UPDATE_INTERVAL minutes, starting now
# this should even out the load on the servers
@@ -122,13 +126,14 @@ class BMWConnectedDriveAccount:
"""Representation of a BMW vehicle."""
def __init__(self, username: str, password: str, region_str: str,
- name: str) -> None:
+ name: str, read_only) -> None:
"""Constructor."""
from bimmer_connected.account import ConnectedDriveAccount
from bimmer_connected.country_selector import get_region_from_name
region = get_region_from_name(region_str)
+ self.read_only = read_only
self.account = ConnectedDriveAccount(username, password, region)
self.name = name
self._update_listeners = []
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index 6a15510cf54..76860702165 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -145,7 +145,7 @@ async def async_get_image(hass, entity_id, timeout=10):
component = hass.data.get(DOMAIN)
if component is None:
- raise HomeAssistantError('Camera component not setup')
+ raise HomeAssistantError('Camera component not set up')
camera = component.get_entity(entity_id)
@@ -214,7 +214,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py
index 911c14e7232..d1b5c7214fc 100644
--- a/homeassistant/components/camera/generic.py
+++ b/homeassistant/components/camera/generic.py
@@ -15,7 +15,7 @@ import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
- HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
+ HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (
PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera)
@@ -42,6 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string,
vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int,
+ vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
@@ -65,6 +66,7 @@ class GenericCamera(Camera):
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
self._frame_interval = 1 / device_info[CONF_FRAMERATE]
self.content_type = device_info[CONF_CONTENT_TYPE]
+ self.verify_ssl = device_info[CONF_VERIFY_SSL]
username = device_info.get(CONF_USERNAME)
password = device_info.get(CONF_PASSWORD)
@@ -108,7 +110,8 @@ class GenericCamera(Camera):
def fetch():
"""Read image from a URL."""
try:
- response = requests.get(url, timeout=10, auth=self._auth)
+ response = requests.get(url, timeout=10, auth=self._auth,
+ verify=self.verify_ssl)
return response.content
except requests.exceptions.RequestException as error:
_LOGGER.error("Error getting camera image: %s", error)
@@ -119,7 +122,8 @@ class GenericCamera(Camera):
# async
else:
try:
- websession = async_get_clientsession(self.hass)
+ websession = async_get_clientsession(
+ self.hass, verify_ssl=self.verify_ssl)
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get(
url, auth=self._auth)
diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py
index bf6700371fd..3f09bc9ee1c 100644
--- a/homeassistant/components/camera/nest.py
+++ b/homeassistant/components/camera/nest.py
@@ -46,7 +46,7 @@ class NestCamera(Camera):
"""Initialize a Nest Camera."""
super(NestCamera, self).__init__()
self.structure = structure
- self.device = device
+ self._device = device
self._location = None
self._name = None
self._online = None
@@ -93,7 +93,7 @@ class NestCamera(Camera):
# 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
+ self._device.is_streaming = False
def turn_on(self):
"""Turn on camera."""
@@ -105,15 +105,15 @@ class NestCamera(Camera):
# 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
+ self._device.is_streaming = True
def update(self):
"""Cache value from Python-nest."""
- self._location = self.device.where
- self._name = self.device.name
- self._online = self.device.online
- self._is_streaming = self.device.is_streaming
- self._is_video_history_enabled = self.device.is_video_history_enabled
+ self._location = self._device.where
+ self._name = self._device.name
+ self._online = self._device.online
+ self._is_streaming = self._device.is_streaming
+ self._is_video_history_enabled = self._device.is_video_history_enabled
if self._is_video_history_enabled:
# NestAware allowed 10/min
@@ -130,7 +130,7 @@ class NestCamera(Camera):
"""Return a still image response from the camera."""
now = utcnow()
if self._ready_for_snapshot(now):
- url = self.device.snapshot_url
+ url = self._device.snapshot_url
try:
response = requests.get(url)
diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py
index 32f8e15748d..251c44c146e 100644
--- a/homeassistant/components/camera/onvif.py
+++ b/homeassistant/components/camera/onvif.py
@@ -183,7 +183,7 @@ class ONVIFHassCamera(Camera):
_LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name)
async def async_added_to_hass(self):
- """Callback when entity is added to hass."""
+ """Handle entity addition to hass."""
if ONVIF_DATA not in self.hass.data:
self.hass.data[ONVIF_DATA] = {}
self.hass.data[ONVIF_DATA][ENTITIES] = []
diff --git a/homeassistant/components/camera/push.py b/homeassistant/components/camera/push.py
index def5c53dd3f..d75f59fb038 100644
--- a/homeassistant/components/camera/push.py
+++ b/homeassistant/components/camera/push.py
@@ -21,6 +21,8 @@ import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
+DEPENDENCIES = ['http']
+
CONF_BUFFER_SIZE = 'buffer'
CONF_IMAGE_FIELD = 'field'
@@ -111,7 +113,7 @@ class PushCamera(Camera):
@property
def state(self):
- """Current state of the camera."""
+ """Return current state of the camera."""
return self._state
async def update_image(self, image, filename):
diff --git a/homeassistant/components/cast/.translations/en.json b/homeassistant/components/cast/.translations/en.json
index 55d79a7d560..5222e4da3a1 100644
--- a/homeassistant/components/cast/.translations/en.json
+++ b/homeassistant/components/cast/.translations/en.json
@@ -6,10 +6,10 @@
},
"step": {
"confirm": {
- "description": "Do you want to setup Google Cast?",
+ "description": "Do you want to set up Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
-}
\ No newline at end of file
+}
diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json
index 7f480de0e8b..eecdecbfdf9 100644
--- a/homeassistant/components/cast/strings.json
+++ b/homeassistant/components/cast/strings.json
@@ -4,7 +4,7 @@
"step": {
"confirm": {
"title": "Google Cast",
- "description": "Do you want to setup Google Cast?"
+ "description": "Do you want to set up Google Cast?"
}
},
"abort": {
diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py
index 90abe2343d2..a3273f67cc2 100644
--- a/homeassistant/components/climate/__init__.py
+++ b/homeassistant/components/climate/__init__.py
@@ -294,7 +294,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
@@ -320,7 +320,7 @@ class ClimateDevice(Entity):
@property
def precision(self):
"""Return the precision of the system."""
- if self.unit_of_measurement == TEMP_CELSIUS:
+ if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
return PRECISION_TENTHS
return PRECISION_WHOLE
@@ -394,11 +394,6 @@ class ClimateDevice(Entity):
return data
- @property
- def unit_of_measurement(self):
- """Return the unit of measurement to display."""
- return self.hass.config.units.temperature_unit
-
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py
index 307c96bce32..9e5ff3bbf20 100644
--- a/homeassistant/components/climate/generic_thermostat.py
+++ b/homeassistant/components/climate/generic_thermostat.py
@@ -16,9 +16,8 @@ from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
- ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
- CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
- STATE_UNKNOWN)
+ STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID,
+ SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
@@ -297,11 +296,8 @@ class GenericThermostat(ClimateDevice):
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
- unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
-
try:
- self._cur_temp = self.hass.config.units.temperature(
- float(state.state), unit)
+ self._cur_temp = float(state.state)
except ValueError as ex:
_LOGGER.error("Unable to update from sensor: %s", ex)
diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py
index 12057e88647..116545afc15 100644
--- a/homeassistant/components/climate/heatmiser.py
+++ b/homeassistant/components/climate/heatmiser.py
@@ -58,7 +58,6 @@ class HeatmiserV3Thermostat(ClimateDevice):
def __init__(self, heatmiser, device, name, serport):
"""Initialize the thermostat."""
self.heatmiser = heatmiser
- self.device = device
self.serport = serport
self._current_temperature = None
self._name = name
diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py
index a2725f6f3aa..dd773bcd993 100644
--- a/homeassistant/components/climate/homematic.py
+++ b/homeassistant/components/climate/homematic.py
@@ -5,12 +5,13 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homematic/
"""
import logging
+
from homeassistant.components.climate import (
- ClimateDevice, STATE_AUTO, SUPPORT_TARGET_TEMPERATURE,
- SUPPORT_OPERATION_MODE)
+ STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
+ ClimateDevice)
from homeassistant.components.homematic import (
- HMDevice, ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT)
-from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
+ ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice)
+from homeassistant.const import ATTR_TEMPERATURE, STATE_UNKNOWN, TEMP_CELSIUS
DEPENDENCIES = ['homematic']
diff --git a/homeassistant/components/climate/homematicip_cloud.py b/homeassistant/components/climate/homematicip_cloud.py
index 8cf47159c10..6b47096df1b 100644
--- a/homeassistant/components/climate/homematicip_cloud.py
+++ b/homeassistant/components/climate/homematicip_cloud.py
@@ -1,19 +1,18 @@
"""
-Support for HomematicIP climate.
+Support for HomematicIP Cloud climate devices.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homematicip_cloud/
"""
-
import logging
from homeassistant.components.climate import (
- ClimateDevice, SUPPORT_TARGET_TEMPERATURE, ATTR_TEMPERATURE,
- STATE_AUTO, STATE_MANUAL)
-from homeassistant.const import TEMP_CELSIUS
+ ATTR_TEMPERATURE, STATE_AUTO, STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE,
+ ClimateDevice)
from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
+from homeassistant.const import TEMP_CELSIUS
_LOGGER = logging.getLogger(__name__)
@@ -27,9 +26,9 @@ HA_STATE_TO_HMIP = {
HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()}
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Set up the HomematicIP climate devices."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Set up the HomematicIP Cloud climate devices."""
pass
@@ -48,7 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
- """Representation of a MomematicIP heating group."""
+ """Representation of a HomematicIP heating group."""
def __init__(self, home, device):
"""Initialize heating group."""
diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py
index f53cf2491dc..a9d26288433 100644
--- a/homeassistant/components/climate/knx.py
+++ b/homeassistant/components/climate/knx.py
@@ -118,17 +118,15 @@ class KNXClimate(ClimateDevice):
def __init__(self, hass, device):
"""Initialize of a KNX climate device."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
- self._unit_of_measurement = TEMP_CELSIUS
-
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
- if self.device.supports_operation_mode:
+ if self._device.supports_operation_mode:
support |= SUPPORT_OPERATION_MODE
return support
@@ -137,12 +135,12 @@ class KNXClimate(ClimateDevice):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -157,46 +155,46 @@ class KNXClimate(ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
- return self._unit_of_measurement
+ return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
- return self.device.temperature.value
+ return self._device.temperature.value
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
- return self.device.setpoint_shift_step
+ return self._device.setpoint_shift_step
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
- return self.device.target_temperature.value
+ return self._device.target_temperature.value
@property
def min_temp(self):
"""Return the minimum temperature."""
- return self.device.target_temperature_min
+ return self._device.target_temperature_min
@property
def max_temp(self):
"""Return the maximum temperature."""
- return self.device.target_temperature_max
+ return self._device.target_temperature_max
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
- await self.device.set_target_temperature(temperature)
+ await self._device.set_target_temperature(temperature)
await self.async_update_ha_state()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
- if self.device.supports_operation_mode:
- return self.device.operation_mode.value
+ if self._device.supports_operation_mode:
+ return self._device.operation_mode.value
return None
@property
@@ -204,11 +202,11 @@ class KNXClimate(ClimateDevice):
"""Return the list of available operation modes."""
return [operation_mode.value for
operation_mode in
- self.device.get_supported_operation_modes()]
+ self._device.get_supported_operation_modes()]
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
- if self.device.supports_operation_mode:
+ if self._device.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(operation_mode)
- await self.device.set_operation_mode(knx_operation_mode)
+ await self._device.set_operation_mode(knx_operation_mode)
diff --git a/homeassistant/components/climate/maxcube.py b/homeassistant/components/climate/maxcube.py
index 712ebb4f4ce..204d5a3649c 100644
--- a/homeassistant/components/climate/maxcube.py
+++ b/homeassistant/components/climate/maxcube.py
@@ -45,7 +45,6 @@ class MaxCubeClimate(ClimateDevice):
def __init__(self, handler, name, rf_address):
"""Initialize MAX! Cube ClimateDevice."""
self._name = name
- self._unit_of_measurement = TEMP_CELSIUS
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
STATE_VACATION]
self._rf_address = rf_address
@@ -81,7 +80,7 @@ class MaxCubeClimate(ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
- return self._unit_of_measurement
+ return TEMP_CELSIUS
@property
def current_temperature(self):
diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py
index a0adc12bfbf..a4a8c76a39f 100644
--- a/homeassistant/components/climate/melissa.py
+++ b/homeassistant/components/climate/melissa.py
@@ -166,7 +166,7 @@ class MelissaClimate(ClimateDevice):
self.send({self._api.STATE: self._api.STATE_OFF})
def send(self, value):
- """Sending action to service."""
+ """Send action to service."""
try:
old_value = self._cur_settings.copy()
self._cur_settings.update(value)
diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py
index fa3943c3e27..04c598cbddb 100644
--- a/homeassistant/components/climate/nest.py
+++ b/homeassistant/components/climate/nest.py
@@ -57,7 +57,7 @@ class NestThermostat(ClimateDevice):
"""Initialize the thermostat."""
self._unit = temp_unit
self.structure = structure
- self.device = device
+ self._device = device
self._fan_list = [STATE_ON, STATE_AUTO]
# Set the default supported features
@@ -68,13 +68,13 @@ class NestThermostat(ClimateDevice):
self._operation_list = [STATE_OFF]
# Add supported nest thermostat features
- if self.device.can_heat:
+ if self._device.can_heat:
self._operation_list.append(STATE_HEAT)
- if self.device.can_cool:
+ if self._device.can_cool:
self._operation_list.append(STATE_COOL)
- if self.device.can_heat and self.device.can_cool:
+ if self._device.can_heat and self._device.can_cool:
self._operation_list.append(STATE_AUTO)
self._support_flags = (self._support_flags |
SUPPORT_TARGET_TEMPERATURE_HIGH |
@@ -83,7 +83,7 @@ class NestThermostat(ClimateDevice):
self._operation_list.append(STATE_ECO)
# feature of device
- self._has_fan = self.device.has_fan
+ self._has_fan = self._device.has_fan
if self._has_fan:
self._support_flags = (self._support_flags | SUPPORT_FAN_MODE)
@@ -124,8 +124,8 @@ class NestThermostat(ClimateDevice):
@property
def unique_id(self):
- """Unique ID for this device."""
- return self.device.serial
+ """Return unique ID for this device."""
+ return self._device.serial
@property
def name(self):
@@ -202,7 +202,7 @@ class NestThermostat(ClimateDevice):
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
try:
if temp is not None:
- self.device.target = temp
+ self._device.target = temp
except nest.nest.APIError as api_error:
_LOGGER.error("An error occurred while setting temperature: %s",
api_error)
@@ -220,7 +220,7 @@ class NestThermostat(ClimateDevice):
_LOGGER.error(
"An error occurred while setting device mode. "
"Invalid operation mode: %s", operation_mode)
- self.device.mode = device_mode
+ self._device.mode = device_mode
@property
def operation_list(self):
@@ -254,7 +254,7 @@ class NestThermostat(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
if self._has_fan:
- self.device.fan = fan_mode.lower()
+ self._device.fan = fan_mode.lower()
@property
def min_temp(self):
@@ -268,20 +268,20 @@ class NestThermostat(ClimateDevice):
def update(self):
"""Cache value from Python-nest."""
- self._location = self.device.where
- self._name = self.device.name
- self._humidity = self.device.humidity
- self._temperature = self.device.temperature
- self._mode = self.device.mode
- self._target_temperature = self.device.target
- self._fan = self.device.fan
+ self._location = self._device.where
+ self._name = self._device.name
+ self._humidity = self._device.humidity
+ self._temperature = self._device.temperature
+ self._mode = self._device.mode
+ self._target_temperature = self._device.target
+ self._fan = self._device.fan
self._away = self.structure.away == 'away'
- self._eco_temperature = self.device.eco_temperature
- self._locked_temperature = self.device.locked_temperature
- self._min_temperature = self.device.min_temperature
- self._max_temperature = self.device.max_temperature
- self._is_locked = self.device.is_locked
- if self.device.temperature_scale == 'C':
+ self._eco_temperature = self._device.eco_temperature
+ self._locked_temperature = self._device.locked_temperature
+ self._min_temperature = self._device.min_temperature
+ self._max_temperature = self._device.max_temperature
+ self._is_locked = self._device.is_locked
+ if self._device.temperature_scale == 'C':
self._temperature_scale = TEMP_CELSIUS
else:
self._temperature_scale = TEMP_FAHRENHEIT
diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py
index c8441a9f7af..d158eb52a9d 100644
--- a/homeassistant/components/climate/radiotherm.py
+++ b/homeassistant/components/climate/radiotherm.py
@@ -120,7 +120,7 @@ class RadioThermostat(ClimateDevice):
def __init__(self, device, hold_temp, away_temps):
"""Initialize the thermostat."""
- self.device = device
+ self._device = device
self._target_temperature = None
self._current_temperature = None
self._current_operation = STATE_IDLE
@@ -137,8 +137,8 @@ class RadioThermostat(ClimateDevice):
# Fan circulate mode is only supported by the CT80 models.
import radiotherm
- self._is_model_ct80 = isinstance(self.device,
- radiotherm.thermostat.CT80)
+ self._is_model_ct80 = isinstance(
+ self._device, radiotherm.thermostat.CT80)
@property
def supported_features(self):
@@ -194,7 +194,7 @@ class RadioThermostat(ClimateDevice):
"""Turn fan on/off."""
code = FAN_MODE_TO_CODE.get(fan_mode, None)
if code is not None:
- self.device.fmode = code
+ self._device.fmode = code
@property
def current_temperature(self):
@@ -234,15 +234,15 @@ class RadioThermostat(ClimateDevice):
# First time - get the name from the thermostat. This is
# normally set in the radio thermostat web app.
if self._name is None:
- self._name = self.device.name['raw']
+ self._name = self._device.name['raw']
# Request the current state from the thermostat.
- data = self.device.tstat['raw']
+ data = self._device.tstat['raw']
current_temp = data['temp']
if current_temp == -1:
_LOGGER.error('%s (%s) was busy (temp == -1)', self._name,
- self.device.host)
+ self._device.host)
return
# Map thermostat values into various STATE_ flags.
@@ -277,30 +277,30 @@ class RadioThermostat(ClimateDevice):
temperature = round_temp(temperature)
if self._current_operation == STATE_COOL:
- self.device.t_cool = temperature
+ self._device.t_cool = temperature
elif self._current_operation == STATE_HEAT:
- self.device.t_heat = temperature
+ self._device.t_heat = temperature
elif self._current_operation == STATE_AUTO:
if self._tstate == STATE_COOL:
- self.device.t_cool = temperature
+ self._device.t_cool = temperature
elif self._tstate == STATE_HEAT:
- self.device.t_heat = temperature
+ self._device.t_heat = temperature
# Only change the hold if requested or if hold mode was turned
# on and we haven't set it yet.
if kwargs.get('hold_changed', False) or not self._hold_set:
if self._hold_temp or self._away:
- self.device.hold = 1
+ self._device.hold = 1
self._hold_set = True
else:
- self.device.hold = 0
+ self._device.hold = 0
def set_time(self):
"""Set device time."""
# Calling this clears any local temperature override and
# reverts to the scheduled temperature.
now = datetime.datetime.now()
- self.device.time = {
+ self._device.time = {
'day': now.weekday(),
'hour': now.hour,
'minute': now.minute
@@ -309,13 +309,13 @@ class RadioThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off)."""
if operation_mode in (STATE_OFF, STATE_AUTO):
- self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
+ self._device.tmode = TEMP_MODE_TO_CODE[operation_mode]
# Setting t_cool or t_heat automatically changes tmode.
elif operation_mode == STATE_COOL:
- self.device.t_cool = self._target_temperature
+ self._device.t_cool = self._target_temperature
elif operation_mode == STATE_HEAT:
- self.device.t_heat = self._target_temperature
+ self._device.t_heat = self._target_temperature
def turn_away_mode_on(self):
"""Turn away on.
diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py
index 363653608e8..a2c4ea0ba99 100644
--- a/homeassistant/components/climate/sensibo.py
+++ b/homeassistant/components/climate/sensibo.py
@@ -71,7 +71,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for dev in (
yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)):
if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]:
- devices.append(SensiboClimate(client, dev))
+ devices.append(SensiboClimate(
+ client, dev, hass.config.units.temperature_unit))
except (aiohttp.client_exceptions.ClientConnectorError,
asyncio.TimeoutError):
_LOGGER.exception('Failed to connect to Sensibo servers.')
@@ -106,7 +107,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class SensiboClimate(ClimateDevice):
"""Representation of a Sensibo device."""
- def __init__(self, client, data):
+ def __init__(self, client, data, units):
"""Build SensiboClimate.
client: aiohttp session.
@@ -115,6 +116,7 @@ class SensiboClimate(ClimateDevice):
self._client = client
self._id = data['id']
self._external_state = None
+ self._units = units
self._do_update(data)
@property
@@ -139,7 +141,7 @@ class SensiboClimate(ClimateDevice):
self._temperatures_list = self._current_capabilities[
'temperatures'].get(temperature_unit_key, {}).get('values', [])
else:
- self._temperature_unit = self.unit_of_measurement
+ self._temperature_unit = self._units
self._temperatures_list = []
self._supported_features = 0
for key in self._ac_states:
@@ -175,7 +177,7 @@ class SensiboClimate(ClimateDevice):
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
- if self.temperature_unit == self.unit_of_measurement:
+ if self.temperature_unit == self.hass.config.units.temperature_unit:
# We are working in same units as the a/c unit. Use whole degrees
# like the API supports.
return 1
diff --git a/homeassistant/components/climate/zhong_hong.py b/homeassistant/components/climate/zhong_hong.py
index 7ff19871ee7..2b66af35224 100644
--- a/homeassistant/components/climate/zhong_hong.py
+++ b/homeassistant/components/climate/zhong_hong.py
@@ -100,7 +100,7 @@ class ZhongHongClimate(ClimateDevice):
async_dispatcher_send(self.hass, SIGNAL_DEVICE_ADDED)
def _after_update(self, climate):
- """Callback to update state."""
+ """Handle state update."""
_LOGGER.debug("async update ha state")
if self._device.current_operation:
self._current_operation = self._device.current_operation.lower()
diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py
index 7c0867e3852..2fac420c39c 100644
--- a/homeassistant/components/config/entity_registry.py
+++ b/homeassistant/components/config/entity_registry.py
@@ -101,7 +101,7 @@ def websocket_update_entity(hass, connection, msg):
@callback
def _entry_dict(entry):
- """Helper to convert entry to API format."""
+ """Convert entry to API format."""
return {
'entity_id': entry.entity_id,
'name': entry.name
diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py
index 84927712741..fcdab835052 100644
--- a/homeassistant/components/config/zwave.py
+++ b/homeassistant/components/config/zwave.py
@@ -212,7 +212,7 @@ class ZWaveProtectionView(HomeAssistantView):
network = hass.data.get(const.DATA_NETWORK)
def _fetch_protection():
- """Helper to get protection data."""
+ """Get protection data."""
node = network.nodes.get(nodeid)
if node is None:
return self.json_message('Node not found', HTTP_NOT_FOUND)
@@ -236,7 +236,7 @@ class ZWaveProtectionView(HomeAssistantView):
protection_data = await request.json()
def _set_protection():
- """Helper to get protection data."""
+ """Set protection data."""
node = network.nodes.get(nodeid)
selection = protection_data["selection"]
value_id = int(protection_data[const.ATTR_VALUE_ID])
diff --git a/homeassistant/components/cover/brunt.py b/homeassistant/components/cover/brunt.py
index 713f06db735..bd27ea30637 100644
--- a/homeassistant/components/cover/brunt.py
+++ b/homeassistant/components/cover/brunt.py
@@ -18,7 +18,7 @@ from homeassistant.components.cover import (
)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['brunt==0.1.2']
+REQUIREMENTS = ['brunt==0.1.3']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/cover/homematic.py
index 2736b656a15..9975b426558 100644
--- a/homeassistant/components/cover/homematic.py
+++ b/homeassistant/components/cover/homematic.py
@@ -6,9 +6,9 @@ https://home-assistant.io/components/cover.homematic/
"""
import logging
-from homeassistant.components.cover import CoverDevice, ATTR_POSITION,\
- ATTR_TILT_POSITION
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
+from homeassistant.components.cover import (
+ ATTR_POSITION, ATTR_TILT_POSITION, CoverDevice)
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/cover/knx.py b/homeassistant/components/cover/knx.py
index 7bb20e4cf1f..59195d73a2f 100644
--- a/homeassistant/components/cover/knx.py
+++ b/homeassistant/components/cover/knx.py
@@ -96,7 +96,7 @@ class KNXCover(CoverDevice):
def __init__(self, hass, device):
"""Initialize the cover."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
@@ -108,12 +108,12 @@ class KNXCover(CoverDevice):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -130,56 +130,56 @@ class KNXCover(CoverDevice):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \
SUPPORT_SET_POSITION | SUPPORT_STOP
- if self.device.supports_angle:
+ if self._device.supports_angle:
supported_features |= SUPPORT_SET_TILT_POSITION
return supported_features
@property
def current_cover_position(self):
"""Return the current position of the cover."""
- return self.device.current_position()
+ return self._device.current_position()
@property
def is_closed(self):
"""Return if the cover is closed."""
- return self.device.is_closed()
+ return self._device.is_closed()
async def async_close_cover(self, **kwargs):
"""Close the cover."""
- if not self.device.is_closed():
- await self.device.set_down()
+ if not self._device.is_closed():
+ await self._device.set_down()
self.start_auto_updater()
async def async_open_cover(self, **kwargs):
"""Open the cover."""
- if not self.device.is_open():
- await self.device.set_up()
+ if not self._device.is_open():
+ await self._device.set_up()
self.start_auto_updater()
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
- await self.device.set_position(position)
+ await self._device.set_position(position)
self.start_auto_updater()
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
- await self.device.stop()
+ await self._device.stop()
self.stop_auto_updater()
@property
def current_cover_tilt_position(self):
"""Return current tilt position of cover."""
- if not self.device.supports_angle:
+ if not self._device.supports_angle:
return None
- return self.device.current_angle()
+ return self._device.current_angle()
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION in kwargs:
tilt_position = kwargs[ATTR_TILT_POSITION]
- await self.device.set_angle(tilt_position)
+ await self._device.set_angle(tilt_position)
def start_auto_updater(self):
"""Start the autoupdater to update HASS while cover is moving."""
@@ -197,7 +197,7 @@ class KNXCover(CoverDevice):
def auto_updater_hook(self, now):
"""Call for the autoupdater."""
self.async_schedule_update_ha_state()
- if self.device.position_reached():
+ if self._device.position_reached():
self.stop_auto_updater()
- self.hass.add_job(self.device.auto_stop_if_necessary())
+ self.hass.add_job(self._device.auto_stop_if_necessary())
diff --git a/homeassistant/components/cover/tellduslive.py b/homeassistant/components/cover/tellduslive.py
index 4a78cb96d06..b4bde037a12 100644
--- a/homeassistant/components/cover/tellduslive.py
+++ b/homeassistant/components/cover/tellduslive.py
@@ -28,19 +28,19 @@ class TelldusLiveCover(TelldusLiveEntity, CoverDevice):
@property
def is_closed(self):
"""Return the current position of the cover."""
- return self.device.is_down
+ return self._device.is_down
def close_cover(self, **kwargs):
"""Close the cover."""
- self.device.down()
+ self._device.down()
self.changed()
def open_cover(self, **kwargs):
"""Open the cover."""
- self.device.up()
+ self._device.up()
self.changed()
def stop_cover(self, **kwargs):
"""Stop the cover."""
- self.device.stop()
+ self._device.stop()
self.changed()
diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py
index eacfe22e818..cf8d891661e 100644
--- a/homeassistant/components/deconz/__init__.py
+++ b/homeassistant/components/deconz/__init__.py
@@ -82,7 +82,7 @@ async def async_setup_entry(hass, config_entry):
@callback
def async_add_device_callback(device_type, device):
- """Called when a new device has been created in deCONZ."""
+ """Handle event of new device creation in deCONZ."""
async_dispatcher_send(
hass, 'deconz_new_{}'.format(device_type), [device])
@@ -105,7 +105,7 @@ async def async_setup_entry(hass, config_entry):
@callback
def async_add_remote(sensors):
- """Setup remote from deCONZ."""
+ """Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py
index 74cb0a77fef..408672a974f 100644
--- a/homeassistant/components/device_tracker/__init__.py
+++ b/homeassistant/components/device_tracker/__init__.py
@@ -330,19 +330,18 @@ class DeviceTracker:
})
# update known_devices.yaml
- self.hass.async_add_job(
+ self.hass.async_create_task(
self.async_update_config(
self.hass.config.path(YAML_DEVICES), dev_id, device)
)
- @asyncio.coroutine
- def async_update_config(self, path, dev_id, device):
+ async def async_update_config(self, path, dev_id, device):
"""Add device to YAML configuration file.
This method is a coroutine.
"""
- with (yield from self._is_updating):
- yield from self.hass.async_add_job(
+ async with self._is_updating:
+ await self.hass.async_add_executor_job(
update_config, self.hass.config.path(YAML_DEVICES),
dev_id, device)
@@ -681,8 +680,7 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
# Initial scan of each mac we also tell about host name for config
seen = set() # type: Any
- @asyncio.coroutine
- def async_device_tracker_scan(now: dt_util.dt.datetime):
+ async def async_device_tracker_scan(now: dt_util.dt.datetime):
"""Handle interval matches."""
if update_lock.locked():
_LOGGER.warning(
@@ -690,18 +688,18 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
"scan interval %s", platform, interval)
return
- with (yield from update_lock):
- found_devices = yield from scanner.async_scan_devices()
+ async with update_lock:
+ found_devices = await scanner.async_scan_devices()
for mac in found_devices:
if mac in seen:
host_name = None
else:
- host_name = yield from scanner.async_get_device_name(mac)
+ host_name = await scanner.async_get_device_name(mac)
seen.add(mac)
try:
- extra_attributes = (yield from
+ extra_attributes = (await
scanner.async_get_extra_attributes(mac))
except NotImplementedError:
extra_attributes = dict()
diff --git a/homeassistant/components/device_tracker/keenetic_ndms2.py b/homeassistant/components/device_tracker/keenetic_ndms2.py
index 4b5e3d6333d..4be6d96eb5a 100644
--- a/homeassistant/components/device_tracker/keenetic_ndms2.py
+++ b/homeassistant/components/device_tracker/keenetic_ndms2.py
@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_PASSWORD, CONF_USERNAME
)
-REQUIREMENTS = ['ndms2_client==0.0.3']
+REQUIREMENTS = ['ndms2_client==0.0.4']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/device_tracker/ritassist.py b/homeassistant/components/device_tracker/ritassist.py
index 9fc50de5062..c41ae9f2ec2 100644
--- a/homeassistant/components/device_tracker/ritassist.py
+++ b/homeassistant/components/device_tracker/ritassist.py
@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers.event import track_utc_time_change
-REQUIREMENTS = ['ritassist==0.5']
+REQUIREMENTS = ['ritassist==0.9.2']
_LOGGER = logging.getLogger(__name__)
@@ -57,7 +57,7 @@ class RitAssistDeviceScanner:
config.get(CONF_PASSWORD))
def setup(self, hass):
- """Setup a timer and start gathering devices."""
+ """Set up a timer and start gathering devices."""
self._refresh()
track_utc_time_change(hass,
lambda now: self._refresh(),
@@ -78,6 +78,10 @@ class RitAssistDeviceScanner:
for device in devices:
if (not self._include or
device.license_plate in self._include):
+
+ if device.active or device.current_address is None:
+ device.get_map_details()
+
self._see(dev_id=device.plate_as_id,
gps=(device.latitude, device.longitude),
attributes=device.state_attributes,
diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py
index 346f381db34..9bf85e39faf 100644
--- a/homeassistant/components/device_tracker/tplink.py
+++ b/homeassistant/components/device_tracker/tplink.py
@@ -69,11 +69,16 @@ class TplinkDeviceScanner(DeviceScanner):
password = config[CONF_PASSWORD]
username = config[CONF_USERNAME]
- self.tplink_client = TpLinkClient(
- password, host=host, username=username)
+ self.success_init = False
+ try:
+ self.tplink_client = TpLinkClient(
+ password, host=host, username=username)
- self.last_results = {}
- self.success_init = self._update_info()
+ self.last_results = {}
+
+ self.success_init = self._update_info()
+ except requests.exceptions.ConnectionError:
+ _LOGGER.debug("ConnectionError in TplinkDeviceScanner")
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
@@ -115,7 +120,11 @@ class Tplink1DeviceScanner(DeviceScanner):
self.password = password
self.last_results = {}
- self.success_init = self._update_info()
+ self.success_init = False
+ try:
+ self.success_init = self._update_info()
+ except requests.exceptions.ConnectionError:
+ _LOGGER.debug("ConnectionError in Tplink1DeviceScanner")
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
diff --git a/homeassistant/components/device_tracker/xiaomi_miio.py b/homeassistant/components/device_tracker/xiaomi_miio.py
index 074d6a1054e..b1cfc0aed4a 100644
--- a/homeassistant/components/device_tracker/xiaomi_miio.py
+++ b/homeassistant/components/device_tracker/xiaomi_miio.py
@@ -20,7 +20,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)),
})
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
def get_scanner(hass, config):
@@ -73,5 +73,8 @@ class XiaomiMiioDeviceScanner(DeviceScanner):
return devices
async def async_get_device_name(self, device):
- """The repeater doesn't provide the name of the associated device."""
+ """Return None.
+
+ The repeater doesn't provide the name of the associated device.
+ """
return None
diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py
index d686b114095..8009f01222b 100644
--- a/homeassistant/components/discovery.py
+++ b/homeassistant/components/discovery.py
@@ -86,11 +86,11 @@ SERVICE_HANDLERS = {
'volumio': ('media_player', 'volumio'),
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
'freebox': ('device_tracker', 'freebox'),
- 'dlna_dmr': ('media_player', 'dlna_dmr'),
}
OPTIONAL_SERVICE_HANDLERS = {
SERVICE_HOMEKIT: ('homekit_controller', None),
+ 'dlna_dmr': ('media_player', 'dlna_dmr'),
}
CONF_IGNORE = 'ignore'
diff --git a/homeassistant/components/doorbird.py b/homeassistant/components/doorbird.py
index 6cd820816e2..c97289b9f07 100644
--- a/homeassistant/components/doorbird.py
+++ b/homeassistant/components/doorbird.py
@@ -139,17 +139,17 @@ class ConfiguredDoorbird():
@property
def name(self):
- """Custom device name."""
+ """Get custom device name."""
return self._name
@property
def device(self):
- """The configured device."""
+ """Get the configured device."""
return self._device
@property
def custom_url(self):
- """Custom url for device."""
+ """Get custom url for device."""
return self._custom_url
@property
diff --git a/homeassistant/components/ecovacs.py b/homeassistant/components/ecovacs.py
new file mode 100644
index 00000000000..2e51b048d15
--- /dev/null
+++ b/homeassistant/components/ecovacs.py
@@ -0,0 +1,87 @@
+"""Parent component for Ecovacs Deebot vacuums.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/ecovacs/
+"""
+
+import logging
+import random
+import string
+
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers import discovery
+from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, \
+ EVENT_HOMEASSISTANT_STOP
+
+REQUIREMENTS = ['sucks==0.9.1']
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = "ecovacs"
+
+CONF_COUNTRY = "country"
+CONF_CONTINENT = "continent"
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Required(CONF_COUNTRY): vol.All(vol.Lower, cv.string),
+ vol.Required(CONF_CONTINENT): vol.All(vol.Lower, cv.string),
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+ECOVACS_DEVICES = "ecovacs_devices"
+
+# Generate a random device ID on each bootup
+ECOVACS_API_DEVICEID = ''.join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(8)
+)
+
+
+def setup(hass, config):
+ """Set up the Ecovacs component."""
+ _LOGGER.debug("Creating new Ecovacs component")
+
+ hass.data[ECOVACS_DEVICES] = []
+
+ from sucks import EcoVacsAPI, VacBot
+
+ ecovacs_api = EcoVacsAPI(ECOVACS_API_DEVICEID,
+ config[DOMAIN].get(CONF_USERNAME),
+ EcoVacsAPI.md5(config[DOMAIN].get(CONF_PASSWORD)),
+ config[DOMAIN].get(CONF_COUNTRY),
+ config[DOMAIN].get(CONF_CONTINENT))
+
+ devices = ecovacs_api.devices()
+ _LOGGER.debug("Ecobot devices: %s", devices)
+
+ for device in devices:
+ _LOGGER.info("Discovered Ecovacs device on account: %s",
+ device['nick'])
+ vacbot = VacBot(ecovacs_api.uid,
+ ecovacs_api.REALM,
+ ecovacs_api.resource,
+ ecovacs_api.user_access_token,
+ device,
+ config[DOMAIN].get(CONF_CONTINENT).lower(),
+ monitor=True)
+ hass.data[ECOVACS_DEVICES].append(vacbot)
+
+ def stop(event: object) -> None:
+ """Shut down open connections to Ecovacs XMPP server."""
+ for device in hass.data[ECOVACS_DEVICES]:
+ _LOGGER.info("Shutting down connection to Ecovacs device %s",
+ device.vacuum['nick'])
+ device.disconnect()
+
+ # Listen for HA stop to disconnect.
+ hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
+
+ if hass.data[ECOVACS_DEVICES]:
+ _LOGGER.debug("Starting vacuum components")
+ discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
+
+ return True
diff --git a/homeassistant/components/egardia.py b/homeassistant/components/egardia.py
index b7da671bb15..3547f4fc76e 100644
--- a/homeassistant/components/egardia.py
+++ b/homeassistant/components/egardia.py
@@ -101,7 +101,7 @@ def setup(hass, config):
server.start()
def handle_stop_event(event):
- """Callback function for HA stop event."""
+ """Handle HA stop event."""
server.stop()
# listen to home assistant stop event
diff --git a/homeassistant/components/fan/insteon_plm.py b/homeassistant/components/fan/insteon.py
similarity index 86%
rename from homeassistant/components/fan/insteon_plm.py
rename to homeassistant/components/fan/insteon.py
index 0911295d090..62fa48935ec 100644
--- a/homeassistant/components/fan/insteon_plm.py
+++ b/homeassistant/components/fan/insteon.py
@@ -2,7 +2,7 @@
Support for INSTEON fans via PowerLinc Modem.
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/fan.insteon_plm/
+https://home-assistant.io/components/fan.insteon/
"""
import asyncio
import logging
@@ -14,9 +14,9 @@ from homeassistant.components.fan import (SPEED_OFF,
FanEntity,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_OFF
-from homeassistant.components.insteon_plm import InsteonPLMEntity
+from homeassistant.components.insteon import InsteonEntity
-DEPENDENCIES = ['insteon_plm']
+DEPENDENCIES = ['insteon']
SPEED_TO_HEX = {SPEED_OFF: 0x00,
SPEED_LOW: 0x3f,
@@ -30,22 +30,22 @@ _LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
- """Set up the INSTEON PLM device class for the hass platform."""
- plm = hass.data['insteon_plm'].get('plm')
+ """Set up the INSTEON device class for the hass platform."""
+ insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
- device = plm.devices[address]
+ device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
_LOGGER.debug('Adding device %s entity %s to Fan platform',
device.address.hex, device.states[state_key].name)
- new_entity = InsteonPLMFan(device, state_key)
+ new_entity = InsteonFan(device, state_key)
async_add_devices([new_entity])
-class InsteonPLMFan(InsteonPLMEntity, FanEntity):
+class InsteonFan(InsteonEntity, FanEntity):
"""An INSTEON fan component."""
@property
diff --git a/homeassistant/components/fan/insteon_local.py b/homeassistant/components/fan/insteon_local.py
deleted file mode 100644
index 28b93c86ed7..00000000000
--- a/homeassistant/components/fan/insteon_local.py
+++ /dev/null
@@ -1,107 +0,0 @@
-"""
-Support for Insteon fans via local hub control.
-
-For more details about this component, please refer to the documentation at
-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)
-
-_CONFIGURING = {}
-_LOGGER = logging.getLogger(__name__)
-
-DEPENDENCIES = ['insteon_local']
-DOMAIN = 'fan'
-
-MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
-MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
-
-SUPPORT_INSTEON_LOCAL = SUPPORT_SET_SPEED
-
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
- """Set up the Insteon local fan platform."""
- insteonhub = hass.data['insteon_local']
- if discovery_info is None:
- return
-
- linked = discovery_info['linked']
- device_list = []
- for device_id in linked:
- if (linked[device_id]['cat_type'] == 'dimmer' and
- linked[device_id]['sku'] == '2475F'):
- device = insteonhub.fan(device_id)
- device_list.append(
- InsteonLocalFanDevice(device)
- )
-
- add_devices(device_list)
-
-
-class InsteonLocalFanDevice(FanEntity):
- """An abstract Class for an Insteon node."""
-
- def __init__(self, node):
- """Initialize the device."""
- self.node = node
- self._speed = SPEED_OFF
-
- @property
- def name(self):
- """Return the name of the node."""
- return self.node.device_id
-
- @property
- def unique_id(self):
- """Return the ID of this Insteon node."""
- return self.node.device_id
-
- @property
- def speed(self) -> str:
- """Return the current speed."""
- return self._speed
-
- @property
- def speed_list(self) -> list:
- """Get the list of available speeds."""
- return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
-
- @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
- def update(self):
- """Update state of the fan."""
- resp = self.node.status()
- if 'cmd2' in resp:
- if resp['cmd2'] == '00':
- self._speed = SPEED_OFF
- elif resp['cmd2'] == '55':
- self._speed = SPEED_LOW
- elif resp['cmd2'] == 'AA':
- self._speed = SPEED_MEDIUM
- elif resp['cmd2'] == 'FF':
- self._speed = SPEED_HIGH
-
- @property
- def supported_features(self):
- """Flag supported features."""
- return SUPPORT_INSTEON_LOCAL
-
- def turn_on(self, speed: str = None, **kwargs) -> None:
- """Turn device on."""
- if speed is None:
- speed = kwargs.get(ATTR_SPEED, SPEED_MEDIUM)
-
- self.set_speed(speed)
-
- def turn_off(self, **kwargs) -> None:
- """Turn device off."""
- self.node.off()
-
- 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/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py
index 1616d388816..ee8f49f897a 100644
--- a/homeassistant/components/fan/xiaomi_miio.py
+++ b/homeassistant/components/fan/xiaomi_miio.py
@@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
'zhimi.humidifier.ca1']),
})
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
ATTR_MODEL = 'model'
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index c3c742f43b1..a436cc483ae 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==20180816.0']
+REQUIREMENTS = ['home-assistant-frontend==20180820.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py
index 63a3e641170..675e86f9d39 100644
--- a/homeassistant/components/google_assistant/smart_home.py
+++ b/homeassistant/components/google_assistant/smart_home.py
@@ -77,7 +77,7 @@ class _GoogleEntity:
domain = state.domain
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
- return [Trait(state) for Trait in trait.TRAITS
+ return [Trait(self.hass, state) for Trait in trait.TRAITS
if Trait.supported(domain, features)]
@callback
@@ -159,7 +159,7 @@ class _GoogleEntity:
executed = False
for trt in self.traits():
if trt.can_execute(command, params):
- await trt.execute(self.hass, command, params)
+ await trt.execute(command, params)
executed = True
break
diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py
index 1d369eb87da..26e80e6f03b 100644
--- a/homeassistant/components/google_assistant/trait.py
+++ b/homeassistant/components/google_assistant/trait.py
@@ -14,7 +14,6 @@ from homeassistant.components import (
)
from homeassistant.const import (
ATTR_ENTITY_ID,
- ATTR_UNIT_OF_MEASUREMENT,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
@@ -50,15 +49,14 @@ TRAITS = []
def register_trait(trait):
- """Decorator to register a trait."""
+ """Decorate a function to register a trait."""
TRAITS.append(trait)
return trait
-def _google_temp_unit(state):
+def _google_temp_unit(units):
"""Return Google temperature unit."""
- if (state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) ==
- TEMP_FAHRENHEIT):
+ if units == TEMP_FAHRENHEIT:
return 'F'
return 'C'
@@ -68,8 +66,9 @@ class _Trait:
commands = []
- def __init__(self, state):
+ def __init__(self, hass, state):
"""Initialize a trait for a state."""
+ self.hass = hass
self.state = state
def sync_attributes(self):
@@ -84,7 +83,7 @@ class _Trait:
"""Test if command can be executed."""
return command in self.commands
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a trait command."""
raise NotImplementedError
@@ -141,24 +140,24 @@ class BrightnessTrait(_Trait):
return response
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a brightness command."""
domain = self.state.domain
if domain == light.DOMAIN:
- await hass.services.async_call(
+ await self.hass.services.async_call(
light.DOMAIN, light.SERVICE_TURN_ON, {
ATTR_ENTITY_ID: self.state.entity_id,
light.ATTR_BRIGHTNESS_PCT: params['brightness']
}, blocking=True)
elif domain == cover.DOMAIN:
- await hass.services.async_call(
+ await self.hass.services.async_call(
cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, {
ATTR_ENTITY_ID: self.state.entity_id,
cover.ATTR_POSITION: params['brightness']
}, blocking=True)
elif domain == media_player.DOMAIN:
- await hass.services.async_call(
+ await self.hass.services.async_call(
media_player.DOMAIN, media_player.SERVICE_VOLUME_SET, {
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL:
@@ -201,7 +200,7 @@ class OnOffTrait(_Trait):
return {'on': self.state.state != cover.STATE_CLOSED}
return {'on': self.state.state != STATE_OFF}
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute an OnOff command."""
domain = self.state.domain
@@ -220,7 +219,7 @@ class OnOffTrait(_Trait):
service_domain = domain
service = SERVICE_TURN_ON if params['on'] else SERVICE_TURN_OFF
- await hass.services.async_call(service_domain, service, {
+ await self.hass.services.async_call(service_domain, service, {
ATTR_ENTITY_ID: self.state.entity_id
}, blocking=True)
@@ -268,14 +267,14 @@ class ColorSpectrumTrait(_Trait):
return (command in self.commands and
'spectrumRGB' in params.get('color', {}))
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a color spectrum command."""
# Convert integer to hex format and left pad with 0's till length 6
hex_value = "{0:06x}".format(params['color']['spectrumRGB'])
color = color_util.color_RGB_to_hs(
*color_util.rgb_hex_to_rgb_list(hex_value))
- await hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
+ await self.hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: self.state.entity_id,
light.ATTR_HS_COLOR: color
}, blocking=True)
@@ -331,7 +330,7 @@ class ColorTemperatureTrait(_Trait):
return (command in self.commands and
'temperature' in params.get('color', {}))
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a color temperature command."""
temp = color_util.color_temperature_kelvin_to_mired(
params['color']['temperature'])
@@ -344,7 +343,7 @@ class ColorTemperatureTrait(_Trait):
"Temperature should be between {} and {}".format(min_temp,
max_temp))
- await hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
+ await self.hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: self.state.entity_id,
light.ATTR_COLOR_TEMP: temp,
}, blocking=True)
@@ -376,12 +375,13 @@ class SceneTrait(_Trait):
"""Return scene query attributes."""
return {}
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a scene command."""
# Don't block for scripts as they can be slow.
- await hass.services.async_call(self.state.domain, SERVICE_TURN_ON, {
- ATTR_ENTITY_ID: self.state.entity_id
- }, blocking=self.state.domain != script.DOMAIN)
+ await self.hass.services.async_call(
+ self.state.domain, SERVICE_TURN_ON, {
+ ATTR_ENTITY_ID: self.state.entity_id
+ }, blocking=self.state.domain != script.DOMAIN)
@register_trait
@@ -425,7 +425,8 @@ class TemperatureSettingTrait(_Trait):
return {
'availableThermostatModes': ','.join(modes),
- 'thermostatTemperatureUnit': _google_temp_unit(self.state),
+ 'thermostatTemperatureUnit': _google_temp_unit(
+ self.hass.config.units.temperature_unit)
}
def query_attributes(self):
@@ -437,7 +438,7 @@ class TemperatureSettingTrait(_Trait):
if operation is not None and operation in self.hass_to_google:
response['thermostatMode'] = self.hass_to_google[operation]
- unit = self.state.attributes[ATTR_UNIT_OF_MEASUREMENT]
+ unit = self.hass.config.units.temperature_unit
current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE)
if current_temp is not None:
@@ -465,10 +466,10 @@ class TemperatureSettingTrait(_Trait):
return response
- async def execute(self, hass, command, params):
+ async def execute(self, command, params):
"""Execute a temperature point or mode command."""
# All sent in temperatures are always in Celsius
- unit = self.state.attributes[ATTR_UNIT_OF_MEASUREMENT]
+ unit = self.hass.config.units.temperature_unit
min_temp = self.state.attributes[climate.ATTR_MIN_TEMP]
max_temp = self.state.attributes[climate.ATTR_MAX_TEMP]
@@ -482,7 +483,7 @@ class TemperatureSettingTrait(_Trait):
"Temperature should be between {} and {}".format(min_temp,
max_temp))
- await hass.services.async_call(
+ await self.hass.services.async_call(
climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: self.state.entity_id,
climate.ATTR_TEMPERATURE: temp
@@ -508,7 +509,7 @@ class TemperatureSettingTrait(_Trait):
"Lower bound for temperature range should be between "
"{} and {}".format(min_temp, max_temp))
- await hass.services.async_call(
+ await self.hass.services.async_call(
climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: self.state.entity_id,
climate.ATTR_TARGET_TEMP_HIGH: temp_high,
@@ -516,7 +517,7 @@ class TemperatureSettingTrait(_Trait):
}, blocking=True)
elif command == COMMAND_THERMOSTAT_SET_MODE:
- await hass.services.async_call(
+ await self.hass.services.async_call(
climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, {
ATTR_ENTITY_ID: self.state.entity_id,
climate.ATTR_OPERATION_MODE:
diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py
index a33e91f3aa9..eda65d1895d 100644
--- a/homeassistant/components/group/__init__.py
+++ b/homeassistant/components/group/__init__.py
@@ -550,12 +550,12 @@ class Group(Entity):
self._async_update_group_state()
async def async_added_to_hass(self):
- """Callback when added to HASS."""
+ """Handle addition to HASS."""
if self.tracking:
self.async_start()
async def async_will_remove_from_hass(self):
- """Callback when removed from HASS."""
+ """Handle removal from HASS."""
if self._async_unsub_state_changed:
self._async_unsub_state_changed()
self._async_unsub_state_changed = None
diff --git a/homeassistant/components/hangouts/.translations/en.json b/homeassistant/components/hangouts/.translations/en.json
new file mode 100644
index 00000000000..d8d160ad5ea
--- /dev/null
+++ b/homeassistant/components/hangouts/.translations/en.json
@@ -0,0 +1,31 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Google Hangouts is already configured",
+ "unknown": "Unknown error occurred."
+ },
+ "error": {
+ "invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
+ "invalid_2fa_method": "Invalig 2FA Method (Verify on Phone).",
+ "invalid_login": "Invalid Login, please try again."
+ },
+ "step": {
+ "2fa": {
+ "data": {
+ "2fa": "2FA Pin"
+ },
+ "description": "",
+ "title": "2-Factor-Authorization"
+ },
+ "user": {
+ "data": {
+ "email": "E-Mail Address",
+ "password": "Password"
+ },
+ "description": "",
+ "title": "Google Hangouts Login"
+ }
+ },
+ "title": "Google Hangouts"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/hangouts/__init__.py b/homeassistant/components/hangouts/__init__.py
new file mode 100644
index 00000000000..89649ecb8e1
--- /dev/null
+++ b/homeassistant/components/hangouts/__init__.py
@@ -0,0 +1,87 @@
+"""
+The hangouts bot component.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/hangouts/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
+from homeassistant.helpers import dispatcher
+
+from .config_flow import configured_hangouts
+from .const import (
+ CONF_BOT, CONF_COMMANDS, CONF_REFRESH_TOKEN, DOMAIN,
+ EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
+ MESSAGE_SCHEMA, SERVICE_SEND_MESSAGE,
+ SERVICE_UPDATE)
+
+REQUIREMENTS = ['hangups==0.4.5']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup(hass, config):
+ """Set up the Hangouts bot component."""
+ config = config.get(DOMAIN, [])
+ hass.data[DOMAIN] = {CONF_COMMANDS: config[CONF_COMMANDS]}
+
+ if configured_hangouts(hass) is None:
+ hass.async_add_job(hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': config_entries.SOURCE_IMPORT}
+ ))
+
+ return True
+
+
+async def async_setup_entry(hass, config):
+ """Set up a config entry."""
+ from hangups.auth import GoogleAuthError
+
+ try:
+ from .hangouts_bot import HangoutsBot
+
+ bot = HangoutsBot(
+ hass,
+ config.data.get(CONF_REFRESH_TOKEN),
+ hass.data[DOMAIN][CONF_COMMANDS])
+ hass.data[DOMAIN][CONF_BOT] = bot
+ except GoogleAuthError as exception:
+ _LOGGER.error("Hangouts failed to log in: %s", str(exception))
+ return False
+
+ dispatcher.async_dispatcher_connect(
+ hass,
+ EVENT_HANGOUTS_CONNECTED,
+ bot.async_handle_update_users_and_conversations)
+
+ dispatcher.async_dispatcher_connect(
+ hass,
+ EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
+ bot.async_update_conversation_commands)
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
+ bot.async_handle_hass_stop)
+
+ await bot.async_connect()
+
+ hass.services.async_register(DOMAIN, SERVICE_SEND_MESSAGE,
+ bot.async_handle_send_message,
+ schema=MESSAGE_SCHEMA)
+ hass.services.async_register(DOMAIN,
+ SERVICE_UPDATE,
+ bot.
+ async_handle_update_users_and_conversations,
+ schema=vol.Schema({}))
+
+ return True
+
+
+async def async_unload_entry(hass, _):
+ """Unload a config entry."""
+ bot = hass.data[DOMAIN].pop(CONF_BOT)
+ await bot.async_disconnect()
+ return True
diff --git a/homeassistant/components/hangouts/config_flow.py b/homeassistant/components/hangouts/config_flow.py
new file mode 100644
index 00000000000..bd81d5053c8
--- /dev/null
+++ b/homeassistant/components/hangouts/config_flow.py
@@ -0,0 +1,107 @@
+"""Config flow to configure Google Hangouts."""
+import voluptuous as vol
+
+from homeassistant import config_entries, data_entry_flow
+from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
+from homeassistant.core import callback
+
+from .const import CONF_2FA, CONF_REFRESH_TOKEN
+from .const import DOMAIN as HANGOUTS_DOMAIN
+
+
+@callback
+def configured_hangouts(hass):
+ """Return the configures Google Hangouts Account."""
+ entries = hass.config_entries.async_entries(HANGOUTS_DOMAIN)
+ if entries:
+ return entries[0]
+ return None
+
+
+@config_entries.HANDLERS.register(HANGOUTS_DOMAIN)
+class HangoutsFlowHandler(data_entry_flow.FlowHandler):
+ """Config flow Google Hangouts."""
+
+ VERSION = 1
+
+ def __init__(self):
+ """Initialize Google Hangouts config flow."""
+ self._credentials = None
+ self._refresh_token = None
+
+ async def async_step_user(self, user_input=None):
+ """Handle a flow start."""
+ errors = {}
+
+ if configured_hangouts(self.hass) is not None:
+ return self.async_abort(reason="already_configured")
+
+ if user_input is not None:
+ from hangups import get_auth
+ from .hangups_utils import (HangoutsCredentials,
+ HangoutsRefreshToken,
+ GoogleAuthError, Google2FAError)
+ self._credentials = HangoutsCredentials(user_input[CONF_EMAIL],
+ user_input[CONF_PASSWORD])
+ self._refresh_token = HangoutsRefreshToken(None)
+ try:
+ await self.hass.async_add_executor_job(get_auth,
+ self._credentials,
+ self._refresh_token)
+
+ return await self.async_step_final()
+ except GoogleAuthError as err:
+ if isinstance(err, Google2FAError):
+ return await self.async_step_2fa()
+ msg = str(err)
+ if msg == 'Unknown verification code input':
+ errors['base'] = 'invalid_2fa_method'
+ else:
+ errors['base'] = 'invalid_login'
+
+ return self.async_show_form(
+ step_id='user',
+ data_schema=vol.Schema({
+ vol.Required(CONF_EMAIL): str,
+ vol.Required(CONF_PASSWORD): str
+ }),
+ errors=errors
+ )
+
+ async def async_step_2fa(self, user_input=None):
+ """Handle the 2fa step, if needed."""
+ errors = {}
+
+ if user_input is not None:
+ from hangups import get_auth
+ from .hangups_utils import GoogleAuthError
+ self._credentials.set_verification_code(user_input[CONF_2FA])
+ try:
+ await self.hass.async_add_executor_job(get_auth,
+ self._credentials,
+ self._refresh_token)
+
+ return await self.async_step_final()
+ except GoogleAuthError:
+ errors['base'] = 'invalid_2fa'
+
+ return self.async_show_form(
+ step_id=CONF_2FA,
+ data_schema=vol.Schema({
+ vol.Required(CONF_2FA): str,
+ }),
+ errors=errors
+ )
+
+ async def async_step_final(self):
+ """Handle the final step, create the config entry."""
+ return self.async_create_entry(
+ title=self._credentials.get_email(),
+ data={
+ CONF_EMAIL: self._credentials.get_email(),
+ CONF_REFRESH_TOKEN: self._refresh_token.get()
+ })
+
+ async def async_step_import(self, _):
+ """Handle a flow import."""
+ return self.async_abort(reason='already_configured')
diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py
new file mode 100644
index 00000000000..7083307f3e2
--- /dev/null
+++ b/homeassistant/components/hangouts/const.py
@@ -0,0 +1,78 @@
+"""Constants for Google Hangouts Component."""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET
+from homeassistant.const import CONF_NAME
+import homeassistant.helpers.config_validation as cv
+
+_LOGGER = logging.getLogger('homeassistant.components.hangouts')
+
+
+DOMAIN = 'hangouts'
+
+CONF_2FA = '2fa'
+CONF_REFRESH_TOKEN = 'refresh_token'
+CONF_BOT = 'bot'
+
+CONF_CONVERSATIONS = 'conversations'
+CONF_DEFAULT_CONVERSATIONS = 'default_conversations'
+
+CONF_COMMANDS = 'commands'
+CONF_WORD = 'word'
+CONF_EXPRESSION = 'expression'
+
+EVENT_HANGOUTS_COMMAND = 'hangouts_command'
+
+EVENT_HANGOUTS_CONNECTED = 'hangouts_connected'
+EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected'
+EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed'
+EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed'
+
+CONF_CONVERSATION_ID = 'id'
+CONF_CONVERSATION_NAME = 'name'
+
+SERVICE_SEND_MESSAGE = 'send_message'
+SERVICE_UPDATE = 'update'
+
+
+TARGETS_SCHEMA = vol.All(
+ vol.Schema({
+ vol.Exclusive(CONF_CONVERSATION_ID, 'id or name'): cv.string,
+ vol.Exclusive(CONF_CONVERSATION_NAME, 'id or name'): cv.string
+ }),
+ cv.has_at_least_one_key(CONF_CONVERSATION_ID, CONF_CONVERSATION_NAME)
+)
+MESSAGE_SEGMENT_SCHEMA = vol.Schema({
+ vol.Required('text'): cv.string,
+ vol.Optional('is_bold'): cv.boolean,
+ vol.Optional('is_italic'): cv.boolean,
+ vol.Optional('is_strikethrough'): cv.boolean,
+ vol.Optional('is_underline'): cv.boolean,
+ vol.Optional('parse_str'): cv.boolean,
+ vol.Optional('link_target'): cv.string
+})
+
+MESSAGE_SCHEMA = vol.Schema({
+ vol.Required(ATTR_TARGET): [TARGETS_SCHEMA],
+ vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA]
+})
+
+COMMAND_SCHEMA = vol.All(
+ # Basic Schema
+ vol.Schema({
+ vol.Exclusive(CONF_WORD, 'trigger'): cv.string,
+ vol.Exclusive(CONF_EXPRESSION, 'trigger'): cv.is_regex,
+ vol.Required(CONF_NAME): cv.string,
+ vol.Optional(CONF_CONVERSATIONS): [TARGETS_SCHEMA]
+ }),
+ # Make sure it's either a word or an expression command
+ cv.has_at_least_one_key(CONF_WORD, CONF_EXPRESSION)
+)
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_COMMANDS, default=[]): [COMMAND_SCHEMA]
+ })
+}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py
new file mode 100644
index 00000000000..d4c5606799d
--- /dev/null
+++ b/homeassistant/components/hangouts/hangouts_bot.py
@@ -0,0 +1,229 @@
+"""The Hangouts Bot."""
+import logging
+import re
+
+from homeassistant.helpers import dispatcher
+
+from .const import (
+ ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, CONF_EXPRESSION, CONF_NAME,
+ CONF_WORD, DOMAIN, EVENT_HANGOUTS_COMMAND, EVENT_HANGOUTS_CONNECTED,
+ EVENT_HANGOUTS_CONVERSATIONS_CHANGED, EVENT_HANGOUTS_DISCONNECTED)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class HangoutsBot:
+ """The Hangouts Bot."""
+
+ def __init__(self, hass, refresh_token, commands):
+ """Set up the client."""
+ self.hass = hass
+ self._connected = False
+
+ self._refresh_token = refresh_token
+
+ self._commands = commands
+
+ self._word_commands = None
+ self._expression_commands = None
+ self._client = None
+ self._user_list = None
+ self._conversation_list = None
+
+ def _resolve_conversation_name(self, name):
+ for conv in self._conversation_list.get_all():
+ if conv.name == name:
+ return conv
+ return None
+
+ def async_update_conversation_commands(self, _):
+ """Refresh the commands for every conversation."""
+ self._word_commands = {}
+ self._expression_commands = {}
+
+ for command in self._commands:
+ if command.get(CONF_CONVERSATIONS):
+ conversations = []
+ for conversation in command.get(CONF_CONVERSATIONS):
+ if 'id' in conversation:
+ conversations.append(conversation['id'])
+ elif 'name' in conversation:
+ conversations.append(self._resolve_conversation_name(
+ conversation['name']).id_)
+ command['_' + CONF_CONVERSATIONS] = conversations
+ else:
+ command['_' + CONF_CONVERSATIONS] = \
+ [conv.id_ for conv in self._conversation_list.get_all()]
+
+ if command.get(CONF_WORD):
+ for conv_id in command['_' + CONF_CONVERSATIONS]:
+ if conv_id not in self._word_commands:
+ self._word_commands[conv_id] = {}
+ word = command[CONF_WORD].lower()
+ self._word_commands[conv_id][word] = command
+ elif command.get(CONF_EXPRESSION):
+ command['_' + CONF_EXPRESSION] = re.compile(
+ command.get(CONF_EXPRESSION))
+
+ for conv_id in command['_' + CONF_CONVERSATIONS]:
+ if conv_id not in self._expression_commands:
+ self._expression_commands[conv_id] = []
+ self._expression_commands[conv_id].append(command)
+
+ try:
+ self._conversation_list.on_event.remove_observer(
+ self._handle_conversation_event)
+ except ValueError:
+ pass
+ self._conversation_list.on_event.add_observer(
+ self._handle_conversation_event)
+
+ def _handle_conversation_event(self, event):
+ from hangups import ChatMessageEvent
+ if event.__class__ is ChatMessageEvent:
+ self._handle_conversation_message(
+ event.conversation_id, event.user_id, event)
+
+ def _handle_conversation_message(self, conv_id, user_id, event):
+ """Handle a message sent to a conversation."""
+ user = self._user_list.get_user(user_id)
+ if user.is_self:
+ return
+
+ _LOGGER.debug("Handling message '%s' from %s",
+ event.text, user.full_name)
+
+ event_data = None
+
+ pieces = event.text.split(' ')
+ cmd = pieces[0].lower()
+ command = self._word_commands.get(conv_id, {}).get(cmd)
+ if command:
+ event_data = {
+ 'command': command[CONF_NAME],
+ 'conversation_id': conv_id,
+ 'user_id': user_id,
+ 'user_name': user.full_name,
+ 'data': pieces[1:]
+ }
+ else:
+ # After single-word commands, check all regex commands in the room
+ for command in self._expression_commands.get(conv_id, []):
+ match = command['_' + CONF_EXPRESSION].match(event.text)
+ if not match:
+ continue
+ event_data = {
+ 'command': command[CONF_NAME],
+ 'conversation_id': conv_id,
+ 'user_id': user_id,
+ 'user_name': user.full_name,
+ 'data': match.groupdict()
+ }
+ if event_data is not None:
+ self.hass.bus.fire(EVENT_HANGOUTS_COMMAND, event_data)
+
+ async def async_connect(self):
+ """Login to the Google Hangouts."""
+ from .hangups_utils import HangoutsRefreshToken, HangoutsCredentials
+
+ from hangups import Client
+ from hangups import get_auth
+ session = await self.hass.async_add_executor_job(
+ get_auth, HangoutsCredentials(None, None, None),
+ HangoutsRefreshToken(self._refresh_token))
+
+ self._client = Client(session)
+ self._client.on_connect.add_observer(self._on_connect)
+ self._client.on_disconnect.add_observer(self._on_disconnect)
+
+ self.hass.loop.create_task(self._client.connect())
+
+ def _on_connect(self):
+ _LOGGER.debug('Connected!')
+ self._connected = True
+ dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED)
+
+ def _on_disconnect(self):
+ """Handle disconnecting."""
+ _LOGGER.debug('Connection lost!')
+ self._connected = False
+ dispatcher.async_dispatcher_send(self.hass,
+ EVENT_HANGOUTS_DISCONNECTED)
+
+ async def async_disconnect(self):
+ """Disconnect the client if it is connected."""
+ if self._connected:
+ await self._client.disconnect()
+
+ async def async_handle_hass_stop(self, _):
+ """Run once when Home Assistant stops."""
+ await self.async_disconnect()
+
+ async def _async_send_message(self, message, targets):
+ conversations = []
+ for target in targets:
+ conversation = None
+ if 'id' in target:
+ conversation = self._conversation_list.get(target['id'])
+ elif 'name' in target:
+ conversation = self._resolve_conversation_name(target['name'])
+ if conversation is not None:
+ conversations.append(conversation)
+
+ if not conversations:
+ return False
+
+ from hangups import ChatMessageSegment, hangouts_pb2
+ messages = []
+ for segment in message:
+ if 'parse_str' in segment and segment['parse_str']:
+ messages.extend(ChatMessageSegment.from_str(segment['text']))
+ else:
+ if 'parse_str' in segment:
+ del segment['parse_str']
+ messages.append(ChatMessageSegment(**segment))
+ messages.append(ChatMessageSegment('',
+ segment_type=hangouts_pb2.
+ SEGMENT_TYPE_LINE_BREAK))
+
+ if not messages:
+ return False
+ for conv in conversations:
+ await conv.send_message(messages)
+
+ async def _async_list_conversations(self):
+ import hangups
+ self._user_list, self._conversation_list = \
+ (await hangups.build_user_conversation_list(self._client))
+ users = {}
+ conversations = {}
+ for user in self._user_list.get_all():
+ users[str(user.id_.chat_id)] = {'full_name': user.full_name,
+ 'is_self': user.is_self}
+
+ for conv in self._conversation_list.get_all():
+ users_in_conversation = {}
+ for user in conv.users:
+ users_in_conversation[str(user.id_.chat_id)] = \
+ {'full_name': user.full_name, 'is_self': user.is_self}
+ conversations[str(conv.id_)] = \
+ {'name': conv.name, 'users': users_in_conversation}
+
+ self.hass.states.async_set("{}.users".format(DOMAIN),
+ len(self._user_list.get_all()),
+ attributes=users)
+ self.hass.states.async_set("{}.conversations".format(DOMAIN),
+ len(self._conversation_list.get_all()),
+ attributes=conversations)
+ dispatcher.async_dispatcher_send(self.hass,
+ EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
+ conversations)
+
+ async def async_handle_send_message(self, service):
+ """Handle the send_message service."""
+ await self._async_send_message(service.data[ATTR_MESSAGE],
+ service.data[ATTR_TARGET])
+
+ async def async_handle_update_users_and_conversations(self, _=None):
+ """Handle the update_users_and_conversations service."""
+ await self._async_list_conversations()
diff --git a/homeassistant/components/hangouts/hangups_utils.py b/homeassistant/components/hangouts/hangups_utils.py
new file mode 100644
index 00000000000..9aff7730201
--- /dev/null
+++ b/homeassistant/components/hangouts/hangups_utils.py
@@ -0,0 +1,81 @@
+"""Utils needed for Google Hangouts."""
+
+from hangups import CredentialsPrompt, GoogleAuthError, RefreshTokenCache
+
+
+class Google2FAError(GoogleAuthError):
+ """A Google authentication request failed."""
+
+
+class HangoutsCredentials(CredentialsPrompt):
+ """Google account credentials.
+
+ This implementation gets the user data as params.
+ """
+
+ def __init__(self, email, password, pin=None):
+ """Google account credentials.
+
+ :param email: Google account email address.
+ :param password: Google account password.
+ :param pin: Google account verification code.
+ """
+ self._email = email
+ self._password = password
+ self._pin = pin
+
+ def get_email(self):
+ """Return email.
+
+ :return: Google account email address.
+ """
+ return self._email
+
+ def get_password(self):
+ """Return password.
+
+ :return: Google account password.
+ """
+ return self._password
+
+ def get_verification_code(self):
+ """Return the verification code.
+
+ :return: Google account verification code.
+ """
+ if self._pin is None:
+ raise Google2FAError()
+ return self._pin
+
+ def set_verification_code(self, pin):
+ """Set the verification code.
+
+ :param pin: Google account verification code.
+ """
+ self._pin = pin
+
+
+class HangoutsRefreshToken(RefreshTokenCache):
+ """Memory-based cache for refresh token."""
+
+ def __init__(self, token):
+ """Memory-based cache for refresh token.
+
+ :param token: Initial refresh token.
+ """
+ super().__init__("")
+ self._token = token
+
+ def get(self):
+ """Get cached refresh token.
+
+ :return: Cached refresh token.
+ """
+ return self._token
+
+ def set(self, refresh_token):
+ """Cache a refresh token.
+
+ :param refresh_token: Refresh token to cache.
+ """
+ self._token = refresh_token
diff --git a/homeassistant/components/hangouts/services.yaml b/homeassistant/components/hangouts/services.yaml
new file mode 100644
index 00000000000..5d314bc2479
--- /dev/null
+++ b/homeassistant/components/hangouts/services.yaml
@@ -0,0 +1,12 @@
+update:
+ description: Updates the list of users and conversations.
+
+send_message:
+ description: Send a notification to a specific target.
+ fields:
+ target:
+ description: List of targets with id or name. [Required]
+ example: '[{"id": "UgxrXzVrARmjx_C6AZx4AaABAagBo-6UCw"}, {"name": "Test Conversation"}]'
+ message:
+ description: List of message segments, only the "text" field is required in every segment. [Required]
+ example: '[{"text":"test", "is_bold": false, "is_italic": false, "is_strikethrough": false, "is_underline": false, "parse_str": false, "link_target": "http://google.com"}, ...]'
\ No newline at end of file
diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json
new file mode 100644
index 00000000000..1b1ae54b41a
--- /dev/null
+++ b/homeassistant/components/hangouts/strings.json
@@ -0,0 +1,31 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Google Hangouts is already configured",
+ "unknown": "Unknown error occurred."
+ },
+ "error": {
+ "invalid_login": "Invalid Login, please try again.",
+ "invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
+ "invalid_2fa_method": "Invalig 2FA Method (Verify on Phone)."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "email": "E-Mail Address",
+ "password": "Password"
+ },
+ "description": "",
+ "title": "Google Hangouts Login"
+ },
+ "2fa": {
+ "data": {
+ "2fa": "2FA Pin"
+ },
+ "description": "",
+ "title": "2-Factor-Authorization"
+ }
+ },
+ "title": "Google Hangouts"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py
index ad2f8b4ac6d..eac02855b0b 100644
--- a/homeassistant/components/homekit/__init__.py
+++ b/homeassistant/components/homekit/__init__.py
@@ -57,7 +57,7 @@ CONFIG_SCHEMA = vol.Schema({
async def async_setup(hass, config):
- """Setup the HomeKit component."""
+ """Set up the HomeKit component."""
_LOGGER.debug('Begin setup HomeKit')
conf = config[DOMAIN]
@@ -196,7 +196,7 @@ class HomeKit():
self.driver = None
def setup(self):
- """Setup bridge and accessory driver."""
+ """Set up bridge and accessory driver."""
from .accessories import HomeBridge, HomeDriver
self.hass.bus.async_listen_once(
diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py
index a7e895f49e2..adf5273b639 100644
--- a/homeassistant/components/homekit/accessories.py
+++ b/homeassistant/components/homekit/accessories.py
@@ -27,17 +27,17 @@ _LOGGER = logging.getLogger(__name__)
def debounce(func):
- """Decorator function. Debounce callbacks form HomeKit."""
+ """Decorate function to debounce callbacks from HomeKit."""
@ha_callback
def call_later_listener(self, *args):
- """Callback listener called from call_later."""
+ """Handle call_later callback."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
self.hass.async_add_job(func, self, *debounce_params[1:])
@wraps(func)
def wrapper(self, *args):
- """Wrapper starts async timer."""
+ """Start async timer."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
debounce_params[0]() # remove listener
@@ -88,7 +88,7 @@ class HomeAccessory(Accessory):
CHAR_STATUS_LOW_BATTERY, value=0)
async def run(self):
- """Method called by accessory after driver is started.
+ """Handle accessory driver started event.
Run inside the HAP-python event loop.
"""
@@ -100,7 +100,7 @@ class HomeAccessory(Accessory):
@ha_callback
def update_state_callback(self, entity_id=None, old_state=None,
new_state=None):
- """Callback from state change listener."""
+ """Handle state change listener callback."""
_LOGGER.debug('New_state: %s', new_state)
if new_state is None:
return
@@ -131,7 +131,7 @@ class HomeAccessory(Accessory):
hk_charging)
def update_state(self, new_state):
- """Method called on state change to update HomeKit value.
+ """Handle state change to update HomeKit value.
Overridden by accessory types.
"""
diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py
index 23a907d43f7..9d60530edd7 100644
--- a/homeassistant/components/homekit/util.py
+++ b/homeassistant/components/homekit/util.py
@@ -109,7 +109,7 @@ def show_setup_message(hass, pincode):
"""Display persistent notification with setup information."""
pin = pincode.decode()
_LOGGER.info('Pincode: %s', pin)
- message = 'To setup Home Assistant in the Home App, enter the ' \
+ message = 'To set up Home Assistant in the Home App, enter the ' \
'following code:\n### {}'.format(pin)
hass.components.persistent_notification.create(
message, 'HomeKit Setup', HOMEKIT_NOTIFY_ID)
diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py
index 76c97793da0..527b8c8f018 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.46']
+REQUIREMENTS = ['pyhomematic==0.1.47']
_LOGGER = logging.getLogger(__name__)
@@ -48,6 +48,8 @@ ATTR_MESSAGE = 'message'
ATTR_MODE = 'mode'
ATTR_TIME = 'time'
ATTR_UNIQUE_ID = 'unique_id'
+ATTR_PARAMSET_KEY = 'paramset_key'
+ATTR_PARAMSET = 'paramset'
EVENT_KEYPRESS = 'homematic.keypress'
EVENT_IMPULSE = 'homematic.impulse'
@@ -58,6 +60,7 @@ SERVICE_RECONNECT = 'reconnect'
SERVICE_SET_VARIABLE_VALUE = 'set_variable_value'
SERVICE_SET_DEVICE_VALUE = 'set_device_value'
SERVICE_SET_INSTALL_MODE = 'set_install_mode'
+SERVICE_PUT_PARAMSET = 'put_paramset'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
@@ -78,7 +81,7 @@ HM_DEVICE_TYPES = {
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
- 'ThermostatGroup'],
+ 'ThermostatGroup', 'IPThermostatWall230V'],
DISCOVER_BINARY_SENSORS: [
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor',
@@ -103,7 +106,7 @@ HM_ATTRIBUTE_SUPPORT = {
'LOW_BAT': ['battery', {0: 'High', 1: 'Low'}],
'ERROR': ['sabotage', {0: 'No', 1: 'Yes'}],
'SABOTAGE': ['sabotage', {0: 'No', 1: 'Yes'}],
- 'RSSI_DEVICE': ['rssi', {}],
+ 'RSSI_PEER': ['rssi', {}],
'VALVE_STATE': ['valve', {}],
'BATTERY_STATE': ['battery', {}],
'CONTROL_MODE': ['mode', {
@@ -232,6 +235,13 @@ SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({
vol.Optional(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
})
+SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema({
+ vol.Required(ATTR_INTERFACE): cv.string,
+ vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
+ vol.Required(ATTR_PARAMSET_KEY): vol.All(cv.string, vol.Upper),
+ vol.Required(ATTR_PARAMSET): dict,
+})
+
@bind_hass
def virtualkey(hass, address, channel, param, interface=None):
@@ -271,6 +281,19 @@ def set_device_value(hass, address, channel, param, value, interface=None):
hass.services.call(DOMAIN, SERVICE_SET_DEVICE_VALUE, data)
+@bind_hass
+def put_paramset(hass, interface, address, paramset_key, paramset):
+ """Call putParamset XML-RPC method of supplied interface."""
+ data = {
+ ATTR_INTERFACE: interface,
+ ATTR_ADDRESS: address,
+ ATTR_PARAMSET_KEY: paramset_key,
+ ATTR_PARAMSET: paramset,
+ }
+
+ hass.services.call(DOMAIN, SERVICE_PUT_PARAMSET, data)
+
+
@bind_hass
def set_install_mode(hass, interface, mode=None, time=None, address=None):
"""Call setInstallMode XML-RPC method of supplied interface."""
@@ -439,6 +462,26 @@ def setup(hass, config):
DOMAIN, SERVICE_SET_INSTALL_MODE, _service_handle_install_mode,
schema=SCHEMA_SERVICE_SET_INSTALL_MODE)
+ def _service_put_paramset(service):
+ """Service to call the putParamset method on a HomeMatic connection."""
+ interface = service.data.get(ATTR_INTERFACE)
+ address = service.data.get(ATTR_ADDRESS)
+ paramset_key = service.data.get(ATTR_PARAMSET_KEY)
+ # When passing in the paramset from a YAML file we get an OrderedDict
+ # here instead of a dict, so add this explicit cast.
+ # The service schema makes sure that this cast works.
+ paramset = dict(service.data.get(ATTR_PARAMSET))
+
+ _LOGGER.debug(
+ "Calling putParamset: %s, %s, %s, %s",
+ interface, address, paramset_key, paramset
+ )
+ homematic.putParamset(interface, address, paramset_key, paramset)
+
+ hass.services.register(
+ DOMAIN, SERVICE_PUT_PARAMSET, _service_put_paramset,
+ schema=SCHEMA_SERVICE_PUT_PARAMSET)
+
return True
diff --git a/homeassistant/components/homematic/services.yaml b/homeassistant/components/homematic/services.yaml
index c2946b51842..044bcfa46ad 100644
--- a/homeassistant/components/homematic/services.yaml
+++ b/homeassistant/components/homematic/services.yaml
@@ -66,3 +66,20 @@ set_install_mode:
address:
description: (Optional) Address of homematic device or BidCoS-RF to learn
example: LEQ3948571
+
+put_paramset:
+ description: Call to putParamset in the RPC XML interface
+ fields:
+ interface:
+ description: The interfaces name from the config
+ example: wireless
+ address:
+ description: Address of Homematic device
+ example: LEQ3948571
+ paramset_key:
+ description: The paramset_key argument to putParamset
+ example: MASTER
+ paramset:
+ description: A paramset dictionary
+ example: '{"WEEK_PROGRAM_POINTER": 1}'
+
diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py
index f2cc8f443ac..05c5c970d2e 100644
--- a/homeassistant/components/homematicip_cloud/__init__.py
+++ b/homeassistant/components/homematicip_cloud/__init__.py
@@ -1,24 +1,23 @@
"""
-Support for HomematicIP components.
+Support for HomematicIP Cloud components.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematicip_cloud/
"""
-
import logging
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
+from homeassistant.const import CONF_NAME
+import homeassistant.helpers.config_validation as cv
-from .const import (
- DOMAIN, HMIPC_HAPID, HMIPC_AUTHTOKEN, HMIPC_NAME,
- CONF_ACCESSPOINT, CONF_AUTHTOKEN, CONF_NAME)
-# Loading the config flow file will register the flow
from .config_flow import configured_haps
-from .hap import HomematicipHAP, HomematicipAuth # noqa: F401
+from .const import (
+ CONF_ACCESSPOINT, CONF_AUTHTOKEN, DOMAIN, HMIPC_AUTHTOKEN, HMIPC_HAPID,
+ HMIPC_NAME)
from .device import HomematicipGenericDevice # noqa: F401
+from .hap import HomematicipAuth, HomematicipHAP # noqa: F401
REQUIREMENTS = ['homematicip==0.9.8']
@@ -34,7 +33,7 @@ CONFIG_SCHEMA = vol.Schema({
async def async_setup(hass, config):
- """Set up the HomematicIP component."""
+ """Set up the HomematicIP Cloud component."""
hass.data[DOMAIN] = {}
accesspoints = config.get(DOMAIN, [])
@@ -54,7 +53,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Set up an accsspoint from a config entry."""
+ """Set up an access point from a config entry."""
hap = HomematicipHAP(hass, entry)
hapid = entry.data[HMIPC_HAPID].replace('-', '').upper()
hass.data[DOMAIN][hapid] = hap
diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py
index 78970031d11..d5045cf151b 100644
--- a/homeassistant/components/homematicip_cloud/config_flow.py
+++ b/homeassistant/components/homematicip_cloud/config_flow.py
@@ -1,25 +1,25 @@
-"""Config flow to configure HomematicIP Cloud."""
+"""Config flow to configure the HomematicIP Cloud component."""
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant.core import callback
-from .const import (
- DOMAIN as HMIPC_DOMAIN, _LOGGER,
- HMIPC_HAPID, HMIPC_AUTHTOKEN, HMIPC_PIN, HMIPC_NAME)
+from .const import DOMAIN as HMIPC_DOMAIN
+from .const import HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN
+from .const import _LOGGER
from .hap import HomematicipAuth
@callback
def configured_haps(hass):
- """Return a set of the configured accesspoints."""
+ """Return a set of the configured access points."""
return set(entry.data[HMIPC_HAPID] for entry
in hass.config_entries.async_entries(HMIPC_DOMAIN))
@config_entries.HANDLERS.register(HMIPC_DOMAIN)
class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler):
- """Config flow HomematicIP Cloud."""
+ """Config flow for the HomematicIP Cloud component."""
VERSION = 1
@@ -44,28 +44,28 @@ class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler):
self.auth = HomematicipAuth(self.hass, user_input)
connected = await self.auth.async_setup()
if connected:
- _LOGGER.info("Connection established")
+ _LOGGER.info("Connection to HomematicIP Cloud established")
return await self.async_step_link()
return self.async_show_form(
step_id='init',
data_schema=vol.Schema({
vol.Required(HMIPC_HAPID): str,
- vol.Optional(HMIPC_PIN): str,
vol.Optional(HMIPC_NAME): str,
+ vol.Optional(HMIPC_PIN): str,
}),
errors=errors
)
async def async_step_link(self, user_input=None):
- """Attempt to link with the HomematicIP Cloud accesspoint."""
+ """Attempt to link with the HomematicIP Cloud access point."""
errors = {}
pressed = await self.auth.async_checkbutton()
if pressed:
authtoken = await self.auth.async_register()
if authtoken:
- _LOGGER.info("Write config entry")
+ _LOGGER.info("Write config entry for HomematicIP Cloud")
return self.async_create_entry(
title=self.auth.config.get(HMIPC_HAPID),
data={
@@ -73,13 +73,13 @@ class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler):
HMIPC_AUTHTOKEN: authtoken,
HMIPC_NAME: self.auth.config.get(HMIPC_NAME)
})
- return self.async_abort(reason='conection_aborted')
+ return self.async_abort(reason='connection_aborted')
errors['base'] = 'press_the_button'
return self.async_show_form(step_id='link', errors=errors)
async def async_step_import(self, import_info):
- """Import a new bridge as a config entry."""
+ """Import a new access point as a config entry."""
hapid = import_info[HMIPC_HAPID]
authtoken = import_info[HMIPC_AUTHTOKEN]
name = import_info[HMIPC_NAME]
@@ -88,13 +88,13 @@ class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler):
if hapid in configured_haps(self.hass):
return self.async_abort(reason='already_configured')
- _LOGGER.info('Imported authentication for %s', hapid)
+ _LOGGER.info("Imported authentication for %s", hapid)
return self.async_create_entry(
title=hapid,
data={
- HMIPC_HAPID: hapid,
HMIPC_AUTHTOKEN: authtoken,
- HMIPC_NAME: name
+ HMIPC_HAPID: hapid,
+ HMIPC_NAME: name,
}
)
diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py
index 54b05c464b5..ba9c37b83d7 100644
--- a/homeassistant/components/homematicip_cloud/const.py
+++ b/homeassistant/components/homematicip_cloud/const.py
@@ -14,7 +14,6 @@ COMPONENTS = [
'switch',
]
-CONF_NAME = 'name'
CONF_ACCESSPOINT = 'accesspoint'
CONF_AUTHTOKEN = 'authtoken'
diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py
index bb21e1df3d5..9c335befda4 100644
--- a/homeassistant/components/homematicip_cloud/device.py
+++ b/homeassistant/components/homematicip_cloud/device.py
@@ -1,25 +1,25 @@
-"""GenericDevice for the HomematicIP Cloud component."""
+"""Generic device for the HomematicIP Cloud component."""
import logging
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-ATTR_HOME_ID = 'home_id'
-ATTR_HOME_NAME = 'home_name'
+ATTR_CONNECTED = 'connected'
ATTR_DEVICE_ID = 'device_id'
ATTR_DEVICE_LABEL = 'device_label'
-ATTR_STATUS_UPDATE = 'status_update'
-ATTR_FIRMWARE_STATE = 'firmware_state'
-ATTR_UNREACHABLE = 'unreachable'
-ATTR_LOW_BATTERY = 'low_battery'
-ATTR_MODEL_TYPE = 'model_type'
-ATTR_GROUP_TYPE = 'group_type'
ATTR_DEVICE_RSSI = 'device_rssi'
ATTR_DUTY_CYCLE = 'duty_cycle'
-ATTR_CONNECTED = 'connected'
-ATTR_SABOTAGE = 'sabotage'
+ATTR_FIRMWARE_STATE = 'firmware_state'
+ATTR_GROUP_TYPE = 'group_type'
+ATTR_HOME_ID = 'home_id'
+ATTR_HOME_NAME = 'home_name'
+ATTR_LOW_BATTERY = 'low_battery'
+ATTR_MODEL_TYPE = 'model_type'
ATTR_OPERATION_LOCK = 'operation_lock'
+ATTR_SABOTAGE = 'sabotage'
+ATTR_STATUS_UPDATE = 'status_update'
+ATTR_UNREACHABLE = 'unreachable'
class HomematicipGenericDevice(Entity):
@@ -30,8 +30,7 @@ class HomematicipGenericDevice(Entity):
self._home = home
self._device = device
self.post = post
- _LOGGER.info('Setting up %s (%s)', self.name,
- self._device.modelType)
+ _LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType)
async def async_added_to_hass(self):
"""Register callbacks."""
@@ -39,16 +38,16 @@ class HomematicipGenericDevice(Entity):
def _device_changed(self, json, **kwargs):
"""Handle device state changes."""
- _LOGGER.debug('Event %s (%s)', self.name, self._device.modelType)
+ _LOGGER.debug("Event %s (%s)", self.name, self._device.modelType)
self.async_schedule_update_ha_state()
@property
def name(self):
"""Return the name of the generic device."""
name = self._device.label
- if (self._home.name is not None and self._home.name != ''):
+ if self._home.name is not None and self._home.name != '':
name = "{} {}".format(self._home.name, name)
- if (self.post is not None and self.post != ''):
+ if self.post is not None and self.post != '':
name = "{} {}".format(name, self.post)
return name
diff --git a/homeassistant/components/homematicip_cloud/errors.py b/homeassistant/components/homematicip_cloud/errors.py
index cb2925d1a70..1102cde6fbe 100644
--- a/homeassistant/components/homematicip_cloud/errors.py
+++ b/homeassistant/components/homematicip_cloud/errors.py
@@ -1,21 +1,21 @@
-"""Errors for the HomematicIP component."""
+"""Errors for the HomematicIP Cloud component."""
from homeassistant.exceptions import HomeAssistantError
class HmipcException(HomeAssistantError):
- """Base class for HomematicIP exceptions."""
+ """Base class for HomematicIP Cloud exceptions."""
class HmipcConnectionError(HmipcException):
- """Unable to connect to the HomematicIP cloud server."""
+ """Unable to connect to the HomematicIP Cloud server."""
class HmipcConnectionWait(HmipcException):
- """Wait for registration to the HomematicIP cloud server."""
+ """Wait for registration to the HomematicIP Cloud server."""
class HmipcRegistrationFailed(HmipcException):
- """Registration on HomematicIP cloud failed."""
+ """Registration on HomematicIP Cloud failed."""
class HmipcPressButton(HmipcException):
diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py
index 9715a5fc024..6fddc7c001e 100644
--- a/homeassistant/components/homematicip_cloud/hap.py
+++ b/homeassistant/components/homematicip_cloud/hap.py
@@ -1,14 +1,13 @@
-"""Accesspoint for the HomematicIP Cloud component."""
+"""Access point for the HomematicIP Cloud component."""
import asyncio
import logging
from homeassistant import config_entries
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.core import callback
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
- HMIPC_HAPID, HMIPC_AUTHTOKEN, HMIPC_PIN, HMIPC_NAME,
- COMPONENTS)
+ COMPONENTS, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN)
from .errors import HmipcConnectionError
_LOGGER = logging.getLogger(__name__)
@@ -74,10 +73,10 @@ class HomematicipAuth:
class HomematicipHAP:
- """Manages HomematicIP http and websocket connection."""
+ """Manages HomematicIP HTTP and WebSocket connection."""
def __init__(self, hass, config_entry):
- """Initialize HomematicIP cloud connection."""
+ """Initialize HomematicIP Cloud connection."""
self.hass = hass
self.config_entry = config_entry
self.home = None
@@ -100,7 +99,7 @@ class HomematicipHAP:
except HmipcConnectionError:
retry_delay = 2 ** min(tries + 1, 6)
_LOGGER.error("Error connecting to HomematicIP with HAP %s. "
- "Retrying in %d seconds.",
+ "Retrying in %d seconds",
self.config_entry.data.get(HMIPC_HAPID), retry_delay)
async def retry_setup(_now):
@@ -113,7 +112,7 @@ class HomematicipHAP:
return False
- _LOGGER.info('Connected to HomematicIP with HAP %s.',
+ _LOGGER.info("Connected to HomematicIP with HAP %s",
self.config_entry.data.get(HMIPC_HAPID))
for component in COMPONENTS:
@@ -127,7 +126,7 @@ class HomematicipHAP:
def async_update(self, *args, **kwargs):
"""Async update the home device.
- Triggered when the hmip HOME_CHANGED event has fired.
+ Triggered when the HMIP HOME_CHANGED event has fired.
There are several occasions for this event to happen.
We are only interested to check whether the access point
is still connected. If not, device state changes cannot
@@ -147,7 +146,7 @@ class HomematicipHAP:
job.add_done_callback(self.get_state_finished)
async def get_state(self):
- """Update hmip state and tell hass."""
+ """Update HMIP state and tell Home Assistant."""
await self.home.get_current_state()
self.update_all()
@@ -161,11 +160,11 @@ class HomematicipHAP:
# Somehow connection could not recover. Will disconnect and
# so reconnect loop is taking over.
_LOGGER.error(
- "updating state after himp access point reconnect failed.")
+ "Updating state after HMIP access point reconnect failed")
self.hass.async_add_job(self.home.disable_events())
def set_all_to_unavailable(self):
- """Set all devices to unavailable and tell Hass."""
+ """Set all devices to unavailable and tell Home Assistant."""
for device in self.home.devices:
device.unreach = True
self.update_all()
@@ -190,7 +189,7 @@ class HomematicipHAP:
return
async def async_connect(self):
- """Start websocket connection."""
+ """Start WebSocket connection."""
from homematicip.base.base_connection import HmipConnectionError
tries = 0
@@ -210,7 +209,7 @@ class HomematicipHAP:
tries += 1
retry_delay = 2 ** min(tries + 1, 6)
_LOGGER.error("Error connecting to HomematicIP with HAP %s. "
- "Retrying in %d seconds.",
+ "Retrying in %d seconds",
self.config_entry.data.get(HMIPC_HAPID), retry_delay)
try:
self._retry_task = self.hass.async_add_job(asyncio.sleep(
@@ -227,7 +226,7 @@ class HomematicipHAP:
if self._retry_task is not None:
self._retry_task.cancel()
self.home.disable_events()
- _LOGGER.info("Closed connection to HomematicIP cloud server.")
+ _LOGGER.info("Closed connection to HomematicIP cloud server")
for component in COMPONENTS:
await self.hass.config_entries.async_forward_entry_unload(
self.config_entry, component)
diff --git a/homeassistant/components/homematicip_cloud/strings.json b/homeassistant/components/homematicip_cloud/strings.json
index 887a3a5780b..f2d38a1dc7b 100644
--- a/homeassistant/components/homematicip_cloud/strings.json
+++ b/homeassistant/components/homematicip_cloud/strings.json
@@ -3,16 +3,16 @@
"title": "HomematicIP Cloud",
"step": {
"init": {
- "title": "Pick HomematicIP Accesspoint",
+ "title": "Pick HomematicIP Access point",
"data": {
- "hapid": "Accesspoint ID (SGTIN)",
+ "hapid": "Access point ID (SGTIN)",
"pin": "Pin Code (optional)",
"name": "Name (optional, used as name prefix for all devices)"
}
},
"link": {
- "title": "Link Accesspoint",
- "description": "Press the blue button on the accesspoint and the submit button to register HomematicIP with Home Assistant.\n\n"
+ "title": "Link Access point",
+ "description": "Press the blue button on the access point and the submit button to register HomematicIP with Home Assistant.\n\n"
}
},
"error": {
@@ -23,8 +23,8 @@
},
"abort": {
"unknown": "Unknown error occurred.",
- "conection_aborted": "Could not connect to HMIP server",
- "already_configured": "Accesspoint is already configured"
+ "connection_aborted": "Could not connect to HMIP server",
+ "already_configured": "Access point is already configured"
}
}
}
diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py
index 9ded94fa6fd..ac08c26229c 100644
--- a/homeassistant/components/http/__init__.py
+++ b/homeassistant/components/http/__init__.py
@@ -232,7 +232,8 @@ class HomeAssistantHTTP:
self.is_ban_enabled = is_ban_enabled
self.ssl_profile = ssl_profile
self._handler = None
- self.server = None
+ self.runner = None
+ self.site = None
def register_view(self, view):
"""Register a view with the WSGI server.
@@ -308,7 +309,7 @@ class HomeAssistantHTTP:
self.app.router.add_route('GET', url_pattern, serve_file)
async def start(self):
- """Start the WSGI server."""
+ """Start the aiohttp server."""
# We misunderstood the startup signal. You're not allowed to change
# anything during startup. Temp workaround.
# pylint: disable=protected-access
@@ -321,7 +322,9 @@ class HomeAssistantHTTP:
context = ssl_util.server_context_intermediate()
else:
context = ssl_util.server_context_modern()
- context.load_cert_chain(self.ssl_certificate, self.ssl_key)
+ await self.hass.async_add_executor_job(
+ context.load_cert_chain, self.ssl_certificate,
+ self.ssl_key)
except OSError as error:
_LOGGER.error("Could not read SSL certificate from %s: %s",
self.ssl_certificate, error)
@@ -329,7 +332,9 @@ class HomeAssistantHTTP:
if self.ssl_peer_certificate:
context.verify_mode = ssl.CERT_REQUIRED
- context.load_verify_locations(cafile=self.ssl_peer_certificate)
+ await self.hass.async_add_executor_job(
+ context.load_verify_locations,
+ self.ssl_peer_certificate)
else:
context = None
@@ -340,21 +345,17 @@ class HomeAssistantHTTP:
# To work around this we now prevent the router from getting frozen
self.app._router.freeze = lambda: None
- self._handler = self.app.make_handler(loop=self.hass.loop)
-
+ self.runner = web.AppRunner(self.app)
+ await self.runner.setup()
+ self.site = web.TCPSite(self.runner, self.server_host,
+ self.server_port, ssl_context=context)
try:
- self.server = await self.hass.loop.create_server(
- self._handler, self.server_host, self.server_port, ssl=context)
+ await self.site.start()
except OSError as error:
_LOGGER.error("Failed to create HTTP server at port %d: %s",
self.server_port, error)
async def stop(self):
- """Stop the WSGI server."""
- if self.server:
- self.server.close()
- await self.server.wait_closed()
- await self.app.shutdown()
- if self._handler:
- await self._handler.shutdown(10)
- await self.app.cleanup()
+ """Stop the aiohttp server."""
+ await self.site.stop()
+ await self.runner.cleanup()
diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py
index ab582066a22..015c386e836 100644
--- a/homeassistant/components/http/ban.py
+++ b/homeassistant/components/http/ban.py
@@ -71,7 +71,7 @@ async def ban_middleware(request, handler):
def log_invalid_auth(func):
- """Decorator to handle invalid auth or failed login attempts."""
+ """Decorate function 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)
diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py
index 555f302f8e1..5698c6048e3 100644
--- a/homeassistant/components/http/cors.py
+++ b/homeassistant/components/http/cors.py
@@ -1,4 +1,4 @@
-"""Provide cors support for the HTTP component."""
+"""Provide CORS support for the HTTP component."""
from aiohttp.hdrs import ACCEPT, ORIGIN, CONTENT_TYPE
@@ -17,7 +17,7 @@ ALLOWED_CORS_HEADERS = [
@callback
def setup_cors(app, origins):
- """Setup cors."""
+ """Set up CORS."""
import aiohttp_cors
cors = aiohttp_cors.setup(app, defaults={
@@ -30,7 +30,7 @@ def setup_cors(app, origins):
cors_added = set()
def _allow_cors(route, config=None):
- """Allow cors on a route."""
+ """Allow CORS on a route."""
if hasattr(route, 'resource'):
path = route.resource
else:
@@ -55,7 +55,7 @@ def setup_cors(app, origins):
return
async def cors_startup(app):
- """Initialize cors when app starts up."""
+ """Initialize CORS when app starts up."""
for route in list(app.router.routes()):
_allow_cors(route)
diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py
index 22ef34de54a..b3b2587fc45 100644
--- a/homeassistant/components/http/view.py
+++ b/homeassistant/components/http/view.py
@@ -11,10 +11,10 @@ import logging
from aiohttp import web
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 Context, is_callback
from homeassistant.const import CONTENT_TYPE_JSON
+from homeassistant.helpers.json import JSONEncoder
from .const import KEY_AUTHENTICATED, KEY_REAL_IP
@@ -44,7 +44,7 @@ class HomeAssistantView:
"""Return a JSON response."""
try:
msg = json.dumps(
- result, sort_keys=True, cls=rem.JSONEncoder).encode('UTF-8')
+ result, sort_keys=True, cls=JSONEncoder).encode('UTF-8')
except TypeError as err:
_LOGGER.error('Unable to serialize to JSON: %s\n%s', err, result)
raise HTTPInternalServerError
diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py
index 672964f765e..9b00f3bd789 100644
--- a/homeassistant/components/ihc/__init__.py
+++ b/homeassistant/components/ihc/__init__.py
@@ -123,7 +123,7 @@ def setup(hass, config):
def autosetup_ihc_products(hass: HomeAssistantType, config, ihc_controller):
- """Auto setup of IHC products from the ihc project file."""
+ """Auto setup of IHC products from the IHC project file."""
project_xml = ihc_controller.get_project()
if not project_xml:
_LOGGER.error("Unable to read project from ICH controller")
@@ -177,7 +177,7 @@ def get_discovery_info(component_setup, groups):
def setup_service_functions(hass: HomeAssistantType, ihc_controller):
- """Setup the IHC service functions."""
+ """Set up the IHC service functions."""
def set_runtime_value_bool(call):
"""Set a IHC runtime bool value service function."""
ihc_id = call.data[ATTR_IHC_ID]
diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py
index 2ccca366d90..93ab81850c9 100644
--- a/homeassistant/components/ihc/ihcdevice.py
+++ b/homeassistant/components/ihc/ihcdevice.py
@@ -57,7 +57,7 @@ class IHCDevice(Entity):
}
def on_ihc_change(self, ihc_id, value):
- """Callback when IHC resource changes.
+ """Handle IHC resource change.
Derived classes must overwrite this to do device specific stuff.
"""
diff --git a/homeassistant/components/ihc/services.yaml b/homeassistant/components/ihc/services.yaml
index 7b6053eff89..a0cc6774fdf 100644
--- a/homeassistant/components/ihc/services.yaml
+++ b/homeassistant/components/ihc/services.yaml
@@ -1,26 +1,26 @@
-# Describes the format for available ihc services
+# Describes the format for available IHC services
set_runtime_value_bool:
- description: Set a boolean runtime value on the ihc controller
+ description: Set a boolean runtime value on the IHC controller
fields:
ihc_id:
- description: The integer ihc resource id
+ description: The integer IHC resource id
value:
description: The boolean value to set
set_runtime_value_int:
- description: Set an integer runtime value on the ihc controller
+ description: Set an integer runtime value on the IHC controller
fields:
ihc_id:
- description: The integer ihc resource id
+ description: The integer IHC resource id
value:
description: The integer value to set
set_runtime_value_float:
- description: Set a float runtime value on the ihc controller
+ description: Set a float runtime value on the IHC controller
fields:
ihc_id:
- description: The integer ihc resource id
+ description: The integer IHC resource id
value:
description: The float value to set
diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/image_processing/opencv.py
index 00ae01f1123..e21ddbe2597 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.15.0']
+REQUIREMENTS = ['numpy==1.15.1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/input_datetime.py b/homeassistant/components/input_datetime.py
index a77b67792f5..df35ae53ba9 100644
--- a/homeassistant/components/input_datetime.py
+++ b/homeassistant/components/input_datetime.py
@@ -4,14 +4,12 @@ Component to offer a way to select a date and / or a time.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_datetime/
"""
-import asyncio
import logging
import datetime
import voluptuous as vol
-from homeassistant.const import (
- ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_UNKNOWN)
+from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -39,6 +37,15 @@ SERVICE_SET_DATETIME_SCHEMA = vol.Schema({
vol.Optional(ATTR_TIME): cv.time,
})
+
+def has_date_or_time(conf):
+ """Check at least date or time is true."""
+ if conf[CONF_HAS_DATE] or conf[CONF_HAS_TIME]:
+ return conf
+
+ raise vol.Invalid('Entity needs at least a date or a time')
+
+
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
cv.slug: vol.All({
@@ -47,23 +54,11 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_HAS_TIME, default=False): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL): cv.string,
- }, cv.has_at_least_one_key_value((CONF_HAS_DATE, True),
- (CONF_HAS_TIME, True)))})
+ }, has_date_or_time)})
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_set_datetime(hass, entity_id, dt_value):
- """Set date and / or time of input_datetime."""
- yield from hass.services.async_call(DOMAIN, SERVICE_SET_DATETIME, {
- ATTR_ENTITY_ID: entity_id,
- ATTR_DATE: dt_value.date(),
- ATTR_TIME: dt_value.time()
- })
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up an input datetime."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -81,32 +76,24 @@ def async_setup(hass, config):
if not entities:
return False
- @asyncio.coroutine
- def async_set_datetime_service(call):
+ async def async_set_datetime_service(entity, call):
"""Handle a call to the input datetime 'set datetime' service."""
- target_inputs = component.async_extract_from_service(call)
+ time = call.data.get(ATTR_TIME)
+ date = call.data.get(ATTR_DATE)
+ if (entity.has_date and not date) or (entity.has_time and not time):
+ _LOGGER.error("Invalid service data for %s "
+ "input_datetime.set_datetime: %s",
+ entity.entity_id, str(call.data))
+ return
- tasks = []
- for input_datetime in target_inputs:
- time = call.data.get(ATTR_TIME)
- date = call.data.get(ATTR_DATE)
- if (input_datetime.has_date() and not date) or \
- (input_datetime.has_time() and not time):
- _LOGGER.error("Invalid service data for "
- "input_datetime.set_datetime: %s",
- str(call.data))
- continue
+ entity.async_set_datetime(date, time)
- tasks.append(input_datetime.async_set_datetime(date, time))
+ component.async_register_entity_service(
+ SERVICE_SET_DATETIME, SERVICE_SET_DATETIME_SCHEMA,
+ async_set_datetime_service
+ )
- if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
-
- hass.services.async_register(
- DOMAIN, SERVICE_SET_DATETIME, async_set_datetime_service,
- schema=SERVICE_SET_DATETIME_SCHEMA)
-
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
return True
@@ -117,14 +104,13 @@ class InputDatetime(Entity):
"""Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
- self._has_date = has_date
- self._has_time = has_time
+ self.has_date = has_date
+ self.has_time = has_time
self._icon = icon
self._initial = initial
self._current_datetime = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Run when entity about to be added."""
restore_val = None
@@ -134,27 +120,18 @@ class InputDatetime(Entity):
# Priority 2: Old state
if restore_val is None:
- old_state = yield from async_get_last_state(self.hass,
- self.entity_id)
+ old_state = await async_get_last_state(self.hass, self.entity_id)
if old_state is not None:
restore_val = old_state.state
if restore_val is not None:
- if not self._has_date:
+ if not self.has_date:
self._current_datetime = dt_util.parse_time(restore_val)
- elif not self._has_time:
+ elif not self.has_time:
self._current_datetime = dt_util.parse_date(restore_val)
else:
self._current_datetime = dt_util.parse_datetime(restore_val)
- def has_date(self):
- """Return whether the input datetime carries a date."""
- return self._has_date
-
- def has_time(self):
- """Return whether the input datetime carries a time."""
- return self._has_time
-
@property
def should_poll(self):
"""If entity should be polled."""
@@ -173,55 +150,50 @@ class InputDatetime(Entity):
@property
def state(self):
"""Return the state of the component."""
- if self._current_datetime is None:
- return STATE_UNKNOWN
-
return self._current_datetime
@property
def state_attributes(self):
"""Return the state attributes."""
attrs = {
- 'has_date': self._has_date,
- 'has_time': self._has_time,
+ 'has_date': self.has_date,
+ 'has_time': self.has_time,
}
if self._current_datetime is None:
return attrs
- if self._has_date and self._current_datetime is not None:
+ if self.has_date and self._current_datetime is not None:
attrs['year'] = self._current_datetime.year
attrs['month'] = self._current_datetime.month
attrs['day'] = self._current_datetime.day
- if self._has_time and self._current_datetime is not None:
+ if self.has_time and self._current_datetime is not None:
attrs['hour'] = self._current_datetime.hour
attrs['minute'] = self._current_datetime.minute
attrs['second'] = self._current_datetime.second
- if self._current_datetime is not None:
- if not self._has_date:
- attrs['timestamp'] = self._current_datetime.hour * 3600 + \
- self._current_datetime.minute * 60 + \
- self._current_datetime.second
- elif not self._has_time:
- extended = datetime.datetime.combine(self._current_datetime,
- datetime.time(0, 0))
- attrs['timestamp'] = extended.timestamp()
- else:
- attrs['timestamp'] = self._current_datetime.timestamp()
+ if not self.has_date:
+ attrs['timestamp'] = self._current_datetime.hour * 3600 + \
+ self._current_datetime.minute * 60 + \
+ self._current_datetime.second
+ elif not self.has_time:
+ extended = datetime.datetime.combine(self._current_datetime,
+ datetime.time(0, 0))
+ attrs['timestamp'] = extended.timestamp()
+ else:
+ attrs['timestamp'] = self._current_datetime.timestamp()
return attrs
- @asyncio.coroutine
def async_set_datetime(self, date_val, time_val):
"""Set a new date / time."""
- if self._has_date and self._has_time and date_val and time_val:
+ if self.has_date and self.has_time and date_val and time_val:
self._current_datetime = datetime.datetime.combine(date_val,
time_val)
- elif self._has_date and not self._has_time and date_val:
+ elif self.has_date and not self.has_time and date_val:
self._current_datetime = date_val
- if self._has_time and not self._has_date and time_val:
+ if self.has_time and not self.has_date and time_val:
self._current_datetime = time_val
- yield from self.async_update_ha_state()
+ self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon/__init__.py
similarity index 73%
rename from homeassistant/components/insteon_plm/__init__.py
rename to homeassistant/components/insteon/__init__.py
index 055015b74f5..212cdbac3b8 100644
--- a/homeassistant/components/insteon_plm/__init__.py
+++ b/homeassistant/components/insteon/__init__.py
@@ -1,8 +1,8 @@
"""
-Support for INSTEON PowerLinc Modem.
+Support for INSTEON Modems (PLM and Hub).
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/insteon_plm/
+https://home-assistant.io/components/insteon/
"""
import asyncio
import collections
@@ -12,18 +12,24 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (CONF_PORT, EVENT_HOMEASSISTANT_STOP,
CONF_PLATFORM,
- CONF_ENTITY_ID)
+ CONF_ENTITY_ID,
+ CONF_HOST)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['insteonplm==0.11.7']
+REQUIREMENTS = ['insteonplm==0.12.3']
_LOGGER = logging.getLogger(__name__)
-DOMAIN = 'insteon_plm'
+DOMAIN = 'insteon'
+CONF_IP_PORT = 'ip_port'
+CONF_HUB_USERNAME = 'username'
+CONF_HUB_PASSWORD = 'password'
CONF_OVERRIDE = 'device_override'
+CONF_PLM_HUB_MSG = ('Must configure either a PLM port or a Hub host, username '
+ 'and password')
CONF_ADDRESS = 'address'
CONF_CAT = 'cat'
CONF_SUBCAT = 'subcat'
@@ -56,8 +62,8 @@ 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_BUTTON_ON = 'insteon.button_on'
+EVENT_BUTTON_OFF = 'insteon.button_off'
EVENT_CONF_BUTTON = 'button'
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
@@ -79,17 +85,34 @@ CONF_X10_SCHEMA = vol.All(
}))
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_PORT): cv.string,
- vol.Optional(CONF_OVERRIDE): vol.All(
- cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA]),
- vol.Optional(CONF_X10_ALL_UNITS_OFF): vol.In(HOUSECODES),
- vol.Optional(CONF_X10_ALL_LIGHTS_ON): vol.In(HOUSECODES),
- vol.Optional(CONF_X10_ALL_LIGHTS_OFF): vol.In(HOUSECODES),
- vol.Optional(CONF_X10): vol.All(
- cv.ensure_list_csv, [CONF_X10_SCHEMA])
- })
-}, extra=vol.ALLOW_EXTRA)
+ DOMAIN: vol.All(
+ vol.Schema(
+ {vol.Exclusive(CONF_PORT, 'plm_or_hub',
+ msg=CONF_PLM_HUB_MSG): cv.isdevice,
+ vol.Exclusive(CONF_HOST, 'plm_or_hub',
+ msg=CONF_PLM_HUB_MSG): cv.string,
+ vol.Optional(CONF_IP_PORT, default=25105): int,
+ vol.Optional(CONF_HUB_USERNAME): cv.string,
+ vol.Optional(CONF_HUB_PASSWORD): cv.string,
+ vol.Optional(CONF_OVERRIDE): vol.All(
+ cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA]),
+ vol.Optional(CONF_X10_ALL_UNITS_OFF): vol.In(HOUSECODES),
+ vol.Optional(CONF_X10_ALL_LIGHTS_ON): vol.In(HOUSECODES),
+ vol.Optional(CONF_X10_ALL_LIGHTS_OFF): vol.In(HOUSECODES),
+ vol.Optional(CONF_X10): vol.All(cv.ensure_list_csv,
+ [CONF_X10_SCHEMA])
+ }, extra=vol.ALLOW_EXTRA, required=True),
+ cv.has_at_least_one_key(CONF_PORT, CONF_HOST),
+ vol.Schema(
+ {vol.Inclusive(CONF_HOST, 'hub',
+ msg=CONF_PLM_HUB_MSG): cv.string,
+ vol.Inclusive(CONF_HUB_USERNAME, 'hub',
+ msg=CONF_PLM_HUB_MSG): cv.string,
+ vol.Inclusive(CONF_HUB_PASSWORD, 'hub',
+ msg=CONF_PLM_HUB_MSG): cv.string,
+ }, extra=vol.ALLOW_EXTRA, required=True))
+ }, extra=vol.ALLOW_EXTRA)
+
ADD_ALL_LINK_SCHEMA = vol.Schema({
vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255),
@@ -116,14 +139,18 @@ X10_HOUSECODE_SCHEMA = vol.Schema({
@asyncio.coroutine
def async_setup(hass, config):
- """Set up the connection to the PLM."""
+ """Set up the connection to the modem."""
import insteonplm
ipdb = IPDB()
- plm = None
+ insteon_modem = None
conf = config[DOMAIN]
port = conf.get(CONF_PORT)
+ host = conf.get(CONF_HOST)
+ ip_port = conf.get(CONF_IP_PORT)
+ username = conf.get(CONF_HUB_USERNAME)
+ password = conf.get(CONF_HUB_PASSWORD)
overrides = conf.get(CONF_OVERRIDE, [])
x10_devices = conf.get(CONF_X10, [])
x10_all_units_off_housecode = conf.get(CONF_X10_ALL_UNITS_OFF)
@@ -131,7 +158,7 @@ def async_setup(hass, config):
x10_all_lights_off_housecode = conf.get(CONF_X10_ALL_LIGHTS_OFF)
@callback
- def async_plm_new_device(device):
+ def async_new_insteon_device(device):
"""Detect device from transport to be delegated to platform."""
for state_key in device.states:
platform_info = ipdb[device.states[state_key]]
@@ -143,7 +170,7 @@ def async_setup(hass, config):
_fire_button_on_off_event)
else:
- _LOGGER.info("New INSTEON PLM device: %s (%s) %s",
+ _LOGGER.info("New INSTEON device: %s (%s) %s",
device.address,
device.states[state_key].name,
platform)
@@ -160,12 +187,12 @@ def async_setup(hass, config):
group = service.data.get(SRV_ALL_LINK_GROUP)
mode = service.data.get(SRV_ALL_LINK_MODE)
link_mode = 1 if mode.lower() == SRV_CONTROLLER else 0
- plm.start_all_linking(link_mode, group)
+ insteon_modem.start_all_linking(link_mode, group)
def del_all_link(service):
"""Delete an INSTEON All-Link between two devices."""
group = service.data.get(SRV_ALL_LINK_GROUP)
- plm.start_all_linking(255, group)
+ insteon_modem.start_all_linking(255, group)
def load_aldb(service):
"""Load the device All-Link database."""
@@ -194,22 +221,22 @@ def async_setup(hass, config):
"""Print the All-Link Database for a device."""
# For now this sends logs to the log file.
# Furture direction is to create an INSTEON control panel.
- print_aldb_to_log(plm.aldb)
+ print_aldb_to_log(insteon_modem.aldb)
def x10_all_units_off(service):
"""Send the X10 All Units Off command."""
housecode = service.data.get(SRV_HOUSECODE)
- plm.x10_all_units_off(housecode)
+ insteon_modem.x10_all_units_off(housecode)
def x10_all_lights_off(service):
"""Send the X10 All Lights Off command."""
housecode = service.data.get(SRV_HOUSECODE)
- plm.x10_all_lights_off(housecode)
+ insteon_modem.x10_all_lights_off(housecode)
def x10_all_lights_on(service):
"""Send the X10 All Lights On command."""
housecode = service.data.get(SRV_HOUSECODE)
- plm.x10_all_lights_on(housecode)
+ insteon_modem.x10_all_lights_on(housecode)
def _register_services():
hass.services.register(DOMAIN, SRV_ADD_ALL_LINK, add_all_link,
@@ -231,11 +258,11 @@ def async_setup(hass, config):
hass.services.register(DOMAIN, SRV_X10_ALL_LIGHTS_ON,
x10_all_lights_on,
schema=X10_HOUSECODE_SCHEMA)
- _LOGGER.debug("Insteon_plm Services registered")
+ _LOGGER.debug("Insteon Services registered")
def _fire_button_on_off_event(address, group, val):
# Firing an event when a button is pressed.
- device = plm.devices[address.hex]
+ device = insteon_modem.devices[address.hex]
state_name = device.states[group].name
button = ("" if state_name == BUTTON_PRESSED_STATE_NAME
else state_name[-1].lower())
@@ -250,13 +277,23 @@ def async_setup(hass, config):
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,
- loop=hass.loop,
- workdir=hass.config.config_dir)
+ if host:
+ _LOGGER.info('Connecting to Insteon Hub on %s', host)
+ conn = yield from insteonplm.Connection.create(
+ host=host,
+ port=ip_port,
+ username=username,
+ password=password,
+ loop=hass.loop,
+ workdir=hass.config.config_dir)
+ else:
+ _LOGGER.info("Looking for Insteon PLM on %s", port)
+ conn = yield from insteonplm.Connection.create(
+ device=port,
+ loop=hass.loop,
+ workdir=hass.config.config_dir)
- plm = conn.protocol
+ insteon_modem = conn.protocol
for device_override in overrides:
#
@@ -265,32 +302,32 @@ def async_setup(hass, config):
address = device_override.get('address')
for prop in device_override:
if prop in [CONF_CAT, CONF_SUBCAT]:
- plm.devices.add_override(address, prop,
- device_override[prop])
+ insteon_modem.devices.add_override(address, prop,
+ device_override[prop])
elif prop in [CONF_FIRMWARE, CONF_PRODUCT_KEY]:
- plm.devices.add_override(address, CONF_PRODUCT_KEY,
- device_override[prop])
+ insteon_modem.devices.add_override(address, CONF_PRODUCT_KEY,
+ device_override[prop])
hass.data[DOMAIN] = {}
- hass.data[DOMAIN]['plm'] = plm
+ hass.data[DOMAIN]['modem'] = insteon_modem
hass.data[DOMAIN]['entities'] = {}
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, conn.close)
- plm.devices.add_device_callback(async_plm_new_device)
+ insteon_modem.devices.add_device_callback(async_new_insteon_device)
if x10_all_units_off_housecode:
- device = plm.add_x10_device(x10_all_units_off_housecode,
- 20,
- 'allunitsoff')
+ device = insteon_modem.add_x10_device(x10_all_units_off_housecode,
+ 20,
+ 'allunitsoff')
if x10_all_lights_on_housecode:
- device = plm.add_x10_device(x10_all_lights_on_housecode,
- 21,
- 'alllightson')
+ device = insteon_modem.add_x10_device(x10_all_lights_on_housecode,
+ 21,
+ 'alllightson')
if x10_all_lights_off_housecode:
- device = plm.add_x10_device(x10_all_lights_off_housecode,
- 22,
- 'alllightsoff')
+ device = insteon_modem.add_x10_device(x10_all_lights_off_housecode,
+ 22,
+ 'alllightsoff')
for device in x10_devices:
housecode = device.get(CONF_HOUSECODE)
unitcode = device.get(CONF_UNITCODE)
@@ -300,11 +337,11 @@ def async_setup(hass, config):
x10_type = 'dimmable'
elif device.get(CONF_PLATFORM) == 'binary_sensor':
x10_type = 'sensor'
- _LOGGER.debug("Adding X10 device to insteonplm: %s %d %s",
+ _LOGGER.debug("Adding X10 device to Insteon: %s %d %s",
housecode, unitcode, x10_type)
- device = plm.add_x10_device(housecode,
- unitcode,
- x10_type)
+ device = insteon_modem.add_x10_device(housecode,
+ unitcode,
+ x10_type)
if device and hasattr(device.states[0x01], 'steps'):
device.states[0x01].steps = steps
@@ -324,11 +361,14 @@ class IPDB:
from insteonplm.states.onOff import (OnOffSwitch,
OnOffSwitch_OutletTop,
OnOffSwitch_OutletBottom,
- OpenClosedRelay)
+ OpenClosedRelay,
+ OnOffKeypadA,
+ OnOffKeypad)
from insteonplm.states.dimmable import (DimmableSwitch,
DimmableSwitch_Fan,
- DimmableRemote)
+ DimmableRemote,
+ DimmableKeypadA)
from insteonplm.states.sensor import (VariableSensor,
OnOffSensor,
@@ -347,6 +387,8 @@ class IPDB:
State(OnOffSwitch_OutletBottom, 'switch'),
State(OpenClosedRelay, 'switch'),
State(OnOffSwitch, 'switch'),
+ State(OnOffKeypadA, 'switch'),
+ State(OnOffKeypad, 'switch'),
State(LeakSensorDryWet, 'binary_sensor'),
State(IoLincSensor, 'binary_sensor'),
@@ -357,6 +399,7 @@ class IPDB:
State(DimmableSwitch_Fan, 'fan'),
State(DimmableSwitch, 'light'),
State(DimmableRemote, 'on_off_events'),
+ State(DimmableKeypadA, 'light'),
State(X10DimmableSwitch, 'light'),
State(X10OnOffSwitch, 'switch'),
@@ -382,11 +425,11 @@ class IPDB:
return None
-class InsteonPLMEntity(Entity):
+class InsteonEntity(Entity):
"""INSTEON abstract base entity."""
def __init__(self, device, state_key):
- """Initialize the INSTEON PLM binary sensor."""
+ """Initialize the INSTEON binary sensor."""
self._insteon_device_state = device.states[state_key]
self._insteon_device = device
self._insteon_device.aldb.add_loaded_callback(self._aldb_loaded)
@@ -429,11 +472,17 @@ class InsteonPLMEntity(Entity):
@callback
def async_entity_update(self, deviceid, statename, val):
"""Receive notification from transport that new data exists."""
+ _LOGGER.debug('Received update for device %s group %d statename %s',
+ self.address, self.group,
+ self._insteon_device_state.name)
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_added_to_hass(self):
"""Register INSTEON update events."""
+ _LOGGER.debug('Tracking updates for device %s group %d statename %s',
+ self.address, self.group,
+ self._insteon_device_state.name)
self._insteon_device_state.register_updates(
self.async_entity_update)
self.hass.data[DOMAIN]['entities'][self.entity_id] = self
@@ -460,7 +509,7 @@ def print_aldb_to_log(aldb):
_LOGGER.info('ALDB load status is %s', aldb.status.name)
if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]:
_LOGGER.warning('Device All-Link database not loaded')
- _LOGGER.warning('Use service insteon_plm.load_aldb first')
+ _LOGGER.warning('Use service insteon.load_aldb first')
return
_LOGGER.info('RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3')
diff --git a/homeassistant/components/insteon_plm/services.yaml b/homeassistant/components/insteon/services.yaml
similarity index 100%
rename from homeassistant/components/insteon_plm/services.yaml
rename to homeassistant/components/insteon/services.yaml
diff --git a/homeassistant/components/insteon_local.py b/homeassistant/components/insteon_local.py
index a18d4e0aa14..003714d0f94 100644
--- a/homeassistant/components/insteon_local.py
+++ b/homeassistant/components/insteon_local.py
@@ -5,82 +5,24 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon_local/
"""
import logging
-import os
-
-import requests
-import voluptuous as vol
-
-from homeassistant.const import (
- CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT, CONF_USERNAME)
-import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.discovery import load_platform
-
-REQUIREMENTS = ['insteonlocal==0.53']
_LOGGER = logging.getLogger(__name__)
-DEFAULT_PORT = 25105
-DEFAULT_TIMEOUT = 10
-DOMAIN = 'insteon_local'
-
-INSTEON_CACHE = '.insteon_local_cache'
-
-INSTEON_PLATFORMS = [
- 'light',
- 'switch',
- 'fan',
-]
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_HOST): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Required(CONF_USERNAME): cv.string,
- vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
- vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
- })
-}, extra=vol.ALLOW_EXTRA)
-
def setup(hass, config):
- """Set up the local Insteon hub."""
- from insteonlocal.Hub import Hub
+ """Set up the insteon_local component.
- conf = config[DOMAIN]
- username = conf.get(CONF_USERNAME)
- password = conf.get(CONF_PASSWORD)
- host = conf.get(CONF_HOST)
- port = conf.get(CONF_PORT)
- timeout = conf.get(CONF_TIMEOUT)
+ This component is deprecated as of release 0.77 and should be removed in
+ release 0.90.
+ """
+ _LOGGER.warning('The insteon_local component has been replaced by '
+ 'the insteon component')
+ _LOGGER.warning('Please see https://home-assistant.io/components/insteon')
- try:
- if not os.path.exists(hass.config.path(INSTEON_CACHE)):
- os.makedirs(hass.config.path(INSTEON_CACHE))
+ hass.components.persistent_notification.create(
+ 'insteon_local has been replaced by the insteon component.
'
+ 'Please see https://home-assistant.io/components/insteon',
+ title='insteon_local Component Deactivated',
+ notification_id='insteon_local')
- insteonhub = Hub(host, username, password, port, timeout, _LOGGER,
- hass.config.path(INSTEON_CACHE))
-
- # Check for successful connection
- insteonhub.get_buffer_status()
- except requests.exceptions.ConnectTimeout:
- _LOGGER.error("Could not connect", exc_info=True)
- return False
- except requests.exceptions.ConnectionError:
- _LOGGER.error("Could not connect", exc_info=True)
- return False
- except requests.exceptions.RequestException:
- if insteonhub.http_code == 401:
- _LOGGER.error("Bad username or password for Insteon_local hub")
- else:
- _LOGGER.error("Error on Insteon_local hub check", exc_info=True)
- return False
-
- linked = insteonhub.get_linked()
-
- hass.data['insteon_local'] = insteonhub
-
- for insteon_platform in INSTEON_PLATFORMS:
- load_platform(hass, insteon_platform, DOMAIN, {'linked': linked},
- config)
-
- return True
+ return False
diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm.py
new file mode 100644
index 00000000000..b89e5679a63
--- /dev/null
+++ b/homeassistant/components/insteon_plm.py
@@ -0,0 +1,30 @@
+"""
+Support for INSTEON PowerLinc Modem.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/insteon_plm/
+"""
+import asyncio
+import logging
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@asyncio.coroutine
+def async_setup(hass, config):
+ """Set up the insteon_plm component.
+
+ This component is deprecated as of release 0.77 and should be removed in
+ release 0.90.
+ """
+ _LOGGER.warning('The insteon_plm component has been replaced by '
+ 'the insteon component')
+ _LOGGER.warning('Please see https://home-assistant.io/components/insteon')
+
+ hass.components.persistent_notification.create(
+ 'insteon_plm has been replaced by the insteon component.
'
+ 'Please see https://home-assistant.io/components/insteon',
+ title='insteon_plm Component Deactivated',
+ notification_id='insteon_plm')
+
+ return False
diff --git a/homeassistant/components/iota.py b/homeassistant/components/iota.py
index ada70f8a9eb..717213da9ac 100644
--- a/homeassistant/components/iota.py
+++ b/homeassistant/components/iota.py
@@ -57,7 +57,7 @@ class IotaDevice(Entity):
"""Representation of a IOTA device."""
def __init__(self, name, seed, iri, is_testnet=False):
- """Initialisation of the IOTA device."""
+ """Initialise the IOTA device."""
self._name = name
self._seed = seed
self.iri = iri
diff --git a/homeassistant/components/juicenet.py b/homeassistant/components/juicenet.py
index 55567d45879..2ed32521f1d 100644
--- a/homeassistant/components/juicenet.py
+++ b/homeassistant/components/juicenet.py
@@ -46,29 +46,29 @@ class JuicenetDevice(Entity):
def __init__(self, device, sensor_type, hass):
"""Initialise the sensor."""
self.hass = hass
- self.device = device
+ self._device = device
self.type = sensor_type
@property
def name(self):
"""Return the name of the device."""
- return self.device.name()
+ return self._device.name()
def update(self):
"""Update state of the device."""
- self.device.update_state()
+ self._device.update_state()
@property
def _manufacturer_device_id(self):
"""Return the manufacturer device id."""
- return self.device.id()
+ return self._device.id()
@property
def _token(self):
"""Return the device API token."""
- return self.device.token()
+ return self._device.token()
@property
def unique_id(self):
"""Return a unique ID."""
- return "{}-{}".format(self.device.id(), self.type)
+ return "{}-{}".format(self._device.id(), self.type)
diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py
index 5b3af3029b4..b15963fa6bd 100644
--- a/homeassistant/components/knx.py
+++ b/homeassistant/components/knx.py
@@ -334,7 +334,7 @@ class KNXExposeSensor:
self.hass, self.entity_id, self._async_entity_changed)
async def _async_entity_changed(self, entity_id, old_state, new_state):
- """Callback after entity changed."""
+ """Handle entity change."""
if new_state is None:
return
await self.device.set(float(new_state.state))
diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py
index 456ad6d69be..bc7f136322b 100644
--- a/homeassistant/components/light/__init__.py
+++ b/homeassistant/components/light/__init__.py
@@ -345,6 +345,8 @@ async def async_setup(hass, config):
update_tasks = []
for light in target_lights:
+ light.async_set_context(service.context)
+
pars = params
if not pars:
pars = params.copy()
@@ -356,7 +358,7 @@ async def async_setup(hass, config):
continue
update_tasks.append(
- light.async_update_ha_state(True, service.context))
+ light.async_update_ha_state(True))
if update_tasks:
await asyncio.wait(update_tasks, loop=hass.loop)
@@ -382,7 +384,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/light/group.py b/homeassistant/components/light/group.py
index b2fdd36abe7..c8b7e98160b 100644
--- a/homeassistant/components/light/group.py
+++ b/homeassistant/components/light/group.py
@@ -80,7 +80,7 @@ class LightGroup(light.Light):
await self.async_update()
async def async_will_remove_from_hass(self):
- """Callback when removed from HASS."""
+ """Handle removal from HASS."""
if self._async_unsub_state_changed is not None:
self._async_unsub_state_changed()
self._async_unsub_state_changed = None
diff --git a/homeassistant/components/light/homematic.py b/homeassistant/components/light/homematic.py
index a3db1ff30ff..2d7c855c538 100644
--- a/homeassistant/components/light/homematic.py
+++ b/homeassistant/components/light/homematic.py
@@ -5,9 +5,10 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.homematic/
"""
import logging
+
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/light/homematicip_cloud.py b/homeassistant/components/light/homematicip_cloud.py
index 617a7209a86..f958879386d 100644
--- a/homeassistant/components/light/homematicip_cloud.py
+++ b/homeassistant/components/light/homematicip_cloud.py
@@ -1,37 +1,35 @@
"""
-Support for HomematicIP light.
+Support for HomematicIP Cloud lights.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.homematicip_cloud/
"""
-
import logging
-from homeassistant.components.light import (
- Light, ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS)
from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
+from homeassistant.components.light import (
+ ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
+ATTR_ENERGY_COUNTER = 'energy_counter_kwh'
ATTR_POWER_CONSUMPTION = 'power_consumption'
-ATTR_ENERGIE_COUNTER = 'energie_counter_kwh'
ATTR_PROFILE_MODE = 'profile_mode'
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Old way of setting up HomematicIP lights."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Old way of setting up HomematicIP Cloud lights."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
- """Set up the HomematicIP lights from a config entry."""
- from homematicip.aio.device import (
- AsyncBrandSwitchMeasuring, AsyncDimmer)
+ """Set up the HomematicIP Cloud lights from a config entry."""
+ from homematicip.aio.device import AsyncBrandSwitchMeasuring, AsyncDimmer
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
@@ -46,7 +44,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipLight(HomematicipGenericDevice, Light):
- """MomematicIP light device."""
+ """Representation of a HomematicIP Cloud light device."""
def __init__(self, home, device):
"""Initialize the light device."""
@@ -67,7 +65,7 @@ class HomematicipLight(HomematicipGenericDevice, Light):
class HomematicipLightMeasuring(HomematicipLight):
- """MomematicIP measuring light device."""
+ """Representation of a HomematicIP Cloud measuring light device."""
@property
def device_state_attributes(self):
@@ -79,13 +77,13 @@ class HomematicipLightMeasuring(HomematicipLight):
round(self._device.currentPowerConsumption, 2)
})
attr.update({
- ATTR_ENERGIE_COUNTER: round(self._device.energyCounter, 2)
+ ATTR_ENERGY_COUNTER: round(self._device.energyCounter, 2)
})
return attr
class HomematicipDimmer(HomematicipGenericDevice, Light):
- """MomematicIP dimmer light device."""
+ """Representation of HomematicIP Cloud dimmer light device."""
def __init__(self, home, device):
"""Initialize the dimmer light device."""
@@ -109,8 +107,7 @@ class HomematicipDimmer(HomematicipGenericDevice, Light):
async def async_turn_on(self, **kwargs):
"""Turn the light on."""
if ATTR_BRIGHTNESS in kwargs:
- await self._device.set_dim_level(
- kwargs[ATTR_BRIGHTNESS]/255.0)
+ await self._device.set_dim_level(kwargs[ATTR_BRIGHTNESS]/255.0)
else:
await self._device.set_dim_level(1)
diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/light/ihc.py
index 5a7e85d50dc..480cfa7ad94 100644
--- a/homeassistant/components/light/ihc.py
+++ b/homeassistant/components/light/ihc.py
@@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the ihc lights platform."""
+ """Set up the IHC lights platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
devices = []
@@ -109,7 +109,7 @@ class IhcLight(IHCDevice, Light):
self.ihc_controller.set_runtime_value_bool(self.ihc_id, False)
def on_ihc_change(self, ihc_id, value):
- """Callback from Ihc notifications."""
+ """Handle IHC notifications."""
if isinstance(value, bool):
self._dimmable = False
self._state = value != 0
diff --git a/homeassistant/components/light/insteon_plm.py b/homeassistant/components/light/insteon.py
similarity index 80%
rename from homeassistant/components/light/insteon_plm.py
rename to homeassistant/components/light/insteon.py
index 8a3b463c2bd..88a9ab01de5 100644
--- a/homeassistant/components/light/insteon_plm.py
+++ b/homeassistant/components/light/insteon.py
@@ -2,40 +2,40 @@
Support for Insteon lights via PowerLinc Modem.
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/light.insteon_plm/
+https://home-assistant.io/components/light.insteon/
"""
import asyncio
import logging
-from homeassistant.components.insteon_plm import InsteonPLMEntity
+from homeassistant.components.insteon import InsteonEntity
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['insteon_plm']
+DEPENDENCIES = ['insteon']
MAX_BRIGHTNESS = 255
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
- """Set up the Insteon PLM device."""
- plm = hass.data['insteon_plm'].get('plm')
+ """Set up the Insteon component."""
+ insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
- device = plm.devices[address]
+ device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
_LOGGER.debug('Adding device %s entity %s to Light platform',
device.address.hex, device.states[state_key].name)
- new_entity = InsteonPLMDimmerDevice(device, state_key)
+ new_entity = InsteonDimmerDevice(device, state_key)
async_add_devices([new_entity])
-class InsteonPLMDimmerDevice(InsteonPLMEntity, Light):
+class InsteonDimmerDevice(InsteonEntity, Light):
"""A Class for an Insteon device."""
@property
diff --git a/homeassistant/components/light/insteon_local.py b/homeassistant/components/light/insteon_local.py
deleted file mode 100644
index e2bc54de517..00000000000
--- a/homeassistant/components/light/insteon_local.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-Support for Insteon dimmers via local hub control.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/light.insteon_local/
-"""
-import logging
-from datetime import timedelta
-
-from homeassistant.components.light import (
- ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
-from homeassistant import util
-
-_CONFIGURING = {}
-_LOGGER = logging.getLogger(__name__)
-
-DEPENDENCIES = ['insteon_local']
-DOMAIN = 'light'
-
-MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
-MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
-
-SUPPORT_INSTEON_LOCAL = SUPPORT_BRIGHTNESS
-
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
- """Set up the Insteon local light platform."""
- insteonhub = hass.data['insteon_local']
- if discovery_info is None:
- return
-
- linked = discovery_info['linked']
- device_list = []
- for device_id in linked:
- if linked[device_id]['cat_type'] == 'dimmer':
- device = insteonhub.dimmer(device_id)
- device_list.append(
- InsteonLocalDimmerDevice(device)
- )
-
- add_devices(device_list)
-
-
-class InsteonLocalDimmerDevice(Light):
- """An abstract Class for an Insteon node."""
-
- def __init__(self, node):
- """Initialize the device."""
- self.node = node
- self._value = 0
-
- @property
- def name(self):
- """Return the name of the node."""
- return self.node.device_id
-
- @property
- def unique_id(self):
- """Return the ID of this Insteon node."""
- return self.node.device_id
-
- @property
- def brightness(self):
- """Return the brightness of this light between 0..255."""
- return self._value
-
- @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
- def update(self):
- """Update state of the light."""
- resp = self.node.status(0)
-
- while 'error' in resp and resp['error'] is True:
- resp = self.node.status(0)
-
- if 'cmd2' in resp:
- self._value = int(resp['cmd2'], 16)
-
- @property
- def is_on(self):
- """Return the boolean response if the node is on."""
- return self._value != 0
-
- @property
- def supported_features(self):
- """Flag supported features."""
- return SUPPORT_INSTEON_LOCAL
-
- def turn_on(self, **kwargs):
- """Turn device on."""
- brightness = 100
- if ATTR_BRIGHTNESS in kwargs:
- brightness = int(kwargs[ATTR_BRIGHTNESS]) / 255 * 100
-
- self.node.change_level(brightness)
-
- def turn_off(self, **kwargs):
- """Turn device off."""
- self.node.off()
diff --git a/homeassistant/components/light/knx.py b/homeassistant/components/light/knx.py
index 8fa2b56d1d2..ee8389fbb71 100644
--- a/homeassistant/components/light/knx.py
+++ b/homeassistant/components/light/knx.py
@@ -79,7 +79,7 @@ class KNXLight(Light):
def __init__(self, hass, device):
"""Initialize of KNX light."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
@@ -89,12 +89,12 @@ class KNXLight(Light):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -109,15 +109,15 @@ class KNXLight(Light):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
- return self.device.current_brightness \
- if self.device.supports_brightness else \
+ return self._device.current_brightness \
+ if self._device.supports_brightness else \
None
@property
def hs_color(self):
"""Return the HS color value."""
- if self.device.supports_color:
- return color_util.color_RGB_to_hs(*self.device.current_color)
+ if self._device.supports_color:
+ return color_util.color_RGB_to_hs(*self._device.current_color)
return None
@property
@@ -143,30 +143,30 @@ class KNXLight(Light):
@property
def is_on(self):
"""Return true if light is on."""
- return self.device.state
+ return self._device.state
@property
def supported_features(self):
"""Flag supported features."""
flags = 0
- if self.device.supports_brightness:
+ if self._device.supports_brightness:
flags |= SUPPORT_BRIGHTNESS
- if self.device.supports_color:
+ if self._device.supports_color:
flags |= SUPPORT_COLOR
return flags
async def async_turn_on(self, **kwargs):
"""Turn the light on."""
if ATTR_BRIGHTNESS in kwargs:
- if self.device.supports_brightness:
- await self.device.set_brightness(int(kwargs[ATTR_BRIGHTNESS]))
+ if self._device.supports_brightness:
+ await self._device.set_brightness(int(kwargs[ATTR_BRIGHTNESS]))
elif ATTR_HS_COLOR in kwargs:
- if self.device.supports_color:
- await self.device.set_color(color_util.color_hs_to_RGB(
+ if self._device.supports_color:
+ await self._device.set_color(color_util.color_hs_to_RGB(
*kwargs[ATTR_HS_COLOR]))
else:
- await self.device.set_on()
+ await self._device.set_on()
async def async_turn_off(self, **kwargs):
"""Turn the light off."""
- await self.device.set_off()
+ await self._device.set_off()
diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py
index 3738fd8f004..8547d7a985d 100644
--- a/homeassistant/components/light/lifx.py
+++ b/homeassistant/components/light/lifx.py
@@ -389,7 +389,7 @@ class LIFXLight(Light):
def __init__(self, device, effects_conductor):
"""Initialize the light."""
- self.device = device
+ self.light = device
self.effects_conductor = effects_conductor
self.registered = True
self.postponed_update = None
@@ -403,28 +403,28 @@ class LIFXLight(Light):
@property
def unique_id(self):
"""Return a unique ID."""
- return self.device.mac_addr
+ return self.light.mac_addr
@property
def name(self):
"""Return the name of the device."""
- return self.device.label
+ return self.light.label
@property
def who(self):
"""Return a string identifying the device."""
- return "%s (%s)" % (self.device.ip_addr, self.name)
+ return "%s (%s)" % (self.light.ip_addr, self.name)
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
- kelvin = lifx_features(self.device)['max_kelvin']
+ kelvin = lifx_features(self.light)['max_kelvin']
return math.floor(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
- kelvin = lifx_features(self.device)['min_kelvin']
+ kelvin = lifx_features(self.light)['min_kelvin']
return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
@@ -432,7 +432,7 @@ class LIFXLight(Light):
"""Flag supported features."""
support = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_EFFECT
- device_features = lifx_features(self.device)
+ device_features = lifx_features(self.light)
if device_features['min_kelvin'] != device_features['max_kelvin']:
support |= SUPPORT_COLOR_TEMP
@@ -441,12 +441,12 @@ class LIFXLight(Light):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
- return convert_16_to_8(self.device.color[2])
+ return convert_16_to_8(self.light.color[2])
@property
def color_temp(self):
"""Return the color temperature."""
- _, sat, _, kelvin = self.device.color
+ _, sat, _, kelvin = self.light.color
if sat:
return None
return color_util.color_temperature_kelvin_to_mired(kelvin)
@@ -454,12 +454,12 @@ class LIFXLight(Light):
@property
def is_on(self):
"""Return true if device is on."""
- return self.device.power_level != 0
+ return self.light.power_level != 0
@property
def effect(self):
"""Return the name of the currently running effect."""
- effect = self.effects_conductor.effect(self.device)
+ effect = self.effects_conductor.effect(self.light)
if effect:
return 'lifx_effect_' + effect.name
return None
@@ -497,7 +497,7 @@ class LIFXLight(Light):
async def set_state(self, **kwargs):
"""Set a color on the light and turn it on/off."""
async with self.lock:
- bulb = self.device
+ bulb = self.light
await self.effects_conductor.stop([bulb])
@@ -545,12 +545,12 @@ class LIFXLight(Light):
async def set_power(self, ack, pwr, duration=0):
"""Send a power change to the device."""
- await ack(partial(self.device.set_power, pwr, duration=duration))
+ await ack(partial(self.light.set_power, pwr, duration=duration))
async def set_color(self, ack, hsbk, kwargs, duration=0):
"""Send a color change to the device."""
- hsbk = merge_hsbk(self.device.color, hsbk)
- await ack(partial(self.device.set_color, hsbk, duration=duration))
+ hsbk = merge_hsbk(self.light.color, hsbk)
+ await ack(partial(self.light.set_color, hsbk, duration=duration))
async def default_effect(self, **kwargs):
"""Start an effect with default parameters."""
@@ -563,7 +563,7 @@ class LIFXLight(Light):
async def async_update(self):
"""Update bulb status."""
if self.available and not self.lock.locked():
- await AwaitAioLIFX().wait(self.device.get_color)
+ await AwaitAioLIFX().wait(self.light.get_color)
class LIFXWhite(LIFXLight):
@@ -600,7 +600,7 @@ class LIFXColor(LIFXLight):
@property
def hs_color(self):
"""Return the hs value."""
- hue, sat, _, _ = self.device.color
+ hue, sat, _, _ = self.light.color
hue = hue / 65535 * 360
sat = sat / 65535 * 100
return (hue, sat) if sat else None
@@ -611,7 +611,7 @@ class LIFXStrip(LIFXColor):
async def set_color(self, ack, hsbk, kwargs, duration=0):
"""Send a color change to the device."""
- bulb = self.device
+ bulb = self.light
num_zones = len(bulb.color_zones)
zones = kwargs.get(ATTR_ZONES)
@@ -659,7 +659,7 @@ class LIFXStrip(LIFXColor):
while self.available and zone < top:
# Each get_color_zones can update 8 zones at once
resp = await AwaitAioLIFX().wait(partial(
- self.device.get_color_zones,
+ self.light.get_color_zones,
start_index=zone))
if resp:
zone += 8
diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py
index 2263a865758..ded58248d8e 100644
--- a/homeassistant/components/light/limitlessled.py
+++ b/homeassistant/components/light/limitlessled.py
@@ -190,7 +190,7 @@ class LimitlessLEDGroup(Light):
@asyncio.coroutine
def async_added_to_hass(self):
- """Called when entity is about to be added to hass."""
+ """Handle entity about to be added to hass event."""
last_state = yield from async_get_last_state(self.hass, self.entity_id)
if last_state:
self._is_on = (last_state.state == STATE_ON)
diff --git a/homeassistant/components/light/lw12wifi.py b/homeassistant/components/light/lw12wifi.py
index f81d8368f98..46bebe8086f 100644
--- a/homeassistant/components/light/lw12wifi.py
+++ b/homeassistant/components/light/lw12wifi.py
@@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup LW-12 WiFi LED Controller platform."""
+ """Set up LW-12 WiFi LED Controller platform."""
import lw12
# Assign configuration variables.
@@ -53,7 +53,7 @@ class LW12WiFi(Light):
"""LW-12 WiFi LED Controller."""
def __init__(self, name, lw12_light):
- """Initialisation of LW-12 WiFi LED Controller.
+ """Initialise LW-12 WiFi LED Controller.
Args:
name: Friendly name for this platform to use.
diff --git a/homeassistant/components/light/mochad.py b/homeassistant/components/light/mochad.py
index 576e244103f..4aeb2c2c79b 100644
--- a/homeassistant/components/light/mochad.py
+++ b/homeassistant/components/light/mochad.py
@@ -54,8 +54,8 @@ class MochadLight(Light):
self._name = dev.get(CONF_NAME,
'x10_light_dev_{}'.format(self._address))
self._comm_type = dev.get(mochad.CONF_COMM_TYPE, 'pl')
- self.device = device.Device(ctrl, self._address,
- comm_type=self._comm_type)
+ self.light = device.Device(ctrl, self._address,
+ comm_type=self._comm_type)
self._brightness = 0
self._state = self._get_device_status()
self._brightness_levels = dev.get(CONF_BRIGHTNESS_LEVELS) - 1
@@ -68,7 +68,7 @@ class MochadLight(Light):
def _get_device_status(self):
"""Get the status of the light from mochad."""
with mochad.REQ_LOCK:
- status = self.device.get_status().rstrip()
+ status = self.light.get_status().rstrip()
return status == 'on'
@property
@@ -98,12 +98,12 @@ class MochadLight(Light):
if self._brightness > brightness:
bdelta = self._brightness - brightness
mochad_brightness = self._calculate_brightness_value(bdelta)
- self.device.send_cmd("dim {}".format(mochad_brightness))
+ self.light.send_cmd("dim {}".format(mochad_brightness))
self._controller.read_data()
elif self._brightness < brightness:
bdelta = brightness - self._brightness
mochad_brightness = self._calculate_brightness_value(bdelta)
- self.device.send_cmd("bright {}".format(mochad_brightness))
+ self.light.send_cmd("bright {}".format(mochad_brightness))
self._controller.read_data()
def turn_on(self, **kwargs):
@@ -112,10 +112,10 @@ class MochadLight(Light):
with mochad.REQ_LOCK:
if self._brightness_levels > 32:
out_brightness = self._calculate_brightness_value(brightness)
- self.device.send_cmd('xdim {}'.format(out_brightness))
+ self.light.send_cmd('xdim {}'.format(out_brightness))
self._controller.read_data()
else:
- self.device.send_cmd("on")
+ self.light.send_cmd("on")
self._controller.read_data()
# There is no persistence for X10 modules so a fresh on command
# will be full brightness
@@ -128,7 +128,7 @@ class MochadLight(Light):
def turn_off(self, **kwargs):
"""Send the command to turn the light on."""
with mochad.REQ_LOCK:
- self.device.send_cmd('off')
+ self.light.send_cmd('off')
self._controller.read_data()
# There is no persistence for X10 modules so we need to prepare
# to track a fresh on command will full brightness
diff --git a/homeassistant/components/light/qwikswitch.py b/homeassistant/components/light/qwikswitch.py
index 528f4f73c53..9d6e8f9169a 100644
--- a/homeassistant/components/light/qwikswitch.py
+++ b/homeassistant/components/light/qwikswitch.py
@@ -27,9 +27,9 @@ class QSLight(QSToggleEntity, Light):
@property
def brightness(self):
"""Return the brightness of this light (0-255)."""
- return self.device.value if self.device.is_dimmer else None
+ return self._device.value if self._device.is_dimmer else None
@property
def supported_features(self):
"""Flag supported features."""
- return SUPPORT_BRIGHTNESS if self.device.is_dimmer else 0
+ return SUPPORT_BRIGHTNESS if self._device.is_dimmer else 0
diff --git a/homeassistant/components/light/tellduslive.py b/homeassistant/components/light/tellduslive.py
index 321cfd677b5..3908d43f62d 100644
--- a/homeassistant/components/light/tellduslive.py
+++ b/homeassistant/components/light/tellduslive.py
@@ -38,7 +38,7 @@ class TelldusLiveLight(TelldusLiveEntity, Light):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
- return self.device.dim_level
+ return self._device.dim_level
@property
def supported_features(self):
@@ -48,15 +48,15 @@ class TelldusLiveLight(TelldusLiveEntity, Light):
@property
def is_on(self):
"""Return true if light is on."""
- return self.device.is_on
+ return self._device.is_on
def turn_on(self, **kwargs):
"""Turn the light on."""
brightness = kwargs.get(ATTR_BRIGHTNESS, self._last_brightness)
- self.device.dim(level=brightness)
+ self._device.dim(level=brightness)
self.changed()
def turn_off(self, **kwargs):
"""Turn the light off."""
- self.device.turn_off()
+ self._device.turn_off()
self.changed()
diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py
index 78dedb12718..f07865473d1 100644
--- a/homeassistant/components/light/wemo.py
+++ b/homeassistant/components/light/wemo.py
@@ -76,39 +76,39 @@ class WemoLight(Light):
def __init__(self, device, update_lights):
"""Initialize the WeMo light."""
self.light_id = device.name
- self.device = device
+ self.wemo = device
self.update_lights = update_lights
@property
def unique_id(self):
"""Return the ID of this light."""
- return self.device.uniqueID
+ return self.wemo.uniqueID
@property
def name(self):
"""Return the name of the light."""
- return self.device.name
+ return self.wemo.name
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
- return self.device.state.get('level', 255)
+ return self.wemo.state.get('level', 255)
@property
def hs_color(self):
"""Return the hs color values of this light."""
- xy_color = self.device.state.get('color_xy')
+ xy_color = self.wemo.state.get('color_xy')
return color_util.color_xy_to_hs(*xy_color) if xy_color else None
@property
def color_temp(self):
"""Return the color temperature of this light in mireds."""
- return self.device.state.get('temperature_mireds')
+ return self.wemo.state.get('temperature_mireds')
@property
def is_on(self):
"""Return true if device is on."""
- return self.device.state['onoff'] != 0
+ return self.wemo.state['onoff'] != 0
@property
def supported_features(self):
@@ -118,7 +118,7 @@ class WemoLight(Light):
@property
def available(self):
"""Return if light is available."""
- return self.device.state['available']
+ return self.wemo.state['available']
def turn_on(self, **kwargs):
"""Turn the light on."""
@@ -128,23 +128,23 @@ class WemoLight(Light):
if hs_color is not None:
xy_color = color_util.color_hs_to_xy(*hs_color)
- self.device.set_color(xy_color, transition=transitiontime)
+ self.wemo.set_color(xy_color, transition=transitiontime)
if ATTR_COLOR_TEMP in kwargs:
colortemp = kwargs[ATTR_COLOR_TEMP]
- self.device.set_temperature(mireds=colortemp,
- transition=transitiontime)
+ self.wemo.set_temperature(mireds=colortemp,
+ transition=transitiontime)
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
- self.device.turn_on(level=brightness, transition=transitiontime)
+ self.wemo.turn_on(level=brightness, transition=transitiontime)
else:
- self.device.turn_on(transition=transitiontime)
+ self.wemo.turn_on(transition=transitiontime)
def turn_off(self, **kwargs):
"""Turn the light off."""
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
- self.device.turn_off(transition=transitiontime)
+ self.wemo.turn_off(transition=transitiontime)
def update(self):
"""Synchronize state with bridge."""
diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py
index fbb8dd66f01..2171d084bf0 100644
--- a/homeassistant/components/light/xiaomi_miio.py
+++ b/homeassistant/components/light/xiaomi_miio.py
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
'philips.light.candle2']),
})
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
# The light does not accept cct values < 1
CCT_MIN = 1
diff --git a/homeassistant/components/lock/bmw_connected_drive.py b/homeassistant/components/lock/bmw_connected_drive.py
index 52734b1259c..e48fd1af3c3 100644
--- a/homeassistant/components/lock/bmw_connected_drive.py
+++ b/homeassistant/components/lock/bmw_connected_drive.py
@@ -17,15 +17,16 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the BMW Connected Drive lock."""
+ """Set up the BMW Connected Drive lock."""
accounts = hass.data[BMW_DOMAIN]
_LOGGER.debug('Found BMW accounts: %s',
', '.join([a.name for a in accounts]))
devices = []
for account in accounts:
- for vehicle in account.account.vehicles:
- device = BMWLock(account, vehicle, 'lock', 'BMW lock')
- devices.append(device)
+ if not account.read_only:
+ for vehicle in account.account.vehicles:
+ device = BMWLock(account, vehicle, 'lock', 'BMW lock')
+ devices.append(device)
add_devices(devices, True)
diff --git a/homeassistant/components/lock/homematic.py b/homeassistant/components/lock/homematic.py
index 0d70849e37e..7c4195d7c8b 100644
--- a/homeassistant/components/lock/homematic.py
+++ b/homeassistant/components/lock/homematic.py
@@ -5,10 +5,10 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.homematic/
"""
import logging
-from homeassistant.components.lock import LockDevice, SUPPORT_OPEN
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
-from homeassistant.const import STATE_UNKNOWN
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
+from homeassistant.components.lock import SUPPORT_OPEN, LockDevice
+from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py
index 793d33e52fa..8f2abb9be19 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.08.04']
+REQUIREMENTS = ['youtube_dl==2018.08.22']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index 31c254c0a39..7c49b095c66 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -461,7 +461,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py
index 099b365c50b..2c66d458095 100644
--- a/homeassistant/components/media_player/cast.py
+++ b/homeassistant/components/media_player/cast.py
@@ -146,7 +146,7 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None:
import pychromecast
def internal_callback(name):
- """Called when zeroconf has discovered a new chromecast."""
+ """Handle zeroconf discovery of a new chromecast."""
mdns = listener.services[name]
_discover_chromecast(hass, ChromecastInfo(*mdns))
@@ -229,7 +229,7 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType,
@callback
def async_cast_discovered(discover: ChromecastInfo) -> None:
- """Callback for when a new chromecast is discovered."""
+ """Handle discovery of a new chromecast."""
if info is not None and info.host_port != discover.host_port:
# Not our requested cast device.
return
@@ -277,17 +277,17 @@ class CastStatusListener:
chromecast.register_connection_listener(self)
def new_cast_status(self, cast_status):
- """Called when a new CastStatus is received."""
+ """Handle reception of a new CastStatus."""
if self._valid:
self._cast_device.new_cast_status(cast_status)
def new_media_status(self, media_status):
- """Called when a new MediaStatus is received."""
+ """Handle reception of a new MediaStatus."""
if self._valid:
self._cast_device.new_media_status(media_status)
def new_connection_status(self, connection_status):
- """Called when a new ConnectionStatus is received."""
+ """Handle reception of a new ConnectionStatus."""
if self._valid:
self._cast_device.new_connection_status(connection_status)
@@ -321,7 +321,7 @@ class CastDevice(MediaPlayerDevice):
"""Create chromecast object when added to hass."""
@callback
def async_cast_discovered(discover: ChromecastInfo):
- """Callback for changing elected leaders / IP."""
+ """Handle discovery of new Chromecast."""
if self._cast_info.uuid is None:
# We can't handle empty UUIDs
return
diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/media_player/channels.py
index 6ccc6061703..71ef5412d9e 100644
--- a/homeassistant/components/media_player/channels.py
+++ b/homeassistant/components/media_player/channels.py
@@ -56,7 +56,7 @@ REQUIREMENTS = ['pychannels==1.0.0']
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the Channels platform."""
+ """Set up the Channels platform."""
device = ChannelsPlayer(
config.get('name'),
config.get(CONF_HOST),
@@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[DATA_CHANNELS].append(device)
def service_handler(service):
- """Handler for services."""
+ """Handle service."""
entity_id = service.data.get(ATTR_ENTITY_ID)
device = next((device for device in hass.data[DATA_CHANNELS] if
diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/media_player/dlna_dmr.py
index ebb1ab8d383..7a0049df867 100644
--- a/homeassistant/components/media_player/dlna_dmr.py
+++ b/homeassistant/components/media_player/dlna_dmr.py
@@ -180,7 +180,7 @@ class DlnaDmrDevice(MediaPlayerDevice):
self._subscription_renew_time = None
async def async_added_to_hass(self):
- """Callback when added."""
+ """Handle addition."""
self._device.on_event = self._on_event
# register unsubscribe on stop
diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py
index 1dfb19a33be..8dd4ebcb120 100644
--- a/homeassistant/components/media_player/emby.py
+++ b/homeassistant/components/media_player/emby.py
@@ -132,7 +132,7 @@ class EmbyDevice(MediaPlayerDevice):
_LOGGER.debug("New Emby Device initialized with ID: %s", device_id)
self.emby = emby
self.device_id = device_id
- self.device = self.emby.devices[self.device_id]
+ self._device = self.emby.devices[self.device_id]
self._hidden = False
self._available = True
@@ -150,11 +150,11 @@ class EmbyDevice(MediaPlayerDevice):
def async_update_callback(self, msg):
"""Handle device updates."""
# Check if we should update progress
- if self.device.media_position:
- if self.device.media_position != self.media_status_last_position:
- self.media_status_last_position = self.device.media_position
+ if self._device.media_position:
+ if self._device.media_position != self.media_status_last_position:
+ self.media_status_last_position = self._device.media_position
self.media_status_received = dt_util.utcnow()
- elif not self.device.is_nowplaying:
+ elif not self._device.is_nowplaying:
# No position, but we have an old value and are still playing
self.media_status_last_position = None
self.media_status_received = None
@@ -187,13 +187,13 @@ class EmbyDevice(MediaPlayerDevice):
@property
def supports_remote_control(self):
"""Return control ability."""
- return self.device.supports_remote_control
+ return self._device.supports_remote_control
@property
def name(self):
"""Return the name of the device."""
- return 'Emby - {} - {}'.format(self.device.client, self.device.name) \
- or DEVICE_DEFAULT_NAME
+ return ('Emby - {} - {}'.format(self._device.client, self._device.name)
+ or DEVICE_DEFAULT_NAME)
@property
def should_poll(self):
@@ -203,7 +203,7 @@ class EmbyDevice(MediaPlayerDevice):
@property
def state(self):
"""Return the state of the device."""
- state = self.device.state
+ state = self._device.state
if state == 'Paused':
return STATE_PAUSED
if state == 'Playing':
@@ -217,17 +217,17 @@ class EmbyDevice(MediaPlayerDevice):
def app_name(self):
"""Return current user as app_name."""
# Ideally the media_player object would have a user property.
- return self.device.username
+ return self._device.username
@property
def media_content_id(self):
"""Content ID of current playing media."""
- return self.device.media_id
+ return self._device.media_id
@property
def media_content_type(self):
"""Content type of current playing media."""
- media_type = self.device.media_type
+ media_type = self._device.media_type
if media_type == 'Episode':
return MEDIA_TYPE_TVSHOW
if media_type == 'Movie':
@@ -245,7 +245,7 @@ class EmbyDevice(MediaPlayerDevice):
@property
def media_duration(self):
"""Return the duration of current playing media in seconds."""
- return self.device.media_runtime
+ return self._device.media_runtime
@property
def media_position(self):
@@ -264,42 +264,42 @@ class EmbyDevice(MediaPlayerDevice):
@property
def media_image_url(self):
"""Return the image URL of current playing media."""
- return self.device.media_image_url
+ return self._device.media_image_url
@property
def media_title(self):
"""Return the title of current playing media."""
- return self.device.media_title
+ return self._device.media_title
@property
def media_season(self):
"""Season of current playing media (TV Show only)."""
- return self.device.media_season
+ return self._device.media_season
@property
def media_series_title(self):
"""Return the title of the series of current playing media (TV)."""
- return self.device.media_series_title
+ return self._device.media_series_title
@property
def media_episode(self):
"""Return the episode of current playing media (TV only)."""
- return self.device.media_episode
+ return self._device.media_episode
@property
def media_album_name(self):
"""Return the album name of current playing media (Music only)."""
- return self.device.media_album_name
+ return self._device.media_album_name
@property
def media_artist(self):
"""Return the artist of current playing media (Music track only)."""
- return self.device.media_artist
+ return self._device.media_artist
@property
def media_album_artist(self):
"""Return the album artist of current playing media (Music only)."""
- return self.device.media_album_artist
+ return self._device.media_album_artist
@property
def supported_features(self):
@@ -313,39 +313,39 @@ class EmbyDevice(MediaPlayerDevice):
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_play()
+ return self._device.media_play()
def async_media_pause(self):
"""Pause the media player.
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_pause()
+ return self._device.media_pause()
def async_media_stop(self):
"""Stop the media player.
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_stop()
+ return self._device.media_stop()
def async_media_next_track(self):
"""Send next track command.
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_next()
+ return self._device.media_next()
def async_media_previous_track(self):
"""Send next track command.
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_previous()
+ return self._device.media_previous()
def async_media_seek(self, position):
"""Send seek command.
This method must be run in the event loop and returns a coroutine.
"""
- return self.device.media_seek(position)
+ return self._device.media_seek(position)
diff --git a/homeassistant/components/media_player/frontier_silicon.py b/homeassistant/components/media_player/frontier_silicon.py
index ab594f47e14..b59c9a199b8 100644
--- a/homeassistant/components/media_player/frontier_silicon.py
+++ b/homeassistant/components/media_player/frontier_silicon.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['afsapi==0.0.3']
+REQUIREMENTS = ['afsapi==0.0.4']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/mediaroom.py b/homeassistant/components/media_player/mediaroom.py
index 32f1bb6e0ae..fd7229f79f5 100644
--- a/homeassistant/components/media_player/mediaroom.py
+++ b/homeassistant/components/media_player/mediaroom.py
@@ -103,7 +103,7 @@ class MediaroomDevice(MediaPlayerDevice):
"""Representation of a Mediaroom set-up-box on the network."""
def set_state(self, mediaroom_state):
- """Helper method to map pymediaroom states to HA states."""
+ """Map pymediaroom state to HA state."""
from pymediaroom import State
state_map = {
diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py
index 4b3dfc2ccbb..be9dfa77377 100644
--- a/homeassistant/components/media_player/mpd.py
+++ b/homeassistant/components/media_player/mpd.py
@@ -32,9 +32,8 @@ DEFAULT_PORT = 6600
PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=120)
-SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | \
- SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_VOLUME_MUTE | \
- SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_SELECT_SOURCE | \
+SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \
+ SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \
SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_SEEK | \
SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON
@@ -72,7 +71,7 @@ class MpdDevice(MediaPlayerDevice):
self._status = None
self._currentsong = None
- self._playlists = []
+ self._playlists = None
self._currentplaylist = None
self._is_connected = False
self._muted = False
@@ -202,12 +201,24 @@ class MpdDevice(MediaPlayerDevice):
@property
def volume_level(self):
"""Return the volume level."""
- return int(self._status['volume'])/100
+ if 'volume' in self._status:
+ return int(self._status['volume'])/100
+ return None
@property
def supported_features(self):
"""Flag media player features that are supported."""
- return SUPPORT_MPD
+ if self._status is None:
+ return None
+
+ supported = SUPPORT_MPD
+ if 'volume' in self._status:
+ supported |= \
+ SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE
+ if self._playlists is not None:
+ supported |= SUPPORT_SELECT_SOURCE
+
+ return supported
@property
def source(self):
@@ -226,27 +237,36 @@ class MpdDevice(MediaPlayerDevice):
@Throttle(PLAYLIST_UPDATE_INTERVAL)
def _update_playlists(self, **kwargs):
"""Update available MPD playlists."""
- self._playlists = []
- for playlist_data in self._client.listplaylists():
- self._playlists.append(playlist_data['playlist'])
+ import mpd
+
+ try:
+ self._playlists = []
+ for playlist_data in self._client.listplaylists():
+ self._playlists.append(playlist_data['playlist'])
+ except mpd.CommandError as error:
+ self._playlists = None
+ _LOGGER.warning("Playlists could not be updated: %s:", error)
def set_volume_level(self, volume):
"""Set volume of media player."""
- self._client.setvol(int(volume * 100))
+ if 'volume' in self._status:
+ self._client.setvol(int(volume * 100))
def volume_up(self):
"""Service to send the MPD the command for volume up."""
- current_volume = int(self._status['volume'])
+ if 'volume' in self._status:
+ current_volume = int(self._status['volume'])
- if current_volume <= 100:
- self._client.setvol(current_volume + 5)
+ if current_volume <= 100:
+ self._client.setvol(current_volume + 5)
def volume_down(self):
"""Service to send the MPD the command for volume down."""
- current_volume = int(self._status['volume'])
+ if 'volume' in self._status:
+ current_volume = int(self._status['volume'])
- if current_volume >= 0:
- self._client.setvol(current_volume - 5)
+ if current_volume >= 0:
+ self._client.setvol(current_volume - 5)
def media_play(self):
"""Service to send the MPD the command for play/pause."""
@@ -270,12 +290,13 @@ class MpdDevice(MediaPlayerDevice):
def mute_volume(self, mute):
"""Mute. Emulated with set_volume_level."""
- if mute is True:
- self._muted_volume = self.volume_level
- self.set_volume_level(0)
- elif mute is False:
- self.set_volume_level(self._muted_volume)
- self._muted = mute
+ if 'volume' in self._status:
+ if mute:
+ self._muted_volume = self.volume_level
+ self.set_volume_level(0)
+ else:
+ self.set_volume_level(self._muted_volume)
+ self._muted = mute
def play_media(self, media_type, media_id, **kwargs):
"""Send the media player the command for playing a playlist."""
diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py
index e3c6f453c35..05947b3d67b 100644
--- a/homeassistant/components/media_player/plex.py
+++ b/homeassistant/components/media_player/plex.py
@@ -454,7 +454,7 @@ class PlexClient(MediaPlayerDevice):
elif self._player_state == 'paused':
self._is_player_active = True
self._state = STATE_PAUSED
- elif self.device:
+ elif self._device:
self._is_player_active = False
self._state = STATE_IDLE
else:
@@ -528,11 +528,6 @@ class PlexClient(MediaPlayerDevice):
"""Return the library name of playing media."""
return self._app_name
- @property
- def device(self):
- """Return the device, if any."""
- return self._device
-
@property
def marked_unavailable(self):
"""Return time device was marked unavailable."""
@@ -671,7 +666,7 @@ class PlexClient(MediaPlayerDevice):
SUPPORT_TURN_OFF)
# Not all devices support playback functionality
# Playback includes volume, stop/play/pause, etc.
- if 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 |
@@ -681,22 +676,22 @@ class PlexClient(MediaPlayerDevice):
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.setVolume(
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.setVolume(
int(volume * 100), self._active_media_plexapi_type)
self._volume_level = volume # store since we can't retrieve
@property
def volume_level(self):
"""Return the volume level of the client (0..1)."""
- if (self._is_player_active and self.device and
+ if (self._is_player_active and self._device and
'playback' in self._device_protocol_capabilities):
return self._volume_level
@property
def is_volume_muted(self):
"""Return boolean if volume is currently muted."""
- if self._is_player_active and self.device:
+ if self._is_player_active and self._device:
return self._volume_muted
def mute_volume(self, mute):
@@ -706,7 +701,7 @@ class PlexClient(MediaPlayerDevice):
- On mute, store volume and set volume to 0
- On unmute, set volume to previously stored volume
"""
- if not (self.device and
+ if not (self._device and
'playback' in self._device_protocol_capabilities):
return
@@ -719,18 +714,18 @@ class PlexClient(MediaPlayerDevice):
def media_play(self):
"""Send play command."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.play(self._active_media_plexapi_type)
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.play(self._active_media_plexapi_type)
def media_pause(self):
"""Send pause command."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.pause(self._active_media_plexapi_type)
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.pause(self._active_media_plexapi_type)
def media_stop(self):
"""Send stop command."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.stop(self._active_media_plexapi_type)
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.stop(self._active_media_plexapi_type)
def turn_off(self):
"""Turn the client off."""
@@ -739,17 +734,17 @@ class PlexClient(MediaPlayerDevice):
def media_next_track(self):
"""Send next track command."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.skipNext(self._active_media_plexapi_type)
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.skipNext(self._active_media_plexapi_type)
def media_previous_track(self):
"""Send previous track command."""
- if self.device and 'playback' in self._device_protocol_capabilities:
- self.device.skipPrevious(self._active_media_plexapi_type)
+ if self._device and 'playback' in self._device_protocol_capabilities:
+ self._device.skipPrevious(self._active_media_plexapi_type)
def play_media(self, media_type, media_id, **kwargs):
"""Play a piece of media."""
- if not (self.device and
+ if not (self._device and
'playback' in self._device_protocol_capabilities):
return
@@ -757,7 +752,7 @@ class PlexClient(MediaPlayerDevice):
media = None
if media_type == 'MUSIC':
- media = self.device.server.library.section(
+ media = self._device.server.library.section(
src['library_name']).get(src['artist_name']).album(
src['album_name']).get(src['track_name'])
elif media_type == 'EPISODE':
@@ -765,9 +760,9 @@ class PlexClient(MediaPlayerDevice):
src['library_name'], src['show_name'],
src['season_number'], src['episode_number'])
elif media_type == 'PLAYLIST':
- media = self.device.server.playlist(src['playlist_name'])
+ media = self._device.server.playlist(src['playlist_name'])
elif media_type == 'VIDEO':
- media = self.device.server.library.section(
+ media = self._device.server.library.section(
src['library_name']).get(src['video_name'])
import plexapi.playlist
@@ -785,13 +780,13 @@ class PlexClient(MediaPlayerDevice):
target_season = None
target_episode = None
- show = self.device.server.library.section(library_name).get(
+ show = self._device.server.library.section(library_name).get(
show_name)
if not season_number:
playlist_name = "{} - {} Episodes".format(
self.entity_id, show_name)
- return self.device.server.createPlaylist(
+ return self._device.server.createPlaylist(
playlist_name, show.episodes())
for season in show.seasons():
@@ -808,7 +803,7 @@ class PlexClient(MediaPlayerDevice):
if not episode_number:
playlist_name = "{} - {} Season {} Episodes".format(
self.entity_id, show_name, str(season_number))
- return self.device.server.createPlaylist(
+ return self._device.server.createPlaylist(
playlist_name, target_season.episodes())
for episode in target_season.episodes():
@@ -826,22 +821,22 @@ class PlexClient(MediaPlayerDevice):
def _client_play_media(self, media, delete=False, **params):
"""Instruct Plex client to play a piece of media."""
- if not (self.device and
+ if not (self._device and
'playback' in self._device_protocol_capabilities):
_LOGGER.error("Client cannot play media: %s", self.entity_id)
return
import plexapi.playqueue
playqueue = plexapi.playqueue.PlayQueue.create(
- self.device.server, media, **params)
+ self._device.server, media, **params)
# Delete dynamic playlists used to build playqueue (ex. play tv season)
if delete:
media.delete()
- server_url = self.device.server.baseurl.split(':')
- self.device.sendCommand('playback/playMedia', **dict({
- 'machineIdentifier': self.device.server.machineIdentifier,
+ server_url = self._device.server.baseurl.split(':')
+ self._device.sendCommand('playback/playMedia', **dict({
+ 'machineIdentifier': self._device.server.machineIdentifier,
'address': server_url[1].strip('/'),
'port': server_url[-1],
'key': media.key,
diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py
index 5375001f75c..96038e7bb2b 100644
--- a/homeassistant/components/media_player/sonos.py
+++ b/homeassistant/components/media_player/sonos.py
@@ -33,6 +33,7 @@ _LOGGER = logging.getLogger(__name__)
# Quiet down soco logging to just actual problems.
logging.getLogger('soco').setLevel(logging.WARNING)
+logging.getLogger('soco.events').setLevel(logging.ERROR)
logging.getLogger('soco.data_structures_entry').setLevel(logging.ERROR)
_SOCO_SERVICES_LOGGER = logging.getLogger('soco.services')
@@ -55,6 +56,7 @@ DATA_SONOS = 'sonos_devices'
SOURCE_LINEIN = 'Line-in'
SOURCE_TV = 'TV'
+CONF_ADVERTISE_ADDR = 'advertise_addr'
CONF_INTERFACE_ADDR = 'interface_addr'
# Service call validation schemas
@@ -73,6 +75,7 @@ ATTR_SONOS_GROUP = 'sonos_group'
UPNP_ERRORS_TO_IGNORE = ['701', '711']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_ADVERTISE_ADDR): cv.string,
vol.Optional(CONF_INTERFACE_ADDR): cv.string,
vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]),
})
@@ -141,25 +144,14 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
def _setup_platform(hass, config, add_devices, discovery_info):
"""Set up the Sonos platform."""
import soco
- import soco.events
- import soco.exceptions
-
- orig_parse_event_xml = soco.events.parse_event_xml
-
- def safe_parse_event_xml(xml):
- """Avoid SoCo 0.14 event thread dying from invalid xml."""
- try:
- return orig_parse_event_xml(xml)
- # pylint: disable=broad-except
- except Exception as ex:
- _LOGGER.debug("Dodged exception: %s %s", type(ex), str(ex))
- return {}
-
- soco.events.parse_event_xml = safe_parse_event_xml
if DATA_SONOS not in hass.data:
hass.data[DATA_SONOS] = SonosData()
+ advertise_addr = config.get(CONF_ADVERTISE_ADDR)
+ if advertise_addr:
+ soco.config.EVENT_ADVERTISE_IP = advertise_addr
+
players = []
if discovery_info:
player = soco.SoCo(discovery_info.get('host'))
@@ -451,7 +443,7 @@ class SonosDevice(MediaPlayerDevice):
def _set_favorites(self):
"""Set available favorites."""
- # SoCo 0.14 raises a generic Exception on invalid xml in favorites.
+ # SoCo 0.16 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:
@@ -863,9 +855,7 @@ class SonosDevice(MediaPlayerDevice):
src = fav.pop()
uri = src.reference.get_uri()
if _is_radio_uri(uri):
- # SoCo 0.14 fails to XML escape the title parameter
- from xml.sax.saxutils import escape
- self.soco.play_uri(uri, title=escape(source))
+ self.soco.play_uri(uri, title=source)
else:
self.soco.clear_queue()
self.soco.add_to_queue(src.reference)
diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py
index 8f14031481a..703c9a963ee 100644
--- a/homeassistant/components/media_player/soundtouch.py
+++ b/homeassistant/components/media_player/soundtouch.py
@@ -166,11 +166,6 @@ class SoundTouchDevice(MediaPlayerDevice):
"""Return specific soundtouch configuration."""
return self._config
- @property
- def device(self):
- """Return Soundtouch device."""
- return self._device
-
def update(self):
"""Retrieve the latest data."""
self._status = self._device.status()
@@ -323,8 +318,8 @@ class SoundTouchDevice(MediaPlayerDevice):
_LOGGER.warning("Unable to create zone without slaves")
else:
_LOGGER.info("Creating zone with master %s",
- self.device.config.name)
- self.device.create_zone([slave.device for slave in slaves])
+ self._device.config.name)
+ self._device.create_zone([slave.device for slave in slaves])
def remove_zone_slave(self, slaves):
"""
@@ -341,8 +336,8 @@ class SoundTouchDevice(MediaPlayerDevice):
_LOGGER.warning("Unable to find slaves to remove")
else:
_LOGGER.info("Removing slaves from zone with master %s",
- self.device.config.name)
- self.device.remove_zone_slave([slave.device for slave in slaves])
+ self._device.config.name)
+ self._device.remove_zone_slave([slave.device for slave in slaves])
def add_zone_slave(self, slaves):
"""
@@ -357,5 +352,5 @@ class SoundTouchDevice(MediaPlayerDevice):
_LOGGER.warning("Unable to find slaves to add")
else:
_LOGGER.info("Adding slaves to zone with master %s",
- self.device.config.name)
- self.device.add_zone_slave([slave.device for slave in slaves])
+ self._device.config.name)
+ self._device.add_zone_slave([slave.device for slave in slaves])
diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py
index 046aecbb92e..40ad0ef209c 100644
--- a/homeassistant/components/media_player/vizio.py
+++ b/homeassistant/components/media_player/vizio.py
@@ -62,7 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device = VizioDevice(host, token, name, volume_step)
if device.validate_setup() is False:
- _LOGGER.error("Failed to setup Vizio TV platform, "
+ _LOGGER.error("Failed to set up Vizio TV platform, "
"please check if host and API key are correct")
return
diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py
index ea4463f5c23..0e01310115f 100644
--- a/homeassistant/components/mqtt_eventstream.py
+++ b/homeassistant/components/mqtt_eventstream.py
@@ -17,7 +17,7 @@ from homeassistant.const import (
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
from homeassistant.core import EventOrigin, State
import homeassistant.helpers.config_validation as cv
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
DOMAIN = 'mqtt_eventstream'
DEPENDENCIES = ['mqtt']
diff --git a/homeassistant/components/mqtt_statestream.py b/homeassistant/components/mqtt_statestream.py
index 205a638c574..592e31cbff1 100644
--- a/homeassistant/components/mqtt_statestream.py
+++ b/homeassistant/components/mqtt_statestream.py
@@ -15,7 +15,7 @@ from homeassistant.core import callback
from homeassistant.components.mqtt import valid_publish_topic
from homeassistant.helpers.entityfilter import generate_filter
from homeassistant.helpers.event import async_track_state_change
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
import homeassistant.helpers.config_validation as cv
CONF_BASE_TOPIC = 'base_topic'
diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py
index e498539f2f9..7ca9ea9f9ea 100644
--- a/homeassistant/components/mysensors/__init__.py
+++ b/homeassistant/components/mysensors/__init__.py
@@ -133,7 +133,7 @@ def setup_mysensors_platform(
async_add_devices=None):
"""Set up a MySensors platform."""
# Only act if called via MySensors by discovery event.
- # Otherwise gateway is not setup.
+ # Otherwise gateway is not set up.
if not discovery_info:
return
if device_args is None:
diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index d25b94bbc17..04163f1ca13 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -119,7 +119,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup Nest from a config entry."""
+ """Set up Nest from a config entry."""
from nest import Nest
nest = Nest(access_token=entry.data['tokens']['access_token'])
@@ -282,12 +282,12 @@ class NestSensorDevice(Entity):
if device is not None:
# device specific
- self.device = device
- self._name = "{} {}".format(self.device.name_long,
+ self._device = device
+ self._name = "{} {}".format(self._device.name_long,
self.variable.replace('_', ' '))
else:
# structure only
- self.device = structure
+ self._device = structure
self._name = "{} {}".format(self.structure.name,
self.variable.replace('_', ' '))
diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py
index 13cd6203ed4..4de35d3f850 100644
--- a/homeassistant/components/notify/__init__.py
+++ b/homeassistant/components/notify/__init__.py
@@ -156,6 +156,8 @@ def async_setup(hass, config):
DOMAIN, platform_name_slug, async_notify_message,
schema=NOTIFY_SERVICE_SCHEMA)
+ hass.config.components.add('{}.{}'.format(DOMAIN, p_type))
+
return True
setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config
diff --git a/homeassistant/components/notify/aws_lambda.py b/homeassistant/components/notify/aws_lambda.py
index 46ac2f89d33..8a3cb900f4b 100644
--- a/homeassistant/components/notify/aws_lambda.py
+++ b/homeassistant/components/notify/aws_lambda.py
@@ -15,7 +15,7 @@ from homeassistant.const import (
from homeassistant.components.notify import (
ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService)
import homeassistant.helpers.config_validation as cv
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
REQUIREMENTS = ['boto3==1.4.7']
diff --git a/homeassistant/components/notify/hangouts.py b/homeassistant/components/notify/hangouts.py
new file mode 100644
index 00000000000..eb2880e8a46
--- /dev/null
+++ b/homeassistant/components/notify/hangouts.py
@@ -0,0 +1,66 @@
+"""
+Hangouts notification service.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/notify.hangouts/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
+ NOTIFY_SERVICE_SCHEMA,
+ BaseNotificationService,
+ ATTR_MESSAGE)
+
+from homeassistant.components.hangouts.const \
+ import (DOMAIN, SERVICE_SEND_MESSAGE,
+ TARGETS_SCHEMA, CONF_DEFAULT_CONVERSATIONS)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = [DOMAIN]
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_DEFAULT_CONVERSATIONS): [TARGETS_SCHEMA]
+})
+
+NOTIFY_SERVICE_SCHEMA = NOTIFY_SERVICE_SCHEMA.extend({
+ vol.Optional(ATTR_TARGET): [TARGETS_SCHEMA]
+})
+
+
+def get_service(hass, config, discovery_info=None):
+ """Get the Hangouts notification service."""
+ return HangoutsNotificationService(config.get(CONF_DEFAULT_CONVERSATIONS))
+
+
+class HangoutsNotificationService(BaseNotificationService):
+ """Send Notifications to Hangouts conversations."""
+
+ def __init__(self, default_conversations):
+ """Set up the notification service."""
+ self._default_conversations = default_conversations
+
+ def send_message(self, message="", **kwargs):
+ """Send the message to the Google Hangouts server."""
+ target_conversations = None
+ if ATTR_TARGET in kwargs:
+ target_conversations = []
+ for target in kwargs.get(ATTR_TARGET):
+ target_conversations.append({'id': target})
+ else:
+ target_conversations = self._default_conversations
+
+ messages = []
+ if 'title' in kwargs:
+ messages.append({'text': kwargs['title'], 'is_bold': True})
+
+ messages.append({'text': message, 'parse_str': True})
+ service_data = {
+ ATTR_TARGET: target_conversations,
+ ATTR_MESSAGE: messages
+ }
+
+ return self.hass.services.call(
+ DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data)
diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py
index 1ed50472004..fa93cc4ba4d 100644
--- a/homeassistant/components/notify/html5.py
+++ b/homeassistant/components/notify/html5.py
@@ -132,17 +132,6 @@ def _load_config(filename):
return {}
-class JSONBytesDecoder(json.JSONEncoder):
- """JSONEncoder to decode bytes objects to unicode."""
-
- # pylint: disable=method-hidden, arguments-differ
- def default(self, obj):
- """Decode object if it's a bytes object, else defer to base class."""
- if isinstance(obj, bytes):
- return obj.decode()
- return json.JSONEncoder.default(self, obj)
-
-
class HTML5PushRegistrationView(HomeAssistantView):
"""Accepts push registrations from a browser."""
diff --git a/homeassistant/components/notify/knx.py b/homeassistant/components/notify/knx.py
index 750e3945569..f9a6a4b25f2 100644
--- a/homeassistant/components/notify/knx.py
+++ b/homeassistant/components/notify/knx.py
@@ -61,13 +61,13 @@ class KNXNotificationService(BaseNotificationService):
def __init__(self, devices):
"""Initialize the service."""
- self.devices = devices
+ self._devices = devices
@property
def targets(self):
"""Return a dictionary of registered targets."""
ret = {}
- for device in self.devices:
+ for device in self._devices:
ret[device.name] = device.name
return ret
@@ -80,11 +80,11 @@ class KNXNotificationService(BaseNotificationService):
async def _async_send_to_all_devices(self, message):
"""Send a notification to knx bus to all connected devices."""
- for device in self.devices:
+ for device in self._devices:
await device.set(message)
async def _async_send_to_device(self, message, names):
"""Send a notification to knx bus to device with given names."""
- for device in self.devices:
+ for device in self._devices:
if device.name in names:
await device.set(message)
diff --git a/homeassistant/components/notify/pushsafer.py b/homeassistant/components/notify/pushsafer.py
index 30068854f2e..443d56521c1 100644
--- a/homeassistant/components/notify/pushsafer.py
+++ b/homeassistant/components/notify/pushsafer.py
@@ -32,6 +32,10 @@ ATTR_ICONCOLOR = 'iconcolor'
ATTR_URL = 'url'
ATTR_URLTITLE = 'urltitle'
ATTR_TIME2LIVE = 'time2live'
+ATTR_PRIORITY = 'priority'
+ATTR_RETRY = 'retry'
+ATTR_EXPIRE = 'expire'
+ATTR_ANSWER = 'answer'
ATTR_PICTURE1 = 'picture1'
# Attributes contained in picture1
@@ -106,6 +110,10 @@ class PushsaferNotificationService(BaseNotificationService):
'u': data.get(ATTR_URL, ""),
'ut': data.get(ATTR_URLTITLE, ""),
'l': data.get(ATTR_TIME2LIVE, ""),
+ 'pr': data.get(ATTR_PRIORITY, ""),
+ 're': data.get(ATTR_RETRY, ""),
+ 'ex': data.get(ATTR_EXPIRE, ""),
+ 'a': data.get(ATTR_ANSWER, ""),
'p': picture1_encoded
}
diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/notify/sendgrid.py
index 92b709af8ad..231a17455d1 100644
--- a/homeassistant/components/notify/sendgrid.py
+++ b/homeassistant/components/notify/sendgrid.py
@@ -14,7 +14,7 @@ from homeassistant.const import (
CONF_API_KEY, CONF_SENDER, CONF_RECIPIENT, CONTENT_TYPE_TEXT_PLAIN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['sendgrid==5.4.1']
+REQUIREMENTS = ['sendgrid==5.6.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/openuv.py b/homeassistant/components/openuv.py
index dd038611ae9..d696f0e5100 100644
--- a/homeassistant/components/openuv.py
+++ b/homeassistant/components/openuv.py
@@ -37,6 +37,7 @@ TOPIC_UPDATE = '{0}_data_update'.format(DOMAIN)
TYPE_CURRENT_OZONE_LEVEL = 'current_ozone_level'
TYPE_CURRENT_UV_INDEX = 'current_uv_index'
+TYPE_CURRENT_UV_LEVEL = 'current_uv_level'
TYPE_MAX_UV_INDEX = 'max_uv_index'
TYPE_PROTECTION_WINDOW = 'uv_protection_window'
TYPE_SAFE_EXPOSURE_TIME_1 = 'safe_exposure_time_type_1'
@@ -59,6 +60,7 @@ SENSORS = {
TYPE_CURRENT_OZONE_LEVEL: (
'Current Ozone Level', 'mdi:vector-triangle', 'du'),
TYPE_CURRENT_UV_INDEX: ('Current UV Index', 'mdi:weather-sunny', 'index'),
+ TYPE_CURRENT_UV_LEVEL: ('Current UV Level', 'mdi:weather-sunny', None),
TYPE_MAX_UV_INDEX: ('Max UV Index', 'mdi:weather-sunny', 'index'),
TYPE_SAFE_EXPOSURE_TIME_1: (
'Skin Type 1 Safe Exposure Time', 'mdi:timer', 'minutes'),
diff --git a/homeassistant/components/panel_custom.py b/homeassistant/components/panel_custom.py
index 0444e7a5b53..740a28a9dec 100644
--- a/homeassistant/components/panel_custom.py
+++ b/homeassistant/components/panel_custom.py
@@ -22,8 +22,13 @@ CONF_URL_PATH = 'url_path'
CONF_CONFIG = 'config'
CONF_WEBCOMPONENT_PATH = 'webcomponent_path'
CONF_JS_URL = 'js_url'
+CONF_MODULE_URL = 'module_url'
CONF_EMBED_IFRAME = 'embed_iframe'
CONF_TRUST_EXTERNAL_SCRIPT = 'trust_external_script'
+CONF_URL_EXCLUSIVE_GROUP = 'url_exclusive_group'
+
+MSG_URL_CONFLICT = \
+ 'Pass in only one of webcomponent_path, module_url or js_url'
DEFAULT_EMBED_IFRAME = False
DEFAULT_TRUST_EXTERNAL = False
@@ -40,8 +45,12 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_SIDEBAR_ICON, default=DEFAULT_ICON): cv.icon,
vol.Optional(CONF_URL_PATH): cv.string,
vol.Optional(CONF_CONFIG): dict,
- vol.Optional(CONF_WEBCOMPONENT_PATH): cv.isfile,
- vol.Optional(CONF_JS_URL): cv.string,
+ vol.Exclusive(CONF_WEBCOMPONENT_PATH, CONF_URL_EXCLUSIVE_GROUP,
+ msg=MSG_URL_CONFLICT): cv.string,
+ vol.Exclusive(CONF_JS_URL, CONF_URL_EXCLUSIVE_GROUP,
+ msg=MSG_URL_CONFLICT): cv.string,
+ vol.Exclusive(CONF_MODULE_URL, CONF_URL_EXCLUSIVE_GROUP,
+ msg=MSG_URL_CONFLICT): cv.string,
vol.Optional(CONF_EMBED_IFRAME,
default=DEFAULT_EMBED_IFRAME): cv.boolean,
vol.Optional(CONF_TRUST_EXTERNAL_SCRIPT,
@@ -66,6 +75,8 @@ async def async_register_panel(
html_url=None,
# JS source of your panel
js_url=None,
+ # JS module of your panel
+ module_url=None,
# If your panel should be run inside an iframe
embed_iframe=DEFAULT_EMBED_IFRAME,
# Should user be asked for confirmation when loading external source
@@ -73,10 +84,10 @@ async def async_register_panel(
# Configuration to be passed to the panel
config=None):
"""Register a new custom panel."""
- if js_url is None and html_url is None:
- raise ValueError('Either js_url or html_url is required.')
- elif js_url and html_url:
- raise ValueError('Pass in either JS url or HTML url, not both.')
+ if js_url is None and html_url is None and module_url is None:
+ raise ValueError('Either js_url, module_url or html_url is required.')
+ elif (js_url and html_url) or (module_url and html_url):
+ raise ValueError('Pass in only one of JS url, Module url or HTML url.')
if config is not None and not isinstance(config, dict):
raise ValueError('Config needs to be a dictionary.')
@@ -90,6 +101,9 @@ async def async_register_panel(
if js_url is not None:
custom_panel_config['js_url'] = js_url
+ if module_url is not None:
+ custom_panel_config['module_url'] = module_url
+
if html_url is not None:
custom_panel_config['html_url'] = html_url
@@ -136,6 +150,9 @@ async def async_setup(hass, config):
if CONF_JS_URL in panel:
kwargs['js_url'] = panel[CONF_JS_URL]
+ elif CONF_MODULE_URL in panel:
+ kwargs['module_url'] = panel[CONF_MODULE_URL]
+
elif not await hass.async_add_job(os.path.isfile, panel_path):
_LOGGER.error('Unable to find webcomponent for %s: %s',
name, panel_path)
diff --git a/homeassistant/components/prometheus.py b/homeassistant/components/prometheus.py
index da986f024a4..5fa768b6983 100644
--- a/homeassistant/components/prometheus.py
+++ b/homeassistant/components/prometheus.py
@@ -49,7 +49,9 @@ def setup(hass, config):
conf = config[DOMAIN]
entity_filter = conf[CONF_FILTER]
namespace = conf.get(CONF_PROM_NAMESPACE)
- metrics = PrometheusMetrics(prometheus_client, entity_filter, namespace)
+ climate_units = hass.config.units.temperature_unit
+ metrics = PrometheusMetrics(prometheus_client, entity_filter, namespace,
+ climate_units)
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_event)
return True
@@ -58,7 +60,8 @@ def setup(hass, config):
class PrometheusMetrics:
"""Model all of the metrics which should be exposed to Prometheus."""
- def __init__(self, prometheus_client, entity_filter, namespace):
+ def __init__(self, prometheus_client, entity_filter, namespace,
+ climate_units):
"""Initialize Prometheus Metrics."""
self.prometheus_client = prometheus_client
self._filter = entity_filter
@@ -67,6 +70,7 @@ class PrometheusMetrics:
else:
self.metrics_prefix = ""
self._metrics = {}
+ self._climate_units = climate_units
def handle_event(self, event):
"""Listen for new messages on the bus, and add them to Prometheus."""
@@ -173,8 +177,7 @@ class PrometheusMetrics:
def _handle_climate(self, state):
temp = state.attributes.get(ATTR_TEMPERATURE)
if temp:
- unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
- if unit == TEMP_FAHRENHEIT:
+ if self._climate_units == TEMP_FAHRENHEIT:
temp = fahrenheit_to_celsius(temp)
metric = self._metric(
'temperature_c', self.prometheus_client.Gauge,
@@ -183,7 +186,7 @@ class PrometheusMetrics:
current_temp = state.attributes.get(ATTR_CURRENT_TEMPERATURE)
if current_temp:
- if unit == TEMP_FAHRENHEIT:
+ if self._climate_units == TEMP_FAHRENHEIT:
current_temp = fahrenheit_to_celsius(current_temp)
metric = self._metric(
'current_temperature_c', self.prometheus_client.Gauge,
diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py
index 63e30a9491e..8af0e8db28d 100644
--- a/homeassistant/components/qwikswitch.py
+++ b/homeassistant/components/qwikswitch.py
@@ -98,13 +98,13 @@ class QSToggleEntity(QSEntity):
def __init__(self, qsid, qsusb):
"""Initialize the ToggleEntity."""
- self.device = qsusb.devices[qsid]
- super().__init__(qsid, self.device.name)
+ self._device = qsusb.devices[qsid]
+ super().__init__(qsid, self._device.name)
@property
def is_on(self):
"""Check if device is on (non-zero)."""
- return self.device.value > 0
+ return self._device.value > 0
async def async_turn_on(self, **kwargs):
"""Turn the device on."""
diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py
index 939985ebfb1..207f2f53a7f 100644
--- a/homeassistant/components/recorder/migration.py
+++ b/homeassistant/components/recorder/migration.py
@@ -1,39 +1,53 @@
"""Schema migration helpers."""
import logging
+import os
from .util import session_scope
_LOGGER = logging.getLogger(__name__)
+PROGRESS_FILE = '.migration_progress'
def migrate_schema(instance):
"""Check if the schema needs to be upgraded."""
from .models import SchemaChanges, SCHEMA_VERSION
+ progress_path = instance.hass.config.path(PROGRESS_FILE)
+
with session_scope(session=instance.get_session()) as session:
res = session.query(SchemaChanges).order_by(
SchemaChanges.change_id.desc()).first()
current_version = getattr(res, 'schema_version', None)
if current_version == SCHEMA_VERSION:
+ # Clean up if old migration left file
+ if os.path.isfile(progress_path):
+ _LOGGER.warning("Found existing migration file, cleaning up")
+ os.remove(instance.hass.config.path(PROGRESS_FILE))
return
- _LOGGER.debug("Database requires upgrade. Schema version: %s",
- current_version)
+ with open(progress_path, 'w'):
+ pass
+
+ _LOGGER.warning("Database requires upgrade. Schema version: %s",
+ current_version)
if current_version is None:
current_version = _inspect_schema_version(instance.engine, session)
_LOGGER.debug("No schema version found. Inspected version: %s",
current_version)
- for version in range(current_version, SCHEMA_VERSION):
- new_version = version + 1
- _LOGGER.info("Upgrading recorder db schema to version %s",
- new_version)
- _apply_update(instance.engine, new_version, current_version)
- session.add(SchemaChanges(schema_version=new_version))
+ try:
+ for version in range(current_version, SCHEMA_VERSION):
+ new_version = version + 1
+ _LOGGER.info("Upgrading recorder db schema to version %s",
+ new_version)
+ _apply_update(instance.engine, new_version, current_version)
+ session.add(SchemaChanges(schema_version=new_version))
- _LOGGER.info("Upgrade to version %s done", new_version)
+ _LOGGER.info("Upgrade to version %s done", new_version)
+ finally:
+ os.remove(instance.hass.config.path(PROGRESS_FILE))
def _create_index(engine, table_name, index_name):
@@ -43,6 +57,7 @@ def _create_index(engine, table_name, index_name):
within the table definition described in the models
"""
from sqlalchemy import Table
+ from sqlalchemy.exc import OperationalError
from . import models
table = Table(table_name, models.Base.metadata)
@@ -53,7 +68,15 @@ def _create_index(engine, table_name, index_name):
_LOGGER.info("Adding index `%s` to database. Note: this can take several "
"minutes on large databases and slow computers. Please "
"be patient!", index_name)
- index.create(engine)
+ try:
+ index.create(engine)
+ except OperationalError as err:
+ if 'already exists' not in str(err).lower():
+ raise
+
+ _LOGGER.warning('Index %s already exists on %s, continueing',
+ index_name, table_name)
+
_LOGGER.debug("Finished creating %s", index_name)
@@ -117,22 +140,37 @@ def _drop_index(engine, table_name, index_name):
def _add_columns(engine, table_name, columns_def):
"""Add columns to a table."""
from sqlalchemy import text
- from sqlalchemy.exc import SQLAlchemyError
+ from sqlalchemy.exc import OperationalError
- columns_def = ['ADD COLUMN {}'.format(col_def) for col_def in columns_def]
+ _LOGGER.info("Adding columns %s to table %s. Note: this can take several "
+ "minutes on large databases and slow computers. Please "
+ "be patient!",
+ ', '.join(column.split(' ')[0] for column in columns_def),
+ table_name)
+
+ columns_def = ['ADD {}'.format(col_def) for col_def in columns_def]
try:
engine.execute(text("ALTER TABLE {table} {columns_def}".format(
table=table_name,
columns_def=', '.join(columns_def))))
return
- except SQLAlchemyError:
- pass
+ except OperationalError:
+ # Some engines support adding all columns at once,
+ # this error is when they dont'
+ _LOGGER.info('Unable to use quick column add. Adding 1 by 1.')
for column_def in columns_def:
- engine.execute(text("ALTER TABLE {table} {column_def}".format(
- table=table_name,
- column_def=column_def)))
+ try:
+ engine.execute(text("ALTER TABLE {table} {column_def}".format(
+ table=table_name,
+ column_def=column_def)))
+ except OperationalError as err:
+ if 'duplicate' not in str(err).lower():
+ raise
+
+ _LOGGER.warning('Column %s already exists on %s, continueing',
+ column_def.split(' ')[1], table_name)
def _apply_update(engine, new_version, old_version):
diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py
index b8b777990f7..700dd57eacf 100644
--- a/homeassistant/components/recorder/models.py
+++ b/homeassistant/components/recorder/models.py
@@ -11,7 +11,7 @@ from sqlalchemy.ext.declarative import declarative_base
import homeassistant.util.dt as dt_util
from homeassistant.core import (
Context, Event, EventOrigin, State, split_entity_id)
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
# SQLAlchemy Schema
# pylint: disable=invalid-name
diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py
index eda09e3af64..1cc48f7adb2 100644
--- a/homeassistant/components/remote/xiaomi_miio.py
+++ b/homeassistant/components/remote/xiaomi_miio.py
@@ -22,7 +22,7 @@ from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
_LOGGER = logging.getLogger(__name__)
@@ -187,11 +187,6 @@ class XiaomiMiioRemote(RemoteDevice):
"""Return the name of the remote."""
return self._name
- @property
- def device(self):
- """Return the remote object."""
- return self._device
-
@property
def hidden(self):
"""Return if we should hide entity."""
@@ -212,7 +207,7 @@ class XiaomiMiioRemote(RemoteDevice):
"""Return False if device is unreachable, else True."""
from miio import DeviceException
try:
- self.device.info()
+ self._device.info()
return True
except DeviceException:
return False
@@ -247,14 +242,14 @@ class XiaomiMiioRemote(RemoteDevice):
_LOGGER.debug("Sending payload: '%s'", payload)
try:
- self.device.play(payload)
+ self._device.play(payload)
except DeviceException as ex:
_LOGGER.error(
"Transmit of IR command failed, %s, exception: %s",
payload, ex)
def send_command(self, command, **kwargs):
- """Wrapper for _send_command."""
+ """Send a command."""
num_repeats = kwargs.get(ATTR_NUM_REPEATS)
delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
diff --git a/homeassistant/components/sabnzbd.py b/homeassistant/components/sabnzbd.py
index b9c75c87c1d..380867a3285 100644
--- a/homeassistant/components/sabnzbd.py
+++ b/homeassistant/components/sabnzbd.py
@@ -110,7 +110,7 @@ async def async_configure_sabnzbd(hass, config, use_ssl, name=DEFAULT_NAME,
async def async_setup(hass, config):
- """Setup the SABnzbd component."""
+ """Set up the SABnzbd component."""
async def sabnzbd_discovered(service, info):
"""Handle service discovery."""
ssl = info.get('properties', {}).get('https', '0') == '1'
@@ -129,7 +129,7 @@ async def async_setup(hass, config):
@callback
def async_setup_sabnzbd(hass, sab_api, config, name):
- """Setup SABnzbd sensors and services."""
+ """Set up SABnzbd sensors and services."""
sab_api_data = SabnzbdApiData(sab_api, name, config.get(CONF_SENSORS, {}))
if config.get(CONF_SENSORS):
@@ -193,7 +193,7 @@ def async_request_configuration(hass, config, host):
return
def success():
- """Setup was successful."""
+ """Signal successful setup."""
conf = load_json(hass.config.path(CONFIG_FILE))
conf[host] = {CONF_API_KEY: api_key}
save_json(hass.config.path(CONFIG_FILE), conf)
diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py
index 7b76836555c..8771a84c1d6 100644
--- a/homeassistant/components/scene/__init__.py
+++ b/homeassistant/components/scene/__init__.py
@@ -95,7 +95,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py
index 8550d175b63..948f844cfd4 100644
--- a/homeassistant/components/sensor/__init__.py
+++ b/homeassistant/components/sensor/__init__.py
@@ -43,7 +43,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/sensor/bmw_connected_drive.py b/homeassistant/components/sensor/bmw_connected_drive.py
index e3331cdc763..deafacc288c 100644
--- a/homeassistant/components/sensor/bmw_connected_drive.py
+++ b/homeassistant/components/sensor/bmw_connected_drive.py
@@ -58,7 +58,10 @@ class BMWConnectedDriveSensor(Entity):
@property
def should_poll(self) -> bool:
- """Data update is triggered from BMWConnectedDriveEntity."""
+ """Return False.
+
+ Data update is triggered from BMWConnectedDriveEntity.
+ """
return False
@property
diff --git a/homeassistant/components/sensor/cert_expiry.py b/homeassistant/components/sensor/cert_expiry.py
index 1ccaf2f6925..00139a30620 100644
--- a/homeassistant/components/sensor/cert_expiry.py
+++ b/homeassistant/components/sensor/cert_expiry.py
@@ -1,5 +1,5 @@
"""
-Counter for the days till a HTTPS (TLS) certificate will expire.
+Counter for the days until an HTTPS (TLS) certificate will expire.
For more details about this sensor please refer to the documentation at
https://home-assistant.io/components/sensor.cert_expiry/
diff --git a/homeassistant/components/sensor/duke_energy.py b/homeassistant/components/sensor/duke_energy.py
index 458a2929d0b..17f118e3cb4 100644
--- a/homeassistant/components/sensor/duke_energy.py
+++ b/homeassistant/components/sensor/duke_energy.py
@@ -28,7 +28,7 @@ LAST_BILL_DAYS_BILLED = "last_bills_days_billed"
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup all Duke Energy meters."""
+ """Set up all Duke Energy meters."""
from pydukeenergy.api import DukeEnergy, DukeEnergyException
try:
@@ -36,7 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config[CONF_PASSWORD],
update_interval=120)
except DukeEnergyException:
- _LOGGER.error("Failed to setup Duke Energy")
+ _LOGGER.error("Failed to set up Duke Energy")
return
add_devices([DukeEnergyMeter(meter) for meter in duke.get_meters()])
diff --git a/homeassistant/components/sensor/fints.py b/homeassistant/components/sensor/fints.py
index ef064e84228..c573f0f0058 100644
--- a/homeassistant/components/sensor/fints.py
+++ b/homeassistant/components/sensor/fints.py
@@ -108,7 +108,7 @@ class FinTsClient:
"""
def __init__(self, credentials: BankCredentials, name: str):
- """Constructor for class FinTsClient."""
+ """Initialize a FinTsClient."""
self._credentials = credentials
self.name = name
@@ -151,14 +151,14 @@ class FinTsClient:
class FinTsAccount(Entity):
- """Sensor for a FinTS balanc account.
+ """Sensor for a FinTS balance account.
A balance account contains an amount of money (=balance). The amount may
also be negative.
"""
def __init__(self, client: FinTsClient, account, name: str) -> None:
- """Constructor for class FinTsAccount."""
+ """Initialize a FinTs balance account."""
self._client = client # type: FinTsClient
self._account = account
self._name = name # type: str
@@ -167,7 +167,10 @@ class FinTsAccount(Entity):
@property
def should_poll(self) -> bool:
- """Data needs to be polled from the bank servers."""
+ """Return True.
+
+ Data needs to be polled from the bank servers.
+ """
return True
def update(self) -> None:
@@ -218,7 +221,7 @@ class FinTsHoldingsAccount(Entity):
"""
def __init__(self, client: FinTsClient, account, name: str) -> None:
- """Constructor for class FinTsHoldingsAccount."""
+ """Initialize a FinTs holdings account."""
self._client = client # type: FinTsClient
self._name = name # type: str
self._account = account
@@ -227,7 +230,10 @@ class FinTsHoldingsAccount(Entity):
@property
def should_poll(self) -> bool:
- """Data needs to be polled from the bank servers."""
+ """Return True.
+
+ Data needs to be polled from the bank servers.
+ """
return True
def update(self) -> None:
diff --git a/homeassistant/components/sensor/homematic.py b/homeassistant/components/sensor/homematic.py
index 60741a9f3c8..0303525abcf 100644
--- a/homeassistant/components/sensor/homematic.py
+++ b/homeassistant/components/sensor/homematic.py
@@ -5,8 +5,9 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.homematic/
"""
import logging
+
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
from homeassistant.const import STATE_UNKNOWN
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
_LOGGER = logging.getLogger(__name__)
@@ -70,7 +71,7 @@ HM_ICON_HA_CAST = {
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Set up the HomeMatic platform."""
+ """Set up the HomeMatic sensor platform."""
if discovery_info is None:
return
@@ -83,7 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HMSensor(HMDevice):
- """Represents various HomeMatic sensors in Home Assistant."""
+ """Representation of a HomeMatic sensor."""
@property
def state(self):
@@ -111,4 +112,4 @@ class HMSensor(HMDevice):
if self._state:
self._data.update({self._state: STATE_UNKNOWN})
else:
- _LOGGER.critical("Can't initialize sensor %s", self._name)
+ _LOGGER.critical("Unable to initialize sensor: %s", self._name)
diff --git a/homeassistant/components/sensor/homematicip_cloud.py b/homeassistant/components/sensor/homematicip_cloud.py
index 7292e3b2f40..7d4944f5f5f 100644
--- a/homeassistant/components/sensor/homematicip_cloud.py
+++ b/homeassistant/components/sensor/homematicip_cloud.py
@@ -1,18 +1,17 @@
"""
-Support for HomematicIP sensors.
+Support for HomematicIP Cloud sensors.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.homematicip_cloud/
"""
-
import logging
from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
from homeassistant.const import (
- TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY,
- DEVICE_CLASS_ILLUMINANCE)
+ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
+ TEMP_CELSIUS)
_LOGGER = logging.getLogger(__name__)
@@ -25,14 +24,14 @@ ATTR_TEMPERATURE_OFFSET = 'temperature_offset'
ATTR_HUMIDITY = 'humidity'
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Set up the HomematicIP sensors devices."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Set up the HomematicIP Cloud sensors devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
- """Set up the HomematicIP sensors from a config entry."""
+ """Set up the HomematicIP Cloud sensors from a config entry."""
from homematicip.device import (
HeatingThermostat, TemperatureHumiditySensorWithoutDisplay,
TemperatureHumiditySensorDisplay, MotionDetectorIndoor)
@@ -54,7 +53,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipAccesspointStatus(HomematicipGenericDevice):
- """Representation of an HomeMaticIP access point."""
+ """Representation of an HomeMaticIP Cloud access point."""
def __init__(self, home):
"""Initialize access point device."""
@@ -82,7 +81,7 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice):
class HomematicipHeatingThermostat(HomematicipGenericDevice):
- """MomematicIP heating thermostat representation."""
+ """Represenation of a HomematicIP heating thermostat device."""
def __init__(self, home, device):
"""Initialize heating thermostat device."""
@@ -115,7 +114,7 @@ class HomematicipHeatingThermostat(HomematicipGenericDevice):
class HomematicipHumiditySensor(HomematicipGenericDevice):
- """MomematicIP humidity device."""
+ """Represenation of a HomematicIP Cloud humidity device."""
def __init__(self, home, device):
"""Initialize the thermometer device."""
@@ -138,7 +137,7 @@ class HomematicipHumiditySensor(HomematicipGenericDevice):
class HomematicipTemperatureSensor(HomematicipGenericDevice):
- """MomematicIP the thermometer device."""
+ """Representation of a HomematicIP Cloud thermometer device."""
def __init__(self, home, device):
"""Initialize the thermometer device."""
@@ -161,7 +160,7 @@ class HomematicipTemperatureSensor(HomematicipGenericDevice):
class HomematicipIlluminanceSensor(HomematicipGenericDevice):
- """MomematicIP the thermometer device."""
+ """Represenation of a HomematicIP Illuminance device."""
def __init__(self, home, device):
"""Initialize the device."""
diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/sensor/ihc.py
index 2dcf2c3f7be..547e6b52d9a 100644
--- a/homeassistant/components/sensor/ihc.py
+++ b/homeassistant/components/sensor/ihc.py
@@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the ihc sensor platform."""
+ """Set up the IHC sensor platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
devices = []
@@ -77,6 +77,6 @@ class IHCSensor(IHCDevice, Entity):
return self._unit_of_measurement
def on_ihc_change(self, ihc_id, value):
- """Callback when ihc resource changes."""
+ """Handle IHC resource change."""
self._state = value
self.schedule_update_ha_state()
diff --git a/homeassistant/components/sensor/insteon_plm.py b/homeassistant/components/sensor/insteon.py
similarity index 61%
rename from homeassistant/components/sensor/insteon_plm.py
rename to homeassistant/components/sensor/insteon.py
index 61f5877ed78..59c0fee7617 100644
--- a/homeassistant/components/sensor/insteon_plm.py
+++ b/homeassistant/components/sensor/insteon.py
@@ -2,35 +2,35 @@
Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.insteon_plm/
+https://home-assistant.io/components/sensor.insteon/
"""
import asyncio
import logging
-from homeassistant.components.insteon_plm import InsteonPLMEntity
+from homeassistant.components.insteon import InsteonEntity
from homeassistant.helpers.entity import Entity
-DEPENDENCIES = ['insteon_plm']
+DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
- """Set up the INSTEON PLM device class for the hass platform."""
- plm = hass.data['insteon_plm'].get('plm')
+ """Set up the INSTEON device class for the hass platform."""
+ insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
- device = plm.devices[address]
+ device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
_LOGGER.debug('Adding device %s entity %s to Sensor platform',
device.address.hex, device.states[state_key].name)
- new_entity = InsteonPLMSensorDevice(device, state_key)
+ new_entity = InsteonSensorDevice(device, state_key)
async_add_devices([new_entity])
-class InsteonPLMSensorDevice(InsteonPLMEntity, Entity):
+class InsteonSensorDevice(InsteonEntity, Entity):
"""A Class for an Insteon device."""
diff --git a/homeassistant/components/sensor/juicenet.py b/homeassistant/components/sensor/juicenet.py
index 0d305ca23c7..033e2d7acad 100644
--- a/homeassistant/components/sensor/juicenet.py
+++ b/homeassistant/components/sensor/juicenet.py
@@ -49,14 +49,14 @@ class JuicenetSensorDevice(JuicenetDevice, Entity):
@property
def name(self):
"""Return the name of the device."""
- return '{} {}'.format(self.device.name(), self._name)
+ return '{} {}'.format(self._device.name(), self._name)
@property
def icon(self):
"""Return the icon of the sensor."""
icon = None
if self.type == 'status':
- status = self.device.getStatus()
+ status = self._device.getStatus()
if status == 'standby':
icon = 'mdi:power-plug-off'
elif status == 'plugged':
@@ -87,19 +87,19 @@ class JuicenetSensorDevice(JuicenetDevice, Entity):
"""Return the state."""
state = None
if self.type == 'status':
- state = self.device.getStatus()
+ state = self._device.getStatus()
elif self.type == 'temperature':
- state = self.device.getTemperature()
+ state = self._device.getTemperature()
elif self.type == 'voltage':
- state = self.device.getVoltage()
+ state = self._device.getVoltage()
elif self.type == 'amps':
- state = self.device.getAmps()
+ state = self._device.getAmps()
elif self.type == 'watts':
- state = self.device.getWatts()
+ state = self._device.getWatts()
elif self.type == 'charge_time':
- state = self.device.getChargeTime()
+ state = self._device.getChargeTime()
elif self.type == 'energy_added':
- state = self.device.getEnergyAdded()
+ state = self._device.getEnergyAdded()
else:
state = 'Unknown'
return state
@@ -109,7 +109,7 @@ class JuicenetSensorDevice(JuicenetDevice, Entity):
"""Return the state attributes."""
attributes = {}
if self.type == 'status':
- man_dev_id = self.device.id()
+ man_dev_id = self._device.id()
if man_dev_id:
attributes["manufacturer_device_id"] = man_dev_id
return attributes
diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py
index 925b16cb4c7..b4d1f6653c0 100644
--- a/homeassistant/components/sensor/knx.py
+++ b/homeassistant/components/sensor/knx.py
@@ -64,7 +64,7 @@ class KNXSensor(Entity):
def __init__(self, hass, device):
"""Initialize of a KNX sensor."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
@@ -74,12 +74,12 @@ class KNXSensor(Entity):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -94,12 +94,12 @@ class KNXSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
- return self.device.resolve_state()
+ return self._device.resolve_state()
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
- return self.device.unit_of_measurement()
+ return self._device.unit_of_measurement()
@property
def device_state_attributes(self):
diff --git a/homeassistant/components/sensor/melissa.py b/homeassistant/components/sensor/melissa.py
index f67722b0198..634ef4ad810 100644
--- a/homeassistant/components/sensor/melissa.py
+++ b/homeassistant/components/sensor/melissa.py
@@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the melissa sensor platform."""
+ """Set up the melissa sensor platform."""
sensors = []
api = hass.data[DATA_MELISSA]
devices = api.fetch_devices().values()
diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py
index d2e1501ad7e..bb1f3e67d4d 100644
--- a/homeassistant/components/sensor/nest.py
+++ b/homeassistant/components/sensor/nest.py
@@ -140,15 +140,15 @@ class NestBasicSensor(NestSensorDevice):
self._unit = SENSOR_UNITS.get(self.variable)
if self.variable in VARIABLE_NAME_MAPPING:
- self._state = getattr(self.device,
+ self._state = getattr(self._device,
VARIABLE_NAME_MAPPING[self.variable])
elif self.variable in PROTECT_SENSOR_TYPES \
and self.variable != 'color_status':
# keep backward compatibility
- state = getattr(self.device, self.variable)
+ state = getattr(self._device, self.variable)
self._state = state.capitalize() if state is not None else None
else:
- self._state = getattr(self.device, self.variable)
+ self._state = getattr(self._device, self.variable)
class NestTempSensor(NestSensorDevice):
@@ -166,12 +166,12 @@ class NestTempSensor(NestSensorDevice):
def update(self):
"""Retrieve latest state."""
- if self.device.temperature_scale == 'C':
+ if self._device.temperature_scale == 'C':
self._unit = TEMP_CELSIUS
else:
self._unit = TEMP_FAHRENHEIT
- temp = getattr(self.device, self.variable)
+ temp = getattr(self._device, self.variable)
if temp is None:
self._state = None
diff --git a/homeassistant/components/sensor/noaa_tides.py b/homeassistant/components/sensor/noaa_tides.py
new file mode 100644
index 00000000000..8527abf84aa
--- /dev/null
+++ b/homeassistant/components/sensor/noaa_tides.py
@@ -0,0 +1,138 @@
+"""
+Support for the NOAA Tides and Currents API.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.noaa_tides/
+"""
+from datetime import datetime, timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_NAME, CONF_TIME_ZONE, CONF_UNIT_SYSTEM)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.entity import Entity
+
+REQUIREMENTS = ['py_noaa==0.3.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_STATION_ID = 'station_id'
+
+DEFAULT_ATTRIBUTION = "Data provided by NOAA"
+DEFAULT_NAME = 'NOAA Tides'
+DEFAULT_TIMEZONE = 'lst_ldt'
+
+SCAN_INTERVAL = timedelta(minutes=60)
+
+TIMEZONES = ['gmt', 'lst', 'lst_ldt']
+UNIT_SYSTEMS = ['english', 'metric']
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_STATION_ID): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_TIME_ZONE, default=DEFAULT_TIMEZONE): vol.In(TIMEZONES),
+ vol.Optional(CONF_UNIT_SYSTEM): vol.In(UNIT_SYSTEMS),
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the NOAA Tides and Currents sensor."""
+ station_id = config[CONF_STATION_ID]
+ name = config.get(CONF_NAME)
+ timezone = config.get(CONF_TIME_ZONE)
+
+ if CONF_UNIT_SYSTEM in config:
+ unit_system = config[CONF_UNIT_SYSTEM]
+ elif hass.config.units.is_metric:
+ unit_system = UNIT_SYSTEMS[1]
+ else:
+ unit_system = UNIT_SYSTEMS[0]
+
+ noaa_sensor = NOAATidesAndCurrentsSensor(
+ name, station_id, timezone, unit_system)
+
+ noaa_sensor.update()
+ if noaa_sensor.data is None:
+ _LOGGER.error("Unable to setup NOAA Tides Sensor")
+ return
+ add_devices([noaa_sensor], True)
+
+
+class NOAATidesAndCurrentsSensor(Entity):
+ """Representation of a NOAA Tides and Currents sensor."""
+
+ def __init__(self, name, station_id, timezone, unit_system):
+ """Initialize the sensor."""
+ self._name = name
+ self._station_id = station_id
+ self._timezone = timezone
+ self._unit_system = unit_system
+ self.data = None
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes of this device."""
+ attr = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
+ if self.data is None:
+ return attr
+ if self.data['hi_lo'][1] == 'H':
+ attr['high_tide_time'] = \
+ self.data.index[1].strftime('%Y-%m-%dT%H:%M')
+ attr['high_tide_height'] = self.data['predicted_wl'][1]
+ attr['low_tide_time'] = \
+ self.data.index[2].strftime('%Y-%m-%dT%H:%M')
+ attr['low_tide_height'] = self.data['predicted_wl'][2]
+ elif self.data['hi_lo'][1] == 'L':
+ attr['low_tide_time'] = \
+ self.data.index[1].strftime('%Y-%m-%dT%H:%M')
+ attr['low_tide_height'] = self.data['predicted_wl'][1]
+ attr['high_tide_time'] = \
+ self.data.index[2].strftime('%Y-%m-%dT%H:%M')
+ attr['high_tide_height'] = self.data['predicted_wl'][2]
+ return attr
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ if self.data is None:
+ return None
+ api_time = self.data.index[0]
+ if self.data['hi_lo'][0] == 'H':
+ tidetime = api_time.strftime('%-I:%M %p')
+ return "High tide at {}".format(tidetime)
+ if self.data['hi_lo'][0] == 'L':
+ tidetime = api_time.strftime('%-I:%M %p')
+ return "Low tide at {}".format(tidetime)
+ return None
+
+ def update(self):
+ """Get the latest data from NOAA Tides and Currents API."""
+ from py_noaa import coops # pylint: disable=import-error
+ begin = datetime.now()
+ delta = timedelta(days=2)
+ end = begin + delta
+ try:
+ df_predictions = coops.get_data(
+ begin_date=begin.strftime("%Y%m%d %H:%M"),
+ end_date=end.strftime("%Y%m%d %H:%M"),
+ stationid=self._station_id,
+ product="predictions",
+ datum="MLLW",
+ interval="hilo",
+ units=self._unit_system,
+ time_zone=self._timezone)
+ self.data = df_predictions.head()
+ _LOGGER.debug("Data = %s", self.data)
+ _LOGGER.debug("Recent Tide data queried with start time set to %s",
+ begin.strftime("%m-%d-%Y %H:%M"))
+ except ValueError as err:
+ _LOGGER.error("Check NOAA Tides and Currents: %s", err.args)
+ self.data = None
diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/sensor/nut.py
index 7126bd89ef9..79ad176e42e 100644
--- a/homeassistant/components/sensor/nut.py
+++ b/homeassistant/components/sensor/nut.py
@@ -164,7 +164,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data = PyNUTData(host, port, alias, username, password)
if data.status is None:
- _LOGGER.error("NUT Sensor has no data, unable to setup")
+ _LOGGER.error("NUT Sensor has no data, unable to set up")
raise PlatformNotReady
_LOGGER.debug('NUT Sensors Available: %s', data.status)
diff --git a/homeassistant/components/sensor/openuv.py b/homeassistant/components/sensor/openuv.py
index b30c2908c40..42ff999bdd5 100644
--- a/homeassistant/components/sensor/openuv.py
+++ b/homeassistant/components/sensor/openuv.py
@@ -11,10 +11,10 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.openuv import (
DATA_UV, DOMAIN, SENSORS, TOPIC_UPDATE, TYPE_CURRENT_OZONE_LEVEL,
- TYPE_CURRENT_UV_INDEX, TYPE_MAX_UV_INDEX, TYPE_SAFE_EXPOSURE_TIME_1,
- TYPE_SAFE_EXPOSURE_TIME_2, TYPE_SAFE_EXPOSURE_TIME_3,
- TYPE_SAFE_EXPOSURE_TIME_4, TYPE_SAFE_EXPOSURE_TIME_5,
- TYPE_SAFE_EXPOSURE_TIME_6, OpenUvEntity)
+ TYPE_CURRENT_UV_INDEX, TYPE_CURRENT_UV_LEVEL, TYPE_MAX_UV_INDEX,
+ TYPE_SAFE_EXPOSURE_TIME_1, TYPE_SAFE_EXPOSURE_TIME_2,
+ TYPE_SAFE_EXPOSURE_TIME_3, TYPE_SAFE_EXPOSURE_TIME_4,
+ TYPE_SAFE_EXPOSURE_TIME_5, TYPE_SAFE_EXPOSURE_TIME_6, OpenUvEntity)
from homeassistant.util.dt import as_local, parse_datetime
DEPENDENCIES = ['openuv']
@@ -31,6 +31,12 @@ EXPOSURE_TYPE_MAP = {
TYPE_SAFE_EXPOSURE_TIME_6: 'st6'
}
+UV_LEVEL_EXTREME = "Extreme"
+UV_LEVEL_VHIGH = "Very High"
+UV_LEVEL_HIGH = "High"
+UV_LEVEL_MODERATE = "Moderate"
+UV_LEVEL_LOW = "Low"
+
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
@@ -105,6 +111,17 @@ class OpenUvSensor(OpenUvEntity):
self._state = data['ozone']
elif self._sensor_type == TYPE_CURRENT_UV_INDEX:
self._state = data['uv']
+ elif self._sensor_type == TYPE_CURRENT_UV_LEVEL:
+ if data['uv'] >= 11:
+ self._state = UV_LEVEL_EXTREME
+ elif data['uv'] >= 8:
+ self._state = UV_LEVEL_VHIGH
+ elif data['uv'] >= 6:
+ self._state = UV_LEVEL_HIGH
+ elif data['uv'] >= 3:
+ self._state = UV_LEVEL_MODERATE
+ else:
+ self._state = UV_LEVEL_LOW
elif self._sensor_type == TYPE_MAX_UV_INDEX:
self._state = data['uv_max']
self._attrs.update({
diff --git a/homeassistant/components/sensor/shodan.py b/homeassistant/components/sensor/shodan.py
index dfc49ce6639..b94d0cc011c 100644
--- a/homeassistant/components/sensor/shodan.py
+++ b/homeassistant/components/sensor/shodan.py
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['shodan==1.9.0']
+REQUIREMENTS = ['shodan==1.9.1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sensor/sht31.py b/homeassistant/components/sensor/sht31.py
index 2aeff8e73d8..04b78c283c7 100644
--- a/homeassistant/components/sensor/sht31.py
+++ b/homeassistant/components/sensor/sht31.py
@@ -47,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the sensor platform."""
+ """Set up the sensor platform."""
from Adafruit_SHT31 import SHT31
i2c_address = config.get(CONF_I2C_ADDRESS)
diff --git a/homeassistant/components/sensor/sigfox.py b/homeassistant/components/sensor/sigfox.py
index 408435a9667..191b3f54f35 100644
--- a/homeassistant/components/sensor/sigfox.py
+++ b/homeassistant/components/sensor/sigfox.py
@@ -77,7 +77,7 @@ class SigfoxAPI:
_LOGGER.error(
"Unable to login to Sigfox API, error code %s", str(
response.status_code))
- raise ValueError('Sigfox component not setup')
+ raise ValueError('Sigfox component not set up')
return True
def get_device_types(self):
diff --git a/homeassistant/components/sensor/tank_utility.py b/homeassistant/components/sensor/tank_utility.py
index 01ace415159..1f565d44325 100644
--- a/homeassistant/components/sensor/tank_utility.py
+++ b/homeassistant/components/sensor/tank_utility.py
@@ -79,15 +79,10 @@ class TankUtilitySensor(Entity):
self._token = token
self._device = device
self._state = STATE_UNKNOWN
- self._name = "Tank Utility " + self.device
+ self._name = "Tank Utility " + self._device
self._unit_of_measurement = SENSOR_UNIT_OF_MEASUREMENT
self._attributes = {}
- @property
- def device(self):
- """Return the device identifier."""
- return self._device
-
@property
def state(self):
"""Return the state of the device."""
@@ -117,14 +112,14 @@ class TankUtilitySensor(Entity):
from tank_utility import auth, device
data = {}
try:
- data = device.get_device_data(self._token, self.device)
+ data = device.get_device_data(self._token, self._device)
except requests.exceptions.HTTPError as http_error:
if (http_error.response.status_code ==
requests.codes.unauthorized): # pylint: disable=no-member
_LOGGER.info("Getting new token")
self._token = auth.get_token(self._email, self._password,
force=True)
- data = device.get_device_data(self._token, self.device)
+ data = device.get_device_data(self._token, self._device)
else:
raise http_error
data.update(data.pop("device", {}))
diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py
index 123c11021b4..9d5a21b37c4 100644
--- a/homeassistant/components/sensor/tellduslive.py
+++ b/homeassistant/components/sensor/tellduslive.py
@@ -67,7 +67,7 @@ class TelldusLiveSensor(TelldusLiveEntity):
@property
def _value(self):
"""Return value of the sensor."""
- return self.device.value(*self._id[1:])
+ return self._device.value(*self._id[1:])
@property
def _value_as_temperature(self):
diff --git a/homeassistant/components/sensor/uscis.py b/homeassistant/components/sensor/uscis.py
index ed3c9ca8587..f93a788092b 100644
--- a/homeassistant/components/sensor/uscis.py
+++ b/homeassistant/components/sensor/uscis.py
@@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setting the platform in HASS and Case Information."""
+ """Set up the platform in HASS and Case Information."""
uscis = UscisSensor(config['case_id'], config[CONF_FRIENDLY_NAME])
uscis.update()
if uscis.valid_case_id:
@@ -72,7 +72,7 @@ class UscisSensor(Entity):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
- """Using Request to access USCIS website and fetch data."""
+ """Fetch data from the USCIS website and update state attributes."""
import uscisstatus
try:
status = uscisstatus.get_case_status(self._case_id)
diff --git a/homeassistant/components/sensor/wirelesstag.py b/homeassistant/components/sensor/wirelesstag.py
index ad2115e9bd3..e5166173cb9 100644
--- a/homeassistant/components/sensor/wirelesstag.py
+++ b/homeassistant/components/sensor/wirelesstag.py
@@ -1,5 +1,5 @@
"""
-Sensor support for Wirelss Sensor Tags platform.
+Sensor support for Wireless Sensor Tags platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.wirelesstag/
@@ -58,7 +58,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the sensor platform."""
+ """Set up the sensor platform."""
platform = hass.data.get(WIRELESSTAG_DOMAIN)
sensors = []
tags = platform.tags
@@ -98,7 +98,7 @@ class WirelessTagSensor(WirelessTagBaseSensor):
else all_sensors)
def __init__(self, api, tag, sensor_type, config):
- """Constructor with platform(api), tag and hass sensor type."""
+ """Initialize a WirelessTag sensor."""
super().__init__(api, tag)
self._sensor_type = sensor_type
diff --git a/homeassistant/components/sensor/xiaomi_miio.py b/homeassistant/components/sensor/xiaomi_miio.py
index 63d93d31cf3..6fb89c5109e 100644
--- a/homeassistant/components/sensor/xiaomi_miio.py
+++ b/homeassistant/components/sensor/xiaomi_miio.py
@@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
ATTR_POWER = 'power'
ATTR_CHARGING = 'charging'
diff --git a/homeassistant/components/sonos/.translations/en.json b/homeassistant/components/sonos/.translations/en.json
index c7aae4302f6..05c9d2fa780 100644
--- a/homeassistant/components/sonos/.translations/en.json
+++ b/homeassistant/components/sonos/.translations/en.json
@@ -6,10 +6,10 @@
},
"step": {
"confirm": {
- "description": "Do you want to setup Sonos?",
+ "description": "Do you want to set up Sonos?",
"title": "Sonos"
}
},
"title": "Sonos"
}
-}
\ No newline at end of file
+}
diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py
index bbc05a3aa61..6c9280195cc 100644
--- a/homeassistant/components/sonos/__init__.py
+++ b/homeassistant/components/sonos/__init__.py
@@ -4,7 +4,7 @@ from homeassistant.helpers import config_entry_flow
DOMAIN = 'sonos'
-REQUIREMENTS = ['SoCo==0.14']
+REQUIREMENTS = ['SoCo==0.16']
async def async_setup(hass, config):
diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json
index 4aa68712d59..0422919c1aa 100644
--- a/homeassistant/components/sonos/strings.json
+++ b/homeassistant/components/sonos/strings.json
@@ -4,7 +4,7 @@
"step": {
"confirm": {
"title": "Sonos",
- "description": "Do you want to setup Sonos?"
+ "description": "Do you want to set up Sonos?"
}
},
"abort": {
diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk.py
index a5b42eb9b5a..28863f6a436 100644
--- a/homeassistant/components/splunk.py
+++ b/homeassistant/components/splunk.py
@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_SSL, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TOKEN, EVENT_STATE_CHANGED)
from homeassistant.helpers import state as state_helper
import homeassistant.helpers.config_validation as cv
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py
index 3ca52a91758..c95c752435a 100644
--- a/homeassistant/components/switch/__init__.py
+++ b/homeassistant/components/switch/__init__.py
@@ -117,7 +117,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry):
- """Setup a config entry."""
+ """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/switch/fritzdect.py
index 9c0f852846a..36a53edefc9 100644
--- a/homeassistant/components/switch/fritzdect.py
+++ b/homeassistant/components/switch/fritzdect.py
@@ -105,7 +105,7 @@ class FritzDectSwitch(SwitchDevice):
return attrs
@property
- def current_power_watt(self):
+ def current_power_w(self):
"""Return the current power usage in Watt."""
try:
return float(self.data.current_consumption)
diff --git a/homeassistant/components/switch/homematic.py b/homeassistant/components/switch/homematic.py
index 487947598bb..2cd4145e87a 100644
--- a/homeassistant/components/switch/homematic.py
+++ b/homeassistant/components/switch/homematic.py
@@ -5,8 +5,9 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.homematic/
"""
import logging
+
+from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice
from homeassistant.components.switch import SwitchDevice
-from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/switch/homematicip_cloud.py b/homeassistant/components/switch/homematicip_cloud.py
index 68884aaaa02..3211cecabfc 100644
--- a/homeassistant/components/switch/homematicip_cloud.py
+++ b/homeassistant/components/switch/homematicip_cloud.py
@@ -1,16 +1,15 @@
"""
-Support for HomematicIP switch.
+Support for HomematicIP Cloud switch.
-For more details about this component, please refer to the documentation at
+For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.homematicip_cloud/
"""
-
import logging
-from homeassistant.components.switch import SwitchDevice
from homeassistant.components.homematicip_cloud import (
- HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN,
- HMIPC_HAPID)
+ HMIPC_HAPID, HomematicipGenericDevice)
+from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
+from homeassistant.components.switch import SwitchDevice
DEPENDENCIES = ['homematicip_cloud']
@@ -21,17 +20,16 @@ ATTR_ENERGIE_COUNTER = 'energie_counter'
ATTR_PROFILE_MODE = 'profile_mode'
-async def async_setup_platform(hass, config, async_add_devices,
- discovery_info=None):
- """Set up the HomematicIP switch devices."""
+async def async_setup_platform(
+ hass, config, async_add_devices, discovery_info=None):
+ """Set up the HomematicIP Cloud switch devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the HomematicIP switch from a config entry."""
from homematicip.device import (
- PlugableSwitch, PlugableSwitchMeasuring,
- BrandSwitchMeasuring)
+ PlugableSwitch, PlugableSwitchMeasuring, BrandSwitchMeasuring)
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
@@ -51,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipSwitch(HomematicipGenericDevice, SwitchDevice):
- """MomematicIP switch device."""
+ """representation of a HomematicIP Cloud switch device."""
def __init__(self, home, device):
"""Initialize the switch device."""
@@ -72,7 +70,7 @@ class HomematicipSwitch(HomematicipGenericDevice, SwitchDevice):
class HomematicipSwitchMeasuring(HomematicipSwitch):
- """MomematicIP measuring switch device."""
+ """Representation of a HomematicIP measuring switch device."""
@property
def current_power_w(self):
diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/switch/ihc.py
index 3f461784693..f744519e430 100644
--- a/homeassistant/components/switch/ihc.py
+++ b/homeassistant/components/switch/ihc.py
@@ -26,7 +26,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the ihc switch platform."""
+ """Set up the IHC switch platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
devices = []
@@ -70,6 +70,6 @@ class IHCSwitch(IHCDevice, SwitchDevice):
self.ihc_controller.set_runtime_value_bool(self.ihc_id, False)
def on_ihc_change(self, ihc_id, value):
- """Callback when the ihc resource changes."""
+ """Handle IHC resource change."""
self._state = value
self.schedule_update_ha_state()
diff --git a/homeassistant/components/switch/insteon_plm.py b/homeassistant/components/switch/insteon.py
similarity index 69%
rename from homeassistant/components/switch/insteon_plm.py
rename to homeassistant/components/switch/insteon.py
index c357d1ccc04..8575b16c69b 100644
--- a/homeassistant/components/switch/insteon_plm.py
+++ b/homeassistant/components/switch/insteon.py
@@ -2,26 +2,26 @@
Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/switch.insteon_plm/
+https://home-assistant.io/components/switch.insteon/
"""
import asyncio
import logging
-from homeassistant.components.insteon_plm import InsteonPLMEntity
+from homeassistant.components.insteon import InsteonEntity
from homeassistant.components.switch import SwitchDevice
-DEPENDENCIES = ['insteon_plm']
+DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
- """Set up the INSTEON PLM device class for the hass platform."""
- plm = hass.data['insteon_plm'].get('plm')
+ """Set up the INSTEON device class for the hass platform."""
+ insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
- device = plm.devices[address]
+ device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
state_name = device.states[state_key].name
@@ -30,17 +30,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
device.address.hex, device.states[state_key].name)
new_entity = None
- if state_name in ['lightOnOff', 'outletTopOnOff', 'outletBottomOnOff',
- 'x10OnOffSwitch']:
- new_entity = InsteonPLMSwitchDevice(device, state_key)
- elif state_name == 'openClosedRelay':
- new_entity = InsteonPLMOpenClosedDevice(device, state_key)
+ if state_name == 'openClosedRelay':
+ new_entity = InsteonOpenClosedDevice(device, state_key)
+ else:
+ new_entity = InsteonSwitchDevice(device, state_key)
if new_entity is not None:
async_add_devices([new_entity])
-class InsteonPLMSwitchDevice(InsteonPLMEntity, SwitchDevice):
+class InsteonSwitchDevice(InsteonEntity, SwitchDevice):
"""A Class for an Insteon device."""
@property
@@ -59,7 +58,7 @@ class InsteonPLMSwitchDevice(InsteonPLMEntity, SwitchDevice):
self._insteon_device_state.off()
-class InsteonPLMOpenClosedDevice(InsteonPLMEntity, SwitchDevice):
+class InsteonOpenClosedDevice(InsteonEntity, SwitchDevice):
"""A Class for an Insteon device."""
@property
diff --git a/homeassistant/components/switch/insteon_local.py b/homeassistant/components/switch/insteon_local.py
deleted file mode 100644
index c4c8a854670..00000000000
--- a/homeassistant/components/switch/insteon_local.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
-Support for Insteon switch devices via local hub support.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/switch.insteon_local/
-"""
-import logging
-from datetime import timedelta
-
-from homeassistant.components.switch import SwitchDevice
-from homeassistant import util
-
-_CONFIGURING = {}
-_LOGGER = logging.getLogger(__name__)
-
-DEPENDENCIES = ['insteon_local']
-DOMAIN = 'switch'
-
-MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
-MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
-
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
- """Set up the Insteon local switch platform."""
- insteonhub = hass.data['insteon_local']
- if discovery_info is None:
- return
-
- linked = discovery_info['linked']
- device_list = []
- for device_id in linked:
- if linked[device_id]['cat_type'] == 'switch':
- device = insteonhub.switch(device_id)
- device_list.append(
- InsteonLocalSwitchDevice(device)
- )
-
- add_devices(device_list)
-
-
-class InsteonLocalSwitchDevice(SwitchDevice):
- """An abstract Class for an Insteon node."""
-
- def __init__(self, node):
- """Initialize the device."""
- self.node = node
- self._state = False
-
- @property
- def name(self):
- """Return the name of the node."""
- return self.node.device_id
-
- @property
- def unique_id(self):
- """Return the ID of this Insteon node."""
- return self.node.device_id
-
- @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
- def update(self):
- """Get the updated status of the switch."""
- resp = self.node.status(0)
-
- while 'error' in resp and resp['error'] is True:
- resp = self.node.status(0)
-
- if 'cmd2' in resp:
- self._state = int(resp['cmd2'], 16) > 0
-
- @property
- def is_on(self):
- """Return the boolean response if the node is on."""
- return self._state
-
- def turn_on(self, **kwargs):
- """Turn device on."""
- self.node.on()
- self._state = True
-
- def turn_off(self, **kwargs):
- """Turn device off."""
- self.node.off()
- self._state = False
diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/switch/knx.py
index c13631ca5e6..4e0b29301fb 100644
--- a/homeassistant/components/switch/knx.py
+++ b/homeassistant/components/switch/knx.py
@@ -63,7 +63,7 @@ class KNXSwitch(SwitchDevice):
def __init__(self, hass, device):
"""Initialize of KNX switch."""
- self.device = device
+ self._device = device
self.hass = hass
self.async_register_callbacks()
@@ -73,12 +73,12 @@ class KNXSwitch(SwitchDevice):
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
- self.device.register_device_updated_cb(after_update_callback)
+ self._device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
- return self.device.name
+ return self._device.name
@property
def available(self):
@@ -93,12 +93,12 @@ class KNXSwitch(SwitchDevice):
@property
def is_on(self):
"""Return true if device is on."""
- return self.device.state
+ return self._device.state
async def async_turn_on(self, **kwargs):
"""Turn the device on."""
- await self.device.set_on()
+ await self._device.set_on()
async def async_turn_off(self, **kwargs):
"""Turn the device off."""
- await self.device.set_off()
+ await self._device.set_off()
diff --git a/homeassistant/components/switch/mochad.py b/homeassistant/components/switch/mochad.py
index f80784271c2..bb3b9c0ea65 100644
--- a/homeassistant/components/switch/mochad.py
+++ b/homeassistant/components/switch/mochad.py
@@ -48,7 +48,7 @@ class MochadSwitch(SwitchDevice):
self._address = dev[CONF_ADDRESS]
self._name = dev.get(CONF_NAME, 'x10_switch_dev_%s' % self._address)
self._comm_type = dev.get(mochad.CONF_COMM_TYPE, 'pl')
- self.device = device.Device(ctrl, self._address,
+ self.switch = device.Device(ctrl, self._address,
comm_type=self._comm_type)
# Init with false to avoid locking HA for long on CM19A (goes from rf
# to pl via TM751, but not other way around)
@@ -71,7 +71,7 @@ class MochadSwitch(SwitchDevice):
try:
# Recycle socket on new command to recover mochad connection
self._controller.reconnect()
- self.device.send_cmd('on')
+ self.switch.send_cmd('on')
# No read data on CM19A which is rf only
if self._comm_type == 'pl':
self._controller.read_data()
@@ -88,7 +88,7 @@ class MochadSwitch(SwitchDevice):
try:
# Recycle socket on new command to recover mochad connection
self._controller.reconnect()
- self.device.send_cmd('off')
+ self.switch.send_cmd('off')
# No read data on CM19A which is rf only
if self._comm_type == 'pl':
self._controller.read_data()
@@ -99,7 +99,7 @@ class MochadSwitch(SwitchDevice):
def _get_device_status(self):
"""Get the status of the switch from mochad."""
with mochad.REQ_LOCK:
- status = self.device.get_status().rstrip()
+ status = self.switch.get_status().rstrip()
return status == 'on'
@property
diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/switch/tellduslive.py
index eec63ebaa5c..ac2b569f81c 100644
--- a/homeassistant/components/switch/tellduslive.py
+++ b/homeassistant/components/switch/tellduslive.py
@@ -28,14 +28,14 @@ class TelldusLiveSwitch(TelldusLiveEntity, ToggleEntity):
@property
def is_on(self):
"""Return true if switch is on."""
- return self.device.is_on
+ return self._device.is_on
def turn_on(self, **kwargs):
"""Turn the switch on."""
- self.device.turn_on()
+ self._device.turn_on()
self.changed()
def turn_off(self, **kwargs):
"""Turn the switch off."""
- self.device.turn_off()
+ self._device.turn_off()
self.changed()
diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py
index 594f290273e..199156afc21 100644
--- a/homeassistant/components/switch/wemo.py
+++ b/homeassistant/components/switch/wemo.py
@@ -77,7 +77,7 @@ class WemoSwitch(SwitchDevice):
self._async_locked_subscription_callback(not updated))
async def _async_locked_subscription_callback(self, force_update):
- """Helper to handle an update from a subscription."""
+ """Handle an update from a subscription."""
# If an update is in progress, we don't do anything
if self._update_lock.locked():
return
diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py
index 37b16f44ea8..56203c0552a 100644
--- a/homeassistant/components/switch/xiaomi_miio.py
+++ b/homeassistant/components/switch/xiaomi_miio.py
@@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
'chuangmi.plug.v3']),
})
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
ATTR_POWER = 'power'
ATTR_TEMPERATURE = 'temperature'
diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py
index c2b7ba9ba0f..58be267bbbc 100644
--- a/homeassistant/components/tellduslive.py
+++ b/homeassistant/components/tellduslive.py
@@ -287,13 +287,14 @@ class TelldusLiveEntity(Entity):
self._id = device_id
self._client = hass.data[DOMAIN]
self._client.entities.append(self)
- self._name = self.device.name
+ self._device = self._client.device(device_id)
+ self._name = self._device.name
_LOGGER.debug('Created device %s', self)
def changed(self):
"""Return the property of the device might have changed."""
- if self.device.name:
- self._name = self.device.name
+ if self._device.name:
+ self._name = self._device.name
self.schedule_update_ha_state()
@property
@@ -301,15 +302,10 @@ class TelldusLiveEntity(Entity):
"""Return the id of the device."""
return self._id
- @property
- def device(self):
- """Return the representation of the device."""
- return self._client.device(self.device_id)
-
@property
def _state(self):
"""Return the state of the device."""
- return self.device.state
+ return self._device.state
@property
def should_poll(self):
@@ -347,16 +343,16 @@ class TelldusLiveEntity(Entity):
from tellduslive import (BATTERY_LOW,
BATTERY_UNKNOWN,
BATTERY_OK)
- if self.device.battery == BATTERY_LOW:
+ if self._device.battery == BATTERY_LOW:
return 1
- if self.device.battery == BATTERY_UNKNOWN:
+ if self._device.battery == BATTERY_UNKNOWN:
return None
- if self.device.battery == BATTERY_OK:
+ if self._device.battery == BATTERY_OK:
return 100
- return self.device.battery # Percentage
+ return self._device.battery # Percentage
@property
def _last_updated(self):
"""Return the last update of a device."""
- return str(datetime.fromtimestamp(self.device.lastUpdated)) \
- if self.device.lastUpdated else None
+ return str(datetime.fromtimestamp(self._device.lastUpdated)) \
+ if self._device.lastUpdated else None
diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py
index 490c11baad7..33f34164b02 100644
--- a/homeassistant/components/tuya.py
+++ b/homeassistant/components/tuya.py
@@ -10,14 +10,14 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
-from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
+from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PLATFORM)
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import (
dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['tuyapy==0.1.2']
+REQUIREMENTS = ['tuyapy==0.1.3']
_LOGGER = logging.getLogger(__name__)
@@ -45,7 +45,8 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_COUNTRYCODE): cv.string
+ vol.Required(CONF_COUNTRYCODE): cv.string,
+ vol.Optional(CONF_PLATFORM, default='tuya'): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
@@ -58,9 +59,10 @@ def setup(hass, config):
username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]
country_code = config[DOMAIN][CONF_COUNTRYCODE]
+ platform = config[DOMAIN][CONF_PLATFORM]
hass.data[DATA_TUYA] = tuya
- tuya.init(username, password, country_code)
+ tuya.init(username, password, country_code, platform)
hass.data[DOMAIN] = {
'entities': {}
}
diff --git a/homeassistant/components/vacuum/ecovacs.py b/homeassistant/components/vacuum/ecovacs.py
new file mode 100644
index 00000000000..e0870a48861
--- /dev/null
+++ b/homeassistant/components/vacuum/ecovacs.py
@@ -0,0 +1,198 @@
+"""
+Support for Ecovacs Ecovacs Vaccums.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/vacuum.neato/
+"""
+import logging
+
+from homeassistant.components.vacuum import (
+ VacuumDevice, SUPPORT_BATTERY, SUPPORT_RETURN_HOME, SUPPORT_CLEAN_SPOT,
+ SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_LOCATE, SUPPORT_FAN_SPEED, SUPPORT_SEND_COMMAND, )
+from homeassistant.components.ecovacs import (
+ ECOVACS_DEVICES)
+from homeassistant.helpers.icon import icon_for_battery_level
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['ecovacs']
+
+SUPPORT_ECOVACS = (
+ SUPPORT_BATTERY | SUPPORT_RETURN_HOME | SUPPORT_CLEAN_SPOT |
+ SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_LOCATE |
+ SUPPORT_STATUS | SUPPORT_SEND_COMMAND | SUPPORT_FAN_SPEED)
+
+ATTR_ERROR = 'error'
+ATTR_COMPONENT_PREFIX = 'component_'
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the Ecovacs vacuums."""
+ vacuums = []
+ for device in hass.data[ECOVACS_DEVICES]:
+ vacuums.append(EcovacsVacuum(device))
+ _LOGGER.debug("Adding Ecovacs Vacuums to Hass: %s", vacuums)
+ add_devices(vacuums, True)
+
+
+class EcovacsVacuum(VacuumDevice):
+ """Ecovacs Vacuums such as Deebot."""
+
+ def __init__(self, device):
+ """Initialize the Ecovacs Vacuum."""
+ self.device = device
+ self.device.connect_and_wait_until_ready()
+ try:
+ self._name = '{}'.format(self.device.vacuum['nick'])
+ except KeyError:
+ # In case there is no nickname defined, use the device id
+ self._name = '{}'.format(self.device.vacuum['did'])
+
+ self._fan_speed = None
+ self._error = None
+ _LOGGER.debug("Vacuum initialized: %s", self.name)
+
+ async def async_added_to_hass(self) -> None:
+ """Set up the event listeners now that hass is ready."""
+ self.device.statusEvents.subscribe(lambda _:
+ self.schedule_update_ha_state())
+ self.device.batteryEvents.subscribe(lambda _:
+ self.schedule_update_ha_state())
+ self.device.lifespanEvents.subscribe(lambda _:
+ self.schedule_update_ha_state())
+ self.device.errorEvents.subscribe(self.on_error)
+
+ def on_error(self, error):
+ """Handle an error event from the robot.
+
+ This will not change the entity's state. If the error caused the state
+ to change, that will come through as a separate on_status event
+ """
+ if error == 'no_error':
+ self._error = None
+ else:
+ self._error = error
+
+ self.hass.bus.fire('ecovacs_error', {
+ 'entity_id': self.entity_id,
+ 'error': error
+ })
+ self.schedule_update_ha_state()
+
+ @property
+ def should_poll(self) -> bool:
+ """Return True if entity has to be polled for state."""
+ return False
+
+ @property
+ def unique_id(self) -> str:
+ """Return an unique ID."""
+ return self.device.vacuum.get('did', None)
+
+ @property
+ def is_on(self):
+ """Return true if vacuum is currently cleaning."""
+ return self.device.is_cleaning
+
+ @property
+ def is_charging(self):
+ """Return true if vacuum is currently charging."""
+ return self.device.is_charging
+
+ @property
+ def name(self):
+ """Return the name of the device."""
+ return self._name
+
+ @property
+ def supported_features(self):
+ """Flag vacuum cleaner robot features that are supported."""
+ return SUPPORT_ECOVACS
+
+ @property
+ def status(self):
+ """Return the status of the vacuum cleaner."""
+ return self.device.vacuum_status
+
+ def return_to_base(self, **kwargs):
+ """Set the vacuum cleaner to return to the dock."""
+ from sucks import Charge
+ self.device.run(Charge())
+
+ @property
+ def battery_icon(self):
+ """Return the battery icon for the vacuum cleaner."""
+ return icon_for_battery_level(
+ battery_level=self.battery_level, charging=self.is_charging)
+
+ @property
+ def battery_level(self):
+ """Return the battery level of the vacuum cleaner."""
+ if self.device.battery_status is not None:
+ return self.device.battery_status * 100
+
+ return super().battery_level
+
+ @property
+ def fan_speed(self):
+ """Return the fan speed of the vacuum cleaner."""
+ return self.device.fan_speed
+
+ @property
+ def fan_speed_list(self):
+ """Get the list of available fan speed steps of the vacuum cleaner."""
+ from sucks import FAN_SPEED_NORMAL, FAN_SPEED_HIGH
+ return [FAN_SPEED_NORMAL, FAN_SPEED_HIGH]
+
+ def turn_on(self, **kwargs):
+ """Turn the vacuum on and start cleaning."""
+ from sucks import Clean
+ self.device.run(Clean())
+
+ def turn_off(self, **kwargs):
+ """Turn the vacuum off stopping the cleaning and returning home."""
+ self.return_to_base()
+
+ def stop(self, **kwargs):
+ """Stop the vacuum cleaner."""
+ from sucks import Stop
+ self.device.run(Stop())
+
+ def clean_spot(self, **kwargs):
+ """Perform a spot clean-up."""
+ from sucks import Spot
+ self.device.run(Spot())
+
+ def locate(self, **kwargs):
+ """Locate the vacuum cleaner."""
+ from sucks import PlaySound
+ self.device.run(PlaySound())
+
+ def set_fan_speed(self, fan_speed, **kwargs):
+ """Set fan speed."""
+ if self.is_on:
+ from sucks import Clean
+ self.device.run(Clean(
+ mode=self.device.clean_status, speed=fan_speed))
+
+ def send_command(self, command, params=None, **kwargs):
+ """Send a command to a vacuum cleaner."""
+ from sucks import VacBotCommand
+ self.device.run(VacBotCommand(command, params))
+
+ @property
+ def device_state_attributes(self):
+ """Return the device-specific state attributes of this vacuum."""
+ data = {}
+ data[ATTR_ERROR] = self._error
+
+ for key, val in self.device.components.items():
+ attr_name = ATTR_COMPONENT_PREFIX + key
+ data[attr_name] = int(val * 100 / 0.2777778)
+ # The above calculation includes a fix for a bug in sucks 0.9.1
+ # When sucks 0.9.2+ is released, it should be changed to the
+ # following:
+ # data[attr_name] = int(val * 100)
+
+ return data
diff --git a/homeassistant/components/vacuum/neato.py b/homeassistant/components/vacuum/neato.py
index 224e763a097..82c5187f7b0 100644
--- a/homeassistant/components/vacuum/neato.py
+++ b/homeassistant/components/vacuum/neato.py
@@ -8,10 +8,10 @@ import logging
from datetime import timedelta
import requests
-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,
+ StateVacuumDevice, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME,
+ SUPPORT_STATE, SUPPORT_STOP, SUPPORT_START, STATE_IDLE,
+ STATE_PAUSED, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR,
SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON,
SUPPORT_LOCATE)
from homeassistant.components.neato import (
@@ -24,8 +24,8 @@ DEPENDENCIES = ['neato']
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_LOCATE
+ SUPPORT_STOP | SUPPORT_START | \
+ SUPPORT_STATE | SUPPORT_MAP | SUPPORT_LOCATE
ATTR_CLEAN_START = 'clean_start'
ATTR_CLEAN_STOP = 'clean_stop'
@@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(dev, True)
-class NeatoConnectedVacuum(VacuumDevice):
+class NeatoConnectedVacuum(StateVacuumDevice):
"""Representation of a Neato Connected Vacuum."""
def __init__(self, hass, robot):
@@ -79,36 +79,30 @@ class NeatoConnectedVacuum(VacuumDevice):
_LOGGER.debug('self._state=%s', self._state)
if self._state['state'] == 1:
if self._state['details']['isCharging']:
+ self._clean_state = STATE_DOCKED
self._status_state = 'Charging'
elif (self._state['details']['isDocked'] and
not self._state['details']['isCharging']):
+ self._clean_state = STATE_DOCKED
self._status_state = 'Docked'
else:
+ self._clean_state = STATE_IDLE
self._status_state = 'Stopped'
elif self._state['state'] == 2:
if ALERTS.get(self._state['error']) is None:
+ self._clean_state = STATE_CLEANING
self._status_state = (
MODE.get(self._state['cleaning']['mode'])
+ ' ' + ACTION.get(self._state['action']))
else:
self._status_state = ALERTS.get(self._state['error'])
elif self._state['state'] == 3:
+ self._clean_state = STATE_PAUSED
self._status_state = 'Paused'
elif self._state['state'] == 4:
+ self._clean_state = STATE_ERROR
self._status_state = ERRORS.get(self._state['error'])
- if (self._state['action'] == 1 or
- self._state['action'] == 2 or
- self._state['action'] == 3 and
- self._state['state'] == 2):
- self._clean_state = STATE_ON
- elif (self._state['action'] == 11 or
- self._state['action'] == 12 and
- self._state['state'] == 2):
- self._clean_state = STATE_ON
- else:
- self._clean_state = STATE_OFF
-
if not self._mapdata.get(self.robot.serial, {}).get('maps', []):
return
self.clean_time_start = (
@@ -147,17 +141,17 @@ class NeatoConnectedVacuum(VacuumDevice):
return self._state['details']['charge']
@property
- def status(self):
+ def state(self):
"""Return the status of the vacuum cleaner."""
- return self._status_state
+ return self._clean_state
@property
- def state_attributes(self):
+ def device_state_attributes(self):
"""Return the state attributes of the vacuum cleaner."""
data = {}
- if self.status is not None:
- data[ATTR_STATUS] = self.status
+ if self._status_state is not None:
+ data[ATTR_STATUS] = self._status_state
if self.battery_level is not None:
data[ATTR_BATTERY_LEVEL] = self.battery_level
@@ -181,38 +175,26 @@ class NeatoConnectedVacuum(VacuumDevice):
return data
- def turn_on(self, **kwargs):
- """Turn the vacuum on and start cleaning."""
- self.robot.start_cleaning()
+ def start(self):
+ """Start cleaning or resume cleaning."""
+ if self._state['state'] == 1:
+ self.robot.start_cleaning()
+ elif self._state['state'] == 3:
+ self.robot.resume_cleaning()
- @property
- def is_on(self):
- """Return true if switch is on."""
- return self._clean_state == STATE_ON
-
- def turn_off(self, **kwargs):
- """Turn the switch off."""
+ def pause(self):
+ """Pause the vacuum."""
self.robot.pause_cleaning()
- self.robot.send_to_base()
def return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
+ self._clean_state = STATE_RETURNING
self.robot.send_to_base()
def stop(self, **kwargs):
"""Stop the vacuum cleaner."""
self.robot.stop_cleaning()
- def start_pause(self, **kwargs):
- """Start, pause or resume the cleaning task."""
- if self._state['state'] == 1:
- self.robot.start_cleaning()
- elif self._state['state'] == 2 and\
- ALERTS.get(self._state['error']) is None:
- 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/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py
index 2c6057e1cf6..5be594e55a2 100644
--- a/homeassistant/components/vacuum/xiaomi_miio.py
+++ b/homeassistant/components/vacuum/xiaomi_miio.py
@@ -21,7 +21,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-miio==0.4.0', 'construct==2.9.41']
+REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
_LOGGER = logging.getLogger(__name__)
@@ -52,6 +52,7 @@ ATTR_DO_NOT_DISTURB_END = 'do_not_disturb_end'
ATTR_MAIN_BRUSH_LEFT = 'main_brush_left'
ATTR_SIDE_BRUSH_LEFT = 'side_brush_left'
ATTR_FILTER_LEFT = 'filter_left'
+ATTR_SENSOR_DIRTY_LEFT = 'sensor_dirty_left'
ATTR_CLEANING_COUNT = 'cleaning_count'
ATTR_CLEANED_TOTAL_AREA = 'total_cleaned_area'
ATTR_CLEANING_TOTAL_TIME = 'total_cleaning_time'
@@ -234,7 +235,12 @@ class MiroboVacuum(StateVacuumDevice):
/ 3600),
ATTR_FILTER_LEFT: int(
self.consumable_state.filter_left.total_seconds()
- / 3600)})
+ / 3600),
+ ATTR_SENSOR_DIRTY_LEFT: int(
+ self.consumable_state.sensor_dirty_left.total_seconds()
+ / 3600)
+ })
+
if self.vacuum_state.got_error:
attrs[ATTR_ERROR] = self.vacuum_state.error
return attrs
diff --git a/homeassistant/components/waterfurnace.py b/homeassistant/components/waterfurnace.py
index de49b5cd437..e9024131af8 100644
--- a/homeassistant/components/waterfurnace.py
+++ b/homeassistant/components/waterfurnace.py
@@ -43,7 +43,7 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, base_config):
- """Setup waterfurnace platform."""
+ """Set up waterfurnace platform."""
import waterfurnace.waterfurnace as wf
config = base_config.get(DOMAIN)
diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py
index 36811337ec1..1ba0e20d553 100644
--- a/homeassistant/components/websocket_api.py
+++ b/homeassistant/components/websocket_api.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
__version__)
from homeassistant.core import Context, callback
from homeassistant.loader import bind_hass
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.components.http import HomeAssistantView
@@ -325,7 +325,6 @@ class ActiveConnection:
await wsock.prepare(request)
self.debug("Connected")
- # Get a reference to current task so we can cancel our connection
self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)
@callback
diff --git a/homeassistant/components/wirelesstag.py b/homeassistant/components/wirelesstag.py
index 0f8f47f5100..19fb2d40b5d 100644
--- a/homeassistant/components/wirelesstag.py
+++ b/homeassistant/components/wirelesstag.py
@@ -80,14 +80,14 @@ class WirelessTagPlatform:
# pylint: disable=no-self-use
def make_push_notitication(self, name, url, content):
- """Factory for notification config."""
+ """Create notification config."""
from wirelesstagpy import NotificationConfig
return NotificationConfig(name, {
'url': url, 'verb': 'POST',
'content': content, 'disabled': False, 'nat': True})
def install_push_notifications(self, binary_sensors):
- """Setup local push notification from tag manager."""
+ """Set up local push notification from tag manager."""
_LOGGER.info("Registering local push notifications.")
configs = []
@@ -129,7 +129,7 @@ class WirelessTagPlatform:
self.hass.config.api.base_url)
def handle_update_tags_event(self, event):
- """Main entry to handle push event from wireless tag manager."""
+ """Handle push event from wireless tag manager."""
_LOGGER.info("push notification for update arrived: %s", event)
dispatcher_send(
self.hass,
@@ -215,7 +215,10 @@ class WirelessTagBaseSensor(Entity):
return 0
def updated_state_value(self):
- """Default implementation formats princial value."""
+ """Return formatted value.
+
+ The default implementation formats principal value.
+ """
return self.decorate_value(self.principal_value)
# pylint: disable=no-self-use
diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py
index 030e342847d..f17e7f02344 100644
--- a/homeassistant/components/zha/__init__.py
+++ b/homeassistant/components/zha/__init__.py
@@ -341,7 +341,7 @@ class Entity(entity.Entity):
application_listener.register_entity(ieee, self)
async def async_added_to_hass(self):
- """Callback once the entity is added to hass.
+ """Handle entity addition to hass.
It is now safe to update the entity state
"""
diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py
index ee19e00266c..3754bf5edbc 100644
--- a/homeassistant/components/zone/__init__.py
+++ b/homeassistant/components/zone/__init__.py
@@ -44,7 +44,7 @@ PLATFORM_SCHEMA = vol.Schema({
async def async_setup(hass, config):
- """Setup configured zones as well as home assistant zone if necessary."""
+ """Set up configured zones as well as home assistant zone if necessary."""
hass.data[DOMAIN] = {}
entities = set()
zone_entries = configured_zones(hass)
diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py
index 2c6d26802bd..94de03686d3 100644
--- a/homeassistant/components/zwave/node_entity.py
+++ b/homeassistant/components/zwave/node_entity.py
@@ -107,7 +107,7 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
@property
def unique_id(self):
- """Unique ID of Z-wave node."""
+ """Return unique ID of Z-wave node."""
return self._unique_id
def network_node_changed(self, node=None, value=None, args=None):
diff --git a/homeassistant/config.py b/homeassistant/config.py
index 6120a20fd63..45505bbbc9b 100644
--- a/homeassistant/config.py
+++ b/homeassistant/config.py
@@ -14,14 +14,16 @@ import voluptuous as vol
from voluptuous.humanize import humanize_error
from homeassistant import auth
-from homeassistant.auth import providers as auth_providers
+from homeassistant.auth import providers as auth_providers,\
+ mfa_modules as auth_mfa_modules
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE,
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
CONF_TIME_ZONE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC,
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)
+ CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_AUTH_MFA_MODULES,
+ CONF_TYPE)
from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import get_component, get_platform
@@ -73,11 +75,9 @@ frontend:
# Enables configuration UI
config:
-http:
- # Secrets are defined in the file secrets.yaml
- # api_password: !secret http_password
- # Uncomment this if you are using SSL/TLS, running in Docker container, etc.
- # base_url: example.duckdns.org:8123
+# Uncomment this if you are using SSL/TLS, running in Docker container, etc.
+# http:
+# base_url: example.duckdns.org:8123
# Checks for available updates
# Note: This component will send some information about your system to
@@ -124,7 +124,7 @@ script: !include scripts.yaml
DEFAULT_SECRETS = """
# Use this file to store secrets like usernames and passwords.
# Learn more at https://home-assistant.io/docs/configuration/secrets/
-http_password: welcome
+some_password: welcome
"""
@@ -166,7 +166,10 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({
CONF_TYPE: vol.NotIn(['insecure_example'],
'The insecure_example auth provider'
' is for testing only.')
- })])
+ })]),
+ vol.Optional(CONF_AUTH_MFA_MODULES):
+ vol.All(cv.ensure_list,
+ [auth_mfa_modules.MULTI_FACTOR_AUTH_MODULE_SCHEMA]),
})
@@ -402,7 +405,8 @@ def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str:
async def async_process_ha_core_config(
- hass: HomeAssistant, config: Dict) -> None:
+ hass: HomeAssistant, config: Dict,
+ has_api_password: bool = False) -> None:
"""Process the [homeassistant] section from the configuration.
This method is a coroutine.
@@ -411,8 +415,19 @@ async def async_process_ha_core_config(
# Only load auth during startup.
if not hasattr(hass, 'auth'):
+ auth_conf = config.get(CONF_AUTH_PROVIDERS)
+
+ if auth_conf is None:
+ auth_conf = [
+ {'type': 'homeassistant'}
+ ]
+ if has_api_password:
+ auth_conf.append({'type': 'legacy_api_password'})
+
setattr(hass, 'auth', await auth.auth_manager_from_config(
- hass, config.get(CONF_AUTH_PROVIDERS, [])))
+ hass,
+ auth_conf,
+ config.get(CONF_AUTH_MFA_MODULES, [])))
hac = hass.config
diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py
index f3b04f64e05..a52484b6941 100644
--- a/homeassistant/config_entries.py
+++ b/homeassistant/config_entries.py
@@ -136,6 +136,7 @@ HANDLERS = Registry()
# Components that have config flows. In future we will auto-generate this list.
FLOWS = [
'cast',
+ 'hangouts',
'deconz',
'homematicip_cloud',
'hue',
@@ -303,7 +304,7 @@ class ConfigEntries:
return result
@callback
- def async_entries(self, domain: str = None) -> List[ConfigEntry]:
+ def async_entries(self, domain: Optional[str] = None) -> List[ConfigEntry]:
"""Return all entries or entries for a specific domain."""
if domain is None:
return list(self._entries)
@@ -321,7 +322,7 @@ class ConfigEntries:
raise UnknownEntry
entry = self._entries.pop(found)
- await self._async_schedule_save()
+ self._async_schedule_save()
unloaded = await entry.async_unload(self.hass)
@@ -373,26 +374,27 @@ class ConfigEntries:
return await entry.async_unload(
self.hass, component=getattr(self.hass.components, component))
- async def _async_finish_flow(self, context, result):
+ async def _async_finish_flow(self, flow, result):
"""Finish a config flow and add an entry."""
- # If no discovery config entries in progress, remove notification.
+ # Remove notification if no other discovery config entries in progress
if not any(ent['context']['source'] in DISCOVERY_SOURCES for ent
- in self.hass.config_entries.flow.async_progress()):
+ in self.hass.config_entries.flow.async_progress()
+ if ent['flow_id'] != flow.flow_id):
self.hass.components.persistent_notification.async_dismiss(
DISCOVERY_NOTIFICATION_ID)
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
- return None
+ return result
entry = ConfigEntry(
version=result['version'],
domain=result['handler'],
title=result['title'],
data=result['data'],
- source=context['source'],
+ source=flow.context['source'],
)
self._entries.append(entry)
- await self._async_schedule_save()
+ self._async_schedule_save()
# Setup entry
if entry.domain in self.hass.config.components:
@@ -403,16 +405,13 @@ class ConfigEntries:
await async_setup_component(
self.hass, entry.domain, self._hass_config)
- # Return Entry if they not from a discovery request
- if context['source'] not in DISCOVERY_SOURCES:
- return entry
-
- return entry
+ result['result'] = entry
+ return result
async def _async_create_flow(self, handler_key, *, context, data):
"""Create a flow for specified handler.
- Handler key is the domain of the component that we want to setup.
+ Handler key is the domain of the component that we want to set up.
"""
component = getattr(self.hass.components, handler_key)
_LOGGER.debug('Handler key: %s', handler_key)
@@ -441,12 +440,16 @@ class ConfigEntries:
flow.init_step = source
return flow
- async def _async_schedule_save(self):
+ def _async_schedule_save(self):
"""Save the entity registry to a file."""
- data = {
+ self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
+
+ @callback
+ def _data_to_save(self):
+ """Return data to save."""
+ return {
'entries': [entry.as_dict() for entry in self._entries]
}
- await self._store.async_save(data, delay=SAVE_DELAY)
async def _old_conf_migrator(old_config):
diff --git a/homeassistant/const.py b/homeassistant/const.py
index 7d99b952ce6..d72bde548d3 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -30,6 +30,7 @@ CONF_API_KEY = 'api_key'
CONF_API_VERSION = 'api_version'
CONF_AT = 'at'
CONF_AUTHENTICATION = 'authentication'
+CONF_AUTH_MFA_MODULES = 'auth_mfa_modules'
CONF_AUTH_PROVIDERS = 'auth_providers'
CONF_BASE = 'base'
CONF_BEFORE = 'before'
diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py
index f820911e396..a54c07fc1b8 100644
--- a/homeassistant/data_entry_flow.py
+++ b/homeassistant/data_entry_flow.py
@@ -49,7 +49,8 @@ class FlowManager:
'context': flow.context,
} for flow in self._progress.values()]
- async def async_init(self, handler: Hashable, *, context: Dict = None,
+ async def async_init(self, handler: Hashable, *,
+ context: Optional[Dict] = None,
data: Any = None) -> Any:
"""Start a configuration flow."""
flow = await self._async_create_flow(
@@ -63,7 +64,7 @@ class FlowManager:
return await self._async_handle_step(flow, flow.init_step, data)
async def async_configure(
- self, flow_id: str, user_input: str = None) -> Any:
+ self, flow_id: str, user_input: Optional[Dict] = None) -> Any:
"""Continue a configuration flow."""
flow = self._progress.get(flow_id)
@@ -85,7 +86,7 @@ class FlowManager:
raise UnknownFlow
async def _async_handle_step(self, flow: Any, step_id: str,
- user_input: Optional[str]) -> Dict:
+ user_input: Optional[Dict]) -> Dict:
"""Handle a step of a flow."""
method = "async_step_{}".format(step_id)
@@ -105,14 +106,17 @@ class FlowManager:
flow.cur_step = (result['step_id'], result['data_schema'])
return result
+ # We pass a copy of the result because we're mutating our version
+ result = await self._async_finish_flow(flow, dict(result))
+
+ # _async_finish_flow may change result type, check it again
+ if result['type'] == RESULT_TYPE_FORM:
+ flow.cur_step = (result['step_id'], result['data_schema'])
+ return result
+
# Abort and Success results both finish the flow
self._progress.pop(flow.flow_id)
- # We pass a copy of the result because we're mutating our version
- entry = await self._async_finish_flow(flow.context, dict(result))
-
- if result['type'] == RESULT_TYPE_CREATE_ENTRY:
- result['result'] = entry
return result
@@ -134,8 +138,9 @@ class FlowHandler:
@callback
def async_show_form(self, *, step_id: str, data_schema: vol.Schema = None,
- errors: Dict = None,
- description_placeholders: Dict = None) -> Dict:
+ errors: Optional[Dict] = None,
+ description_placeholders: Optional[Dict] = None) \
+ -> Dict:
"""Return the definition of a form to gather user input."""
return {
'type': RESULT_TYPE_FORM,
diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py
index 26de41387f5..bbd863b5693 100644
--- a/homeassistant/helpers/config_validation.py
+++ b/homeassistant/helpers/config_validation.py
@@ -60,21 +60,6 @@ def has_at_least_one_key(*keys: str) -> Callable:
return validate
-def has_at_least_one_key_value(*items: list) -> Callable:
- """Validate that at least one (key, value) pair exists."""
- def validate(obj: Dict) -> Dict:
- """Test (key,value) exist in dict."""
- if not isinstance(obj, dict):
- raise vol.Invalid('expected dictionary')
-
- for item in obj.items():
- if item in items:
- return obj
- raise vol.Invalid('must contain one of {}.'.format(str(items)))
-
- return validate
-
-
def boolean(value: Any) -> bool:
"""Validate and coerce a boolean value."""
if isinstance(value, str):
diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py
new file mode 100644
index 00000000000..3276763a967
--- /dev/null
+++ b/homeassistant/helpers/device_registry.py
@@ -0,0 +1,121 @@
+"""Provide a way to connect entities belonging to one device."""
+import logging
+import uuid
+
+import attr
+
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+_LOGGER = logging.getLogger(__name__)
+
+DATA_REGISTRY = 'device_registry'
+
+STORAGE_KEY = 'core.device_registry'
+STORAGE_VERSION = 1
+SAVE_DELAY = 10
+
+
+@attr.s(slots=True, frozen=True)
+class DeviceEntry:
+ """Device Registry Entry."""
+
+ identifiers = attr.ib(type=list)
+ manufacturer = attr.ib(type=str)
+ model = attr.ib(type=str)
+ connection = attr.ib(type=list)
+ name = attr.ib(type=str, default=None)
+ sw_version = attr.ib(type=str, default=None)
+ id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+
+
+class DeviceRegistry:
+ """Class to hold a registry of devices."""
+
+ def __init__(self, hass):
+ """Initialize the device registry."""
+ self.hass = hass
+ self.devices = None
+ self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+
+ @callback
+ def async_get_device(self, identifiers: str, connections: tuple):
+ """Check if device is registered."""
+ for device in self.devices:
+ if any(iden in device.identifiers for iden in identifiers) or \
+ any(conn in device.connection for conn in connections):
+ return device
+ return None
+
+ @callback
+ def async_get_or_create(self, identifiers, manufacturer, model,
+ connection, *, name=None, sw_version=None):
+ """Get device. Create if it doesn't exist."""
+ device = self.async_get_device(identifiers, connection)
+
+ if device is not None:
+ return device
+
+ device = DeviceEntry(
+ identifiers=identifiers,
+ manufacturer=manufacturer,
+ model=model,
+ connection=connection,
+ name=name,
+ sw_version=sw_version
+ )
+
+ self.devices.append(device)
+ self.async_schedule_save()
+
+ return device
+
+ async def async_load(self):
+ """Load the device registry."""
+ devices = await self._store.async_load()
+
+ if devices is None:
+ self.devices = []
+ return
+
+ self.devices = [DeviceEntry(**device) for device in devices['devices']]
+
+ @callback
+ def async_schedule_save(self):
+ """Schedule saving the device registry."""
+ self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
+
+ @callback
+ def _data_to_save(self):
+ """Return data of device registry to store in a file."""
+ data = {}
+
+ data['devices'] = [
+ {
+ 'id': entry.id,
+ 'identifiers': entry.identifiers,
+ 'manufacturer': entry.manufacturer,
+ 'model': entry.model,
+ 'connection': entry.connection,
+ 'name': entry.name,
+ 'sw_version': entry.sw_version,
+ } for entry in self.devices
+ ]
+
+ return data
+
+
+@bind_hass
+async def async_get_registry(hass) -> DeviceRegistry:
+ """Return device registry instance."""
+ task = hass.data.get(DATA_REGISTRY)
+
+ if task is None:
+ async def _load_reg():
+ registry = DeviceRegistry(hass)
+ await registry.async_load()
+ return registry
+
+ task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg())
+
+ return await task
diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py
index 7d0730a969c..698cee0fcc2 100644
--- a/homeassistant/helpers/discovery.py
+++ b/homeassistant/helpers/discovery.py
@@ -159,7 +159,7 @@ async def async_load_platform(hass, component, platform, discovered=None,
setup_success = await setup.async_setup_component(
hass, component, hass_config)
- # No need to fire event if we could not setup component
+ # No need to fire event if we could not set up component
if not setup_success:
return
diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py
index c356c266db6..78806e65ef1 100644
--- a/homeassistant/helpers/entity.py
+++ b/homeassistant/helpers/entity.py
@@ -1,5 +1,6 @@
"""An abstract class for entities."""
import asyncio
+from datetime import timedelta
import logging
import functools as ft
from timeit import default_timer as timer
@@ -16,6 +17,7 @@ from homeassistant.config import DATA_CUSTOMIZE
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify
from homeassistant.util.async_ import run_callback_threadsafe
+from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
SLOW_UPDATE_WARNING = 10
@@ -85,6 +87,10 @@ class Entity:
# Hold list for functions to call on remove.
_on_remove = None
+ # Context
+ _context = None
+ _context_set = None
+
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
@@ -124,6 +130,14 @@ class Entity:
"""
return None
+ @property
+ def device(self):
+ """Return device specific attributes.
+
+ Implemented by platform classes.
+ """
+ return None
+
@property
def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
@@ -173,13 +187,24 @@ class Entity:
"""Flag supported features."""
return None
+ @property
+ def context_recent_time(self):
+ """Time that a context is considered recent."""
+ return timedelta(seconds=5)
+
# DO NOT OVERWRITE
# These properties and methods are either managed by Home Assistant or they
# are used to perform a very specific function. Overwriting these may
# produce undesirable effects in the entity's operation.
+ @callback
+ def async_set_context(self, context):
+ """Set the context the entity currently operates under."""
+ self._context = context
+ self._context_set = dt_util.utcnow()
+
@asyncio.coroutine
- def async_update_ha_state(self, force_refresh=False, context=None):
+ def async_update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity.
If force_refresh == True will update entity before setting state.
@@ -278,8 +303,14 @@ class Entity:
# Could not convert state to float
pass
+ if (self._context is not None and
+ dt_util.utcnow() - self._context_set >
+ self.context_recent_time):
+ self._context = None
+ self._context_set = None
+
self.hass.states.async_set(
- self.entity_id, state, attr, self.force_update, context)
+ self.entity_id, state, attr, self.force_update, self._context)
def schedule_update_ha_state(self, force_refresh=False):
"""Schedule an update ha state change task.
@@ -347,7 +378,7 @@ class Entity:
@callback
def async_registry_updated(self, old, new):
- """Called when the entity registry has been updated."""
+ """Handle entity registry update."""
self.registry_name = new.name
if new.entity_id == self.entity_id:
diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py
index cf035095a84..09f8838b160 100644
--- a/homeassistant/helpers/entity_component.py
+++ b/homeassistant/helpers/entity_component.py
@@ -52,7 +52,7 @@ class EntityComponent:
in self._platforms.values())
def get_entity(self, entity_id):
- """Helper method to get an entity."""
+ """Get an entity."""
for platform in self._platforms.values():
entity = platform.entities.get(entity_id)
if entity is not None:
@@ -94,7 +94,7 @@ class EntityComponent:
self.hass, self.domain, component_platform_discovered)
async def async_setup_entry(self, config_entry):
- """Setup a config entry."""
+ """Set up a config entry."""
platform_type = config_entry.domain
platform = await async_prepare_setup_platform(
self.hass, self.config, self.domain, platform_type)
@@ -243,7 +243,7 @@ class EntityComponent:
def _async_init_entity_platform(self, platform_type, platform,
scan_interval=None, entity_namespace=None):
- """Helper to initialize an entity platform."""
+ """Initialize an entity platform."""
if scan_interval is None:
scan_interval = self.scan_interval
diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py
index dc1e376f471..c65aa5e98c2 100644
--- a/homeassistant/helpers/entity_platform.py
+++ b/homeassistant/helpers/entity_platform.py
@@ -8,7 +8,6 @@ from homeassistant.util.async_ import (
run_callback_threadsafe, run_coroutine_threadsafe)
from .event import async_track_time_interval, async_call_later
-from .entity_registry import async_get_registry
SLOW_SETUP_WARNING = 10
SLOW_SETUP_MAX_WAIT = 60
@@ -71,13 +70,13 @@ class EntityPlatform:
self.parallel_updates = None
async def async_setup(self, platform_config, discovery_info=None):
- """Setup the platform from a config file."""
+ """Set up the platform from a config file."""
platform = self.platform
hass = self.hass
@callback
def async_create_setup_task():
- """Get task to setup platform."""
+ """Get task to set up platform."""
if getattr(platform, 'async_setup_platform', None):
return platform.async_setup_platform(
hass, platform_config,
@@ -93,21 +92,21 @@ class EntityPlatform:
await self._async_setup_platform(async_create_setup_task)
async def async_setup_entry(self, config_entry):
- """Setup the platform from a config entry."""
+ """Set up the platform from a config entry."""
# Store it so that we can save config entry ID in entity registry
self.config_entry = config_entry
platform = self.platform
@callback
def async_create_setup_task():
- """Get task to setup platform."""
+ """Get task to set up platform."""
return platform.async_setup_entry(
self.hass, config_entry, self._async_schedule_add_entities)
return await self._async_setup_platform(async_create_setup_task)
async def _async_setup_platform(self, async_create_setup_task, tries=0):
- """Helper to setup a platform via config file or config entry.
+ """Set up a platform via config file or config entry.
async_create_setup_task creates a coroutine that sets up platform.
"""
@@ -169,7 +168,7 @@ class EntityPlatform:
warn_task.cancel()
def _schedule_add_entities(self, new_entities, update_before_add=False):
- """Synchronously schedule adding entities for a single platform."""
+ """Schedule adding entities for a single platform, synchronously."""
run_callback_threadsafe(
self.hass.loop,
self._async_schedule_add_entities, list(new_entities),
@@ -209,11 +208,14 @@ class EntityPlatform:
hass = self.hass
component_entities = set(hass.states.async_entity_ids(self.domain))
- registry = await async_get_registry(hass)
-
+ device_registry = await \
+ hass.helpers.device_registry.async_get_registry()
+ entity_registry = await \
+ hass.helpers.entity_registry.async_get_registry()
tasks = [
self._async_add_entity(entity, update_before_add,
- component_entities, registry)
+ component_entities, entity_registry,
+ device_registry)
for entity in new_entities]
# No entities for processing
@@ -233,8 +235,9 @@ class EntityPlatform:
)
async def _async_add_entity(self, entity, update_before_add,
- component_entities, registry):
- """Helper method to add an entity to the platform."""
+ component_entities, entity_registry,
+ device_registry):
+ """Add an entity to the platform."""
if entity is None:
raise ValueError('Entity cannot be None')
@@ -269,10 +272,21 @@ class EntityPlatform:
else:
config_entry_id = None
- entry = registry.async_get_or_create(
+ device = entity.device
+ if device is not None:
+ device = device_registry.async_get_or_create(
+ device['identifiers'], device['manufacturer'],
+ device['model'], device['connection'],
+ sw_version=device.get('sw_version'))
+ device_id = device.id
+ else:
+ device_id = None
+
+ entry = entity_registry.async_get_or_create(
self.domain, self.platform_name, entity.unique_id,
suggested_object_id=suggested_object_id,
- config_entry_id=config_entry_id)
+ config_entry_id=config_entry_id,
+ device_id=device_id)
if entry.disabled:
self.logger.info(
@@ -288,7 +302,7 @@ class EntityPlatform:
# 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
elif (entity.entity_id is not None and
- registry.async_is_registered(entity.entity_id)):
+ entity_registry.async_is_registered(entity.entity_id)):
# If entity already registered, convert entity id to suggestion
suggested_object_id = split_entity_id(entity.entity_id)[1]
entity.entity_id = None
@@ -302,7 +316,7 @@ class EntityPlatform:
suggested_object_id = '{} {}'.format(self.entity_namespace,
suggested_object_id)
- entity.entity_id = registry.async_generate_entity_id(
+ entity.entity_id = entity_registry.async_generate_entity_id(
self.domain, suggested_object_id)
# Make sure it is valid in case an entity set the value themselves
diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py
index 2fa64ff8680..804ee4235d0 100644
--- a/homeassistant/helpers/entity_registry.py
+++ b/homeassistant/helpers/entity_registry.py
@@ -6,15 +6,10 @@ identified by their domain, platform and a unique id provided by that platform.
The Entity Registry will persist itself 10 seconds after a new entity is
registered. Registering a new entity while a timer is in progress resets the
timer.
-
-After initializing, call EntityRegistry.async_ensure_loaded to load the data
-from disk.
"""
-
from collections import OrderedDict
from itertools import chain
import logging
-import os
import weakref
import attr
@@ -22,7 +17,7 @@ import attr
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
+from homeassistant.util.yaml import load_yaml
PATH_REGISTRY = 'entity_registry.yaml'
DATA_REGISTRY = 'entity_registry'
@@ -32,6 +27,9 @@ _UNDEF = object()
DISABLED_HASS = 'hass'
DISABLED_USER = 'user'
+STORAGE_VERSION = 1
+STORAGE_KEY = 'core.entity_registry'
+
@attr.s(slots=True, frozen=True)
class RegistryEntry:
@@ -41,6 +39,7 @@ class RegistryEntry:
unique_id = attr.ib(type=str)
platform = attr.ib(type=str)
name = attr.ib(type=str, default=None)
+ device_id = attr.ib(type=str, default=None)
config_entry_id = attr.ib(type=str, default=None)
disabled_by = attr.ib(
type=str, default=None,
@@ -79,8 +78,7 @@ class EntityRegistry:
"""Initialize the registry."""
self.hass = hass
self.entities = None
- self._load_task = None
- self._sched_save = None
+ self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
@callback
def async_is_registered(self, entity_id):
@@ -110,7 +108,8 @@ class EntityRegistry:
@callback
def async_get_or_create(self, domain, platform, unique_id, *,
- suggested_object_id=None, config_entry_id=None):
+ suggested_object_id=None, config_entry_id=None,
+ device_id=None):
"""Get entity. Create if it doesn't exist."""
entity_id = self.async_get_entity_id(domain, platform, unique_id)
if entity_id:
@@ -119,7 +118,8 @@ class EntityRegistry:
return entry
self._async_update_entity(
- entity_id, config_entry_id=config_entry_id)
+ entity_id, config_entry_id=config_entry_id,
+ device_id=device_id)
return self.entities[entity_id]
entity_id = self.async_generate_entity_id(
@@ -128,6 +128,7 @@ class EntityRegistry:
entity = RegistryEntry(
entity_id=entity_id,
config_entry_id=config_entry_id,
+ device_id=device_id,
unique_id=unique_id,
platform=platform,
)
@@ -149,7 +150,8 @@ class EntityRegistry:
@callback
def _async_update_entity(self, entity_id, *, name=_UNDEF,
- config_entry_id=_UNDEF, new_entity_id=_UNDEF):
+ config_entry_id=_UNDEF, new_entity_id=_UNDEF,
+ device_id=_UNDEF):
"""Private facing update properties method."""
old = self.entities[entity_id]
@@ -162,6 +164,9 @@ class EntityRegistry:
config_entry_id != old.config_entry_id):
changes['config_entry_id'] = config_entry_id
+ if (device_id is not _UNDEF and device_id != old.device_id):
+ changes['device_id'] = device_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')
@@ -199,71 +204,74 @@ class EntityRegistry:
return new
- async def async_ensure_loaded(self):
- """Load the registry from disk."""
- if self.entities is not None:
- return
-
- if self._load_task is None:
- self._load_task = self.hass.async_add_job(self._async_load)
-
- await self._load_task
-
- async def _async_load(self):
+ async def async_load(self):
"""Load the entity registry."""
- path = self.hass.config.path(PATH_REGISTRY)
+ data = await self.hass.helpers.storage.async_migrator(
+ self.hass.config.path(PATH_REGISTRY), self._store,
+ old_conf_load_func=load_yaml,
+ old_conf_migrate_func=_async_migrate
+ )
entities = OrderedDict()
- if os.path.isfile(path):
- data = await self.hass.async_add_job(load_yaml, path)
-
- for entity_id, info in data.items():
- entities[entity_id] = RegistryEntry(
- entity_id=entity_id,
- config_entry_id=info.get('config_entry_id'),
- unique_id=info['unique_id'],
- platform=info['platform'],
- name=info.get('name'),
- disabled_by=info.get('disabled_by')
+ if data is not None:
+ for entity in data['entities']:
+ entities[entity['entity_id']] = RegistryEntry(
+ entity_id=entity['entity_id'],
+ config_entry_id=entity.get('config_entry_id'),
+ device_id=entity.get('device_id'),
+ unique_id=entity['unique_id'],
+ platform=entity['platform'],
+ name=entity.get('name'),
+ disabled_by=entity.get('disabled_by')
)
self.entities = entities
- self._load_task = None
@callback
def async_schedule_save(self):
"""Schedule saving the entity registry."""
- if self._sched_save is not None:
- self._sched_save.cancel()
+ self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
- self._sched_save = self.hass.loop.call_later(
- SAVE_DELAY, self.hass.async_add_job, self._async_save
- )
+ @callback
+ def _data_to_save(self):
+ """Return data of entity registry to store in a file."""
+ data = {}
- async def _async_save(self):
- """Save the entity registry to a file."""
- self._sched_save = None
- data = OrderedDict()
-
- for entry in self.entities.values():
- data[entry.entity_id] = {
+ data['entities'] = [
+ {
+ 'entity_id': entry.entity_id,
'config_entry_id': entry.config_entry_id,
+ 'device_id': entry.device_id,
'unique_id': entry.unique_id,
'platform': entry.platform,
'name': entry.name,
- }
+ } for entry in self.entities.values()
+ ]
- await self.hass.async_add_job(
- save_yaml, self.hass.config.path(PATH_REGISTRY), data)
+ return data
@bind_hass
async def async_get_registry(hass) -> EntityRegistry:
"""Return entity registry instance."""
- registry = hass.data.get(DATA_REGISTRY)
+ task = hass.data.get(DATA_REGISTRY)
- if registry is None:
- registry = hass.data[DATA_REGISTRY] = EntityRegistry(hass)
+ if task is None:
+ async def _load_reg():
+ registry = EntityRegistry(hass)
+ await registry.async_load()
+ return registry
- await registry.async_ensure_loaded()
- return registry
+ task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg())
+
+ return await task
+
+
+async def _async_migrate(entities):
+ """Migrate the YAML config file to storage helper format."""
+ return {
+ 'entities': [
+ {'entity_id': entity_id, **info}
+ for entity_id, info in entities.items()
+ ]
+ }
diff --git a/homeassistant/helpers/json.py b/homeassistant/helpers/json.py
new file mode 100644
index 00000000000..c28ee8c5c2c
--- /dev/null
+++ b/homeassistant/helpers/json.py
@@ -0,0 +1,27 @@
+"""Helpers to help with encoding Home Assistant objects in JSON."""
+from datetime import datetime
+import json
+import logging
+
+from typing import Any
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class JSONEncoder(json.JSONEncoder):
+ """JSONEncoder that supports Home Assistant objects."""
+
+ # pylint: disable=method-hidden
+ def default(self, o: Any) -> Any:
+ """Convert Home Assistant objects.
+
+ Hand other objects to the original method.
+ """
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, set):
+ return list(o)
+ if hasattr(o, 'as_dict'):
+ return o.as_dict()
+
+ return json.JSONEncoder.default(self, o)
diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py
index acad72a860a..fcdc3cfe856 100644
--- a/homeassistant/helpers/service.py
+++ b/homeassistant/helpers/service.py
@@ -218,13 +218,15 @@ async def _handle_service_platform_call(func, data, entities, context):
if not entity.available:
continue
+ entity.async_set_context(context)
+
if isinstance(func, str):
await getattr(entity, func)(**data)
else:
await func(entity, data)
if entity.should_poll:
- tasks.append(entity.async_update_ha_state(True, context))
+ tasks.append(entity.async_update_ha_state(True))
if tasks:
await asyncio.wait(tasks)
diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py
index a68b489868d..95e6925b2a4 100644
--- a/homeassistant/helpers/storage.py
+++ b/homeassistant/helpers/storage.py
@@ -2,7 +2,7 @@
import asyncio
import logging
import os
-from typing import Dict, Optional
+from typing import Dict, Optional, Callable
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
@@ -15,17 +15,19 @@ _LOGGER = logging.getLogger(__name__)
@bind_hass
-async def async_migrator(hass, old_path, store, *, old_conf_migrate_func=None):
- """Helper function to migrate old data to a store and then load data.
+async def async_migrator(hass, old_path, store, *,
+ old_conf_load_func=json.load_json,
+ old_conf_migrate_func=None):
+ """Migrate old data to a store and then load data.
async def old_conf_migrate_func(old_data)
"""
def load_old_config():
- """Helper to load old config."""
+ """Load old config."""
if not os.path.isfile(old_path):
return None
- return json.load_json(old_path)
+ return old_conf_load_func(old_path)
config = await hass.async_add_executor_job(load_old_config)
@@ -52,7 +54,7 @@ class Store:
self._data = None
self._unsub_delay_listener = None
self._unsub_stop_listener = None
- self._write_lock = asyncio.Lock()
+ self._write_lock = asyncio.Lock(loop=hass.loop)
self._load_task = None
@property
@@ -75,9 +77,14 @@ class Store:
return await self._load_task
async def _async_load(self):
- """Helper to load the data."""
+ """Load the data."""
+ # Check if we have a pending write
if self._data is not None:
data = self._data
+
+ # If we didn't generate data yet, do it now.
+ if 'data_func' in data:
+ data['data'] = data.pop('data_func')()
else:
data = await self.hass.async_add_executor_job(
json.load_json, self.path)
@@ -95,8 +102,8 @@ class Store:
self._load_task = None
return stored
- async def async_save(self, data: Dict, *, delay: Optional[int] = None):
- """Save data with an optional delay."""
+ async def async_save(self, data):
+ """Save data."""
self._data = {
'version': self.version,
'key': self.key,
@@ -104,11 +111,20 @@ class Store:
}
self._async_cleanup_delay_listener()
+ self._async_cleanup_stop_listener()
+ await self._async_handle_write_data()
- if delay is None:
- self._async_cleanup_stop_listener()
- await self._async_handle_write_data()
- return
+ @callback
+ def async_delay_save(self, data_func: Callable[[], Dict],
+ delay: Optional[int] = None):
+ """Save data with an optional delay."""
+ self._data = {
+ 'version': self.version,
+ 'key': self.key,
+ 'data_func': data_func,
+ }
+
+ self._async_cleanup_delay_listener()
self._unsub_delay_listener = async_call_later(
self.hass, delay, self._async_callback_delayed_write)
@@ -149,8 +165,12 @@ class Store:
await self._async_handle_write_data()
async def _async_handle_write_data(self, *_args):
- """Handler to handle writing the config."""
+ """Handle writing the config."""
data = self._data
+
+ if 'data_func' in data:
+ data['data'] = data.pop('data_func')()
+
self._data = None
async with self._write_lock:
diff --git a/homeassistant/remote.py b/homeassistant/remote.py
deleted file mode 100644
index c254dd500f7..00000000000
--- a/homeassistant/remote.py
+++ /dev/null
@@ -1,316 +0,0 @@
-"""
-Support for an interface to work with a remote instance of Home Assistant.
-
-If a connection error occurs while communicating with the API a
-HomeAssistantError will be raised.
-
-For more details about the Python API, please refer to the documentation at
-https://home-assistant.io/developers/python_api/
-"""
-from datetime import datetime
-import enum
-import json
-import logging
-import urllib.parse
-
-from typing import Optional, Dict, Any, List
-
-from aiohttp.hdrs import METH_GET, METH_POST, METH_DELETE, CONTENT_TYPE
-import requests
-
-from homeassistant import core as ha
-from homeassistant.const import (
- URL_API, SERVER_PORT, URL_API_CONFIG, URL_API_EVENTS, URL_API_STATES,
- URL_API_SERVICES, CONTENT_TYPE_JSON, HTTP_HEADER_HA_AUTH,
- URL_API_EVENTS_EVENT, URL_API_STATES_ENTITY, URL_API_SERVICES_SERVICE)
-from homeassistant.exceptions import HomeAssistantError
-
-_LOGGER = logging.getLogger(__name__)
-
-
-class APIStatus(enum.Enum):
- """Representation of an API status."""
-
- OK = "ok"
- INVALID_PASSWORD = "invalid_password"
- CANNOT_CONNECT = "cannot_connect"
- UNKNOWN = "unknown"
-
- def __str__(self) -> str:
- """Return the state."""
- return self.value # type: ignore
-
-
-class API:
- """Object to pass around Home Assistant API location and credentials."""
-
- def __init__(self, host: str, api_password: Optional[str] = None,
- port: Optional[int] = SERVER_PORT,
- use_ssl: bool = False) -> None:
- """Init the API."""
- _LOGGER.warning('This class is deprecated and will be removed in 0.77')
- self.host = host
- self.port = port
- self.api_password = api_password
-
- if host.startswith(("http://", "https://")):
- self.base_url = host
- elif use_ssl:
- self.base_url = "https://{}".format(host)
- else:
- self.base_url = "http://{}".format(host)
-
- if port is not None:
- self.base_url += ':{}'.format(port)
-
- self.status = None # type: Optional[APIStatus]
- self._headers = {CONTENT_TYPE: CONTENT_TYPE_JSON}
-
- if api_password is not None:
- self._headers[HTTP_HEADER_HA_AUTH] = api_password
-
- def validate_api(self, force_validate: bool = False) -> bool:
- """Test if we can communicate with the API."""
- if self.status is None or force_validate:
- self.status = validate_api(self)
-
- return self.status == APIStatus.OK
-
- 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 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_str, timeout=timeout,
- headers=self._headers)
-
- return requests.request(
- method, url, data=data_str, timeout=timeout,
- headers=self._headers)
-
- except requests.exceptions.ConnectionError:
- _LOGGER.exception("Error connecting to server")
- raise HomeAssistantError("Error connecting to server")
-
- except requests.exceptions.Timeout:
- error = "Timeout when talking to {}".format(self.host)
- _LOGGER.exception(error)
- raise HomeAssistantError(error)
-
- def __repr__(self) -> str:
- """Return the representation of the API."""
- return "".format(
- self.base_url, 'yes' if self.api_password is not None else 'no')
-
-
-class JSONEncoder(json.JSONEncoder):
- """JSONEncoder that supports Home Assistant objects."""
-
- # pylint: disable=method-hidden
- def default(self, o: Any) -> Any:
- """Convert Home Assistant objects.
-
- Hand other objects to the original method.
- """
- if isinstance(o, datetime):
- return o.isoformat()
- if isinstance(o, set):
- return list(o)
- if hasattr(o, 'as_dict'):
- return o.as_dict()
-
- return json.JSONEncoder.default(self, o)
-
-
-def validate_api(api: API) -> APIStatus:
- """Make a call to validate API."""
- try:
- req = api(METH_GET, URL_API)
-
- if req.status_code == 200:
- return APIStatus.OK
-
- if req.status_code == 401:
- return APIStatus.INVALID_PASSWORD
-
- return APIStatus.UNKNOWN
-
- except HomeAssistantError:
- return APIStatus.CANNOT_CONNECT
-
-
-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 {} # type: ignore
-
- except (HomeAssistantError, ValueError):
- # ValueError if req.json() can't parse the json
- _LOGGER.exception("Unexpected result retrieving event listeners")
-
- return {}
-
-
-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)
-
- if req.status_code != 200:
- _LOGGER.error("Error firing event: %d - %s",
- req.status_code, req.text)
-
- except HomeAssistantError:
- _LOGGER.exception("Error firing event")
-
-
-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))
-
- # req.status_code == 422 if entity does not exist
-
- return ha.State.from_dict(req.json()) \
- if req.status_code == 200 else None
-
- except (HomeAssistantError, ValueError):
- # ValueError if req.json() can't parse the json
- _LOGGER.exception("Error fetching state")
-
- return None
-
-
-def get_states(api: API) -> List[ha.State]:
- """Query given API for all states."""
- try:
- req = api(METH_GET,
- URL_API_STATES)
-
- return [ha.State.from_dict(item) for
- item in req.json()]
-
- except (HomeAssistantError, ValueError, AttributeError):
- # ValueError if req.json() can't parse the json
- _LOGGER.exception("Error fetching states")
-
- return []
-
-
-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).
- """
- try:
- req = api(METH_DELETE, URL_API_STATES_ENTITY.format(entity_id))
-
- if req.status_code in (200, 404):
- return True
-
- _LOGGER.error("Error removing state: %d - %s",
- req.status_code, req.text)
- return False
- except HomeAssistantError:
- _LOGGER.exception("Error removing state")
-
- return False
-
-
-def set_state(api: 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.
- """
- attributes = attributes or {}
-
- data = {'state': new_state,
- 'attributes': attributes,
- 'force_update': force_update}
-
- try:
- req = api(METH_POST, URL_API_STATES_ENTITY.format(entity_id), data)
-
- if req.status_code not in (200, 201):
- _LOGGER.error("Error changing state: %d - %s",
- req.status_code, req.text)
- return False
-
- return True
-
- except HomeAssistantError:
- _LOGGER.exception("Error setting state")
-
- return False
-
-
-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 bool(cur_state and cur_state.state == state)
-
-
-def get_services(api: API) -> Dict:
- """Return a list of dicts.
-
- Each dict has a string "domain" and a list of strings "services".
- """
- try:
- req = api(METH_GET, URL_API_SERVICES)
-
- return req.json() if req.status_code == 200 else {} # type: ignore
-
- except (HomeAssistantError, ValueError):
- # ValueError if req.json() can't parse the json
- _LOGGER.exception("Got unexpected services result")
-
- return {}
-
-
-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,
- URL_API_SERVICES_SERVICE.format(domain, service),
- service_data, timeout=timeout)
-
- if req.status_code != 200:
- _LOGGER.error("Error calling service: %d - %s",
- req.status_code, req.text)
-
- except HomeAssistantError:
- _LOGGER.exception("Error calling service")
-
-
-def get_config(api: API) -> Dict:
- """Return configuration."""
- try:
- req = api(METH_GET, URL_API_CONFIG)
-
- if req.status_code != 200:
- return {}
-
- result = req.json()
- if 'components' in result:
- result['components'] = set(result['components'])
- return result # type: ignore
-
- except (HomeAssistantError, ValueError):
- # ValueError if req.json() can't parse the JSON
- _LOGGER.exception("Got unexpected configuration results")
-
- return {}
diff --git a/homeassistant/scripts/auth.py b/homeassistant/scripts/auth.py
index d141faa4c27..be57957ef8c 100644
--- a/homeassistant/scripts/auth.py
+++ b/homeassistant/scripts/auth.py
@@ -5,15 +5,15 @@ import logging
import os
from homeassistant.auth import auth_manager_from_config
+from homeassistant.auth.providers import homeassistant as hass_auth
from homeassistant.core import HomeAssistant
from homeassistant.config import get_default_config_dir
-from homeassistant.auth.providers import homeassistant as hass_auth
def run(args):
"""Handle Home Assistant auth provider script."""
parser = argparse.ArgumentParser(
- description=("Manage Home Assistant users"))
+ description="Manage Home Assistant users")
parser.add_argument(
'--script', choices=['auth'])
parser.add_argument(
@@ -56,7 +56,7 @@ async def run_command(hass, args):
hass.config.config_dir = os.path.join(os.getcwd(), args.config)
hass.auth = await auth_manager_from_config(hass, [{
'type': 'homeassistant',
- }])
+ }], [])
provider = hass.auth.auth_providers[0]
await provider.async_initialize()
await args.func(hass, provider, args)
diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py
index d7be5b1a91c..e0c933df5bb 100644
--- a/homeassistant/scripts/check_config.py
+++ b/homeassistant/scripts/check_config.py
@@ -325,7 +325,7 @@ def check_ha_config_file(hass):
# Merge packages
merge_packages_config(
hass, config, core_config.get(CONF_PACKAGES, {}), _pack_error)
- del core_config[CONF_PACKAGES]
+ core_config.pop(CONF_PACKAGES, None)
# Ensure we have no None values after merge
for key, value in config.items():
diff --git a/homeassistant/setup.py b/homeassistant/setup.py
index 31404b978eb..41201264da2 100644
--- a/homeassistant/setup.py
+++ b/homeassistant/setup.py
@@ -64,7 +64,7 @@ async def _async_process_dependencies(
if dep in loader.DEPENDENCY_BLACKLIST]
if blacklisted:
- _LOGGER.error("Unable to setup dependencies of %s: "
+ _LOGGER.error("Unable to set up dependencies of %s: "
"found blacklisted dependencies: %s",
name, ', '.join(blacklisted))
return False
@@ -81,7 +81,7 @@ async def _async_process_dependencies(
in enumerate(results) if not res]
if failed:
- _LOGGER.error("Unable to setup dependencies of %s. "
+ _LOGGER.error("Unable to set up dependencies of %s. "
"Setup failed for dependencies: %s",
name, ', '.join(failed))
@@ -238,7 +238,7 @@ async def async_process_deps_reqs(
hass, config, name, module.DEPENDENCIES) # type: ignore
if not dep_success:
- raise HomeAssistantError("Could not setup all dependencies.")
+ raise HomeAssistantError("Could not set up all dependencies.")
if not hass.config.skip_pip and hasattr(module, 'REQUIREMENTS'):
req_success = await requirements.async_process_requirements(
diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py
index 64c9f4f02c9..1e74c500fc1 100644
--- a/homeassistant/util/__init__.py
+++ b/homeassistant/util/__init__.py
@@ -154,7 +154,7 @@ class OrderedEnum(enum.Enum):
class OrderedSet(MutableSet[T]):
"""Ordered set taken from http://code.activestate.com/recipes/576694/."""
- def __init__(self, iterable: Iterable[T] = None) -> None:
+ def __init__(self, iterable: Optional[Iterable[T]] = None) -> None:
"""Initialize the set."""
self.end = end = [] # type: List[Any]
end += [None, end, end] # sentinel node for doubly linked list
@@ -260,7 +260,7 @@ class Throttle:
"""
def __init__(self, min_time: timedelta,
- limit_no_throttle: timedelta = None) -> None:
+ limit_no_throttle: Optional[timedelta] = None) -> None:
"""Initialize the throttle."""
self.min_time = min_time
self.limit_no_throttle = limit_no_throttle
diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py
index ce6775b9ea7..729195fb3fd 100644
--- a/homeassistant/util/dt.py
+++ b/homeassistant/util/dt.py
@@ -53,7 +53,7 @@ def utcnow() -> dt.datetime:
return dt.datetime.now(UTC)
-def now(time_zone: dt.tzinfo = None) -> dt.datetime:
+def now(time_zone: Optional[dt.tzinfo] = None) -> dt.datetime:
"""Get now in specified time zone."""
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
@@ -97,8 +97,8 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime:
return UTC.localize(dt.datetime.utcfromtimestamp(timestamp))
-def start_of_local_day(dt_or_d:
- Union[dt.date, dt.datetime] = None) -> dt.datetime:
+def start_of_local_day(
+ dt_or_d: Union[dt.date, dt.datetime, None] = 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
diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py
index 9433046e688..feefa65c0f6 100644
--- a/homeassistant/util/package.py
+++ b/homeassistant/util/package.py
@@ -32,7 +32,7 @@ def install_package(package: str, upgrade: bool = True,
"""
# Not using 'import pip; pip.main([])' because it breaks the logger
with INSTALL_LOCK:
- if check_package_exists(package):
+ if package_loadable(package):
return True
_LOGGER.info('Attempting install of %s', package)
@@ -61,8 +61,8 @@ def install_package(package: str, upgrade: bool = True,
return True
-def check_package_exists(package: str) -> bool:
- """Check if a package is installed globally or in lib_dir.
+def package_loadable(package: str) -> bool:
+ """Check if a package is what will be loaded when we import it.
Returns True when the requirement is met.
Returns False when the package is not installed or doesn't meet req.
@@ -73,8 +73,14 @@ def check_package_exists(package: str) -> bool:
# This is a zip file
req = pkg_resources.Requirement.parse(urlparse(package).fragment)
- env = pkg_resources.Environment()
- return any(dist in req for dist in env[req.project_name])
+ for path in sys.path:
+ for dist in pkg_resources.find_distributions(path):
+ # If the project name is the same, it will be the one that is
+ # loaded when we import it.
+ if dist.project_name == req.project_name:
+ return dist in req
+
+ return False
async def async_get_user_site(deps_dir: str) -> str:
diff --git a/mypy.ini b/mypy.ini
index 875aec5eda7..1ffdaa0e509 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -3,6 +3,7 @@ check_untyped_defs = true
disallow_untyped_calls = true
follow_imports = silent
ignore_missing_imports = true
+no_implicit_optional = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_return_any = true
diff --git a/requirements_all.txt b/requirements_all.txt
index 9f0fe83b5ac..73a59319b10 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -59,7 +59,7 @@ PyXiaomiGateway==0.9.5
RtmAPI==0.7.0
# homeassistant.components.sonos
-SoCo==0.14
+SoCo==0.16
# homeassistant.components.sensor.travisci
TravisPy==0.3.5
@@ -77,7 +77,7 @@ YesssSMS==0.1.1b3
abodepy==0.13.1
# homeassistant.components.media_player.frontier_silicon
-afsapi==0.0.3
+afsapi==0.0.4
# homeassistant.components.device_tracker.automatic
aioautomatic==0.6.5
@@ -205,7 +205,7 @@ braviarc-homeassistant==0.3.7.dev0
broadlink==0.9.0
# homeassistant.components.cover.brunt
-brunt==0.1.2
+brunt==0.1.3
# homeassistant.components.device_tracker.bluetooth_tracker
bt_proximity==0.1.2
@@ -413,6 +413,9 @@ ha-ffmpeg==1.9
# homeassistant.components.media_player.philips_js
ha-philipsjs==0.0.5
+# homeassistant.components.hangouts
+hangups==0.4.5
+
# homeassistant.components.sensor.geo_rss_events
haversine==0.4.5
@@ -435,7 +438,7 @@ hole==0.3.0
holidays==0.9.6
# homeassistant.components.frontend
-home-assistant-frontend==20180816.0
+home-assistant-frontend==20180820.0
# homeassistant.components.homekit_controller
# homekit==0.10
@@ -468,11 +471,8 @@ ihcsdk==2.2.0
# homeassistant.components.sensor.influxdb
influxdb==5.0.0
-# homeassistant.components.insteon_local
-insteonlocal==0.53
-
-# homeassistant.components.insteon_plm
-insteonplm==0.11.7
+# homeassistant.components.insteon
+insteonplm==0.12.3
# homeassistant.components.sensor.iperf3
iperf3==0.1.10
@@ -594,7 +594,7 @@ nad_receiver==0.0.9
nanoleaf==0.4.1
# homeassistant.components.device_tracker.keenetic_ndms2
-ndms2_client==0.0.3
+ndms2_client==0.0.4
# homeassistant.components.sensor.netdata
netdata==0.1.2
@@ -616,7 +616,7 @@ nuheat==0.3.0
# homeassistant.components.binary_sensor.trend
# homeassistant.components.image_processing.opencv
-numpy==1.15.0
+numpy==1.15.1
# homeassistant.components.google
oauth2client==4.0.0
@@ -741,6 +741,9 @@ pyTibber==0.4.1
# homeassistant.components.switch.dlink
pyW215==0.6.0
+# homeassistant.components.sensor.noaa_tides
+# py_noaa==0.3.0
+
# homeassistant.components.cover.ryobi_gdo
py_ryobi_gdo==0.0.10
@@ -876,7 +879,7 @@ pyhik==0.1.8
pyhiveapi==0.2.14
# homeassistant.components.homematic
-pyhomematic==0.1.46
+pyhomematic==0.1.47
# homeassistant.components.sensor.hydroquebec
pyhydroquebec==2.2.2
@@ -1088,7 +1091,7 @@ python-juicenet==0.0.5
# homeassistant.components.sensor.xiaomi_miio
# homeassistant.components.switch.xiaomi_miio
# homeassistant.components.vacuum.xiaomi_miio
-python-miio==0.4.0
+python-miio==0.4.1
# homeassistant.components.media_player.mpd
python-mpd2==1.0.0
@@ -1167,7 +1170,7 @@ pytradfri[async]==5.5.1
pyunifi==2.13
# homeassistant.components.upnp
-pyupnp-async==0.1.0.2
+pyupnp-async==0.1.1.0
# homeassistant.components.binary_sensor.uptimerobot
pyuptimerobot==0.0.5
@@ -1227,7 +1230,7 @@ rflink==0.0.37
ring_doorbell==0.2.1
# homeassistant.components.device_tracker.ritassist
-ritassist==0.5
+ritassist==0.9.2
# homeassistant.components.notify.rocketchat
rocketchat-API==0.6.1
@@ -1260,7 +1263,7 @@ schiene==0.22
scsgate==0.1.0
# homeassistant.components.notify.sendgrid
-sendgrid==5.4.1
+sendgrid==5.6.0
# homeassistant.components.light.sensehat
# homeassistant.components.sensor.sensehat
@@ -1273,7 +1276,7 @@ sense_energy==0.4.1
sharp_aquos_rc==0.3.2
# homeassistant.components.sensor.shodan
-shodan==1.9.0
+shodan==1.9.1
# homeassistant.components.notify.simplepush
simplepush==1.1.4
@@ -1339,6 +1342,9 @@ statsd==3.2.1
# homeassistant.components.sensor.steam_online
steamodd==4.21
+# homeassistant.components.ecovacs
+sucks==0.9.1
+
# homeassistant.components.camera.onvif
suds-passworddigest-homeassistant==0.1.2a0.dev0
@@ -1392,7 +1398,7 @@ tplink==0.2.1
transmissionrpc==0.11
# homeassistant.components.tuya
-tuyapy==0.1.2
+tuyapy==0.1.3
# homeassistant.components.twilio
twilio==5.7.0
@@ -1489,7 +1495,7 @@ yeelight==0.4.0
yeelightsunflower==0.0.10
# homeassistant.components.media_extractor
-youtube_dl==2018.08.04
+youtube_dl==2018.08.22
# homeassistant.components.light.zengge
zengge==0.2
diff --git a/requirements_test.txt b/requirements_test.txt
index 5c2bd3404ed..e50ef699848 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -3,15 +3,15 @@
# new version
asynctest==0.12.2
coveralls==1.2.0
-flake8-docstrings==1.0.3
+flake8-docstrings==1.3.0
flake8==3.5
mock-open==1.3.1
mypy==0.620
-pydocstyle==1.1.1
+pydocstyle==2.1.1
pylint==2.1.1
pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
pytest-timeout==1.3.1
-pytest==3.7.1
+pytest==3.7.2
requests_mock==1.5.2
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 0958b6d7280..f4f087bd6d4 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -4,17 +4,17 @@
# new version
asynctest==0.12.2
coveralls==1.2.0
-flake8-docstrings==1.0.3
+flake8-docstrings==1.3.0
flake8==3.5
mock-open==1.3.1
mypy==0.620
-pydocstyle==1.1.1
+pydocstyle==2.1.1
pylint==2.1.1
pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
pytest-timeout==1.3.1
-pytest==3.7.1
+pytest==3.7.2
requests_mock==1.5.2
@@ -25,7 +25,7 @@ HAP-python==2.2.2
PyRMVtransport==0.0.7
# homeassistant.components.sonos
-SoCo==0.14
+SoCo==0.16
# homeassistant.components.device_tracker.automatic
aioautomatic==0.6.5
@@ -71,6 +71,9 @@ gTTS-token==1.1.1
# homeassistant.components.ffmpeg
ha-ffmpeg==1.9
+# homeassistant.components.hangouts
+hangups==0.4.5
+
# homeassistant.components.sensor.geo_rss_events
haversine==0.4.5
@@ -81,7 +84,7 @@ hbmqtt==0.9.2
holidays==0.9.6
# homeassistant.components.frontend
-home-assistant-frontend==20180816.0
+home-assistant-frontend==20180820.0
# homeassistant.components.homematicip_cloud
homematicip==0.9.8
@@ -102,7 +105,7 @@ mficlient==0.3.0
# homeassistant.components.binary_sensor.trend
# homeassistant.components.image_processing.opencv
-numpy==1.15.0
+numpy==1.15.1
# homeassistant.components.mqtt
# homeassistant.components.shiftr
@@ -171,7 +174,7 @@ pytradfri[async]==5.5.1
pyunifi==2.13
# homeassistant.components.upnp
-pyupnp-async==0.1.0.2
+pyupnp-async==0.1.1.0
# homeassistant.components.notify.html5
pywebpush==1.6.0
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index 7652d29086b..e26393bb800 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -34,6 +34,7 @@ COMMENT_REQUIREMENTS = (
'credstash',
'bme680',
'homekit',
+ 'py_noaa',
)
TEST_REQUIREMENTS = (
@@ -50,6 +51,7 @@ TEST_REQUIREMENTS = (
'feedparser',
'foobot_async',
'gTTS-token',
+ 'hangups',
'HAP-python',
'ha-ffmpeg',
'haversine',
@@ -104,7 +106,8 @@ TEST_REQUIREMENTS = (
IGNORE_PACKAGES = (
'homeassistant.components.recorder.models',
- 'homeassistant.components.homekit.*'
+ 'homeassistant.components.homekit.*',
+ 'homeassistant.components.hangouts.hangups_utils'
)
IGNORE_PIN = ('colorlog>2.1,<3', 'keyring>=9.3,<10.0', 'urllib3')
@@ -155,7 +158,7 @@ def core_requirements():
def comment_requirement(req):
- """Some requirements don't install on all systems."""
+ """Comment out requirement. Some don't install on all systems."""
return any(ign in req for ign in COMMENT_REQUIREMENTS)
@@ -165,8 +168,10 @@ def gather_modules():
errors = []
- for package in sorted(explore_module('homeassistant.components', True) +
- explore_module('homeassistant.scripts', True)):
+ for package in sorted(
+ explore_module('homeassistant.components', True) +
+ explore_module('homeassistant.scripts', True) +
+ explore_module('homeassistant.auth', True)):
try:
module = importlib.import_module(package)
except ImportError:
@@ -292,7 +297,7 @@ def validate_constraints_file(data):
def main(validate):
- """Main section of the script."""
+ """Run the script."""
if not os.path.isfile('requirements_all.txt'):
print('Run this from HA root dir')
return 1
diff --git a/script/inspect_schemas.py b/script/inspect_schemas.py
index f2fdff22f7a..46d5cf92ecc 100755
--- a/script/inspect_schemas.py
+++ b/script/inspect_schemas.py
@@ -18,7 +18,7 @@ def explore_module(package):
def main():
- """Main section of the script."""
+ """Run the script."""
if not os.path.isfile('requirements_all.txt'):
print('Run this from HA root dir')
return
diff --git a/script/lazytox.py b/script/lazytox.py
index f0388a0fdcb..7f2340c726f 100755
--- a/script/lazytox.py
+++ b/script/lazytox.py
@@ -153,7 +153,7 @@ async def lint(files):
async def main():
- """The main loop."""
+ """Run the main loop."""
# Ensure we are in the homeassistant root
os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
diff --git a/script/translations_download_split.py b/script/translations_download_split.py
index 03718cf7cab..180c7281a2f 100755
--- a/script/translations_download_split.py
+++ b/script/translations_download_split.py
@@ -88,7 +88,7 @@ def save_language_translations(lang, translations):
def main():
- """Main section of the script."""
+ """Run the script."""
if not os.path.isfile("requirements_all.txt"):
print("Run this from HA root dir")
return
diff --git a/script/translations_upload_merge.py b/script/translations_upload_merge.py
index ce0a14c85e6..c1a039363cd 100755
--- a/script/translations_upload_merge.py
+++ b/script/translations_upload_merge.py
@@ -73,7 +73,7 @@ def get_translation_dict(translations, component, platform):
def main():
- """Main section of the script."""
+ """Run the script."""
if not os.path.isfile("requirements_all.txt"):
print("Run this from HA root dir")
return
diff --git a/tests/auth/mfa_modules/__init__.py b/tests/auth/mfa_modules/__init__.py
new file mode 100644
index 00000000000..a49a158d1b0
--- /dev/null
+++ b/tests/auth/mfa_modules/__init__.py
@@ -0,0 +1 @@
+"""Tests for the multi-factor auth modules."""
diff --git a/tests/auth/mfa_modules/test_insecure_example.py b/tests/auth/mfa_modules/test_insecure_example.py
new file mode 100644
index 00000000000..9d90532728a
--- /dev/null
+++ b/tests/auth/mfa_modules/test_insecure_example.py
@@ -0,0 +1,127 @@
+"""Test the example module auth module."""
+from homeassistant import auth, data_entry_flow
+from homeassistant.auth.mfa_modules import auth_mfa_module_from_config
+from homeassistant.auth.models import Credentials
+from tests.common import MockUser
+
+
+async def test_validate(hass):
+ """Test validating pin."""
+ auth_module = await auth_mfa_module_from_config(hass, {
+ 'type': 'insecure_example',
+ 'data': [{'user_id': 'test-user', 'pin': '123456'}]
+ })
+
+ result = await auth_module.async_validation(
+ 'test-user', {'pin': '123456'})
+ assert result is True
+
+ result = await auth_module.async_validation(
+ 'test-user', {'pin': 'invalid'})
+ assert result is False
+
+ result = await auth_module.async_validation(
+ 'invalid-user', {'pin': '123456'})
+ assert result is False
+
+
+async def test_setup_user(hass):
+ """Test setup user."""
+ auth_module = await auth_mfa_module_from_config(hass, {
+ 'type': 'insecure_example',
+ 'data': []
+ })
+
+ await auth_module.async_setup_user(
+ 'test-user', {'pin': '123456'})
+ assert len(auth_module._data) == 1
+
+ result = await auth_module.async_validation(
+ 'test-user', {'pin': '123456'})
+ assert result is True
+
+
+async def test_depose_user(hass):
+ """Test despose user."""
+ auth_module = await auth_mfa_module_from_config(hass, {
+ 'type': 'insecure_example',
+ 'data': [{'user_id': 'test-user', 'pin': '123456'}]
+ })
+ assert len(auth_module._data) == 1
+
+ await auth_module.async_depose_user('test-user')
+ assert len(auth_module._data) == 0
+
+
+async def test_is_user_setup(hass):
+ """Test is user setup."""
+ auth_module = await auth_mfa_module_from_config(hass, {
+ 'type': 'insecure_example',
+ 'data': [{'user_id': 'test-user', 'pin': '123456'}]
+ })
+ assert await auth_module.async_is_user_setup('test-user') is True
+ assert await auth_module.async_is_user_setup('invalid-user') is False
+
+
+async def test_login(hass):
+ """Test login flow with auth module."""
+ hass.auth = await auth.auth_manager_from_config(hass, [{
+ 'type': 'insecure_example',
+ 'users': [{'username': 'test-user', 'password': 'test-pass'}],
+ }], [{
+ 'type': 'insecure_example',
+ 'data': [{'user_id': 'mock-user', 'pin': '123456'}]
+ }])
+ user = MockUser(
+ id='mock-user',
+ is_owner=False,
+ is_active=False,
+ name='Paulus',
+ ).add_to_auth_manager(hass.auth)
+ await hass.auth.async_link_user(user, Credentials(
+ id='mock-id',
+ auth_provider_type='insecure_example',
+ auth_provider_id=None,
+ data={'username': 'test-user'},
+ is_new=False,
+ ))
+
+ provider = hass.auth.auth_providers[0]
+ result = await hass.auth.login_flow.async_init(
+ (provider.type, provider.id))
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ result = await hass.auth.login_flow.async_configure(
+ result['flow_id'], {
+ 'username': 'incorrect-user',
+ 'password': 'test-pass',
+ })
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_auth'
+
+ result = await hass.auth.login_flow.async_configure(
+ result['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'incorrect-pass',
+ })
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_auth'
+
+ result = await hass.auth.login_flow.async_configure(
+ result['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'mfa'
+ assert result['data_schema'].schema.get('pin') == str
+
+ result = await hass.auth.login_flow.async_configure(
+ result['flow_id'], {'pin': 'invalid-code'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_auth'
+
+ result = await hass.auth.login_flow.async_configure(
+ result['flow_id'], {'pin': '123456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['data'].id == 'mock-user'
diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py
index 9db6293d98a..c92f8539b17 100644
--- a/tests/auth/providers/test_homeassistant.py
+++ b/tests/auth/providers/test_homeassistant.py
@@ -4,7 +4,7 @@ from unittest.mock import Mock
import pytest
from homeassistant import data_entry_flow
-from homeassistant.auth import auth_manager_from_config
+from homeassistant.auth import auth_manager_from_config, auth_store
from homeassistant.auth.providers import (
auth_provider_from_config, homeassistant as hass_auth)
@@ -24,7 +24,7 @@ async def test_adding_user(data, hass):
async def test_adding_user_duplicate_username(data, hass):
- """Test adding a user."""
+ """Test adding a user with duplicate username."""
data.add_auth('test-user', 'test-pass')
with pytest.raises(hass_auth.InvalidUser):
data.add_auth('test-user', 'other-pass')
@@ -37,7 +37,7 @@ async def test_validating_password_invalid_user(data, hass):
async def test_validating_password_invalid_password(data, hass):
- """Test validating an invalid user."""
+ """Test validating an invalid password."""
data.add_auth('test-user', 'test-pass')
with pytest.raises(hass_auth.InvalidAuth):
@@ -67,8 +67,9 @@ async def test_login_flow_validates(data, hass):
data.add_auth('test-user', 'test-pass')
await data.async_save()
- provider = hass_auth.HassAuthProvider(hass, None, {})
- flow = hass_auth.LoginFlow(provider)
+ provider = hass_auth.HassAuthProvider(hass, auth_store.AuthStore(hass),
+ {'type': 'homeassistant'})
+ flow = await provider.async_login_flow({})
result = await flow.async_step_init()
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
@@ -91,6 +92,7 @@ async def test_login_flow_validates(data, hass):
'password': 'test-pass',
})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['data']['username'] == 'test-user'
async def test_saving_loading(data, hass):
@@ -122,7 +124,7 @@ async def test_new_users_populate_values(hass, data):
manager = await auth_manager_from_config(hass, [{
'type': 'homeassistant'
- }])
+ }], [])
provider = manager.auth_providers[0]
credentials = await provider.async_get_or_create_credentials({
'username': 'hello'
diff --git a/tests/auth/providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py
index b472e4c95df..d50e8b0de96 100644
--- a/tests/auth/providers/test_insecure_example.py
+++ b/tests/auth/providers/test_insecure_example.py
@@ -40,7 +40,7 @@ def manager(hass, store, provider):
"""Mock manager."""
return AuthManager(hass, store, {
(provider.type, provider.id): provider
- })
+ }, {})
async def test_create_new_credential(manager, provider):
diff --git a/tests/auth/providers/test_legacy_api_password.py b/tests/auth/providers/test_legacy_api_password.py
index 0c129088c8b..60916798d5b 100644
--- a/tests/auth/providers/test_legacy_api_password.py
+++ b/tests/auth/providers/test_legacy_api_password.py
@@ -3,7 +3,7 @@ from unittest.mock import Mock
import pytest
-from homeassistant import auth
+from homeassistant import auth, data_entry_flow
from homeassistant.auth import auth_store
from homeassistant.auth.providers import legacy_api_password
@@ -27,7 +27,7 @@ def manager(hass, store, provider):
"""Mock manager."""
return auth.AuthManager(hass, store, {
(provider.type, provider.id): provider
- })
+ }, {})
async def test_create_new_credential(manager, provider):
@@ -51,17 +51,6 @@ async def test_only_one_credentials(manager, provider):
assert credentials2.is_new is False
-async def test_verify_not_load(hass, provider):
- """Test we raise if http module not load."""
- with pytest.raises(ValueError):
- provider.async_validate_login('test-password')
- hass.http = Mock(api_password=None)
- with pytest.raises(ValueError):
- provider.async_validate_login('test-password')
- hass.http = Mock(api_password='test-password')
- provider.async_validate_login('test-password')
-
-
async def test_verify_login(hass, provider):
"""Test login using legacy api password auth provider."""
hass.http = Mock(api_password='test-password')
@@ -69,3 +58,45 @@ async def test_verify_login(hass, provider):
hass.http = Mock(api_password='test-password')
with pytest.raises(legacy_api_password.InvalidAuthError):
provider.async_validate_login('invalid-password')
+
+
+async def test_login_flow_abort(hass, manager):
+ """Test wrong config."""
+ for http in (
+ None,
+ Mock(api_password=None),
+ Mock(api_password=''),
+ ):
+ hass.http = http
+
+ result = await manager.login_flow.async_init(
+ handler=('legacy_api_password', None)
+ )
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'no_api_password_set'
+
+
+async def test_login_flow_works(hass, manager):
+ """Test wrong config."""
+ hass.http = Mock(api_password='hello')
+ result = await manager.login_flow.async_init(
+ handler=('legacy_api_password', None)
+ )
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ result = await manager.login_flow.async_configure(
+ flow_id=result['flow_id'],
+ user_input={
+ 'password': 'not-hello'
+ }
+ )
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_auth'
+
+ result = await manager.login_flow.async_configure(
+ flow_id=result['flow_id'],
+ user_input={
+ 'password': 'hello'
+ }
+ )
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
diff --git a/tests/auth/providers/test_trusted_networks.py b/tests/auth/providers/test_trusted_networks.py
index ca8b5bd90a2..4839c72a86a 100644
--- a/tests/auth/providers/test_trusted_networks.py
+++ b/tests/auth/providers/test_trusted_networks.py
@@ -28,7 +28,7 @@ def manager(hass, store, provider):
"""Mock manager."""
return auth.AuthManager(hass, store, {
(provider.type, provider.id): provider
- })
+ }, {})
async def test_trusted_networks_credentials(manager, provider):
@@ -72,7 +72,7 @@ async def test_login_flow(manager, provider):
user = await manager.async_create_user("test-user")
# trusted network didn't loaded
- flow = await provider.async_credential_flow({'ip_address': '127.0.0.1'})
+ flow = await provider.async_login_flow({'ip_address': '127.0.0.1'})
step = await flow.async_step_init()
assert step['step_id'] == 'init'
assert step['errors']['base'] == 'invalid_auth'
@@ -80,13 +80,13 @@ async def test_login_flow(manager, provider):
provider.hass.http = Mock(trusted_networks=['192.168.0.1'])
# not from trusted network
- flow = await provider.async_credential_flow({'ip_address': '127.0.0.1'})
+ flow = await provider.async_login_flow({'ip_address': '127.0.0.1'})
step = await flow.async_step_init()
assert step['step_id'] == 'init'
assert step['errors']['base'] == 'invalid_auth'
# from trusted network, list users
- flow = await provider.async_credential_flow({'ip_address': '192.168.0.1'})
+ flow = await provider.async_login_flow({'ip_address': '192.168.0.1'})
step = await flow.async_step_init()
assert step['step_id'] == 'init'
diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py
index da5daca7cf6..f724b40a71f 100644
--- a/tests/auth/test_init.py
+++ b/tests/auth/test_init.py
@@ -7,6 +7,7 @@ import pytest
from homeassistant import auth, data_entry_flow
from homeassistant.auth import (
models as auth_models, auth_store, const as auth_const)
+from homeassistant.auth.mfa_modules import SESSION_EXPIRATION
from homeassistant.util import dt as dt_util
from tests.common import (
MockUser, ensure_auth_manager_loaded, flush_store, CLIENT_ID)
@@ -40,7 +41,7 @@ async def test_auth_manager_from_config_validates_config_and_id(mock_hass):
'type': 'insecure_example',
'id': 'another',
'users': [],
- }])
+ }], [])
providers = [{
'name': provider.name,
@@ -58,7 +59,65 @@ async def test_auth_manager_from_config_validates_config_and_id(mock_hass):
}]
-async def test_create_new_user(hass, hass_storage):
+async def test_auth_manager_from_config_auth_modules(mock_hass):
+ """Test get auth modules."""
+ manager = await auth.auth_manager_from_config(mock_hass, [{
+ 'name': 'Test Name',
+ 'type': 'insecure_example',
+ 'users': [],
+ }, {
+ 'name': 'Test Name 2',
+ 'type': 'insecure_example',
+ 'id': 'another',
+ 'users': [],
+ }], [{
+ 'name': 'Module 1',
+ 'type': 'insecure_example',
+ 'data': [],
+ }, {
+ 'name': 'Module 2',
+ 'type': 'insecure_example',
+ 'id': 'another',
+ 'data': [],
+ }, {
+ 'name': 'Duplicate ID',
+ 'type': 'insecure_example',
+ 'id': 'another',
+ 'data': [],
+ }])
+
+ providers = [{
+ 'name': provider.name,
+ 'type': provider.type,
+ 'id': provider.id,
+ } for provider in manager.auth_providers]
+ assert providers == [{
+ 'name': 'Test Name',
+ 'type': 'insecure_example',
+ 'id': None,
+ }, {
+ 'name': 'Test Name 2',
+ 'type': 'insecure_example',
+ 'id': 'another',
+ }]
+
+ modules = [{
+ 'name': module.name,
+ 'type': module.type,
+ 'id': module.id,
+ } for module in manager.auth_mfa_modules]
+ assert modules == [{
+ 'name': 'Module 1',
+ 'type': 'insecure_example',
+ 'id': 'insecure_example',
+ }, {
+ 'name': 'Module 2',
+ 'type': 'insecure_example',
+ 'id': 'another',
+ }]
+
+
+async def test_create_new_user(hass):
"""Test creating new user."""
manager = await auth.auth_manager_from_config(hass, [{
'type': 'insecure_example',
@@ -67,7 +126,7 @@ async def test_create_new_user(hass, hass_storage):
'password': 'test-pass',
'name': 'Test Name'
}]
- }])
+ }], [])
step = await manager.login_flow.async_init(('insecure_example', None))
assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
@@ -77,8 +136,7 @@ async def test_create_new_user(hass, hass_storage):
'password': 'test-pass',
})
assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
- credentials = step['result']
- user = await manager.async_get_or_create_user(credentials)
+ user = step['result']
assert user is not None
assert user.is_owner is False
assert user.name == 'Test Name'
@@ -93,7 +151,8 @@ async def test_login_as_existing_user(mock_hass):
'password': 'test-pass',
'name': 'Test Name'
}]
- }])
+ }], [])
+ mock_hass.auth = manager
ensure_auth_manager_loaded(manager)
# Add a fake user that we're not going to log in with
@@ -134,9 +193,8 @@ async def test_login_as_existing_user(mock_hass):
'password': 'test-pass',
})
assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
- credentials = step['result']
- user = await manager.async_get_or_create_user(credentials)
+ user = step['result']
assert user is not None
assert user.id == 'mock-user'
assert user.is_owner is False
@@ -159,23 +217,25 @@ async def test_linking_user_to_two_auth_providers(hass, hass_storage):
'username': 'another-user',
'password': 'another-password',
}]
- }])
+ }], [])
step = await manager.login_flow.async_init(('insecure_example', None))
step = await manager.login_flow.async_configure(step['flow_id'], {
'username': 'test-user',
'password': 'test-pass',
})
- user = await manager.async_get_or_create_user(step['result'])
+ user = step['result']
assert user is not None
- step = await manager.login_flow.async_init(('insecure_example',
- 'another-provider'))
+ step = await manager.login_flow.async_init(
+ ('insecure_example', 'another-provider'),
+ context={'credential_only': True})
step = await manager.login_flow.async_configure(step['flow_id'], {
'username': 'another-user',
'password': 'another-password',
})
- await manager.async_link_user(user, step['result'])
+ new_credential = step['result']
+ await manager.async_link_user(user, new_credential)
assert len(user.credentials) == 2
@@ -190,14 +250,14 @@ async def test_saving_loading(hass, hass_storage):
'username': 'test-user',
'password': 'test-pass',
}]
- }])
+ }], [])
step = await manager.login_flow.async_init(('insecure_example', None))
step = await manager.login_flow.async_configure(step['flow_id'], {
'username': 'test-user',
'password': 'test-pass',
})
- user = await manager.async_get_or_create_user(step['result'])
+ user = step['result']
await manager.async_activate_user(user)
await manager.async_create_refresh_token(user, CLIENT_ID)
@@ -211,7 +271,7 @@ async def test_saving_loading(hass, hass_storage):
async def test_cannot_retrieve_expired_access_token(hass):
"""Test that we cannot retrieve expired access tokens."""
- manager = await auth.auth_manager_from_config(hass, [])
+ manager = await auth.auth_manager_from_config(hass, [], [])
user = MockUser().add_to_auth_manager(manager)
refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
assert refresh_token.user.id is user.id
@@ -236,7 +296,7 @@ async def test_cannot_retrieve_expired_access_token(hass):
async def test_generating_system_user(hass):
"""Test that we can add a system user."""
- manager = await auth.auth_manager_from_config(hass, [])
+ manager = await auth.auth_manager_from_config(hass, [], [])
user = await manager.async_create_system_user('Hass.io')
token = await manager.async_create_refresh_token(user)
assert user.system_generated
@@ -246,7 +306,7 @@ async def test_generating_system_user(hass):
async def test_refresh_token_requires_client_for_user(hass):
"""Test that we can add a system user."""
- manager = await auth.auth_manager_from_config(hass, [])
+ manager = await auth.auth_manager_from_config(hass, [], [])
user = MockUser().add_to_auth_manager(manager)
assert user.system_generated is False
@@ -260,7 +320,7 @@ async def test_refresh_token_requires_client_for_user(hass):
async def test_refresh_token_not_requires_client_for_system_user(hass):
"""Test that we can add a system user."""
- manager = await auth.auth_manager_from_config(hass, [])
+ manager = await auth.auth_manager_from_config(hass, [], [])
user = await manager.async_create_system_user('Hass.io')
assert user.system_generated is True
@@ -274,10 +334,304 @@ async def test_refresh_token_not_requires_client_for_system_user(hass):
async def test_cannot_deactive_owner(mock_hass):
"""Test that we cannot deactive the owner."""
- manager = await auth.auth_manager_from_config(mock_hass, [])
+ manager = await auth.auth_manager_from_config(mock_hass, [], [])
owner = MockUser(
is_owner=True,
).add_to_auth_manager(manager)
with pytest.raises(ValueError):
await manager.async_deactivate_user(owner)
+
+
+async def test_remove_refresh_token(mock_hass):
+ """Test that we can remove a refresh token."""
+ manager = await auth.auth_manager_from_config(mock_hass, [], [])
+ user = MockUser().add_to_auth_manager(manager)
+ refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
+ access_token = manager.async_create_access_token(refresh_token)
+
+ await manager.async_remove_refresh_token(refresh_token)
+
+ assert (
+ await manager.async_get_refresh_token(refresh_token.id) is None
+ )
+ assert (
+ await manager.async_validate_access_token(access_token) is None
+ )
+
+
+async def test_login_with_auth_module(mock_hass):
+ """Test login as existing user with auth module."""
+ manager = await auth.auth_manager_from_config(mock_hass, [{
+ 'type': 'insecure_example',
+ 'users': [{
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ 'name': 'Test Name'
+ }],
+ }], [{
+ 'type': 'insecure_example',
+ 'data': [{
+ 'user_id': 'mock-user',
+ 'pin': 'test-pin'
+ }]
+ }])
+ mock_hass.auth = manager
+ ensure_auth_manager_loaded(manager)
+
+ # Add fake user with credentials for example auth provider.
+ user = MockUser(
+ id='mock-user',
+ is_owner=False,
+ is_active=False,
+ name='Paulus',
+ ).add_to_auth_manager(manager)
+ user.credentials.append(auth_models.Credentials(
+ id='mock-id',
+ auth_provider_type='insecure_example',
+ auth_provider_id=None,
+ data={'username': 'test-user'},
+ is_new=False,
+ ))
+
+ step = await manager.login_flow.async_init(('insecure_example', None))
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+
+ # After auth_provider validated, request auth module input form
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'pin': 'invalid-pin',
+ })
+
+ # Invalid auth error
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+ assert step['errors'] == {'base': 'invalid_auth'}
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'pin': 'test-pin',
+ })
+
+ # Finally passed, get user
+ assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ user = step['result']
+ assert user is not None
+ assert user.id == 'mock-user'
+ assert user.is_owner is False
+ assert user.is_active is False
+ assert user.name == 'Paulus'
+
+
+async def test_login_with_multi_auth_module(mock_hass):
+ """Test login as existing user with multiple auth modules."""
+ manager = await auth.auth_manager_from_config(mock_hass, [{
+ 'type': 'insecure_example',
+ 'users': [{
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ 'name': 'Test Name'
+ }],
+ }], [{
+ 'type': 'insecure_example',
+ 'data': [{
+ 'user_id': 'mock-user',
+ 'pin': 'test-pin'
+ }]
+ }, {
+ 'type': 'insecure_example',
+ 'id': 'module2',
+ 'data': [{
+ 'user_id': 'mock-user',
+ 'pin': 'test-pin2'
+ }]
+ }])
+ mock_hass.auth = manager
+ ensure_auth_manager_loaded(manager)
+
+ # Add fake user with credentials for example auth provider.
+ user = MockUser(
+ id='mock-user',
+ is_owner=False,
+ is_active=False,
+ name='Paulus',
+ ).add_to_auth_manager(manager)
+ user.credentials.append(auth_models.Credentials(
+ id='mock-id',
+ auth_provider_type='insecure_example',
+ auth_provider_id=None,
+ data={'username': 'test-user'},
+ is_new=False,
+ ))
+
+ step = await manager.login_flow.async_init(('insecure_example', None))
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+
+ # After auth_provider validated, request select auth module
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'select_mfa_module'
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'multi_factor_auth_module': 'module2',
+ })
+
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'pin': 'test-pin2',
+ })
+
+ # Finally passed, get user
+ assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ user = step['result']
+ assert user is not None
+ assert user.id == 'mock-user'
+ assert user.is_owner is False
+ assert user.is_active is False
+ assert user.name == 'Paulus'
+
+
+async def test_auth_module_expired_session(mock_hass):
+ """Test login as existing user."""
+ manager = await auth.auth_manager_from_config(mock_hass, [{
+ 'type': 'insecure_example',
+ 'users': [{
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ 'name': 'Test Name'
+ }],
+ }], [{
+ 'type': 'insecure_example',
+ 'data': [{
+ 'user_id': 'mock-user',
+ 'pin': 'test-pin'
+ }]
+ }])
+ mock_hass.auth = manager
+ ensure_auth_manager_loaded(manager)
+
+ # Add fake user with credentials for example auth provider.
+ user = MockUser(
+ id='mock-user',
+ is_owner=False,
+ is_active=False,
+ name='Paulus',
+ ).add_to_auth_manager(manager)
+ user.credentials.append(auth_models.Credentials(
+ id='mock-id',
+ auth_provider_type='insecure_example',
+ auth_provider_id=None,
+ data={'username': 'test-user'},
+ is_new=False,
+ ))
+
+ step = await manager.login_flow.async_init(('insecure_example', None))
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+
+ with patch('homeassistant.util.dt.utcnow',
+ return_value=dt_util.utcnow() + SESSION_EXPIRATION):
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'pin': 'test-pin',
+ })
+ # Invalid auth due session timeout
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+ assert step['errors']['base'] == 'login_expired'
+
+ # The second try will fail as well
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'pin': 'test-pin',
+ })
+ assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert step['step_id'] == 'mfa'
+ assert step['errors']['base'] == 'login_expired'
+
+
+async def test_enable_mfa_for_user(hass, hass_storage):
+ """Test enable mfa module for user."""
+ manager = await auth.auth_manager_from_config(hass, [{
+ 'type': 'insecure_example',
+ 'users': [{
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ }]
+ }], [{
+ 'type': 'insecure_example',
+ 'data': [],
+ }])
+
+ step = await manager.login_flow.async_init(('insecure_example', None))
+ step = await manager.login_flow.async_configure(step['flow_id'], {
+ 'username': 'test-user',
+ 'password': 'test-pass',
+ })
+ user = step['result']
+ assert user is not None
+
+ # new user don't have mfa enabled
+ modules = await manager.async_get_enabled_mfa(user)
+ assert len(modules) == 0
+
+ module = manager.get_auth_mfa_module('insecure_example')
+ # mfa module don't have data
+ assert bool(module._data) is False
+
+ # test enable mfa for user
+ await manager.async_enable_user_mfa(user, 'insecure_example',
+ {'pin': 'test-pin'})
+ assert len(module._data) == 1
+ assert module._data[0] == {'user_id': user.id, 'pin': 'test-pin'}
+
+ # test get enabled mfa
+ modules = await manager.async_get_enabled_mfa(user)
+ assert len(modules) == 1
+ assert 'insecure_example' in modules
+
+ # re-enable mfa for user will override
+ await manager.async_enable_user_mfa(user, 'insecure_example',
+ {'pin': 'test-pin-new'})
+ assert len(module._data) == 1
+ assert module._data[0] == {'user_id': user.id, 'pin': 'test-pin-new'}
+ modules = await manager.async_get_enabled_mfa(user)
+ assert len(modules) == 1
+ assert 'insecure_example' in modules
+
+ # system user cannot enable mfa
+ system_user = await manager.async_create_system_user('system-user')
+ with pytest.raises(ValueError):
+ await manager.async_enable_user_mfa(system_user, 'insecure_example',
+ {'pin': 'test-pin'})
+ assert len(module._data) == 1
+ modules = await manager.async_get_enabled_mfa(system_user)
+ assert len(modules) == 0
+
+ # disable mfa for user
+ await manager.async_disable_user_mfa(user, 'insecure_example')
+ assert bool(module._data) is False
+
+ # test get enabled mfa
+ modules = await manager.async_get_enabled_mfa(user)
+ assert len(modules) == 0
+
+ # disable mfa for user don't enabled just silent fail
+ await manager.async_disable_user_mfa(user, 'insecure_example')
diff --git a/tests/common.py b/tests/common.py
index 81e4774ccd4..738c51fb3f0 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -118,19 +118,35 @@ def async_test_home_assistant(loop):
hass = ha.HomeAssistant(loop)
hass.config.async_load = Mock()
store = auth_store.AuthStore(hass)
- hass.auth = auth.AuthManager(hass, store, {})
+ hass.auth = auth.AuthManager(hass, store, {}, {})
ensure_auth_manager_loaded(hass.auth)
INSTANCES.append(hass)
orig_async_add_job = hass.async_add_job
+ orig_async_add_executor_job = hass.async_add_executor_job
+ orig_async_create_task = hass.async_create_task
def async_add_job(target, *args):
- """Add a magic mock."""
+ """Add job."""
if isinstance(target, Mock):
return mock_coro(target(*args))
return orig_async_add_job(target, *args)
+ def async_add_executor_job(target, *args):
+ """Add executor job."""
+ if isinstance(target, Mock):
+ return mock_coro(target(*args))
+ return orig_async_add_executor_job(target, *args)
+
+ def async_create_task(coroutine):
+ """Create task."""
+ if isinstance(coroutine, Mock):
+ return mock_coro()
+ return orig_async_create_task(coroutine)
+
hass.async_add_job = async_add_job
+ hass.async_add_executor_job = async_add_executor_job
+ hass.async_create_task = async_create_task
hass.config.location_name = 'test home'
hass.config.config_dir = get_test_config_dir()
@@ -307,7 +323,12 @@ def mock_registry(hass, mock_entries=None):
"""Mock the Entity Registry."""
registry = entity_registry.EntityRegistry(hass)
registry.entities = mock_entries or {}
- hass.data[entity_registry.DATA_REGISTRY] = registry
+
+ async def _get_reg():
+ return registry
+
+ hass.data[entity_registry.DATA_REGISTRY] = \
+ hass.loop.create_task(_get_reg())
return registry
@@ -321,7 +342,7 @@ class MockUser(auth_models.User):
'is_owner': is_owner,
'is_active': is_active,
'name': name,
- 'system_generated': system_generated
+ 'system_generated': system_generated,
}
if id is not None:
kwargs['id'] = id
@@ -339,7 +360,7 @@ class MockUser(auth_models.User):
async def register_auth_provider(hass, config):
- """Helper to register an auth provider."""
+ """Register an auth provider."""
provider = await auth_providers.auth_provider_from_config(
hass, hass.auth._store, config)
assert provider is not None, 'Invalid config specified'
@@ -728,7 +749,7 @@ class MockEntity(entity.Entity):
return self._handle('available')
def _handle(self, attr):
- """Helper for the attributes."""
+ """Return attribute value."""
if attr in self._values:
return self._values[attr]
return getattr(super(), attr)
diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py
index e4b29d43e48..02e528db914 100644
--- a/tests/components/alarm_control_panel/test_manual.py
+++ b/tests/components/alarm_control_panel/test_manual.py
@@ -22,7 +22,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
"""Test the manual alarm module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/alarm_control_panel/test_manual_mqtt.py b/tests/components/alarm_control_panel/test_manual_mqtt.py
index 719352c5419..5b601f089dd 100644
--- a/tests/components/alarm_control_panel/test_manual_mqtt.py
+++ b/tests/components/alarm_control_panel/test_manual_mqtt.py
@@ -21,7 +21,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
"""Test the manual_mqtt alarm module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py
index dee9b3959ca..ce152a3d7c9 100644
--- a/tests/components/alarm_control_panel/test_mqtt.py
+++ b/tests/components/alarm_control_panel/test_mqtt.py
@@ -21,7 +21,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py
index cf8535653a9..88c69194407 100644
--- a/tests/components/alexa/test_smart_home.py
+++ b/tests/components/alexa/test_smart_home.py
@@ -5,9 +5,10 @@ from uuid import uuid4
import pytest
+from homeassistant.core import Context, callback
from homeassistant.const import (
- TEMP_FAHRENHEIT, STATE_LOCKED, STATE_UNLOCKED,
- STATE_UNKNOWN)
+ TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_LOCKED,
+ STATE_UNLOCKED, STATE_UNKNOWN)
from homeassistant.setup import async_setup_component
from homeassistant.components import alexa
from homeassistant.components.alexa import smart_home
@@ -18,6 +19,17 @@ from tests.common import async_mock_service
DEFAULT_CONFIG = smart_home.Config(should_expose=lambda entity_id: True)
+@pytest.fixture
+def events(hass):
+ """Fixture that catches alexa events."""
+ events = []
+ hass.bus.async_listen(
+ smart_home.EVENT_ALEXA_SMART_HOME,
+ callback(lambda e: events.append(e))
+ )
+ yield events
+
+
def get_new_request(namespace, name, endpoint=None):
"""Generate a new API message."""
raw_msg = {
@@ -145,7 +157,7 @@ def assert_endpoint_capabilities(endpoint, *interfaces):
@asyncio.coroutine
-def test_switch(hass):
+def test_switch(hass, events):
"""Test switch discovery."""
device = ('switch.test', 'on', {'friendly_name': "Test switch"})
appliance = yield from discovery_test(device, hass)
@@ -695,6 +707,7 @@ def test_unknown_sensor(hass):
async def test_thermostat(hass):
"""Test thermostat discovery."""
+ hass.config.units.temperature_unit = TEMP_FAHRENHEIT
device = (
'climate.test_thermostat',
'cool',
@@ -709,7 +722,6 @@ async def test_thermostat(hass):
'operation_list': ['heat', 'cool', 'auto', 'off'],
'min_temp': 50,
'max_temp': 90,
- 'unit_of_measurement': TEMP_FAHRENHEIT,
}
)
appliance = await discovery_test(device, hass)
@@ -826,6 +838,7 @@ async def test_thermostat(hass):
payload={'thermostatMode': {'value': 'INVALID'}}
)
assert msg['event']['payload']['type'] == 'UNSUPPORTED_THERMOSTAT_MODE'
+ hass.config.units.temperature_unit = TEMP_CELSIUS
@asyncio.coroutine
@@ -963,23 +976,26 @@ def assert_request_calls_service(
response_type='Response',
payload=None):
"""Assert an API request calls a hass service."""
+ context = Context()
request = get_new_request(namespace, name, endpoint)
if payload:
request['directive']['payload'] = payload
domain, service_name = service.split('.')
- call = async_mock_service(hass, domain, service_name)
+ calls = async_mock_service(hass, domain, service_name)
msg = yield from smart_home.async_handle_message(
- hass, DEFAULT_CONFIG, request)
+ hass, DEFAULT_CONFIG, request, context)
yield from hass.async_block_till_done()
- assert len(call) == 1
+ assert len(calls) == 1
+ call = calls[0]
assert 'event' in msg
- assert call[0].data['entity_id'] == endpoint.replace('#', '.')
+ assert call.data['entity_id'] == endpoint.replace('#', '.')
assert msg['event']['header']['name'] == response_type
+ assert call.context == context
- return call[0], msg
+ return call, msg
@asyncio.coroutine
@@ -1372,3 +1388,53 @@ def test_api_select_input(hass, domain, payload, source_list, idx):
hass,
payload={'input': payload})
assert call.data['source'] == source_list[idx]
+
+
+async def test_logging_request(hass, events):
+ """Test that we log requests."""
+ context = Context()
+ request = get_new_request('Alexa.Discovery', 'Discover')
+ await smart_home.async_handle_message(
+ hass, DEFAULT_CONFIG, request, context)
+
+ # To trigger event listener
+ await hass.async_block_till_done()
+
+ assert len(events) == 1
+ event = events[0]
+
+ assert event.data['request'] == {
+ 'namespace': 'Alexa.Discovery',
+ 'name': 'Discover',
+ }
+ assert event.data['response'] == {
+ 'namespace': 'Alexa.Discovery',
+ 'name': 'Discover.Response'
+ }
+ assert event.context == context
+
+
+async def test_logging_request_with_entity(hass, events):
+ """Test that we log requests."""
+ context = Context()
+ request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#xy')
+ await smart_home.async_handle_message(
+ hass, DEFAULT_CONFIG, request, context)
+
+ # To trigger event listener
+ await hass.async_block_till_done()
+
+ assert len(events) == 1
+ event = events[0]
+
+ assert event.data['request'] == {
+ 'namespace': 'Alexa.PowerController',
+ 'name': 'TurnOn',
+ 'entity_id': 'switch.xy'
+ }
+ # Entity doesn't exist
+ assert event.data['response'] == {
+ 'namespace': 'Alexa',
+ 'name': 'ErrorResponse'
+ }
+ assert event.context == context
diff --git a/tests/components/auth/__init__.py b/tests/components/auth/__init__.py
index ce94d1ecbfa..799d31f3db8 100644
--- a/tests/components/auth/__init__.py
+++ b/tests/components/auth/__init__.py
@@ -15,11 +15,14 @@ BASE_CONFIG = [{
}]
}]
+EMPTY_CONFIG = []
+
async def async_setup_auth(hass, aiohttp_client, provider_configs=BASE_CONFIG,
- setup_api=False):
- """Helper to setup authentication and create a HTTP client."""
- hass.auth = await auth.auth_manager_from_config(hass, provider_configs)
+ module_configs=EMPTY_CONFIG, setup_api=False):
+ """Set up authentication and create an HTTP client."""
+ hass.auth = await auth.auth_manager_from_config(
+ hass, provider_configs, module_configs)
ensure_auth_manager_loaded(hass.auth)
await async_setup_component(hass, 'auth', {
'http': {
diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py
index 75e61af2e71..d30ead10cb2 100644
--- a/tests/components/auth/test_indieauth.py
+++ b/tests/components/auth/test_indieauth.py
@@ -1,4 +1,5 @@
"""Tests for the client validator."""
+import asyncio
from unittest.mock import patch
import pytest
@@ -6,6 +7,18 @@ import pytest
from homeassistant.components.auth import indieauth
from tests.common import mock_coro
+from tests.test_util.aiohttp import AiohttpClientMocker
+
+
+@pytest.fixture
+def mock_session():
+ """Mock aiohttp.ClientSession."""
+ mocker = AiohttpClientMocker()
+
+ with patch('aiohttp.ClientSession',
+ side_effect=lambda *args, **kwargs:
+ mocker.create_session(asyncio.get_event_loop())):
+ yield mocker
def test_client_id_scheme():
@@ -120,9 +133,9 @@ async def test_verify_redirect_uri():
)
-async def test_find_link_tag(hass, aioclient_mock):
+async def test_find_link_tag(hass, mock_session):
"""Test finding link tag."""
- aioclient_mock.get("http://127.0.0.1:8000", text="""
+ mock_session.get("http://127.0.0.1:8000", text="""
@@ -142,11 +155,15 @@ async def test_find_link_tag(hass, aioclient_mock):
]
-async def test_find_link_tag_max_size(hass, aioclient_mock):
+async def test_find_link_tag_max_size(hass, mock_session):
"""Test finding link tag."""
- text = ("0" * 1024 * 10) + ''
- aioclient_mock.get("http://127.0.0.1:8000", text=text)
+ text = ''.join([
+ '',
+ ("0" * 1024 * 10),
+ '',
+ ])
+ mock_session.get("http://127.0.0.1:8000", text=text)
redirect_uris = await indieauth.fetch_redirect_uris(
hass, "http://127.0.0.1:8000")
- assert redirect_uris == []
+ assert redirect_uris == ["http://127.0.0.1:8000/wine"]
diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py
index f1a1bb5bd3c..7b9dda6acb3 100644
--- a/tests/components/auth/test_init.py
+++ b/tests/components/auth/test_init.py
@@ -3,13 +3,14 @@ from datetime import timedelta
from unittest.mock import patch
from homeassistant.auth.models import Credentials
+from homeassistant.components.auth import RESULT_TYPE_USER
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from homeassistant.components import auth
from . import async_setup_auth
-from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI
+from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI, MockUser
async def test_login_new_user_and_trying_refresh_token(hass, aiohttp_client):
@@ -74,26 +75,26 @@ async def test_login_new_user_and_trying_refresh_token(hass, aiohttp_client):
assert resp.status == 200
-def test_credential_store_expiration():
- """Test that the credential store will not return expired tokens."""
- store, retrieve = auth._create_cred_store()
+def test_auth_code_store_expiration():
+ """Test that the auth code store will not return expired tokens."""
+ store, retrieve = auth._create_auth_code_store()
client_id = 'bla'
- credentials = 'creds'
+ user = MockUser(id='mock_user')
now = utcnow()
with patch('homeassistant.util.dt.utcnow', return_value=now):
- code = store(client_id, credentials)
+ code = store(client_id, user)
with patch('homeassistant.util.dt.utcnow',
return_value=now + timedelta(minutes=10)):
- assert retrieve(client_id, code) is None
+ assert retrieve(client_id, RESULT_TYPE_USER, code) is None
with patch('homeassistant.util.dt.utcnow', return_value=now):
- code = store(client_id, credentials)
+ code = store(client_id, user)
with patch('homeassistant.util.dt.utcnow',
return_value=now + timedelta(minutes=9, seconds=59)):
- assert retrieve(client_id, code) == credentials
+ assert retrieve(client_id, RESULT_TYPE_USER, code) == user
async def test_ws_current_user(hass, hass_ws_client, hass_access_token):
@@ -223,3 +224,46 @@ async def test_refresh_token_different_client_id(hass, aiohttp_client):
await hass.auth.async_validate_access_token(tokens['access_token'])
is not None
)
+
+
+async def test_revoking_refresh_token(hass, aiohttp_client):
+ """Test that we can revoke refresh tokens."""
+ 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)
+
+ # Test that we can create an access token
+ 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 (
+ await hass.auth.async_validate_access_token(tokens['access_token'])
+ is not None
+ )
+
+ # Revoke refresh token
+ resp = await client.post('/auth/token', data={
+ 'token': refresh_token.token,
+ 'action': 'revoke',
+ })
+ assert resp.status == 200
+
+ # Old access token should be no longer valid
+ assert (
+ await hass.auth.async_validate_access_token(tokens['access_token'])
+ is None
+ )
+
+ # Test that we no longer can create an access token
+ resp = await client.post('/auth/token', data={
+ 'client_id': CLIENT_ID,
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token.token,
+ })
+
+ assert resp.status == 400
diff --git a/tests/components/auth/test_init_link_user.py b/tests/components/auth/test_init_link_user.py
index e209e0ee856..6c9fdf3fbc2 100644
--- a/tests/components/auth/test_init_link_user.py
+++ b/tests/components/auth/test_init_link_user.py
@@ -5,7 +5,7 @@ from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI
async def async_get_code(hass, aiohttp_client):
- """Helper for link user tests that returns authorization code."""
+ """Return authorization code for link user tests."""
config = [{
'name': 'Example',
'type': 'insecure_example',
@@ -34,6 +34,7 @@ async def async_get_code(hass, aiohttp_client):
'client_id': CLIENT_ID,
'handler': ['insecure_example', '2nd auth'],
'redirect_uri': CLIENT_REDIRECT_URI,
+ 'type': 'link_user',
})
assert resp.status == 200
step = await resp.json()
diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py
index aea6e517e38..c52ea7b9d29 100644
--- a/tests/components/automation/test_event.py
+++ b/tests/components/automation/test_event.py
@@ -13,14 +13,14 @@ class TestAutomationEvent(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
self.calls = []
@callback
def record_call(service):
- """Helper for recording the call."""
+ """Record the call."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py
index b1990fb80aa..c3bd6c224af 100644
--- a/tests/components/automation/test_init.py
+++ b/tests/components/automation/test_init.py
@@ -22,7 +22,7 @@ class TestAutomation(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test', 'automation')
diff --git a/tests/components/automation/test_litejet.py b/tests/components/automation/test_litejet.py
index e6445415490..ca6f7796cfc 100644
--- a/tests/components/automation/test_litejet.py
+++ b/tests/components/automation/test_litejet.py
@@ -23,7 +23,7 @@ class TestLiteJetTrigger(unittest.TestCase):
@mock.patch('pylitejet.LiteJet')
def setup_method(self, method, mock_pylitejet):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.start()
diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py
index d1eb0d63ee8..8ec5351af94 100644
--- a/tests/components/automation/test_mqtt.py
+++ b/tests/components/automation/test_mqtt.py
@@ -14,7 +14,7 @@ class TestAutomationMQTT(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
mock_mqtt_component(self.hass)
@@ -22,7 +22,7 @@ class TestAutomationMQTT(unittest.TestCase):
@callback
def record_call(service):
- """Helper to record calls."""
+ """Record calls."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py
index de453675a57..49565c37222 100644
--- a/tests/components/automation/test_numeric_state.py
+++ b/tests/components/automation/test_numeric_state.py
@@ -18,14 +18,14 @@ class TestAutomationNumericState(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
self.calls = []
@callback
def record_call(service):
- """Helper to record calls."""
+ """Record calls."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py
index 22c84b88935..6b1a8914aad 100644
--- a/tests/components/automation/test_state.py
+++ b/tests/components/automation/test_state.py
@@ -19,7 +19,7 @@ class TestAutomationState(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
self.hass.states.set('test.entity', 'hello')
diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py
index 355d088719f..4556b7cbe45 100644
--- a/tests/components/automation/test_sun.py
+++ b/tests/components/automation/test_sun.py
@@ -19,7 +19,7 @@ class TestAutomationSun(unittest.TestCase):
"""Test the sun automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
setup_component(self.hass, sun.DOMAIN, {
diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py
index 937fa16988a..ef064a2c1d8 100644
--- a/tests/components/automation/test_template.py
+++ b/tests/components/automation/test_template.py
@@ -14,7 +14,7 @@ class TestAutomationTemplate(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
self.hass.states.set('test.entity', 'hello')
@@ -22,7 +22,7 @@ class TestAutomationTemplate(unittest.TestCase):
@callback
def record_call(service):
- """Helper to record calls."""
+ """Record calls."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py
index 5d5d5ea29ec..5f928cf92a0 100644
--- a/tests/components/automation/test_time.py
+++ b/tests/components/automation/test_time.py
@@ -18,14 +18,14 @@ class TestAutomationTime(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
self.calls = []
@callback
def record_call(service):
- """Helper to record calls."""
+ """Record calls."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py
index 3dc4b75b8ae..baa3bdc1d28 100644
--- a/tests/components/automation/test_zone.py
+++ b/tests/components/automation/test_zone.py
@@ -13,7 +13,7 @@ class TestAutomationZone(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'group')
assert setup_component(self.hass, zone.DOMAIN, {
@@ -29,7 +29,7 @@ class TestAutomationZone(unittest.TestCase):
@callback
def record_call(service):
- """Helper to record calls."""
+ """Record calls."""
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py
index 07389c7c8a9..d469fc65e8e 100644
--- a/tests/components/binary_sensor/test_command_line.py
+++ b/tests/components/binary_sensor/test_command_line.py
@@ -12,7 +12,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
"""Test the Command line Binary sensor."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/binary_sensor/test_ffmpeg.py
index da9350008d8..4e6629c0afd 100644
--- a/tests/components/binary_sensor/test_ffmpeg.py
+++ b/tests/components/binary_sensor/test_ffmpeg.py
@@ -11,7 +11,7 @@ class TestFFmpegNoiseSetup:
"""Test class for ffmpeg."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -29,7 +29,7 @@ class TestFFmpegNoiseSetup:
self.hass.stop()
def test_setup_component(self):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
@@ -38,7 +38,7 @@ class TestFFmpegNoiseSetup:
@patch('haffmpeg.SensorNoise.open_sensor', return_value=mock_coro())
def test_setup_component_start(self, mock_start):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
@@ -53,7 +53,7 @@ class TestFFmpegNoiseSetup:
@patch('haffmpeg.SensorNoise')
def test_setup_component_start_callback(self, mock_ffmpeg):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
@@ -76,7 +76,7 @@ class TestFFmpegMotionSetup:
"""Test class for ffmpeg."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -94,7 +94,7 @@ class TestFFmpegMotionSetup:
self.hass.stop()
def test_setup_component(self):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
@@ -103,7 +103,7 @@ class TestFFmpegMotionSetup:
@patch('haffmpeg.SensorMotion.open_sensor', return_value=mock_coro())
def test_setup_component_start(self, mock_start):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
@@ -118,7 +118,7 @@ class TestFFmpegMotionSetup:
@patch('haffmpeg.SensorMotion')
def test_setup_component_start_callback(self, mock_ffmpeg):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py
index 71eba2df950..57050c2cbf5 100644
--- a/tests/components/binary_sensor/test_mqtt.py
+++ b/tests/components/binary_sensor/test_mqtt.py
@@ -16,7 +16,7 @@ class TestSensorMQTT(unittest.TestCase):
"""Test the MQTT sensor."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py
index 4d1d85d30fb..117c32203eb 100644
--- a/tests/components/binary_sensor/test_nx584.py
+++ b/tests/components/binary_sensor/test_nx584.py
@@ -21,7 +21,7 @@ class TestNX584SensorSetup(unittest.TestCase):
"""Test the NX584 sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self._mock_client = mock.patch.object(nx584_client, 'Client')
self._mock_client.start()
diff --git a/tests/components/binary_sensor/test_rest.py b/tests/components/binary_sensor/test_rest.py
index d0670bf5154..d1c26624452 100644
--- a/tests/components/binary_sensor/test_rest.py
+++ b/tests/components/binary_sensor/test_rest.py
@@ -19,7 +19,7 @@ class TestRestBinarySensorSetup(unittest.TestCase):
"""Tests for setting up the REST binary sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -121,7 +121,7 @@ class TestRestBinarySensor(unittest.TestCase):
"""Tests for REST binary sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.rest = Mock('RestData')
self.rest.update = Mock('RestData.update',
diff --git a/tests/components/binary_sensor/test_tcp.py b/tests/components/binary_sensor/test_tcp.py
index 8602de84d25..69673f09a46 100644
--- a/tests/components/binary_sensor/test_tcp.py
+++ b/tests/components/binary_sensor/test_tcp.py
@@ -13,7 +13,7 @@ class TestTCPBinarySensor(unittest.TestCase):
"""Test the TCP Binary Sensor."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py
index 62623a04f3c..eba3a368f56 100644
--- a/tests/components/binary_sensor/test_template.py
+++ b/tests/components/binary_sensor/test_template.py
@@ -23,7 +23,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/components/binary_sensor/test_workday.py b/tests/components/binary_sensor/test_workday.py
index 893745ce3de..5aa5a5dad5c 100644
--- a/tests/components/binary_sensor/test_workday.py
+++ b/tests/components/binary_sensor/test_workday.py
@@ -16,7 +16,7 @@ class TestWorkdaySetup:
"""Test class for workday sensor."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# Set valid default config for test
@@ -97,7 +97,7 @@ class TestWorkdaySetup:
self.hass.stop()
def test_setup_component_province(self):
- """Setup workday component."""
+ """Set up workday component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor',
self.config_province)
@@ -145,7 +145,7 @@ class TestWorkdaySetup:
assert entity.state == 'off'
def test_setup_component_noprovince(self):
- """Setup workday component."""
+ """Set up workday component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor',
self.config_noprovince)
@@ -191,7 +191,7 @@ class TestWorkdaySetup:
assert entity.state == 'on'
def test_setup_component_invalidprovince(self):
- """Setup workday component."""
+ """Set up workday component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor',
self.config_invalidprovince)
diff --git a/tests/components/calendar/test_google.py b/tests/components/calendar/test_google.py
index d176cd758b4..e07f7a6306a 100644
--- a/tests/components/calendar/test_google.py
+++ b/tests/components/calendar/test_google.py
@@ -25,7 +25,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.http = Mock()
diff --git a/tests/components/camera/test_generic.py b/tests/components/camera/test_generic.py
index 01edca1e996..b981fced320 100644
--- a/tests/components/camera/test_generic.py
+++ b/tests/components/camera/test_generic.py
@@ -1,5 +1,6 @@
"""The tests for generic camera component."""
import asyncio
+
from unittest import mock
from homeassistant.setup import async_setup_component
@@ -32,6 +33,50 @@ def test_fetching_url(aioclient_mock, hass, aiohttp_client):
assert aioclient_mock.call_count == 2
+@asyncio.coroutine
+def test_fetching_without_verify_ssl(aioclient_mock, hass, aiohttp_client):
+ """Test that it fetches the given url when ssl verify is off."""
+ aioclient_mock.get('https://example.com', text='hello world')
+
+ yield from async_setup_component(hass, 'camera', {
+ 'camera': {
+ 'name': 'config_test',
+ 'platform': 'generic',
+ 'still_image_url': 'https://example.com',
+ 'username': 'user',
+ 'password': 'pass',
+ 'verify_ssl': 'false',
+ }})
+
+ client = yield from aiohttp_client(hass.http.app)
+
+ resp = yield from client.get('/api/camera_proxy/camera.config_test')
+
+ assert resp.status == 200
+
+
+@asyncio.coroutine
+def test_fetching_url_with_verify_ssl(aioclient_mock, hass, aiohttp_client):
+ """Test that it fetches the given url when ssl verify is explicitly on."""
+ aioclient_mock.get('https://example.com', text='hello world')
+
+ yield from async_setup_component(hass, 'camera', {
+ 'camera': {
+ 'name': 'config_test',
+ 'platform': 'generic',
+ 'still_image_url': 'https://example.com',
+ 'username': 'user',
+ 'password': 'pass',
+ 'verify_ssl': 'true',
+ }})
+
+ client = yield from aiohttp_client(hass.http.app)
+
+ resp = yield from client.get('/api/camera_proxy/camera.config_test')
+
+ assert resp.status == 200
+
+
@asyncio.coroutine
def test_limit_refetch(aioclient_mock, hass, aiohttp_client):
"""Test that it fetches the given url."""
diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py
index cf902ca1779..053fa6d29dc 100644
--- a/tests/components/camera/test_init.py
+++ b/tests/components/camera/test_init.py
@@ -34,7 +34,7 @@ class TestSetupCamera:
"""Test class for setup camera."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -42,7 +42,7 @@ class TestSetupCamera:
self.hass.stop()
def test_setup_component(self):
- """Setup demo platform on camera component."""
+ """Set up demo platform on camera component."""
config = {
camera.DOMAIN: {
'platform': 'demo'
@@ -57,7 +57,7 @@ class TestGetImage:
"""Test class for camera."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
setup_component(
diff --git a/tests/components/camera/test_push.py b/tests/components/camera/test_push.py
index 78053e540f5..f9a3c62aa4a 100644
--- a/tests/components/camera/test_push.py
+++ b/tests/components/camera/test_push.py
@@ -30,7 +30,7 @@ async def test_bad_posting(aioclient_mock, hass, aiohttp_client):
assert resp.status == 400
-async def test_posting_url(aioclient_mock, hass, aiohttp_client):
+async def test_posting_url(hass, aiohttp_client):
"""Test that posting to api endpoint works."""
await async_setup_component(hass, 'camera', {
'camera': {
@@ -38,7 +38,7 @@ async def test_posting_url(aioclient_mock, hass, aiohttp_client):
'name': 'config_test',
}})
- client = await async_setup_auth(hass, aiohttp_client)
+ client = await aiohttp_client(hass.http.app)
files = {'image': io.BytesIO(b'fake')}
# initial state
diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py
index 18292d32a02..328ba5096ea 100644
--- a/tests/components/camera/test_uvc.py
+++ b/tests/components/camera/test_uvc.py
@@ -17,7 +17,7 @@ class TestUVCSetup(unittest.TestCase):
"""Test the UVC camera platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -134,7 +134,7 @@ class TestUVCSetup(unittest.TestCase):
@mock.patch.object(uvc, 'UnifiVideoCamera')
@mock.patch('uvcclient.nvr.UVCRemote')
def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc):
- """Setup test for NVR errors during indexing."""
+ """Set up test for NVR errors during indexing."""
config = {
'platform': 'uvc',
'nvr': 'foo',
@@ -163,7 +163,7 @@ class TestUVCSetup(unittest.TestCase):
@mock.patch('uvcclient.nvr.UVCRemote.__init__')
def setup_nvr_errors_during_initialization(self, error, mock_remote,
mock_uvc):
- """Setup test for NVR errors during initialization."""
+ """Set up test for NVR errors during initialization."""
config = {
'platform': 'uvc',
'nvr': 'foo',
@@ -195,7 +195,7 @@ class TestUVC(unittest.TestCase):
"""Test class for UVC."""
def setup_method(self, method):
- """Setup the mock camera."""
+ """Set up the mock camera."""
self.nvr = mock.MagicMock()
self.uuid = 'uuid'
self.name = 'name'
diff --git a/tests/components/climate/test_demo.py b/tests/components/climate/test_demo.py
index b2633a75583..0cd6d288536 100644
--- a/tests/components/climate/test_demo.py
+++ b/tests/components/climate/test_demo.py
@@ -19,7 +19,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test the demo climate hvac."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
self.assertTrue(setup_component(self.hass, climate.DOMAIN, {
diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py
index 7bc0b0a18e7..ac587db13fa 100644
--- a/tests/components/climate/test_generic_thermostat.py
+++ b/tests/components/climate/test_generic_thermostat.py
@@ -9,7 +9,6 @@ import homeassistant.core as ha
from homeassistant.core import callback, CoreState, State
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.const import (
- ATTR_UNIT_OF_MEASUREMENT,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_ON,
@@ -42,7 +41,7 @@ class TestSetupClimateGenericThermostat(unittest.TestCase):
"""Test the Generic thermostat with custom config."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
@@ -79,7 +78,7 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase):
"""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
self.assertTrue(run_coroutine_threadsafe(
@@ -141,18 +140,16 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase):
self.assertEqual(STATE_ON,
self.hass.states.get(heater_switch).state)
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
class TestClimateGenericThermostat(unittest.TestCase):
"""Test the Generic thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -222,30 +219,15 @@ class TestClimateGenericThermostat(unittest.TestCase):
state = self.hass.states.get(ENTITY)
self.assertEqual(23, state.attributes.get('temperature'))
- def test_sensor_bad_unit(self):
- """Test sensor that have bad unit."""
- state = self.hass.states.get(ENTITY)
- temp = state.attributes.get('current_temperature')
- unit = state.attributes.get('unit_of_measurement')
-
- self._setup_sensor(22.0, unit='bad_unit')
- self.hass.block_till_done()
-
- state = self.hass.states.get(ENTITY)
- self.assertEqual(unit, state.attributes.get('unit_of_measurement'))
- self.assertEqual(temp, state.attributes.get('current_temperature'))
-
def test_sensor_bad_value(self):
"""Test sensor that have None as state."""
state = self.hass.states.get(ENTITY)
temp = state.attributes.get('current_temperature')
- unit = state.attributes.get('unit_of_measurement')
self._setup_sensor(None)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
- self.assertEqual(unit, state.attributes.get('unit_of_measurement'))
self.assertEqual(temp, state.attributes.get('current_temperature'))
def test_set_target_temp_heater_on(self):
@@ -367,14 +349,12 @@ class TestClimateGenericThermostat(unittest.TestCase):
self.assertEqual(SERVICE_TURN_ON, call.service)
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -391,7 +371,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
"""Test the Generic thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -532,14 +512,12 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(0, len(self.calls))
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -556,7 +534,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
"""Test the Generic Thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -626,14 +604,12 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
self.assertEqual(SERVICE_TURN_OFF, call.service)
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -650,7 +626,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
"""Test the Generic thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -719,14 +695,12 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
self.assertEqual(SERVICE_TURN_OFF, call.service)
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -743,7 +717,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
"""Test the Generic Thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -812,14 +786,12 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
"""Send a time changed event."""
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -836,7 +808,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
"""Test the Generic Thermostat."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
assert setup_component(self.hass, climate.DOMAIN, {'climate': {
@@ -904,14 +876,12 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
"""Send a time changed event."""
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
@@ -999,7 +969,7 @@ class TestClimateGenericThermostatRestoreState(unittest.TestCase):
"""Test generic thermostat when restore state from HA startup."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.temperature_unit = TEMP_CELSIUS
@@ -1047,14 +1017,12 @@ class TestClimateGenericThermostatRestoreState(unittest.TestCase):
'ac_mode': True
}})
- def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
- """Setup the test sensor."""
- self.hass.states.set(ENT_SENSOR, temp, {
- ATTR_UNIT_OF_MEASUREMENT: unit
- })
+ def _setup_sensor(self, temp):
+ """Set up the test sensor."""
+ self.hass.states.set(ENT_SENSOR, temp)
def _setup_switch(self, is_on):
- """Setup the test switch."""
+ """Set up the test switch."""
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
self.calls = []
diff --git a/tests/components/climate/test_mqtt.py b/tests/components/climate/test_mqtt.py
index 5db77331cd4..f46a23e4f97 100644
--- a/tests/components/climate/test_mqtt.py
+++ b/tests/components/climate/test_mqtt.py
@@ -35,7 +35,7 @@ class TestMQTTClimate(unittest.TestCase):
"""Test the mqtt climate hvac."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
self.hass.config.units = METRIC_SYSTEM
diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py
index f4c6ee9c7da..af36c1c8f95 100644
--- a/tests/components/counter/test_init.py
+++ b/tests/components/counter/test_init.py
@@ -4,7 +4,7 @@ import asyncio
import unittest
import logging
-from homeassistant.core import CoreState, State
+from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.counter import (
DOMAIN, decrement, increment, reset, CONF_INITIAL, CONF_STEP, CONF_NAME,
@@ -202,3 +202,24 @@ def test_no_initial_state_and_no_restore_state(hass):
state = hass.states.get('counter.test1')
assert state
assert int(state.state) == 0
+
+
+async def test_counter_context(hass):
+ """Test that counter context works."""
+ assert await async_setup_component(hass, 'counter', {
+ 'counter': {
+ 'test': {}
+ }
+ })
+
+ state = hass.states.get('counter.test')
+ assert state is not None
+
+ await hass.services.async_call('counter', 'increment', {
+ 'entity_id': state.entity_id,
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('counter.test')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py
index b7049d35021..346c3f94683 100644
--- a/tests/components/cover/test_command_line.py
+++ b/tests/components/cover/test_command_line.py
@@ -17,7 +17,7 @@ class TestCommandCover(unittest.TestCase):
"""Test the cover command line platform."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.rs = cmd_rs.CommandCover(self.hass, 'foo',
'command_open', 'command_close',
diff --git a/tests/components/cover/test_demo.py b/tests/components/cover/test_demo.py
index 9d26a6a4f4a..65aa9a9b9ef 100644
--- a/tests/components/cover/test_demo.py
+++ b/tests/components/cover/test_demo.py
@@ -14,7 +14,7 @@ class TestCoverDemo(unittest.TestCase):
"""Test the Demo cover."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {'cover': {
'platform': 'demo',
diff --git a/tests/components/cover/test_group.py b/tests/components/cover/test_group.py
index 288e1c5e047..028845983a0 100644
--- a/tests/components/cover/test_group.py
+++ b/tests/components/cover/test_group.py
@@ -35,7 +35,7 @@ class TestMultiCover(unittest.TestCase):
"""Test the group cover platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py
index aea6398e3ae..ad68c2416ca 100644
--- a/tests/components/cover/test_mqtt.py
+++ b/tests/components/cover/test_mqtt.py
@@ -15,7 +15,7 @@ class TestCoverMQTT(unittest.TestCase):
"""Test the MQTT cover."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/cover/test_rfxtrx.py b/tests/components/cover/test_rfxtrx.py
index be2c456296b..ab8b8f9a93c 100644
--- a/tests/components/cover/test_rfxtrx.py
+++ b/tests/components/cover/test_rfxtrx.py
@@ -14,7 +14,7 @@ class TestCoverRfxtrx(unittest.TestCase):
"""Test the Rfxtrx cover platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component('rfxtrx')
diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py
index 956b407eeaa..8c5af618288 100644
--- a/tests/components/device_tracker/test_asuswrt.py
+++ b/tests/components/device_tracker/test_asuswrt.py
@@ -116,7 +116,7 @@ WAKE_DEVICES_NO_IP = {
def setup_module():
- """Setup the test module."""
+ """Set up the test module."""
global FAKEFILE
FAKEFILE = get_test_config_dir('fake_file')
with open(FAKEFILE, 'w') as out:
@@ -138,7 +138,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
hass = None
def setup_method(self, _):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'zone')
@@ -500,7 +500,7 @@ class TestSshConnection(unittest.TestCase):
"""Testing SshConnection."""
def setUp(self):
- """Setup test env."""
+ """Set up test env."""
self.connection = SshConnection(
'fake', 'fake', 'fake', 'fake', 'fake')
self.connection._connected = True
@@ -544,7 +544,7 @@ class TestTelnetConnection(unittest.TestCase):
"""Testing TelnetConnection."""
def setUp(self):
- """Setup test env."""
+ """Set up test env."""
self.connection = TelnetConnection(
'fake', 'fake', 'fake', 'fake')
self.connection._connected = True
diff --git a/tests/components/device_tracker/test_ddwrt.py b/tests/components/device_tracker/test_ddwrt.py
index 416b7be4a8a..3e60e1bae46 100644
--- a/tests/components/device_tracker/test_ddwrt.py
+++ b/tests/components/device_tracker/test_ddwrt.py
@@ -41,7 +41,7 @@ class TestDdwrt(unittest.TestCase):
super().run(result)
def setup_method(self, _):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'zone')
@@ -220,7 +220,7 @@ class TestDdwrt(unittest.TestCase):
with requests_mock.Mocker() as mock_request:
mock_request.register_uri(
'GET', r'http://%s/Status_Wireless.live.asp' % TEST_HOST,
- # First request has to work to setup connection
+ # First request has to work to set up connection
[{'text': load_fixture('Ddwrt_Status_Wireless.txt')},
# Second request to get active devices fails
{'text': None}])
diff --git a/tests/components/device_tracker/test_geofency.py b/tests/components/device_tracker/test_geofency.py
index a955dd0cc11..d84940d9fbf 100644
--- a/tests/components/device_tracker/test_geofency.py
+++ b/tests/components/device_tracker/test_geofency.py
@@ -122,7 +122,7 @@ def geofency_client(loop, hass, aiohttp_client):
@pytest.fixture(autouse=True)
def setup_zones(loop, hass):
- """Setup Zone config in HA."""
+ """Set up Zone config in HA."""
assert loop.run_until_complete(async_setup_component(
hass, zone.DOMAIN, {
'zone': {
diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py
index 0b17b4e0ac8..b1b68ff92df 100644
--- a/tests/components/device_tracker/test_init.py
+++ b/tests/components/device_tracker/test_init.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, CONF_PLATFORM, ATTR_ICON)
import homeassistant.components.device_tracker as device_tracker
from homeassistant.exceptions import HomeAssistantError
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
from tests.common import (
get_test_home_assistant, fire_time_changed,
@@ -39,7 +39,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.yaml_devices = self.hass.config.path(device_tracker.YAML_DEVICES)
@@ -323,7 +323,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
@callback
def listener(event):
- """Helper method that will verify our event got called."""
+ """Record that our event got called."""
test_events.append(event)
self.hass.bus.listen("device_tracker_new_device", listener)
diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py
index 90adccf7703..7cfef8f5219 100644
--- a/tests/components/device_tracker/test_locative.py
+++ b/tests/components/device_tracker/test_locative.py
@@ -11,7 +11,7 @@ from homeassistant.components.device_tracker.locative import URL
def _url(data=None):
- """Helper method to generate URLs."""
+ """Generate URL."""
data = data or {}
data = "&".join(["{}={}".format(name, value) for
name, value in data.items()])
diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py
index de7865517a8..8e4d0dc2769 100644
--- a/tests/components/device_tracker/test_mqtt.py
+++ b/tests/components/device_tracker/test_mqtt.py
@@ -19,7 +19,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase):
"""Test MQTT device tracker platform."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
diff --git a/tests/components/device_tracker/test_mqtt_json.py b/tests/components/device_tracker/test_mqtt_json.py
index 8ab6346f19b..41c1d9c0885 100644
--- a/tests/components/device_tracker/test_mqtt_json.py
+++ b/tests/components/device_tracker/test_mqtt_json.py
@@ -29,7 +29,7 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase):
"""Test JSON MQTT device tracker platform."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py
index 37a3e570b53..8883ea22600 100644
--- a/tests/components/device_tracker/test_owntracks.py
+++ b/tests/components/device_tracker/test_owntracks.py
@@ -327,7 +327,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT):
# pylint: disable=invalid-name
def setup_method(self, _):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
mock_component(self.hass, 'group')
@@ -1316,7 +1316,7 @@ class TestDeviceTrackerOwnTrackConfigs(BaseMQTT):
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
mock_component(self.hass, 'group')
diff --git a/tests/components/device_tracker/test_tplink.py b/tests/components/device_tracker/test_tplink.py
index 88e38108133..b9f1f5f5e5a 100644
--- a/tests/components/device_tracker/test_tplink.py
+++ b/tests/components/device_tracker/test_tplink.py
@@ -16,7 +16,7 @@ class TestTplink4DeviceScanner(unittest.TestCase):
"""Tests for the Tplink4DeviceScanner class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/device_tracker/test_unifi_direct.py b/tests/components/device_tracker/test_unifi_direct.py
index d1ede721142..1f9cbd24f12 100644
--- a/tests/components/device_tracker/test_unifi_direct.py
+++ b/tests/components/device_tracker/test_unifi_direct.py
@@ -31,7 +31,7 @@ class TestComponentsDeviceTrackerUnifiDirect(unittest.TestCase):
'unifi_direct.UnifiDeviceScanner'
def setup_method(self, _):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'zone')
diff --git a/tests/components/device_tracker/test_upc_connect.py b/tests/components/device_tracker/test_upc_connect.py
index 6294ba3467a..6b38edc3ce9 100644
--- a/tests/components/device_tracker/test_upc_connect.py
+++ b/tests/components/device_tracker/test_upc_connect.py
@@ -37,7 +37,7 @@ class TestUPCConnect:
"""Tests for the Ddwrt device tracker platform."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'zone')
mock_component(self.hass, 'group')
@@ -52,7 +52,7 @@ class TestUPCConnect:
'UPCDeviceScanner.async_scan_devices',
return_value=async_scan_devices_mock)
def test_setup_platform(self, scan_mock, aioclient_mock):
- """Setup a platform."""
+ """Set up a platform."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'}
@@ -74,7 +74,7 @@ class TestUPCConnect:
@patch('homeassistant.components.device_tracker._LOGGER.error')
def test_setup_platform_timeout_webservice(self, mock_error,
aioclient_mock):
- """Setup a platform with api timeout."""
+ """Set up a platform with api timeout."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'},
@@ -97,7 +97,7 @@ class TestUPCConnect:
@patch('homeassistant.components.device_tracker._LOGGER.error')
def test_setup_platform_timeout_loginpage(self, mock_error,
aioclient_mock):
- """Setup a platform with timeout on loginpage."""
+ """Set up a platform with timeout on loginpage."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
exc=asyncio.TimeoutError()
@@ -120,7 +120,7 @@ class TestUPCConnect:
str(mock_error.call_args_list[-1])
def test_scan_devices(self, aioclient_mock):
- """Setup a upc platform and scan device."""
+ """Set up a upc platform and scan device."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'}
@@ -156,7 +156,7 @@ class TestUPCConnect:
'70:EE:50:27:A1:38']
def test_scan_devices_without_session(self, aioclient_mock):
- """Setup a upc platform and scan device with no token."""
+ """Set up a upc platform and scan device with no token."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'}
@@ -197,7 +197,7 @@ class TestUPCConnect:
'70:EE:50:27:A1:38']
def test_scan_devices_without_session_wrong_re(self, aioclient_mock):
- """Setup a upc platform and scan device with no token and wrong."""
+ """Set up a upc platform and scan device with no token and wrong."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'}
@@ -237,7 +237,7 @@ class TestUPCConnect:
assert mac_list == []
def test_scan_devices_parse_error(self, aioclient_mock):
- """Setup a upc platform and scan device with parse error."""
+ """Set up a upc platform and scan device with parse error."""
aioclient_mock.get(
"http://{}/common_page/login.html".format(self.host),
cookies={'sessionToken': '654321'}
diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py
index c99d273a458..3920a45ddf6 100644
--- a/tests/components/emulated_hue/test_hue_api.py
+++ b/tests/components/emulated_hue/test_hue_api.py
@@ -26,7 +26,7 @@ JSON_HEADERS = {CONTENT_TYPE: const.CONTENT_TYPE_JSON}
@pytest.fixture
def hass_hue(loop, hass):
- """Setup a Home Assistant instance for these tests."""
+ """Set up a Home Assistant instance for these tests."""
# We need to do this to get access to homeassistant/turn_(on,off)
loop.run_until_complete(
core_components.async_setup(hass, {core.DOMAIN: {}}))
diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py
index 8315de34e06..f5377b1812c 100644
--- a/tests/components/emulated_hue/test_upnp.py
+++ b/tests/components/emulated_hue/test_upnp.py
@@ -50,7 +50,7 @@ class TestEmulatedHue(unittest.TestCase):
@classmethod
def setUpClass(cls):
- """Setup the class."""
+ """Set up the class."""
cls.hass = hass = get_test_home_assistant()
# We need to do this to get access to homeassistant/turn_(on,off)
diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py
index 0d066af8cf6..69680fb1cfd 100644
--- a/tests/components/fan/test_demo.py
+++ b/tests/components/fan/test_demo.py
@@ -15,7 +15,7 @@ class TestDemoFan(unittest.TestCase):
"""Test the fan demo platform."""
def get_entity(self):
- """Helper method to get the fan entity."""
+ """Get the fan entity."""
return self.hass.states.get(FAN_ENTITY_ID)
def setUp(self):
diff --git a/tests/components/fan/test_dyson.py b/tests/components/fan/test_dyson.py
index 2953ea2754b..a935210784b 100644
--- a/tests/components/fan/test_dyson.py
+++ b/tests/components/fan/test_dyson.py
@@ -68,7 +68,7 @@ class DysonTest(unittest.TestCase):
"""Dyson Sensor component test class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/fan/test_mqtt.py b/tests/components/fan/test_mqtt.py
index 9060d7b9986..7f69e56218b 100644
--- a/tests/components/fan/test_mqtt.py
+++ b/tests/components/fan/test_mqtt.py
@@ -13,7 +13,7 @@ class TestMqttFan(unittest.TestCase):
"""Test the MQTT fan platform."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/fan/test_template.py b/tests/components/fan/test_template.py
index 53eb9e8e2d4..e229083069d 100644
--- a/tests/components/fan/test_template.py
+++ b/tests/components/fan/test_template.py
@@ -33,7 +33,7 @@ class TestTemplateFan:
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup."""
+ """Set up."""
self.hass = get_test_home_assistant()
self.calls = []
diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py
index d45680d132e..d9682940bdc 100644
--- a/tests/components/google_assistant/test_google_assistant.py
+++ b/tests/components/google_assistant/test_google_assistant.py
@@ -232,10 +232,10 @@ def test_query_climate_request(hass_fixture, assistant_client):
def test_query_climate_request_f(hass_fixture, assistant_client):
"""Test a query request."""
# Mock demo devices as fahrenheit to see if we convert to celsius
+ hass_fixture.config.units.temperature_unit = const.TEMP_FAHRENHEIT
for entity_id in ('climate.hvac', 'climate.heatpump', 'climate.ecobee'):
state = hass_fixture.states.get(entity_id)
attr = dict(state.attributes)
- attr[const.ATTR_UNIT_OF_MEASUREMENT] = const.TEMP_FAHRENHEIT
hass_fixture.states.async_set(entity_id, state.state, attr)
reqid = '5711642932632160984'
@@ -282,6 +282,7 @@ def test_query_climate_request_f(hass_fixture, assistant_client):
'thermostatMode': 'cool',
'thermostatHumidityAmbient': 54,
}
+ hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS
@asyncio.coroutine
diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py
index 1f7ee011e61..c18ed4b7bf3 100644
--- a/tests/components/google_assistant/test_trait.py
+++ b/tests/components/google_assistant/test_trait.py
@@ -3,7 +3,7 @@ import pytest
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
- ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
+ TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.core import State, DOMAIN as HA_DOMAIN
from homeassistant.components import (
climate,
@@ -27,7 +27,7 @@ async def test_brightness_light(hass):
assert trait.BrightnessTrait.supported(light.DOMAIN,
light.SUPPORT_BRIGHTNESS)
- trt = trait.BrightnessTrait(State('light.bla', light.STATE_ON, {
+ trt = trait.BrightnessTrait(hass, State('light.bla', light.STATE_ON, {
light.ATTR_BRIGHTNESS: 243
}))
@@ -38,7 +38,7 @@ async def test_brightness_light(hass):
}
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
- await trt.execute(hass, trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
'brightness': 50
})
assert len(calls) == 1
@@ -53,7 +53,7 @@ async def test_brightness_cover(hass):
assert trait.BrightnessTrait.supported(cover.DOMAIN,
cover.SUPPORT_SET_POSITION)
- trt = trait.BrightnessTrait(State('cover.bla', cover.STATE_OPEN, {
+ trt = trait.BrightnessTrait(hass, State('cover.bla', cover.STATE_OPEN, {
cover.ATTR_CURRENT_POSITION: 75
}))
@@ -65,7 +65,7 @@ async def test_brightness_cover(hass):
calls = async_mock_service(
hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
- await trt.execute(hass, trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
'brightness': 50
})
assert len(calls) == 1
@@ -80,7 +80,7 @@ async def test_brightness_media_player(hass):
assert trait.BrightnessTrait.supported(media_player.DOMAIN,
media_player.SUPPORT_VOLUME_SET)
- trt = trait.BrightnessTrait(State(
+ trt = trait.BrightnessTrait(hass, State(
'media_player.bla', media_player.STATE_PLAYING, {
media_player.ATTR_MEDIA_VOLUME_LEVEL: .3
}))
@@ -93,7 +93,7 @@ async def test_brightness_media_player(hass):
calls = async_mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET)
- await trt.execute(hass, trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_BRIGHTNESS_ABSOLUTE, {
'brightness': 60
})
assert len(calls) == 1
@@ -107,7 +107,7 @@ async def test_onoff_group(hass):
"""Test OnOff trait support for group domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('group.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('group.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -115,13 +115,13 @@ async def test_onoff_group(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('group.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('group.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, HA_DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -130,7 +130,7 @@ async def test_onoff_group(hass):
}
off_calls = async_mock_service(hass, HA_DOMAIN, SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -143,7 +143,7 @@ async def test_onoff_input_boolean(hass):
"""Test OnOff trait support for input_boolean domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('input_boolean.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('input_boolean.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -151,13 +151,13 @@ async def test_onoff_input_boolean(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('input_boolean.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('input_boolean.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, input_boolean.DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -167,7 +167,7 @@ async def test_onoff_input_boolean(hass):
off_calls = async_mock_service(hass, input_boolean.DOMAIN,
SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -180,7 +180,7 @@ async def test_onoff_switch(hass):
"""Test OnOff trait support for switch domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('switch.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('switch.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -188,13 +188,13 @@ async def test_onoff_switch(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('switch.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('switch.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, switch.DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -203,7 +203,7 @@ async def test_onoff_switch(hass):
}
off_calls = async_mock_service(hass, switch.DOMAIN, SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -216,7 +216,7 @@ async def test_onoff_fan(hass):
"""Test OnOff trait support for fan domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('fan.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('fan.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -224,13 +224,13 @@ async def test_onoff_fan(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('fan.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('fan.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, fan.DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -239,7 +239,7 @@ async def test_onoff_fan(hass):
}
off_calls = async_mock_service(hass, fan.DOMAIN, SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -252,7 +252,7 @@ async def test_onoff_light(hass):
"""Test OnOff trait support for light domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('light.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('light.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -260,13 +260,13 @@ async def test_onoff_light(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('light.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('light.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -275,7 +275,7 @@ async def test_onoff_light(hass):
}
off_calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -288,7 +288,7 @@ async def test_onoff_cover(hass):
"""Test OnOff trait support for cover domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('cover.bla', cover.STATE_OPEN))
+ trt_on = trait.OnOffTrait(hass, State('cover.bla', cover.STATE_OPEN))
assert trt_on.sync_attributes() == {}
@@ -296,13 +296,13 @@ async def test_onoff_cover(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('cover.bla', cover.STATE_CLOSED))
+ trt_off = trait.OnOffTrait(hass, State('cover.bla', cover.STATE_CLOSED))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -312,7 +312,7 @@ async def test_onoff_cover(hass):
off_calls = async_mock_service(hass, cover.DOMAIN,
cover.SERVICE_CLOSE_COVER)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -325,7 +325,7 @@ async def test_onoff_media_player(hass):
"""Test OnOff trait support for media_player domain."""
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
- trt_on = trait.OnOffTrait(State('media_player.bla', STATE_ON))
+ trt_on = trait.OnOffTrait(hass, State('media_player.bla', STATE_ON))
assert trt_on.sync_attributes() == {}
@@ -333,13 +333,13 @@ async def test_onoff_media_player(hass):
'on': True
}
- trt_off = trait.OnOffTrait(State('media_player.bla', STATE_OFF))
+ trt_off = trait.OnOffTrait(hass, State('media_player.bla', STATE_OFF))
assert trt_off.query_attributes() == {
'on': False
}
on_calls = async_mock_service(hass, media_player.DOMAIN, SERVICE_TURN_ON)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': True
})
assert len(on_calls) == 1
@@ -348,7 +348,7 @@ async def test_onoff_media_player(hass):
}
off_calls = async_mock_service(hass, media_player.DOMAIN, SERVICE_TURN_OFF)
- await trt_on.execute(hass, trait.COMMAND_ONOFF, {
+ await trt_on.execute(trait.COMMAND_ONOFF, {
'on': False
})
assert len(off_calls) == 1
@@ -363,7 +363,7 @@ async def test_color_spectrum_light(hass):
assert trait.ColorSpectrumTrait.supported(light.DOMAIN,
light.SUPPORT_COLOR)
- trt = trait.ColorSpectrumTrait(State('light.bla', STATE_ON, {
+ trt = trait.ColorSpectrumTrait(hass, State('light.bla', STATE_ON, {
light.ATTR_HS_COLOR: (0, 94),
}))
@@ -389,7 +389,7 @@ async def test_color_spectrum_light(hass):
})
calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON)
- await trt.execute(hass, trait.COMMAND_COLOR_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_COLOR_ABSOLUTE, {
'color': {
'spectrumRGB': 1052927
}
@@ -407,7 +407,7 @@ async def test_color_temperature_light(hass):
assert trait.ColorTemperatureTrait.supported(light.DOMAIN,
light.SUPPORT_COLOR_TEMP)
- trt = trait.ColorTemperatureTrait(State('light.bla', STATE_ON, {
+ trt = trait.ColorTemperatureTrait(hass, State('light.bla', STATE_ON, {
light.ATTR_MIN_MIREDS: 200,
light.ATTR_COLOR_TEMP: 300,
light.ATTR_MAX_MIREDS: 500,
@@ -438,14 +438,14 @@ async def test_color_temperature_light(hass):
calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON)
with pytest.raises(helpers.SmartHomeError) as err:
- await trt.execute(hass, trait.COMMAND_COLOR_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_COLOR_ABSOLUTE, {
'color': {
'temperature': 5555
}
})
assert err.value.code == const.ERR_VALUE_OUT_OF_RANGE
- await trt.execute(hass, trait.COMMAND_COLOR_ABSOLUTE, {
+ await trt.execute(trait.COMMAND_COLOR_ABSOLUTE, {
'color': {
'temperature': 2857
}
@@ -461,13 +461,13 @@ async def test_scene_scene(hass):
"""Test Scene trait support for scene domain."""
assert trait.SceneTrait.supported(scene.DOMAIN, 0)
- trt = trait.SceneTrait(State('scene.bla', scene.STATE))
+ trt = trait.SceneTrait(hass, State('scene.bla', scene.STATE))
assert trt.sync_attributes() == {}
assert trt.query_attributes() == {}
assert trt.can_execute(trait.COMMAND_ACTIVATE_SCENE, {})
calls = async_mock_service(hass, scene.DOMAIN, SERVICE_TURN_ON)
- await trt.execute(hass, trait.COMMAND_ACTIVATE_SCENE, {})
+ await trt.execute(trait.COMMAND_ACTIVATE_SCENE, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'scene.bla',
@@ -478,13 +478,13 @@ async def test_scene_script(hass):
"""Test Scene trait support for script domain."""
assert trait.SceneTrait.supported(script.DOMAIN, 0)
- trt = trait.SceneTrait(State('script.bla', STATE_OFF))
+ trt = trait.SceneTrait(hass, State('script.bla', STATE_OFF))
assert trt.sync_attributes() == {}
assert trt.query_attributes() == {}
assert trt.can_execute(trait.COMMAND_ACTIVATE_SCENE, {})
calls = async_mock_service(hass, script.DOMAIN, SERVICE_TURN_ON)
- await trt.execute(hass, trait.COMMAND_ACTIVATE_SCENE, {})
+ await trt.execute(trait.COMMAND_ACTIVATE_SCENE, {})
# We don't wait till script execution is done.
await hass.async_block_till_done()
@@ -501,7 +501,9 @@ async def test_temperature_setting_climate_range(hass):
assert trait.TemperatureSettingTrait.supported(
climate.DOMAIN, climate.SUPPORT_OPERATION_MODE)
- trt = trait.TemperatureSettingTrait(State(
+ hass.config.units.temperature_unit = TEMP_FAHRENHEIT
+
+ trt = trait.TemperatureSettingTrait(hass, State(
'climate.bla', climate.STATE_AUTO, {
climate.ATTR_CURRENT_TEMPERATURE: 70,
climate.ATTR_CURRENT_HUMIDITY: 25,
@@ -515,8 +517,7 @@ async def test_temperature_setting_climate_range(hass):
climate.ATTR_TARGET_TEMP_HIGH: 75,
climate.ATTR_TARGET_TEMP_LOW: 65,
climate.ATTR_MIN_TEMP: 50,
- climate.ATTR_MAX_TEMP: 80,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT,
+ climate.ATTR_MAX_TEMP: 80
}))
assert trt.sync_attributes() == {
'availableThermostatModes': 'off,cool,heat,heatcool',
@@ -535,7 +536,7 @@ async def test_temperature_setting_climate_range(hass):
calls = async_mock_service(
hass, climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE)
- await trt.execute(hass, trait.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, {
+ await trt.execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, {
'thermostatTemperatureSetpointHigh': 25,
'thermostatTemperatureSetpointLow': 20,
})
@@ -548,7 +549,7 @@ async def test_temperature_setting_climate_range(hass):
calls = async_mock_service(
hass, climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE)
- await trt.execute(hass, trait.COMMAND_THERMOSTAT_SET_MODE, {
+ await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, {
'thermostatMode': 'heatcool',
})
assert len(calls) == 1
@@ -558,11 +559,11 @@ async def test_temperature_setting_climate_range(hass):
}
with pytest.raises(helpers.SmartHomeError) as err:
- await trt.execute(
- hass, trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
- 'thermostatTemperatureSetpoint': -100,
- })
+ await trt.execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
+ 'thermostatTemperatureSetpoint': -100,
+ })
assert err.value.code == const.ERR_VALUE_OUT_OF_RANGE
+ hass.config.units.temperature_unit = TEMP_CELSIUS
async def test_temperature_setting_climate_setpoint(hass):
@@ -571,7 +572,9 @@ async def test_temperature_setting_climate_setpoint(hass):
assert trait.TemperatureSettingTrait.supported(
climate.DOMAIN, climate.SUPPORT_OPERATION_MODE)
- trt = trait.TemperatureSettingTrait(State(
+ hass.config.units.temperature_unit = TEMP_CELSIUS
+
+ trt = trait.TemperatureSettingTrait(hass, State(
'climate.bla', climate.STATE_AUTO, {
climate.ATTR_OPERATION_MODE: climate.STATE_COOL,
climate.ATTR_OPERATION_LIST: [
@@ -581,8 +584,7 @@ async def test_temperature_setting_climate_setpoint(hass):
climate.ATTR_MIN_TEMP: 10,
climate.ATTR_MAX_TEMP: 30,
climate.ATTR_TEMPERATURE: 18,
- climate.ATTR_CURRENT_TEMPERATURE: 20,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
+ climate.ATTR_CURRENT_TEMPERATURE: 20
}))
assert trt.sync_attributes() == {
'availableThermostatModes': 'off,cool',
@@ -601,12 +603,11 @@ async def test_temperature_setting_climate_setpoint(hass):
hass, climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE)
with pytest.raises(helpers.SmartHomeError):
- await trt.execute(
- hass, trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
- 'thermostatTemperatureSetpoint': -100,
- })
+ await trt.execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
+ 'thermostatTemperatureSetpoint': -100,
+ })
- await trt.execute(hass, trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
+ await trt.execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {
'thermostatTemperatureSetpoint': 19,
})
assert len(calls) == 1
diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py
index a5e9bbc0b82..47101dd415a 100644
--- a/tests/components/group/test_init.py
+++ b/tests/components/group/test_init.py
@@ -19,7 +19,7 @@ class TestComponentsGroup(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -28,7 +28,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass.stop()
def test_setup_group_with_mixed_groupable_states(self):
- """Try to setup a group with mixed groupable states."""
+ """Try to set up a group with mixed groupable states."""
self.hass.states.set('light.Bowl', STATE_ON)
self.hass.states.set('device_tracker.Paulus', STATE_HOME)
group.Group.create_group(
@@ -41,7 +41,7 @@ class TestComponentsGroup(unittest.TestCase):
group.ENTITY_ID_FORMAT.format('person_and_light')).state)
def test_setup_group_with_a_non_existing_state(self):
- """Try to setup a group with a non existing state."""
+ """Try to set up a group with a non existing state."""
self.hass.states.set('light.Bowl', STATE_ON)
grp = group.Group.create_group(
@@ -62,7 +62,7 @@ class TestComponentsGroup(unittest.TestCase):
self.assertEqual(STATE_UNKNOWN, grp.state)
def test_setup_empty_group(self):
- """Try to setup an empty group."""
+ """Try to set up an empty group."""
grp = group.Group.create_group(self.hass, 'nothing', [])
self.assertEqual(STATE_UNKNOWN, grp.state)
diff --git a/tests/components/hangouts/__init__.py b/tests/components/hangouts/__init__.py
new file mode 100644
index 00000000000..81174356c2e
--- /dev/null
+++ b/tests/components/hangouts/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Hangouts Component."""
diff --git a/tests/components/hangouts/test_config_flow.py b/tests/components/hangouts/test_config_flow.py
new file mode 100644
index 00000000000..af9bb018919
--- /dev/null
+++ b/tests/components/hangouts/test_config_flow.py
@@ -0,0 +1,92 @@
+"""Tests for the Google Hangouts config flow."""
+
+from unittest.mock import patch
+
+from homeassistant import data_entry_flow
+from homeassistant.components.hangouts import config_flow
+
+
+async def test_flow_works(hass, aioclient_mock):
+ """Test config flow without 2fa."""
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth'):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'test@test.com'
+
+
+async def test_flow_works_with_2fa(hass, aioclient_mock):
+ """Test config flow with 2fa."""
+ from homeassistant.components.hangouts.hangups_utils import Google2FAError
+
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth', side_effect=Google2FAError):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == '2fa'
+
+ with patch('hangups.get_auth'):
+ result = await flow.async_step_2fa({'2fa': 123456})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'test@test.com'
+
+
+async def test_flow_with_unknown_2fa(hass, aioclient_mock):
+ """Test config flow with invalid 2fa method."""
+ from homeassistant.components.hangouts.hangups_utils import GoogleAuthError
+
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth',
+ side_effect=GoogleAuthError('Unknown verification code input')):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_2fa_method'
+
+
+async def test_flow_invalid_login(hass, aioclient_mock):
+ """Test config flow with invalid 2fa method."""
+ from homeassistant.components.hangouts.hangups_utils import GoogleAuthError
+
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth',
+ side_effect=GoogleAuthError):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_login'
+
+
+async def test_flow_invalid_2fa(hass, aioclient_mock):
+ """Test config flow with 2fa."""
+ from homeassistant.components.hangouts.hangups_utils import Google2FAError
+
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth', side_effect=Google2FAError):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == '2fa'
+
+ with patch('hangups.get_auth', side_effect=Google2FAError):
+ result = await flow.async_step_2fa({'2fa': 123456})
+
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['errors']['base'] == 'invalid_2fa'
diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py
index 45c340e58c4..687a9e9513c 100644
--- a/tests/components/homekit/test_type_thermostats.py
+++ b/tests/components/homekit/test_type_thermostats.py
@@ -12,8 +12,8 @@ from homeassistant.components.climate import (
from homeassistant.components.homekit.const import (
PROP_MAX_VALUE, PROP_MIN_VALUE)
from homeassistant.const import (
- ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
- CONF_TEMPERATURE_UNIT, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
+ ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_TEMPERATURE_UNIT, STATE_OFF,
+ TEMP_FAHRENHEIT)
from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@@ -58,8 +58,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
hass.states.async_set(entity_id, STATE_HEAT,
{ATTR_OPERATION_MODE: STATE_HEAT,
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 18.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 18.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 1
@@ -70,8 +69,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
hass.states.async_set(entity_id, STATE_HEAT,
{ATTR_OPERATION_MODE: STATE_HEAT,
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 23.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 23.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 0
@@ -82,8 +80,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
hass.states.async_set(entity_id, STATE_COOL,
{ATTR_OPERATION_MODE: STATE_COOL,
ATTR_TEMPERATURE: 20.0,
- ATTR_CURRENT_TEMPERATURE: 25.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 25.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 20.0
assert acc.char_current_heat_cool.value == 2
@@ -94,8 +91,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
hass.states.async_set(entity_id, STATE_COOL,
{ATTR_OPERATION_MODE: STATE_COOL,
ATTR_TEMPERATURE: 20.0,
- ATTR_CURRENT_TEMPERATURE: 19.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 19.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 20.0
assert acc.char_current_heat_cool.value == 0
@@ -106,8 +102,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
hass.states.async_set(entity_id, STATE_OFF,
{ATTR_OPERATION_MODE: STATE_OFF,
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 18.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 18.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 0
@@ -119,8 +114,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 18.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 18.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 1
@@ -132,8 +126,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 25.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 25.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 2
@@ -145,8 +138,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
- ATTR_CURRENT_TEMPERATURE: 22.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 22.0})
await hass.async_block_till_done()
assert acc.char_target_temp.value == 22.0
assert acc.char_current_heat_cool.value == 0
@@ -201,8 +193,7 @@ async def test_auto_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_TARGET_TEMP_HIGH: 22.0,
ATTR_TARGET_TEMP_LOW: 20.0,
- ATTR_CURRENT_TEMPERATURE: 18.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 18.0})
await hass.async_block_till_done()
assert acc.char_heating_thresh_temp.value == 20.0
assert acc.char_cooling_thresh_temp.value == 22.0
@@ -215,8 +206,7 @@ async def test_auto_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_TARGET_TEMP_HIGH: 23.0,
ATTR_TARGET_TEMP_LOW: 19.0,
- ATTR_CURRENT_TEMPERATURE: 24.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 24.0})
await hass.async_block_till_done()
assert acc.char_heating_thresh_temp.value == 19.0
assert acc.char_cooling_thresh_temp.value == 23.0
@@ -229,8 +219,7 @@ async def test_auto_thermostat(hass, hk_driver, cls):
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_TARGET_TEMP_HIGH: 23.0,
ATTR_TARGET_TEMP_LOW: 19.0,
- ATTR_CURRENT_TEMPERATURE: 21.0,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
+ ATTR_CURRENT_TEMPERATURE: 21.0})
await hass.async_block_till_done()
assert acc.char_heating_thresh_temp.value == 19.0
assert acc.char_cooling_thresh_temp.value == 23.0
@@ -334,8 +323,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls):
ATTR_TARGET_TEMP_HIGH: 75.2,
ATTR_TARGET_TEMP_LOW: 68,
ATTR_TEMPERATURE: 71.6,
- ATTR_CURRENT_TEMPERATURE: 73.4,
- ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
+ ATTR_CURRENT_TEMPERATURE: 73.4})
await hass.async_block_till_done()
assert acc.char_heating_thresh_temp.value == 20.0
assert acc.char_cooling_thresh_temp.value == 24.0
diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py
index 8e7a62e2e9f..e96531c0961 100644
--- a/tests/components/http/test_auth.py
+++ b/tests/components/http/test_auth.py
@@ -38,7 +38,7 @@ async def mock_handler(request):
@pytest.fixture
def app(hass):
- """Fixture to setup a web.Application."""
+ """Fixture to set up a web.Application."""
app = web.Application()
app['hass'] = hass
app.router.add_get('/', mock_handler)
@@ -48,7 +48,7 @@ def app(hass):
@pytest.fixture
def app2(hass):
- """Fixture to setup a web.Application without real_ip middleware."""
+ """Fixture to set up a web.Application without real_ip middleware."""
app = web.Application()
app['hass'] = hass
app.router.add_get('/', mock_handler)
diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py
index a510d2b3829..c95146d5cca 100644
--- a/tests/components/http/test_cors.py
+++ b/tests/components/http/test_cors.py
@@ -49,7 +49,7 @@ async def mock_handler(request):
@pytest.fixture
def client(loop, aiohttp_client):
- """Fixture to setup a web.Application."""
+ """Fixture to set up a web.Application."""
app = web.Application()
app.router.add_get('/', mock_handler)
setup_cors(app, [TRUSTED_ORIGIN])
diff --git a/tests/components/http/test_real_ip.py b/tests/components/http/test_real_ip.py
index 6cf6fec6bce..c28d810d41b 100644
--- a/tests/components/http/test_real_ip.py
+++ b/tests/components/http/test_real_ip.py
@@ -8,7 +8,7 @@ from homeassistant.components.http.const import KEY_REAL_IP
async def mock_handler(request):
- """Handler that returns the real IP as text."""
+ """Return the real IP as text."""
return web.Response(text=str(request[KEY_REAL_IP]))
diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py
index ea656ba8fc6..d12270cd908 100644
--- a/tests/components/hue/test_init.py
+++ b/tests/components/hue/test_init.py
@@ -8,7 +8,7 @@ from tests.common import mock_coro, MockConfigEntry
async def test_setup_with_no_config(hass):
- """Test that we do not discover anything or try to setup a bridge."""
+ """Test that we do not discover anything or try to set up a bridge."""
with patch.object(hass, 'config_entries') as mock_config_entries, \
patch.object(hue, 'configured_hosts', return_value=[]):
assert await async_setup_component(hass, hue.DOMAIN, {}) is True
diff --git a/tests/components/image_processing/test_facebox.py b/tests/components/image_processing/test_facebox.py
index b1d9fb8bf79..62e47a07c08 100644
--- a/tests/components/image_processing/test_facebox.py
+++ b/tests/components/image_processing/test_facebox.py
@@ -141,13 +141,13 @@ def test_valid_file_path():
async def test_setup_platform(hass, mock_healthybox):
- """Setup platform with one entity."""
+ """Set up platform with one entity."""
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
assert hass.states.get(VALID_ENTITY_ID)
async def test_setup_platform_with_auth(hass, mock_healthybox):
- """Setup platform with one entity and auth."""
+ """Set up platform with one entity and auth."""
valid_config_auth = VALID_CONFIG.copy()
valid_config_auth[ip.DOMAIN][CONF_USERNAME] = MOCK_USERNAME
valid_config_auth[ip.DOMAIN][CONF_PASSWORD] = MOCK_PASSWORD
@@ -297,7 +297,7 @@ async def test_teach_service(
async def test_setup_platform_with_name(hass, mock_healthybox):
- """Setup platform with one entity and a name."""
+ """Set up platform with one entity and a name."""
named_entity_id = 'image_processing.{}'.format(MOCK_NAME)
valid_config_named = VALID_CONFIG.copy()
diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py
index ab2e3be11d6..4240e173b26 100644
--- a/tests/components/image_processing/test_init.py
+++ b/tests/components/image_processing/test_init.py
@@ -16,7 +16,7 @@ class TestSetupImageProcessing:
"""Test class for setup image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -24,7 +24,7 @@ class TestSetupImageProcessing:
self.hass.stop()
def test_setup_component(self):
- """Setup demo platform on image_process component."""
+ """Set up demo platform on image_process component."""
config = {
ip.DOMAIN: {
'platform': 'demo'
@@ -35,7 +35,7 @@ class TestSetupImageProcessing:
setup_component(self.hass, ip.DOMAIN, config)
def test_setup_component_with_service(self):
- """Setup demo platform on image_process component test service."""
+ """Set up demo platform on image_process component test service."""
config = {
ip.DOMAIN: {
'platform': 'demo'
@@ -52,7 +52,7 @@ class TestImageProcessing:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
setup_component(
@@ -113,7 +113,7 @@ class TestImageProcessingAlpr:
"""Test class for alpr image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
config = {
@@ -149,7 +149,7 @@ class TestImageProcessingAlpr:
self.hass.stop()
def test_alpr_event_single_call(self, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.demo_alpr')
@@ -168,7 +168,7 @@ class TestImageProcessingAlpr:
assert event_data[0]['entity_id'] == 'image_processing.demo_alpr'
def test_alpr_event_double_call(self, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.demo_alpr')
@@ -192,7 +192,7 @@ class TestImageProcessingAlpr:
new_callable=PropertyMock(return_value=95))
def test_alpr_event_single_call_confidence(self, confidence_mock,
aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.demo_alpr')
@@ -215,7 +215,7 @@ class TestImageProcessingFace:
"""Test class for face image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
config = {
@@ -251,7 +251,7 @@ class TestImageProcessingFace:
self.hass.stop()
def test_face_event_call(self, aioclient_mock):
- """Setup and scan a picture and test faces from event."""
+ """Set up and scan a picture and test faces from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.demo_face')
@@ -276,7 +276,7 @@ class TestImageProcessingFace:
'DemoImageProcessingFace.confidence',
new_callable=PropertyMock(return_value=None))
def test_face_event_call_no_confidence(self, mock_config, aioclient_mock):
- """Setup and scan a picture and test faces from event."""
+ """Set up and scan a picture and test faces from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.demo_face')
diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/image_processing/test_microsoft_face_detect.py
index acc2519c9b7..9047c5b8475 100644
--- a/tests/components/image_processing/test_microsoft_face_detect.py
+++ b/tests/components/image_processing/test_microsoft_face_detect.py
@@ -15,7 +15,7 @@ class TestMicrosoftFaceDetectSetup:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -25,7 +25,7 @@ class TestMicrosoftFaceDetectSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_platform(self, store_mock):
- """Setup platform with one entity."""
+ """Set up platform with one entity."""
config = {
ip.DOMAIN: {
'platform': 'microsoft_face_detect',
@@ -51,7 +51,7 @@ class TestMicrosoftFaceDetectSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_platform_name(self, store_mock):
- """Setup platform with one entity and set name."""
+ """Set up platform with one entity and set name."""
config = {
ip.DOMAIN: {
'platform': 'microsoft_face_detect',
@@ -78,7 +78,7 @@ class TestMicrosoftFaceDetect:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -108,7 +108,7 @@ class TestMicrosoftFaceDetect:
'MicrosoftFaceDetectEntity.should_poll',
new_callable=PropertyMock(return_value=False))
def test_ms_detect_process_image(self, poll_mock, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(
self.endpoint_url.format("persongroups"),
text=load_fixture('microsoft_face_persongroups.json')
diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/image_processing/test_microsoft_face_identify.py
index 8797f661767..6d3eae38728 100644
--- a/tests/components/image_processing/test_microsoft_face_identify.py
+++ b/tests/components/image_processing/test_microsoft_face_identify.py
@@ -15,7 +15,7 @@ class TestMicrosoftFaceIdentifySetup:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -25,7 +25,7 @@ class TestMicrosoftFaceIdentifySetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_platform(self, store_mock):
- """Setup platform with one entity."""
+ """Set up platform with one entity."""
config = {
ip.DOMAIN: {
'platform': 'microsoft_face_identify',
@@ -51,7 +51,7 @@ class TestMicrosoftFaceIdentifySetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_platform_name(self, store_mock):
- """Setup platform with one entity and set name."""
+ """Set up platform with one entity and set name."""
config = {
ip.DOMAIN: {
'platform': 'microsoft_face_identify',
@@ -79,7 +79,7 @@ class TestMicrosoftFaceIdentify:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -109,7 +109,7 @@ class TestMicrosoftFaceIdentify:
'MicrosoftFaceIdentifyEntity.should_poll',
new_callable=PropertyMock(return_value=False))
def test_ms_identify_process_image(self, poll_mock, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(
self.endpoint_url.format("persongroups"),
text=load_fixture('microsoft_face_persongroups.json')
diff --git a/tests/components/image_processing/test_openalpr_cloud.py b/tests/components/image_processing/test_openalpr_cloud.py
index 65e735a6f7e..2d6015e3fe7 100644
--- a/tests/components/image_processing/test_openalpr_cloud.py
+++ b/tests/components/image_processing/test_openalpr_cloud.py
@@ -16,7 +16,7 @@ class TestOpenAlprCloudSetup:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -24,7 +24,7 @@ class TestOpenAlprCloudSetup:
self.hass.stop()
def test_setup_platform(self):
- """Setup platform with one entity."""
+ """Set up platform with one entity."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_cloud',
@@ -45,7 +45,7 @@ class TestOpenAlprCloudSetup:
assert self.hass.states.get('image_processing.openalpr_demo_camera')
def test_setup_platform_name(self):
- """Setup platform with one entity and set name."""
+ """Set up platform with one entity and set name."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_cloud',
@@ -67,7 +67,7 @@ class TestOpenAlprCloudSetup:
assert self.hass.states.get('image_processing.test_local')
def test_setup_platform_without_api_key(self):
- """Setup platform with one entity without api_key."""
+ """Set up platform with one entity without api_key."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_cloud',
@@ -85,7 +85,7 @@ class TestOpenAlprCloudSetup:
setup_component(self.hass, ip.DOMAIN, config)
def test_setup_platform_without_region(self):
- """Setup platform with one entity without region."""
+ """Set up platform with one entity without region."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_cloud',
@@ -107,7 +107,7 @@ class TestOpenAlprCloud:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
config = {
@@ -151,7 +151,7 @@ class TestOpenAlprCloud:
self.hass.stop()
def test_openalpr_process_image(self, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.post(
OPENALPR_API_URL, params=self.params,
text=load_fixture('alpr_cloud.json'), status=200
@@ -179,7 +179,7 @@ class TestOpenAlprCloud:
'image_processing.test_local'
def test_openalpr_process_image_api_error(self, aioclient_mock):
- """Setup and scan a picture and test api error."""
+ """Set up and scan a picture and test api error."""
aioclient_mock.post(
OPENALPR_API_URL, params=self.params,
text="{'error': 'error message'}", status=400
@@ -195,7 +195,7 @@ class TestOpenAlprCloud:
assert len(self.alpr_events) == 0
def test_openalpr_process_image_api_timeout(self, aioclient_mock):
- """Setup and scan a picture and test api error."""
+ """Set up and scan a picture and test api error."""
aioclient_mock.post(
OPENALPR_API_URL, params=self.params,
exc=asyncio.TimeoutError()
diff --git a/tests/components/image_processing/test_openalpr_local.py b/tests/components/image_processing/test_openalpr_local.py
index 38e94166c5a..772d66670a0 100644
--- a/tests/components/image_processing/test_openalpr_local.py
+++ b/tests/components/image_processing/test_openalpr_local.py
@@ -30,7 +30,7 @@ class TestOpenAlprLocalSetup:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -38,7 +38,7 @@ class TestOpenAlprLocalSetup:
self.hass.stop()
def test_setup_platform(self):
- """Setup platform with one entity."""
+ """Set up platform with one entity."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_local',
@@ -58,7 +58,7 @@ class TestOpenAlprLocalSetup:
assert self.hass.states.get('image_processing.openalpr_demo_camera')
def test_setup_platform_name(self):
- """Setup platform with one entity and set name."""
+ """Set up platform with one entity and set name."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_local',
@@ -79,7 +79,7 @@ class TestOpenAlprLocalSetup:
assert self.hass.states.get('image_processing.test_local')
def test_setup_platform_without_region(self):
- """Setup platform with one entity without region."""
+ """Set up platform with one entity without region."""
config = {
ip.DOMAIN: {
'platform': 'openalpr_local',
@@ -100,7 +100,7 @@ class TestOpenAlprLocal:
"""Test class for image processing."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
config = {
@@ -143,7 +143,7 @@ class TestOpenAlprLocal:
@patch('asyncio.create_subprocess_exec',
return_value=mock_async_subprocess())
def test_openalpr_process_image(self, popen_mock, aioclient_mock):
- """Setup and scan a picture and test plates from event."""
+ """Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
ip.scan(self.hass, entity_id='image_processing.test_local')
diff --git a/tests/components/light/test_demo.py b/tests/components/light/test_demo.py
index 8ba6385166b..db575bba5ba 100644
--- a/tests/components/light/test_demo.py
+++ b/tests/components/light/test_demo.py
@@ -15,7 +15,7 @@ class TestDemoLight(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(self.hass, light.DOMAIN, {'light': {
'platform': 'demo',
diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py
index 4d779eef461..66dbadb5c38 100644
--- a/tests/components/light/test_init.py
+++ b/tests/components/light/test_init.py
@@ -14,7 +14,7 @@ from homeassistant.components import light
from homeassistant.helpers.intent import IntentHandleError
from tests.common import (
- async_mock_service, mock_service, get_test_home_assistant)
+ async_mock_service, mock_service, get_test_home_assistant, mock_storage)
class TestLight(unittest.TestCase):
@@ -22,7 +22,7 @@ class TestLight(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -333,10 +333,11 @@ class TestLight(unittest.TestCase):
"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'}}
- ))
+ with mock_storage():
+ self.assertTrue(setup_component(
+ self.hass, light.DOMAIN,
+ {light.DOMAIN: {CONF_PLATFORM: 'test'}}
+ ))
dev, _, _ = platform.DEVICES
light.turn_on(self.hass, dev.entity_id)
@@ -371,10 +372,11 @@ class TestLight(unittest.TestCase):
"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'}}
- ))
+ with mock_storage():
+ 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))
diff --git a/tests/components/light/test_litejet.py b/tests/components/light/test_litejet.py
index dd4b4b4a56e..3040c95e0ac 100644
--- a/tests/components/light/test_litejet.py
+++ b/tests/components/light/test_litejet.py
@@ -21,7 +21,7 @@ class TestLiteJetLight(unittest.TestCase):
@mock.patch('pylitejet.LiteJet')
def setup_method(self, method, mock_pylitejet):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.start()
diff --git a/tests/components/light/test_mochad.py b/tests/components/light/test_mochad.py
index 5c82ab06085..fa122777ca4 100644
--- a/tests/components/light/test_mochad.py
+++ b/tests/components/light/test_mochad.py
@@ -28,7 +28,7 @@ class TestMochadSwitchSetup(unittest.TestCase):
THING = 'light'
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -57,7 +57,7 @@ class TestMochadLight(unittest.TestCase):
"""Test for mochad light platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
controller_mock = mock.MagicMock()
dev_dict = {'address': 'a1', 'name': 'fake_light',
@@ -76,25 +76,25 @@ class TestMochadLight(unittest.TestCase):
def test_turn_on_with_no_brightness(self):
"""Test turn_on."""
self.light.turn_on()
- self.light.device.send_cmd.assert_called_once_with('on')
+ self.light.light.send_cmd.assert_called_once_with('on')
def test_turn_on_with_brightness(self):
"""Test turn_on."""
self.light.turn_on(brightness=45)
- self.light.device.send_cmd.assert_has_calls(
+ self.light.light.send_cmd.assert_has_calls(
[mock.call('on'), mock.call('dim 25')])
def test_turn_off(self):
"""Test turn_off."""
self.light.turn_off()
- self.light.device.send_cmd.assert_called_once_with('off')
+ self.light.light.send_cmd.assert_called_once_with('off')
class TestMochadLight256Levels(unittest.TestCase):
"""Test for mochad light platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
controller_mock = mock.MagicMock()
dev_dict = {'address': 'a1', 'name': 'fake_light',
@@ -109,24 +109,24 @@ class TestMochadLight256Levels(unittest.TestCase):
def test_turn_on_with_no_brightness(self):
"""Test turn_on."""
self.light.turn_on()
- self.light.device.send_cmd.assert_called_once_with('xdim 255')
+ self.light.light.send_cmd.assert_called_once_with('xdim 255')
def test_turn_on_with_brightness(self):
"""Test turn_on."""
self.light.turn_on(brightness=45)
- self.light.device.send_cmd.assert_called_once_with('xdim 45')
+ self.light.light.send_cmd.assert_called_once_with('xdim 45')
def test_turn_off(self):
"""Test turn_off."""
self.light.turn_off()
- self.light.device.send_cmd.assert_called_once_with('off')
+ self.light.light.send_cmd.assert_called_once_with('off')
class TestMochadLight64Levels(unittest.TestCase):
"""Test for mochad light platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
controller_mock = mock.MagicMock()
dev_dict = {'address': 'a1', 'name': 'fake_light',
@@ -141,14 +141,14 @@ class TestMochadLight64Levels(unittest.TestCase):
def test_turn_on_with_no_brightness(self):
"""Test turn_on."""
self.light.turn_on()
- self.light.device.send_cmd.assert_called_once_with('xdim 63')
+ self.light.light.send_cmd.assert_called_once_with('xdim 63')
def test_turn_on_with_brightness(self):
"""Test turn_on."""
self.light.turn_on(brightness=45)
- self.light.device.send_cmd.assert_called_once_with('xdim 11')
+ self.light.light.send_cmd.assert_called_once_with('xdim 11')
def test_turn_off(self):
"""Test turn_off."""
self.light.turn_off()
- self.light.device.send_cmd.assert_called_once_with('off')
+ self.light.light.send_cmd.assert_called_once_with('off')
diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py
index 404d60c0a2e..1245411dcc4 100644
--- a/tests/components/light/test_mqtt.py
+++ b/tests/components/light/test_mqtt.py
@@ -158,7 +158,7 @@ class TestLightMQTT(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py
index f16685b3575..90875285f17 100644
--- a/tests/components/light/test_mqtt_json.py
+++ b/tests/components/light/test_mqtt_json.py
@@ -107,7 +107,7 @@ class TestLightMQTTJSON(unittest.TestCase):
"""Test the MQTT JSON light."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py
index 1cf09f2ccb5..8f92d659b9b 100644
--- a/tests/components/light/test_mqtt_template.py
+++ b/tests/components/light/test_mqtt_template.py
@@ -43,7 +43,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
"""Test the MQTT Template light."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/light/test_rfxtrx.py b/tests/components/light/test_rfxtrx.py
index a1f63e45748..8a8e94ec179 100644
--- a/tests/components/light/test_rfxtrx.py
+++ b/tests/components/light/test_rfxtrx.py
@@ -14,7 +14,7 @@ class TestLightRfxtrx(unittest.TestCase):
"""Test the Rfxtrx light platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'rfxtrx')
diff --git a/tests/components/light/test_template.py b/tests/components/light/test_template.py
index 962760672f1..cc481fabb5c 100644
--- a/tests/components/light/test_template.py
+++ b/tests/components/light/test_template.py
@@ -20,7 +20,7 @@ class TestTemplateLight:
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.calls = []
diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py
index 1d774248f35..500cc7f9a6a 100644
--- a/tests/components/lock/test_demo.py
+++ b/tests/components/lock/test_demo.py
@@ -14,7 +14,7 @@ class TestLockDemo(unittest.TestCase):
"""Test the demo lock."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(self.hass, lock.DOMAIN, {
'lock': {
diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py
index f87b8f8b74b..4ef8532f39e 100644
--- a/tests/components/lock/test_mqtt.py
+++ b/tests/components/lock/test_mqtt.py
@@ -13,7 +13,7 @@ class TestLockMQTT(unittest.TestCase):
"""Test the MQTT lock."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py
index 11e324e9132..5908ea02a37 100644
--- a/tests/components/media_player/test_async_helpers.py
+++ b/tests/components/media_player/test_async_helpers.py
@@ -123,7 +123,7 @@ class TestAsyncMediaPlayer(unittest.TestCase):
"""Test the media_player module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.player = AsyncMediaPlayer(self.hass)
@@ -176,7 +176,7 @@ class TestSyncMediaPlayer(unittest.TestCase):
"""Test the media_player module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.player = SyncMediaPlayer(self.hass)
diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py
index 47be39c68e5..c3e777ea334 100644
--- a/tests/components/media_player/test_cast.py
+++ b/tests/components/media_player/test_cast.py
@@ -48,7 +48,7 @@ def get_fake_chromecast_info(host='192.168.178.42', port=8009,
async def async_setup_cast(hass, config=None, discovery_info=None):
- """Helper to setup the cast platform."""
+ """Set up the cast platform."""
if config is None:
config = {}
add_devices = Mock()
@@ -62,7 +62,7 @@ async def async_setup_cast(hass, config=None, discovery_info=None):
async def async_setup_cast_internal_discovery(hass, config=None,
discovery_info=None):
- """Setup the cast platform and the discovery."""
+ """Set up the cast platform and the discovery."""
listener = MagicMock(services={})
with patch('pychromecast.start_discovery',
@@ -85,7 +85,7 @@ async def async_setup_cast_internal_discovery(hass, config=None,
async def async_setup_media_player_cast(hass: HomeAssistantType,
info: ChromecastInfo):
- """Setup the cast platform with async_setup_component."""
+ """Set up the cast platform with async_setup_component."""
chromecast = get_fake_chromecast(info)
cast.CastStatusListener = MagicMock()
diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py
index 65ca2eb6a01..121018e7541 100644
--- a/tests/components/media_player/test_demo.py
+++ b/tests/components/media_player/test_demo.py
@@ -25,7 +25,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
"""Test the media_player module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -227,7 +227,7 @@ class TestMediaPlayerWeb(unittest.TestCase):
"""Test the media player web views sensor."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
assert setup_component(self.hass, http.DOMAIN, {
diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/media_player/test_samsungtv.py
index 349067f7cd3..6dd61157851 100644
--- a/tests/components/media_player/test_samsungtv.py
+++ b/tests/components/media_player/test_samsungtv.py
@@ -52,7 +52,7 @@ class TestSamsungTv(unittest.TestCase):
@MockDependency('samsungctl')
@MockDependency('wakeonlan')
def setUp(self, samsung_mock, wol_mock):
- """Setting up test environment."""
+ """Set up test environment."""
self.hass = tests.common.get_test_home_assistant()
self.hass.start()
self.hass.block_till_done()
diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py
index 7d0d675f66f..cd2ce2b9707 100644
--- a/tests/components/media_player/test_sonos.py
+++ b/tests/components/media_player/test_sonos.py
@@ -135,7 +135,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def monkey_available(self):
diff --git a/tests/components/media_player/test_soundtouch.py b/tests/components/media_player/test_soundtouch.py
index 2da2622e08a..62356e6afca 100644
--- a/tests/components/media_player/test_soundtouch.py
+++ b/tests/components/media_player/test_soundtouch.py
@@ -148,7 +148,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
"""Bose Soundtouch test class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
logging.disable(logging.CRITICAL)
diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py
index c9a1cdc79d8..99c16670764 100644
--- a/tests/components/media_player/test_universal.py
+++ b/tests/components/media_player/test_universal.py
@@ -94,7 +94,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
@property
def volume_level(self):
- """The volume level of player."""
+ """Return the volume level of player."""
return self._volume_level
@property
@@ -158,7 +158,7 @@ class TestMediaPlayer(unittest.TestCase):
"""Test the media_player module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_mp_1 = MockMediaPlayer(self.hass, 'mock1')
diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/media_player/test_yamaha.py
index 980284737a2..a55429c0c7b 100644
--- a/tests/components/media_player/test_yamaha.py
+++ b/tests/components/media_player/test_yamaha.py
@@ -33,7 +33,7 @@ class TestYamahaMediaPlayer(unittest.TestCase):
"""Test the Yamaha media player."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.main_zone = _create_zone_mock('Main zone', 'http://main')
self.device = FakeYamahaDevice(
diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py
index 05c5de71b8c..ecbc7cb9b02 100644
--- a/tests/components/mqtt/test_init.py
+++ b/tests/components/mqtt/test_init.py
@@ -45,7 +45,7 @@ class TestMQTTComponent(unittest.TestCase):
"""Test the MQTT component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
self.calls = []
@@ -56,7 +56,7 @@ class TestMQTTComponent(unittest.TestCase):
@callback
def record_calls(self, *args):
- """Helper for recording calls."""
+ """Record calls."""
self.calls.append(args)
def aiohttp_client_stops_on_home_assistant_start(self):
@@ -188,7 +188,7 @@ class TestMQTTCallbacks(unittest.TestCase):
"""Test the MQTT callbacks."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_client(self.hass)
self.calls = []
@@ -199,7 +199,7 @@ class TestMQTTCallbacks(unittest.TestCase):
@callback
def record_calls(self, *args):
- """Helper for recording calls."""
+ """Record calls."""
self.calls.append(args)
def aiohttp_client_starts_on_home_assistant_mqtt_setup(self):
diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py
index d5d54f457d6..c761c47542f 100644
--- a/tests/components/mqtt/test_server.py
+++ b/tests/components/mqtt/test_server.py
@@ -18,7 +18,7 @@ class TestMQTT:
"""Test the MQTT component."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
@@ -33,7 +33,7 @@ class TestMQTT:
def test_creating_config_with_http_pass_only(self, mock_mqtt):
"""Test if the MQTT server failed starts.
- Since 0.77, MQTT server has to setup its own password.
+ Since 0.77, MQTT server has to set up its own password.
If user has api_password but don't have mqtt.password, MQTT component
will fail to start
"""
@@ -51,7 +51,7 @@ class TestMQTT:
def test_creating_config_with_pass_and_no_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
- Since 0.77, MQTT server has to setup its own password.
+ Since 0.77, MQTT server has to set up its own password.
"""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
@@ -74,7 +74,7 @@ class TestMQTT:
def test_creating_config_with_pass_and_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
- Since 0.77, MQTT server has to setup its own password.
+ Since 0.77, MQTT server has to set up its own password.
"""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
diff --git a/tests/components/notify/test_apns.py b/tests/components/notify/test_apns.py
index 0bd0333a6fb..dc120dc4ff6 100644
--- a/tests/components/notify/test_apns.py
+++ b/tests/components/notify/test_apns.py
@@ -28,7 +28,7 @@ class TestApns(unittest.TestCase):
"""Test the APNS component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py
index 2575e1418f4..57933063ba1 100644
--- a/tests/components/notify/test_command_line.py
+++ b/tests/components/notify/test_command_line.py
@@ -13,7 +13,7 @@ class TestCommandLine(unittest.TestCase):
"""Test the command line notifications."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py
index 71b472afe74..f9c107a447e 100644
--- a/tests/components/notify/test_demo.py
+++ b/tests/components/notify/test_demo.py
@@ -20,7 +20,7 @@ class TestNotifyDemo(unittest.TestCase):
"""Test the demo notify."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.events = []
self.calls = []
@@ -73,7 +73,7 @@ class TestNotifyDemo(unittest.TestCase):
@callback
def record_calls(self, *args):
- """Helper for recording calls."""
+ """Record calls."""
self.calls.append(args)
def test_sending_none_message(self):
diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py
index d59bbe4d720..fb4ab42e97b 100644
--- a/tests/components/notify/test_file.py
+++ b/tests/components/notify/test_file.py
@@ -16,7 +16,7 @@ class TestNotifyFile(unittest.TestCase):
"""Test the file notify."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py
index 8e7ef4348f7..bbd7c11ffeb 100644
--- a/tests/components/notify/test_group.py
+++ b/tests/components/notify/test_group.py
@@ -14,7 +14,7 @@ class TestNotifyGroup(unittest.TestCase):
"""Test the notify.group platform."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.events = []
self.service1 = demo.DemoNotificationService(self.hass)
diff --git a/tests/components/notify/test_smtp.py b/tests/components/notify/test_smtp.py
index 29e34974c6c..fca0e3c79e3 100644
--- a/tests/components/notify/test_smtp.py
+++ b/tests/components/notify/test_smtp.py
@@ -19,7 +19,7 @@ class TestNotifySmtp(unittest.TestCase):
"""Test the smtp notify."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mailer = MockSMTP('localhost', 25, 5, 'test@test.com', 1,
'testuser', 'testpass',
diff --git a/tests/components/persistent_notification/test_init.py b/tests/components/persistent_notification/test_init.py
index df780675a18..a609247b839 100644
--- a/tests/components/persistent_notification/test_init.py
+++ b/tests/components/persistent_notification/test_init.py
@@ -9,7 +9,7 @@ class TestPersistentNotification:
"""Test persistent notification component."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
setup_component(self.hass, pn.DOMAIN, {})
diff --git a/tests/components/recorder/models_original.py b/tests/components/recorder/models_original.py
index 990414d7713..7096e84c82b 100644
--- a/tests/components/recorder/models_original.py
+++ b/tests/components/recorder/models_original.py
@@ -14,7 +14,7 @@ from sqlalchemy.ext.declarative import declarative_base
import homeassistant.util.dt as dt_util
from homeassistant.core import Event, EventOrigin, State, split_entity_id
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
# SQLAlchemy Schema
# pylint: disable=invalid-name
diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py
index 191c0d6e733..7460a65b0ce 100644
--- a/tests/components/recorder/test_init.py
+++ b/tests/components/recorder/test_init.py
@@ -19,7 +19,7 @@ class TestRecorder(unittest.TestCase):
"""Test the recorder module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
init_recorder_component(self.hass)
self.hass.start()
@@ -92,7 +92,7 @@ def hass_recorder():
hass = get_test_home_assistant()
def setup_recorder(config=None):
- """Setup with params."""
+ """Set up with params."""
init_recorder_component(hass, config)
hass.start()
hass.block_till_done()
diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py
index 5ac9b3adb81..93da4ec109b 100644
--- a/tests/components/recorder/test_migrate.py
+++ b/tests/components/recorder/test_migrate.py
@@ -5,11 +5,11 @@ from unittest.mock import patch, call
import pytest
from sqlalchemy import create_engine
+from sqlalchemy.pool import StaticPool
from homeassistant.bootstrap import async_setup_component
-from homeassistant.components.recorder import wait_connection_ready, migration
-from homeassistant.components.recorder.models import SCHEMA_VERSION
-from homeassistant.components.recorder.const import DATA_INSTANCE
+from homeassistant.components.recorder import (
+ wait_connection_ready, migration, const, models)
from tests.components.recorder import models_original
@@ -37,8 +37,8 @@ def test_schema_update_calls(hass):
yield from wait_connection_ready(hass)
update.assert_has_calls([
- call(hass.data[DATA_INSTANCE].engine, version+1, 0) for version
- in range(0, SCHEMA_VERSION)])
+ call(hass.data[const.DATA_INSTANCE].engine, version+1, 0) for version
+ in range(0, models.SCHEMA_VERSION)])
@asyncio.coroutine
@@ -65,3 +65,28 @@ def test_invalid_update():
"""Test that an invalid new version raises an exception."""
with pytest.raises(ValueError):
migration._apply_update(None, -1, 0)
+
+
+def test_forgiving_add_column():
+ """Test that add column will continue if column exists."""
+ engine = create_engine(
+ 'sqlite://',
+ poolclass=StaticPool
+ )
+ engine.execute('CREATE TABLE hello (id int)')
+ migration._add_columns(engine, 'hello', [
+ 'context_id CHARACTER(36)',
+ ])
+ migration._add_columns(engine, 'hello', [
+ 'context_id CHARACTER(36)',
+ ])
+
+
+def test_forgiving_add_index():
+ """Test that add index will continue if index exists."""
+ engine = create_engine(
+ 'sqlite://',
+ poolclass=StaticPool
+ )
+ models.Base.metadata.create_all(engine)
+ migration._create_index(engine, "states", "ix_states_context_id")
diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py
index 91aa69b4484..f33236f0ceb 100644
--- a/tests/components/recorder/test_purge.py
+++ b/tests/components/recorder/test_purge.py
@@ -16,7 +16,7 @@ class TestRecorderPurge(unittest.TestCase):
"""Base class for common recorder tests."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
init_recorder_component(self.hass)
self.hass.start()
diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py
index ad130b1ca91..83d109fcfc5 100644
--- a/tests/components/recorder/test_util.py
+++ b/tests/components/recorder/test_util.py
@@ -14,7 +14,7 @@ def hass_recorder():
hass = get_test_home_assistant()
def setup_recorder(config=None):
- """Setup with params."""
+ """Set up with params."""
init_recorder_component(hass, config)
hass.start()
hass.block_till_done()
diff --git a/tests/components/remote/test_demo.py b/tests/components/remote/test_demo.py
index ed9968c2d10..a0290987ff2 100644
--- a/tests/components/remote/test_demo.py
+++ b/tests/components/remote/test_demo.py
@@ -15,7 +15,7 @@ class TestDemoRemote(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(self.hass, remote.DOMAIN, {'remote': {
'platform': 'demo',
diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py
index b4d2ff98688..d98ec941f8b 100644
--- a/tests/components/remote/test_init.py
+++ b/tests/components/remote/test_init.py
@@ -19,7 +19,7 @@ class TestRemote(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py
index a832e249832..3298d7648d9 100644
--- a/tests/components/scene/test_init.py
+++ b/tests/components/scene/test_init.py
@@ -14,7 +14,7 @@ class TestScene(unittest.TestCase):
"""Test the scene component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
test_light = loader.get_component(self.hass, 'light.test')
test_light.init()
diff --git a/tests/components/scene/test_litejet.py b/tests/components/scene/test_litejet.py
index 37a9aa5b2b5..864ffc41735 100644
--- a/tests/components/scene/test_litejet.py
+++ b/tests/components/scene/test_litejet.py
@@ -21,7 +21,7 @@ class TestLiteJetScene(unittest.TestCase):
@mock.patch('pylitejet.LiteJet')
def setup_method(self, method, mock_pylitejet):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.start()
diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py
index 808f8cff6a1..82cdef014b9 100644
--- a/tests/components/sensor/test_command_line.py
+++ b/tests/components/sensor/test_command_line.py
@@ -11,7 +11,7 @@ class TestCommandSensorSensor(unittest.TestCase):
"""Test the Command line sensor."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/sensor/test_dyson.py b/tests/components/sensor/test_dyson.py
index dcbafcae6e3..cc32872e6f0 100644
--- a/tests/components/sensor/test_dyson.py
+++ b/tests/components/sensor/test_dyson.py
@@ -52,7 +52,7 @@ class DysonTest(unittest.TestCase):
"""Dyson Sensor component test class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/sensor/test_filter.py b/tests/components/sensor/test_filter.py
index cf2cc9c4205..b0683b04aa0 100644
--- a/tests/components/sensor/test_filter.py
+++ b/tests/components/sensor/test_filter.py
@@ -17,7 +17,7 @@ class TestFilterSensor(unittest.TestCase):
"""Test the Data Filter sensor."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
raw_values = [20, 19, 18, 21, 22, 0]
self.values = []
diff --git a/tests/components/sensor/test_google_wifi.py b/tests/components/sensor/test_google_wifi.py
index 1004c20b314..55afedab536 100644
--- a/tests/components/sensor/test_google_wifi.py
+++ b/tests/components/sensor/test_google_wifi.py
@@ -36,7 +36,7 @@ class TestGoogleWifiSetup(unittest.TestCase):
"""Tests for setting up the Google Wifi sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -83,7 +83,7 @@ class TestGoogleWifiSensor(unittest.TestCase):
"""Tests for Google Wifi sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
with requests_mock.Mocker() as mock_req:
self.setup_api(MOCK_DATA, mock_req)
@@ -93,7 +93,7 @@ class TestGoogleWifiSensor(unittest.TestCase):
self.hass.stop()
def setup_api(self, data, mock_req):
- """Setup API with fake data."""
+ """Set up API with fake data."""
resource = '{}{}{}'.format(
'http://', 'localhost', google_wifi.ENDPOINT)
now = datetime(1970, month=1, day=1)
diff --git a/tests/components/sensor/test_imap_email_content.py b/tests/components/sensor/test_imap_email_content.py
index cd5c079a431..a07d94e3dcd 100644
--- a/tests/components/sensor/test_imap_email_content.py
+++ b/tests/components/sensor/test_imap_email_content.py
@@ -17,7 +17,7 @@ class FakeEMailReader:
"""A test class for sending test emails."""
def __init__(self, messages):
- """Setup the fake email reader."""
+ """Set up the fake email reader."""
self._messages = messages
def connect(self):
@@ -35,7 +35,7 @@ class EmailContentSensor(unittest.TestCase):
"""Test the IMAP email content sensor."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py
index ae967449ef2..a10246ad777 100644
--- a/tests/components/sensor/test_mfi.py
+++ b/tests/components/sensor/test_mfi.py
@@ -31,7 +31,7 @@ class TestMfiSensorSetup(unittest.TestCase):
}
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
@@ -131,7 +131,7 @@ class TestMfiSensor(unittest.TestCase):
"""Test for mFi sensor platform."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.port = mock.MagicMock()
self.sensor = mfi.MfiSensor(self.port, self.hass)
diff --git a/tests/components/sensor/test_mhz19.py b/tests/components/sensor/test_mhz19.py
index 6d071489691..421035995dc 100644
--- a/tests/components/sensor/test_mhz19.py
+++ b/tests/components/sensor/test_mhz19.py
@@ -15,7 +15,7 @@ class TestMHZ19Sensor(unittest.TestCase):
hass = None
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/components/sensor/test_moldindicator.py b/tests/components/sensor/test_moldindicator.py
index 32cd0206dec..4f1b40bf9ef 100644
--- a/tests/components/sensor/test_moldindicator.py
+++ b/tests/components/sensor/test_moldindicator.py
@@ -15,7 +15,7 @@ class TestSensorMoldIndicator(unittest.TestCase):
"""Test the MoldIndicator sensor."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.states.set('test.indoortemp', '20',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py
index 2583f52b3d2..feef647b7b7 100644
--- a/tests/components/sensor/test_mqtt.py
+++ b/tests/components/sensor/test_mqtt.py
@@ -5,13 +5,14 @@ from datetime import timedelta, datetime
from unittest.mock import patch
import homeassistant.core as ha
-from homeassistant.setup import setup_component
+from homeassistant.setup import setup_component, async_setup_component
import homeassistant.components.sensor as sensor
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE
import homeassistant.util.dt as dt_util
from tests.common import mock_mqtt_component, fire_mqtt_message, \
- assert_setup_component
+ assert_setup_component, async_fire_mqtt_message, \
+ async_mock_mqtt_component
from tests.common import get_test_home_assistant, mock_component
@@ -19,7 +20,7 @@ class TestSensorMQTT(unittest.TestCase):
"""Test the MQTT sensor."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
@@ -331,27 +332,6 @@ class TestSensorMQTT(unittest.TestCase):
state.attributes.get('val'))
self.assertEqual('100', state.state)
- def test_unique_id(self):
- """Test unique id option only creates one sensor per unique_id."""
- assert setup_component(self.hass, sensor.DOMAIN, {
- sensor.DOMAIN: [{
- 'platform': 'mqtt',
- 'name': 'Test 1',
- 'state_topic': 'test-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }, {
- 'platform': 'mqtt',
- 'name': 'Test 2',
- 'state_topic': 'test-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }]
- })
-
- fire_mqtt_message(self.hass, 'test-topic', 'payload')
- self.hass.block_till_done()
-
- assert len(self.hass.states.all()) == 1
-
def test_invalid_device_class(self):
"""Test device_class option with invalid value."""
with assert_setup_component(0):
@@ -384,3 +364,26 @@ class TestSensorMQTT(unittest.TestCase):
assert state.attributes['device_class'] == 'temperature'
state = self.hass.states.get('sensor.test_2')
assert 'device_class' not in state.attributes
+
+
+async def test_unique_id(hass):
+ """Test unique id option only creates one sensor per unique_id."""
+ await async_mock_mqtt_component(hass)
+ assert await async_setup_component(hass, sensor.DOMAIN, {
+ sensor.DOMAIN: [{
+ 'platform': 'mqtt',
+ 'name': 'Test 1',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }, {
+ 'platform': 'mqtt',
+ 'name': 'Test 2',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }]
+ })
+
+ async_fire_mqtt_message(hass, 'test-topic', 'payload')
+ await hass.async_block_till_done()
+
+ assert len(hass.states.async_all()) == 1
diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py
index c79017338e1..88fa611b2a6 100644
--- a/tests/components/sensor/test_mqtt_room.py
+++ b/tests/components/sensor/test_mqtt_room.py
@@ -50,7 +50,7 @@ class TestMQTTRoomSensor(unittest.TestCase):
"""Test the room presence sensor."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_mqtt_component(self.hass)
self.assertTrue(setup_component(self.hass, sensor.DOMAIN, {
diff --git a/tests/components/sensor/test_rest.py b/tests/components/sensor/test_rest.py
index f2362867979..4d40ad394cd 100644
--- a/tests/components/sensor/test_rest.py
+++ b/tests/components/sensor/test_rest.py
@@ -19,7 +19,7 @@ class TestRestSensorSetup(unittest.TestCase):
"""Tests for setting up the REST sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -121,7 +121,7 @@ class TestRestSensor(unittest.TestCase):
"""Tests for REST sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.initial_state = 'initial_state'
self.rest = Mock('rest.RestData')
@@ -267,7 +267,7 @@ class TestRestData(unittest.TestCase):
"""Tests for RestData."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.method = "GET"
self.resource = "http://localhost"
self.verify_ssl = True
diff --git a/tests/components/sensor/test_rfxtrx.py b/tests/components/sensor/test_rfxtrx.py
index e049eabbe56..3f577127a11 100644
--- a/tests/components/sensor/test_rfxtrx.py
+++ b/tests/components/sensor/test_rfxtrx.py
@@ -15,7 +15,7 @@ class TestSensorRfxtrx(unittest.TestCase):
"""Test the Rfxtrx sensor platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'rfxtrx')
diff --git a/tests/components/sensor/test_season.py b/tests/components/sensor/test_season.py
index 5c071982f7f..4c82399648e 100644
--- a/tests/components/sensor/test_season.py
+++ b/tests/components/sensor/test_season.py
@@ -66,7 +66,7 @@ class TestSeason(unittest.TestCase):
self.DEVICE = device
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/sensor/test_statistics.py b/tests/components/sensor/test_statistics.py
index 48ebf720633..466b89cc0d1 100644
--- a/tests/components/sensor/test_statistics.py
+++ b/tests/components/sensor/test_statistics.py
@@ -17,7 +17,7 @@ class TestStatisticsSensor(unittest.TestCase):
"""Test the Statistics sensor."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.values = [17, 20, 15.2, 5, 3.8, 9.2, 6.7, 14, 6]
self.count = len(self.values)
diff --git a/tests/components/sensor/test_tcp.py b/tests/components/sensor/test_tcp.py
index 4c1e976ea51..cbc097955c8 100644
--- a/tests/components/sensor/test_tcp.py
+++ b/tests/components/sensor/test_tcp.py
@@ -39,7 +39,7 @@ class TestTCPSensor(unittest.TestCase):
"""Test the TCP Sensor."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py
index 6861d3a5070..2211f092d7b 100644
--- a/tests/components/sensor/test_template.py
+++ b/tests/components/sensor/test_template.py
@@ -11,7 +11,7 @@ class TestTemplateSensor:
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/components/sensor/test_yweather.py b/tests/components/sensor/test_yweather.py
index aeee47bfa80..2912229d712 100644
--- a/tests/components/sensor/test_yweather.py
+++ b/tests/components/sensor/test_yweather.py
@@ -100,7 +100,7 @@ class YahooWeatherMock():
@property
def Now(self): # pylint: disable=invalid-name
- """Current weather data."""
+ """Return current weather data."""
if self.woeid == '111':
raise ValueError
return self._data['query']['results']['channel']['item']['condition']
@@ -129,7 +129,7 @@ class TestWeather(unittest.TestCase):
"""Test the Yahoo weather component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py
index 40f999fa43b..9ec0507627d 100644
--- a/tests/components/switch/test_command_line.py
+++ b/tests/components/switch/test_command_line.py
@@ -17,7 +17,7 @@ class TestCommandSwitch(unittest.TestCase):
"""Test the command switch."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py
index 155ed85dac2..f9ea88c5254 100644
--- a/tests/components/switch/test_flux.py
+++ b/tests/components/switch/test_flux.py
@@ -17,7 +17,7 @@ class TestSwitchFlux(unittest.TestCase):
"""Test the Flux switch platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py
index 55e44299294..579898437ca 100644
--- a/tests/components/switch/test_init.py
+++ b/tests/components/switch/test_init.py
@@ -15,7 +15,7 @@ class TestSwitch(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
platform = loader.get_component(self.hass, 'switch.test')
platform.init()
diff --git a/tests/components/switch/test_litejet.py b/tests/components/switch/test_litejet.py
index e0d6e290def..45e5509c169 100644
--- a/tests/components/switch/test_litejet.py
+++ b/tests/components/switch/test_litejet.py
@@ -21,7 +21,7 @@ class TestLiteJetSwitch(unittest.TestCase):
@mock.patch('pylitejet.LiteJet')
def setup_method(self, method, mock_pylitejet):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.start()
diff --git a/tests/components/switch/test_mfi.py b/tests/components/switch/test_mfi.py
index ae3edec2102..d2bf3c57ab6 100644
--- a/tests/components/switch/test_mfi.py
+++ b/tests/components/switch/test_mfi.py
@@ -49,7 +49,7 @@ class TestMfiSwitch(unittest.TestCase):
"""Test for mFi switch platform."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.port = mock.MagicMock()
self.switch = mfi.MfiSwitch(self.port)
diff --git a/tests/components/switch/test_mochad.py b/tests/components/switch/test_mochad.py
index a5e6e2c9ae6..bfbd67e6b0c 100644
--- a/tests/components/switch/test_mochad.py
+++ b/tests/components/switch/test_mochad.py
@@ -29,7 +29,7 @@ class TestMochadSwitchSetup(unittest.TestCase):
THING = 'switch'
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
@@ -58,7 +58,7 @@ class TestMochadSwitch(unittest.TestCase):
"""Test for mochad switch platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
controller_mock = mock.MagicMock()
dev_dict = {'address': 'a1', 'name': 'fake_switch'}
@@ -76,9 +76,9 @@ class TestMochadSwitch(unittest.TestCase):
def test_turn_on(self):
"""Test turn_on."""
self.switch.turn_on()
- self.switch.device.send_cmd.assert_called_once_with('on')
+ self.switch.switch.send_cmd.assert_called_once_with('on')
def test_turn_off(self):
"""Test turn_off."""
self.switch.turn_off()
- self.switch.device.send_cmd.assert_called_once_with('off')
+ self.switch.switch.send_cmd.assert_called_once_with('off')
diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py
index 7cd5a42b4a3..c9bfd02156f 100644
--- a/tests/components/switch/test_mqtt.py
+++ b/tests/components/switch/test_mqtt.py
@@ -15,7 +15,7 @@ class TestSwitchMQTT(unittest.TestCase):
"""Test the MQTT switch."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
diff --git a/tests/components/switch/test_rest.py b/tests/components/switch/test_rest.py
index e3f11ec19a0..cb27ab40855 100644
--- a/tests/components/switch/test_rest.py
+++ b/tests/components/switch/test_rest.py
@@ -14,7 +14,7 @@ class TestRestSwitchSetup:
"""Tests for setting up the REST switch platform."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -95,7 +95,7 @@ class TestRestSwitch:
"""Tests for REST switch platform."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.name = 'foo'
self.method = 'post'
diff --git a/tests/components/switch/test_rfxtrx.py b/tests/components/switch/test_rfxtrx.py
index 938093aa95b..ae242a1dafb 100644
--- a/tests/components/switch/test_rfxtrx.py
+++ b/tests/components/switch/test_rfxtrx.py
@@ -14,7 +14,7 @@ class TestSwitchRfxtrx(unittest.TestCase):
"""Test the Rfxtrx switch platform."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
mock_component(self.hass, 'rfxtrx')
diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py
index 8f7bbda8e98..47766e31f4d 100644
--- a/tests/components/switch/test_template.py
+++ b/tests/components/switch/test_template.py
@@ -16,7 +16,7 @@ class TestTemplateSwitch:
# pylint: disable=invalid-name
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.calls = []
diff --git a/tests/components/switch/test_wake_on_lan.py b/tests/components/switch/test_wake_on_lan.py
index 167c3bb35ac..abe1532cec7 100644
--- a/tests/components/switch/test_wake_on_lan.py
+++ b/tests/components/switch/test_wake_on_lan.py
@@ -33,7 +33,7 @@ class TestWOLSwitch(unittest.TestCase):
"""Test the wol switch."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py
index d9eb33be37d..90d732ac38e 100644
--- a/tests/components/test_alert.py
+++ b/tests/components/test_alert.py
@@ -36,7 +36,7 @@ class TestAlert(unittest.TestCase):
"""Test the alert module."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_api.py b/tests/components/test_api.py
index 2be1168b86a..6f6b4e93068 100644
--- a/tests/components/test_api.py
+++ b/tests/components/test_api.py
@@ -154,7 +154,7 @@ def test_api_fire_event_with_no_data(hass, mock_api_client):
@ha.callback
def listener(event):
- """Helper method that will verify our event got called."""
+ """Record that our event got called."""
test_value.append(1)
hass.bus.async_listen_once("test.event_no_data", listener)
@@ -174,7 +174,7 @@ def test_api_fire_event_with_data(hass, mock_api_client):
@ha.callback
def listener(event):
- """Helper method that will verify that our event got called.
+ """Record that our event got called.
Also test if our data came through.
"""
@@ -200,7 +200,7 @@ def test_api_fire_event_with_invalid_json(hass, mock_api_client):
@ha.callback
def listener(event):
- """Helper method that will verify our event got called."""
+ """Record that our event got called."""
test_value.append(1)
hass.bus.async_listen_once("test_event_bad_data", listener)
@@ -281,7 +281,7 @@ def test_api_call_service_no_data(hass, mock_api_client):
@ha.callback
def listener(service_call):
- """Helper method that will verify that our service got called."""
+ """Record that our service got called."""
test_value.append(1)
hass.services.async_register("test_domain", "test_service", listener)
@@ -300,7 +300,7 @@ def test_api_call_service_with_data(hass, mock_api_client):
@ha.callback
def listener(service_call):
- """Helper method that will verify that our service got called.
+ """Record that our service got called.
Also test if our data came through.
"""
@@ -440,7 +440,7 @@ async def test_api_fire_event_context(hass, mock_api_client,
@ha.callback
def listener(event):
- """Helper method that will verify our event got called."""
+ """Record that our event got called."""
test_value.append(event)
hass.bus.async_listen("test.event", listener)
diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py
index 809c02548dc..22f0d6646aa 100644
--- a/tests/components/test_configurator.py
+++ b/tests/components/test_configurator.py
@@ -13,7 +13,7 @@ class TestConfigurator(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/components/test_datadog.py b/tests/components/test_datadog.py
index f1820c4d250..f9724989f97 100644
--- a/tests/components/test_datadog.py
+++ b/tests/components/test_datadog.py
@@ -20,7 +20,7 @@ class TestDatadog(unittest.TestCase):
"""Test the Datadog component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_demo.py b/tests/components/test_demo.py
index 258e3d96297..b0b2524180f 100644
--- a/tests/components/test_demo.py
+++ b/tests/components/test_demo.py
@@ -7,7 +7,7 @@ import pytest
from homeassistant.setup import async_setup_component
from homeassistant.components import demo, device_tracker
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
@pytest.fixture(autouse=True)
diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py
index 774185c51c1..35d53f9a5c8 100644
--- a/tests/components/test_device_sun_light_trigger.py
+++ b/tests/components/test_device_sun_light_trigger.py
@@ -18,7 +18,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
"""Test the device sun light trigger module."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.scanner = loader.get_component(
diff --git a/tests/components/test_discovery.py b/tests/components/test_discovery.py
index 8b997cb911c..d4566bc0b03 100644
--- a/tests/components/test_discovery.py
+++ b/tests/components/test_discovery.py
@@ -47,7 +47,7 @@ def netdisco_mock():
async def mock_discovery(hass, discoveries, config=BASE_CONFIG):
- """Helper to mock discoveries."""
+ """Mock discoveries."""
result = await async_setup_component(hass, 'discovery', config)
assert result
diff --git a/tests/components/test_dyson.py b/tests/components/test_dyson.py
index 38f3e60dcf4..19c39754eb2 100644
--- a/tests/components/test_dyson.py
+++ b/tests/components/test_dyson.py
@@ -36,7 +36,7 @@ class DysonTest(unittest.TestCase):
"""Dyson parent component test class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_feedreader.py b/tests/components/test_feedreader.py
index 336d19664b4..dd98ebaf189 100644
--- a/tests/components/test_feedreader.py
+++ b/tests/components/test_feedreader.py
@@ -79,7 +79,7 @@ class TestFeedreaderComponent(unittest.TestCase):
VALID_CONFIG_3))
def setup_manager(self, feed_data, max_entries=DEFAULT_MAX_ENTRIES):
- """Generic test setup method."""
+ """Set up feed manager."""
events = []
@callback
diff --git a/tests/components/test_ffmpeg.py b/tests/components/test_ffmpeg.py
index 44c3a1dd695..76b1300774b 100644
--- a/tests/components/test_ffmpeg.py
+++ b/tests/components/test_ffmpeg.py
@@ -42,7 +42,7 @@ class TestFFmpegSetup:
"""Test class for ffmpeg."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self):
@@ -50,14 +50,14 @@ class TestFFmpegSetup:
self.hass.stop()
def test_setup_component(self):
- """Setup ffmpeg component."""
+ """Set up ffmpeg component."""
with assert_setup_component(2):
setup_component(self.hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
assert self.hass.data[ffmpeg.DATA_FFMPEG].binary == 'ffmpeg'
def test_setup_component_test_service(self):
- """Setup ffmpeg component test services."""
+ """Set up ffmpeg component test services."""
with assert_setup_component(2):
setup_component(self.hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -68,7 +68,7 @@ class TestFFmpegSetup:
@asyncio.coroutine
def test_setup_component_test_register(hass):
- """Setup ffmpeg component test register."""
+ """Set up ffmpeg component test register."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -83,7 +83,7 @@ def test_setup_component_test_register(hass):
@asyncio.coroutine
def test_setup_component_test_register_no_startup(hass):
- """Setup ffmpeg component test register without startup."""
+ """Set up ffmpeg component test register without startup."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -98,7 +98,7 @@ def test_setup_component_test_register_no_startup(hass):
@asyncio.coroutine
def test_setup_component_test_service_start(hass):
- """Setup ffmpeg component test service start."""
+ """Set up ffmpeg component test service start."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -114,7 +114,7 @@ def test_setup_component_test_service_start(hass):
@asyncio.coroutine
def test_setup_component_test_service_stop(hass):
- """Setup ffmpeg component test service stop."""
+ """Set up ffmpeg component test service stop."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -130,7 +130,7 @@ def test_setup_component_test_service_stop(hass):
@asyncio.coroutine
def test_setup_component_test_service_restart(hass):
- """Setup ffmpeg component test service restart."""
+ """Set up ffmpeg component test service restart."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -147,7 +147,7 @@ def test_setup_component_test_service_restart(hass):
@asyncio.coroutine
def test_setup_component_test_service_start_with_entity(hass):
- """Setup ffmpeg component test service start."""
+ """Set up ffmpeg component test service start."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -164,7 +164,7 @@ def test_setup_component_test_service_start_with_entity(hass):
@asyncio.coroutine
def test_setup_component_test_run_test_false(hass):
- """Setup ffmpeg component test run_test false."""
+ """Set up ffmpeg component test run_test false."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {
@@ -180,7 +180,7 @@ def test_setup_component_test_run_test_false(hass):
@asyncio.coroutine
def test_setup_component_test_run_test(hass):
- """Setup ffmpeg component test run_test."""
+ """Set up ffmpeg component test run_test."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
@@ -206,7 +206,7 @@ def test_setup_component_test_run_test(hass):
@asyncio.coroutine
def test_setup_component_test_run_test_test_fail(hass):
- """Setup ffmpeg component test run_test."""
+ """Set up ffmpeg component test run_test."""
with assert_setup_component(2):
yield from async_setup_component(
hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
diff --git a/tests/components/test_folder_watcher.py b/tests/components/test_folder_watcher.py
index b5ac9cca9d9..451d9ae3e0e 100644
--- a/tests/components/test_folder_watcher.py
+++ b/tests/components/test_folder_watcher.py
@@ -8,7 +8,7 @@ from tests.common import MockDependency
async def test_invalid_path_setup(hass):
- """Test that an invalid path is not setup."""
+ """Test that an invalid path is not set up."""
assert not await async_setup_component(
hass, folder_watcher.DOMAIN, {
folder_watcher.DOMAIN: {
diff --git a/tests/components/test_google.py b/tests/components/test_google.py
index 0ee066fcfee..b8dc29b5dea 100644
--- a/tests/components/test_google.py
+++ b/tests/components/test_google.py
@@ -14,7 +14,7 @@ class TestGoogle(unittest.TestCase):
"""Test the Google component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py
index 892fe5b5f4d..7ceda9e191e 100644
--- a/tests/components/test_graphite.py
+++ b/tests/components/test_graphite.py
@@ -17,7 +17,7 @@ class TestGraphite(unittest.TestCase):
"""Test the Graphite component."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.gf = graphite.GraphiteFeeder(self.hass, 'foo', 123, 'ha')
diff --git a/tests/components/test_history.py b/tests/components/test_history.py
index b348498b07e..ef2f7a17a19 100644
--- a/tests/components/test_history.py
+++ b/tests/components/test_history.py
@@ -17,7 +17,7 @@ class TestComponentHistory(unittest.TestCase):
"""Test History component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_history_graph.py b/tests/components/test_history_graph.py
index 554f7f29dd7..9b7733a7ec2 100644
--- a/tests/components/test_history_graph.py
+++ b/tests/components/test_history_graph.py
@@ -10,7 +10,7 @@ class TestGraph(unittest.TestCase):
"""Test the Google component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_igd.py b/tests/components/test_igd.py
index 87f992267c6..eb42b3127f6 100644
--- a/tests/components/test_igd.py
+++ b/tests/components/test_igd.py
@@ -47,7 +47,7 @@ class MockResp(MagicMock):
@pytest.fixture
def mock_msearch_first(*args, **kwargs):
- """Wrapper to async mock function."""
+ """Wrap async mock msearch_first."""
async def async_mock_msearch_first(*args, **kwargs):
"""Mock msearch_first."""
return MockResp(*args, **kwargs)
@@ -58,7 +58,7 @@ def mock_msearch_first(*args, **kwargs):
@pytest.fixture
def mock_async_exception(*args, **kwargs):
- """Wrapper to async mock function with exception."""
+ """Wrap async mock exception."""
async def async_mock_exception(*args, **kwargs):
return Exception
@@ -102,7 +102,8 @@ async def test_setup_succeeds_if_specify_ip(hass, mock_msearch_first):
return_value='127.0.0.1'):
result = await async_setup_component(hass, 'upnp', {
'upnp': {
- 'local_ip': '192.168.0.10'
+ 'local_ip': '192.168.0.10',
+ 'port_mapping': 'True'
}
})
@@ -118,7 +119,9 @@ async def test_no_config_maps_hass_local_to_remote_port(hass,
mock_msearch_first):
"""Test by default we map local to remote port."""
result = await async_setup_component(hass, 'upnp', {
- 'upnp': {}
+ 'upnp': {
+ 'port_mapping': 'True'
+ }
})
assert result
@@ -134,6 +137,7 @@ async def test_map_hass_to_remote_port(hass,
"""Test mapping hass to remote port."""
result = await async_setup_component(hass, 'upnp', {
'upnp': {
+ 'port_mapping': 'True',
'ports': {
'hass': 1000
}
@@ -157,6 +161,7 @@ async def test_map_internal_to_remote_ports(hass,
result = await async_setup_component(hass, 'upnp', {
'upnp': {
+ 'port_mapping': 'True',
'ports': ports
}
})
diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py
index e2323aca855..7d1b7527612 100644
--- a/tests/components/test_influxdb.py
+++ b/tests/components/test_influxdb.py
@@ -21,7 +21,7 @@ class TestInfluxDB(unittest.TestCase):
"""Test the InfluxDB component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.handler_method = None
self.hass.bus.listen = mock.Mock()
@@ -96,7 +96,7 @@ class TestInfluxDB(unittest.TestCase):
assert not setup_component(self.hass, influxdb.DOMAIN, config)
def _setup(self, **kwargs):
- """Setup the client."""
+ """Set up the client."""
config = {
'influxdb': {
'host': 'host',
diff --git a/tests/components/test_init.py b/tests/components/test_init.py
index 1e565054637..355f3dc0e96 100644
--- a/tests/components/test_init.py
+++ b/tests/components/test_init.py
@@ -25,7 +25,7 @@ class TestComponentsCore(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(run_coroutine_threadsafe(
comps.async_setup(self.hass, {}), self.hass.loop
diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py
index e39b12481bc..999e7ac100f 100644
--- a/tests/components/test_input_boolean.py
+++ b/tests/components/test_input_boolean.py
@@ -4,7 +4,7 @@ import asyncio
import unittest
import logging
-from homeassistant.core import CoreState, State
+from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_boolean import (
DOMAIN, is_on, toggle, turn_off, turn_on, CONF_INITIAL)
@@ -22,7 +22,7 @@ class TestInputBoolean(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -158,3 +158,24 @@ def test_initial_state_overrules_restore_state(hass):
state = hass.states.get('input_boolean.b2')
assert state
assert state.state == 'on'
+
+
+async def test_input_boolean_context(hass):
+ """Test that input_boolean context works."""
+ assert await async_setup_component(hass, 'input_boolean', {
+ 'input_boolean': {
+ 'ac': {CONF_INITIAL: True},
+ }
+ })
+
+ state = hass.states.get('input_boolean.ac')
+ assert state is not None
+
+ await hass.services.async_call('input_boolean', 'turn_off', {
+ 'entity_id': state.entity_id,
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('input_boolean.ac')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/test_input_datetime.py b/tests/components/test_input_datetime.py
index 5d3f1782831..9ced2aaa072 100644
--- a/tests/components/test_input_datetime.py
+++ b/tests/components/test_input_datetime.py
@@ -4,20 +4,29 @@ import asyncio
import unittest
import datetime
-from homeassistant.core import CoreState, State
+from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_datetime import (
- DOMAIN, async_set_datetime)
+ DOMAIN, ATTR_ENTITY_ID, ATTR_DATE, ATTR_TIME, SERVICE_SET_DATETIME)
from tests.common import get_test_home_assistant, mock_restore_cache
+async def async_set_datetime(hass, entity_id, dt_value):
+ """Set date and / or time of input_datetime."""
+ await hass.services.async_call(DOMAIN, SERVICE_SET_DATETIME, {
+ ATTR_ENTITY_ID: entity_id,
+ ATTR_DATE: dt_value.date(),
+ ATTR_TIME: dt_value.time()
+ }, blocking=True)
+
+
class TestInputDatetime(unittest.TestCase):
"""Test the input datetime component."""
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -57,7 +66,6 @@ def test_set_datetime(hass):
dt_obj = datetime.datetime(2017, 9, 7, 19, 46)
yield from async_set_datetime(hass, entity_id, dt_obj)
- yield from hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == str(dt_obj)
@@ -89,7 +97,6 @@ def test_set_datetime_time(hass):
time_portion = dt_obj.time()
yield from async_set_datetime(hass, entity_id, dt_obj)
- yield from hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == str(time_portion)
@@ -144,7 +151,6 @@ def test_set_datetime_date(hass):
date_portion = dt_obj.date()
yield from async_set_datetime(hass, entity_id, dt_obj)
- yield from hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == str(date_portion)
@@ -202,3 +208,27 @@ def test_restore_state(hass):
state_bogus = hass.states.get('input_datetime.test_bogus_data')
assert state_bogus.state == str(initial)
+
+
+async def test_input_datetime_context(hass):
+ """Test that input_datetime context works."""
+ assert await async_setup_component(hass, 'input_datetime', {
+ 'input_datetime': {
+ 'only_date': {
+ 'has_date': True,
+ }
+ }
+ })
+
+ state = hass.states.get('input_datetime.only_date')
+ assert state is not None
+
+ await hass.services.async_call('input_datetime', 'set_datetime', {
+ 'entity_id': state.entity_id,
+ 'date': '2018-01-02'
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('input_datetime.only_date')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/test_input_number.py b/tests/components/test_input_number.py
index fde940efa1a..659aaa524d9 100644
--- a/tests/components/test_input_number.py
+++ b/tests/components/test_input_number.py
@@ -3,7 +3,7 @@
import asyncio
import unittest
-from homeassistant.core import CoreState, State
+from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_number import (
DOMAIN, set_value, increment, decrement)
@@ -16,7 +16,7 @@ class TestInputNumber(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -236,3 +236,27 @@ def test_no_initial_state_and_no_restore_state(hass):
state = hass.states.get('input_number.b1')
assert state
assert float(state.state) == 0
+
+
+async def test_input_number_context(hass):
+ """Test that input_number context works."""
+ assert await async_setup_component(hass, 'input_number', {
+ 'input_number': {
+ 'b1': {
+ 'min': 0,
+ 'max': 100,
+ },
+ }
+ })
+
+ state = hass.states.get('input_number.b1')
+ assert state is not None
+
+ await hass.services.async_call('input_number', 'increment', {
+ 'entity_id': state.entity_id,
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('input_number.b1')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py
index 20d9656d8b0..1c73abfbb94 100644
--- a/tests/components/test_input_select.py
+++ b/tests/components/test_input_select.py
@@ -5,7 +5,7 @@ import unittest
from tests.common import get_test_home_assistant, mock_restore_cache
-from homeassistant.core import State
+from homeassistant.core import State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_select import (
ATTR_OPTIONS, DOMAIN, SERVICE_SET_OPTIONS,
@@ -19,7 +19,7 @@ class TestInputSelect(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -276,3 +276,30 @@ def test_initial_state_overrules_restore_state(hass):
state = hass.states.get('input_select.s2')
assert state
assert state.state == 'middle option'
+
+
+async def test_input_select_context(hass):
+ """Test that input_select context works."""
+ assert await async_setup_component(hass, 'input_select', {
+ 'input_select': {
+ 's1': {
+ 'options': [
+ 'first option',
+ 'middle option',
+ 'last option',
+ ],
+ }
+ }
+ })
+
+ state = hass.states.get('input_select.s1')
+ assert state is not None
+
+ await hass.services.async_call('input_select', 'select_next', {
+ 'entity_id': state.entity_id,
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('input_select.s1')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/test_input_text.py b/tests/components/test_input_text.py
index c288375ec8f..7c8a0e65023 100644
--- a/tests/components/test_input_text.py
+++ b/tests/components/test_input_text.py
@@ -3,7 +3,7 @@
import asyncio
import unittest
-from homeassistant.core import CoreState, State
+from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_text import (DOMAIN, set_value)
@@ -15,7 +15,7 @@ class TestInputText(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -180,3 +180,27 @@ def test_no_initial_state_and_no_restore_state(hass):
state = hass.states.get('input_text.b1')
assert state
assert str(state.state) == 'unknown'
+
+
+async def test_input_text_context(hass):
+ """Test that input_text context works."""
+ assert await async_setup_component(hass, 'input_text', {
+ 'input_text': {
+ 't1': {
+ 'initial': 'bla',
+ }
+ }
+ })
+
+ state = hass.states.get('input_text.t1')
+ assert state is not None
+
+ await hass.services.async_call('input_text', 'set_value', {
+ 'entity_id': state.entity_id,
+ 'value': 'new_value',
+ }, True, Context(user_id='abcd'))
+
+ state2 = hass.states.get('input_text.t1')
+ assert state2 is not None
+ assert state.state != state2.state
+ assert state2.context.user_id == 'abcd'
diff --git a/tests/components/test_introduction.py b/tests/components/test_introduction.py
index 99b373961cc..b7099d04878 100644
--- a/tests/components/test_introduction.py
+++ b/tests/components/test_introduction.py
@@ -11,7 +11,7 @@ class TestIntroduction(unittest.TestCase):
"""Test Introduction."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_kira.py b/tests/components/test_kira.py
index a80d766c3fd..67ab679800f 100644
--- a/tests/components/test_kira.py
+++ b/tests/components/test_kira.py
@@ -36,7 +36,7 @@ class TestKiraSetup(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
_base_mock = MagicMock()
pykira = _base_mock.pykira
diff --git a/tests/components/test_litejet.py b/tests/components/test_litejet.py
index dfbcb9d99d8..3b46e9d274c 100644
--- a/tests/components/test_litejet.py
+++ b/tests/components/test_litejet.py
@@ -12,7 +12,7 @@ class TestLiteJet(unittest.TestCase):
"""Test the litejet component."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.start()
self.hass.block_till_done()
diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py
index a3a5273ed4e..cf78fbec352 100644
--- a/tests/components/test_logbook.py
+++ b/tests/components/test_logbook.py
@@ -26,7 +26,7 @@ class TestComponentLogbook(unittest.TestCase):
EMPTY_CONFIG = logbook.CONFIG_SCHEMA({logbook.DOMAIN: {}})
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
init_recorder_component(self.hass) # Force an in memory DB
assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG)
diff --git a/tests/components/test_logentries.py b/tests/components/test_logentries.py
index bff80c958f3..843a043cee6 100644
--- a/tests/components/test_logentries.py
+++ b/tests/components/test_logentries.py
@@ -14,7 +14,7 @@ class TestLogentries(unittest.TestCase):
"""Test the Logentries component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_logger.py b/tests/components/test_logger.py
index a55a66c6505..f774af2169c 100644
--- a/tests/components/test_logger.py
+++ b/tests/components/test_logger.py
@@ -24,7 +24,7 @@ class TestUpdater(unittest.TestCase):
"""Test logger component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.log_filter = None
@@ -34,7 +34,7 @@ class TestUpdater(unittest.TestCase):
self.hass.stop()
def setup_logger(self, config):
- """Setup logger and save log filter."""
+ """Set up logger and save log filter."""
setup_component(self.hass, logger.DOMAIN, config)
self.log_filter = logging.root.handlers[-1].filters[0]
diff --git a/tests/components/test_microsoft_face.py b/tests/components/test_microsoft_face.py
index 92f840b8033..601d5e7ebcc 100644
--- a/tests/components/test_microsoft_face.py
+++ b/tests/components/test_microsoft_face.py
@@ -13,7 +13,7 @@ class TestMicrosoftFaceSetup:
"""Test the microsoft face component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -31,21 +31,21 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_component(self, mock_update):
- """Setup component."""
+ """Set up component."""
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_component_wrong_api_key(self, mock_update):
- """Setup component without api key."""
+ """Set up component without api key."""
with assert_setup_component(0, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, {mf.DOMAIN: {}})
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_setup_component_test_service(self, mock_update):
- """Setup component."""
+ """Set up component."""
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
@@ -57,7 +57,7 @@ class TestMicrosoftFaceSetup:
assert self.hass.services.has_service(mf.DOMAIN, 'face_person')
def test_setup_component_test_entities(self, aioclient_mock):
- """Setup component."""
+ """Set up component."""
aioclient_mock.get(
self.endpoint_url.format("persongroups"),
text=load_fixture('microsoft_face_persongroups.json')
@@ -95,7 +95,7 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_service_groups(self, mock_update, aioclient_mock):
- """Setup component, test groups services."""
+ """Set up component, test groups services."""
aioclient_mock.put(
self.endpoint_url.format("persongroups/service_group"),
status=200, text="{}"
@@ -123,7 +123,7 @@ class TestMicrosoftFaceSetup:
assert len(aioclient_mock.mock_calls) == 2
def test_service_person(self, aioclient_mock):
- """Setup component, test person services."""
+ """Set up component, test person services."""
aioclient_mock.get(
self.endpoint_url.format("persongroups"),
text=load_fixture('microsoft_face_persongroups.json')
@@ -175,7 +175,7 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_service_train(self, mock_update, aioclient_mock):
- """Setup component, test train groups services."""
+ """Set up component, test train groups services."""
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
@@ -192,7 +192,7 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.camera.async_get_image',
return_value=mock_coro(camera.Image('image/jpeg', b'Test')))
def test_service_face(self, camera_mock, aioclient_mock):
- """Setup component, test person face services."""
+ """Set up component, test person face services."""
aioclient_mock.get(
self.endpoint_url.format("persongroups"),
text=load_fixture('microsoft_face_persongroups.json')
@@ -229,7 +229,7 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_service_status_400(self, mock_update, aioclient_mock):
- """Setup component, test groups services with error."""
+ """Set up component, test groups services with error."""
aioclient_mock.put(
self.endpoint_url.format("persongroups/service_group"),
status=400, text="{'error': {'message': 'Error'}}"
@@ -248,7 +248,7 @@ class TestMicrosoftFaceSetup:
@patch('homeassistant.components.microsoft_face.'
'MicrosoftFace.update_store', return_value=mock_coro())
def test_service_status_timeout(self, mock_update, aioclient_mock):
- """Setup component, test groups services with timeout."""
+ """Set up component, test groups services with timeout."""
aioclient_mock.put(
self.endpoint_url.format("persongroups/service_group"),
status=400, exc=asyncio.TimeoutError()
diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py
index 8da1311c87d..1613198e4ce 100644
--- a/tests/components/test_mqtt_eventstream.py
+++ b/tests/components/test_mqtt_eventstream.py
@@ -6,7 +6,7 @@ from homeassistant.setup import setup_component
import homeassistant.components.mqtt_eventstream as eventstream
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import State, callback
-from homeassistant.remote import JSONEncoder
+from homeassistant.helpers.json import JSONEncoder
import homeassistant.util.dt as dt_util
from tests.common import (
@@ -22,7 +22,7 @@ class TestMqttEventStream:
"""Test the MQTT eventstream module."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_mqtt = mock_mqtt_component(self.hass)
diff --git a/tests/components/test_mqtt_statestream.py b/tests/components/test_mqtt_statestream.py
index 4cf79e679cd..97c4c0647e8 100644
--- a/tests/components/test_mqtt_statestream.py
+++ b/tests/components/test_mqtt_statestream.py
@@ -16,7 +16,7 @@ class TestMqttStateStream:
"""Test the MQTT statestream module."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_mqtt = mock_mqtt_component(self.hass)
diff --git a/tests/components/test_panel_custom.py b/tests/components/test_panel_custom.py
index 596aa1b3c0b..c265324179d 100644
--- a/tests/components/test_panel_custom.py
+++ b/tests/components/test_panel_custom.py
@@ -114,3 +114,73 @@ async def test_js_webcomponent(hass):
assert panel.frontend_url_path == 'nice_url'
assert panel.sidebar_icon == 'mdi:iconicon'
assert panel.sidebar_title == 'Sidebar Title'
+
+
+async def test_module_webcomponent(hass):
+ """Test if a js module is found in config panels dir."""
+ config = {
+ 'panel_custom': {
+ 'name': 'todo-mvc',
+ 'module_url': '/local/bla.js',
+ 'sidebar_title': 'Sidebar Title',
+ 'sidebar_icon': 'mdi:iconicon',
+ 'url_path': 'nice_url',
+ 'config': {
+ 'hello': 'world',
+ },
+ 'embed_iframe': True,
+ 'trust_external_script': True,
+ }
+ }
+
+ result = await setup.async_setup_component(
+ hass, 'panel_custom', config
+ )
+ assert result
+
+ panels = hass.data.get(frontend.DATA_PANELS, [])
+
+ assert panels
+ assert 'nice_url' in panels
+
+ panel = panels['nice_url']
+
+ assert panel.config == {
+ 'hello': 'world',
+ '_panel_custom': {
+ 'module_url': '/local/bla.js',
+ 'name': 'todo-mvc',
+ 'embed_iframe': True,
+ 'trust_external': True,
+ }
+ }
+ assert panel.frontend_url_path == 'nice_url'
+ assert panel.sidebar_icon == 'mdi:iconicon'
+ assert panel.sidebar_title == 'Sidebar Title'
+
+
+async def test_url_option_conflict(hass):
+ """Test config with multiple url options."""
+ to_try = [
+ {'panel_custom': {
+ 'name': 'todo-mvc',
+ 'module_url': '/local/bla.js',
+ 'js_url': '/local/bla.js',
+ }
+ }, {'panel_custom': {
+ 'name': 'todo-mvc',
+ 'webcomponent_path': '/local/bla.html',
+ 'js_url': '/local/bla.js',
+ }}, {'panel_custom': {
+ 'name': 'todo-mvc',
+ 'webcomponent_path': '/local/bla.html',
+ 'module_url': '/local/bla.js',
+ 'js_url': '/local/bla.js',
+ }}
+ ]
+
+ for config in to_try:
+ result = await setup.async_setup_component(
+ hass, 'panel_custom', config
+ )
+ assert not result
diff --git a/tests/components/test_panel_iframe.py b/tests/components/test_panel_iframe.py
index 214eda04ad8..3ac06c09a26 100644
--- a/tests/components/test_panel_iframe.py
+++ b/tests/components/test_panel_iframe.py
@@ -11,7 +11,7 @@ class TestPanelIframe(unittest.TestCase):
"""Test the panel_iframe component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_pilight.py b/tests/components/test_pilight.py
index 24052a56839..e630a354f45 100644
--- a/tests/components/test_pilight.py
+++ b/tests/components/test_pilight.py
@@ -41,11 +41,11 @@ class PilightDaemonSim:
pass
def send_code(self, call): # pylint: disable=no-self-use
- """Called pilight.send service is called."""
+ """Handle pilight.send service callback."""
_LOGGER.error('PilightDaemonSim payload: ' + str(call))
def start(self):
- """Called homeassistant.start is called.
+ """Handle homeassistant.start callback.
Also sends one test message after start up
"""
@@ -56,11 +56,11 @@ class PilightDaemonSim:
self.called = True
def stop(self): # pylint: disable=no-self-use
- """Called homeassistant.stop is called."""
+ """Handle homeassistant.stop callback."""
_LOGGER.error('PilightDaemonSim stop')
def set_callback(self, function):
- """Callback called on event pilight.pilight_received."""
+ """Handle pilight.pilight_received event callback."""
self.callback = function
_LOGGER.error('PilightDaemonSim callback: ' + str(function))
@@ -70,7 +70,7 @@ class TestPilight(unittest.TestCase):
"""Test the Pilight component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.skip_teardown_stop = False
@@ -351,7 +351,7 @@ class TestPilightCallrateThrottler(unittest.TestCase):
"""Test the Throttler used to throttle calls to send_code."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py
index 42f1cbf4b43..f69ace46014 100644
--- a/tests/components/test_proximity.py
+++ b/tests/components/test_proximity.py
@@ -12,7 +12,7 @@ class TestProximity(unittest.TestCase):
"""Test the Proximity component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.states.set(
'zone.home', 'zoning',
diff --git a/tests/components/test_rest_command.py b/tests/components/test_rest_command.py
index 097fb799d40..b66628a3562 100644
--- a/tests/components/test_rest_command.py
+++ b/tests/components/test_rest_command.py
@@ -14,7 +14,7 @@ class TestRestCommandSetup:
"""Test the rest command component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
@@ -51,7 +51,7 @@ class TestRestCommandComponent:
"""Test the rest command component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.url = "https://example.com/"
self.config = {
rc.DOMAIN: {
@@ -81,7 +81,7 @@ class TestRestCommandComponent:
self.hass.stop()
def test_setup_tests(self):
- """Setup test config and test it."""
+ """Set up test config and test it."""
with assert_setup_component(4):
setup_component(self.hass, rc.DOMAIN, self.config)
diff --git a/tests/components/test_rfxtrx.py b/tests/components/test_rfxtrx.py
index 1730d3a5371..93bf0b16dc5 100644
--- a/tests/components/test_rfxtrx.py
+++ b/tests/components/test_rfxtrx.py
@@ -15,7 +15,7 @@ class TestRFXTRX(unittest.TestCase):
"""Test the Rfxtrx component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_rss_feed_template.py b/tests/components/test_rss_feed_template.py
index 36f68e57c9f..64876dbea44 100644
--- a/tests/components/test_rss_feed_template.py
+++ b/tests/components/test_rss_feed_template.py
@@ -9,7 +9,7 @@ from homeassistant.setup import async_setup_component
@pytest.fixture
def mock_http_client(loop, hass, aiohttp_client):
- """Setup test fixture."""
+ """Set up test fixture."""
config = {
'rss_feed_template': {
'testfeed': {
diff --git a/tests/components/test_script.py b/tests/components/test_script.py
index c4282cdfbaf..8ad782cf697 100644
--- a/tests/components/test_script.py
+++ b/tests/components/test_script.py
@@ -18,7 +18,7 @@ class TestScriptComponent(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/components/test_shell_command.py b/tests/components/test_shell_command.py
index a1acffd62e5..e945befbb84 100644
--- a/tests/components/test_shell_command.py
+++ b/tests/components/test_shell_command.py
@@ -33,7 +33,7 @@ class TestShellCommand(unittest.TestCase):
"""Test the shell_command component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started.
+ """Set up things to be run when tests are started.
Also seems to require a child watcher attached to the loop when run
from pytest.
diff --git a/tests/components/test_splunk.py b/tests/components/test_splunk.py
index 38143119112..173c822ddb6 100644
--- a/tests/components/test_splunk.py
+++ b/tests/components/test_splunk.py
@@ -16,7 +16,7 @@ class TestSplunk(unittest.TestCase):
"""Test the Splunk component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py
index 5fd907fe0b1..6bd00e50646 100644
--- a/tests/components/test_statsd.py
+++ b/tests/components/test_statsd.py
@@ -16,7 +16,7 @@ class TestStatsd(unittest.TestCase):
"""Test the StatsD component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py
index d5a4ecfcb81..aa94bf2bdd3 100644
--- a/tests/components/test_sun.py
+++ b/tests/components/test_sun.py
@@ -17,7 +17,7 @@ class TestSun(unittest.TestCase):
"""Test the sun module."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py
index 28ffcac2b13..23b669928f4 100644
--- a/tests/components/test_updater.py
+++ b/tests/components/test_updater.py
@@ -46,7 +46,7 @@ def test_new_version_shows_entity_after_hour(
res = yield from async_setup_component(
hass, updater.DOMAIN, {updater.DOMAIN: {}})
- assert res, 'Updater failed to setup'
+ assert res, 'Updater failed to set up'
with patch('homeassistant.components.updater.current_version',
MOCK_VERSION):
@@ -65,7 +65,7 @@ def test_same_version_not_show_entity(
res = yield from async_setup_component(
hass, updater.DOMAIN, {updater.DOMAIN: {}})
- assert res, 'Updater failed to setup'
+ assert res, 'Updater failed to set up'
with patch('homeassistant.components.updater.current_version',
MOCK_VERSION):
@@ -85,7 +85,7 @@ def test_disable_reporting(hass, mock_get_uuid, mock_get_newest_version):
hass, updater.DOMAIN, {updater.DOMAIN: {
'reporting': False
}})
- assert res, 'Updater failed to setup'
+ assert res, 'Updater failed to set up'
with patch('homeassistant.components.updater.current_version',
MOCK_VERSION):
@@ -187,7 +187,7 @@ def test_new_version_shows_entity_after_hour_hassio(
res = yield from async_setup_component(
hass, updater.DOMAIN, {updater.DOMAIN: {}})
- assert res, 'Updater failed to setup'
+ assert res, 'Updater failed to set up'
with patch('homeassistant.components.updater.current_version',
MOCK_VERSION):
diff --git a/tests/components/test_weblink.py b/tests/components/test_weblink.py
index f35398e034c..8e71c89cdd6 100644
--- a/tests/components/test_weblink.py
+++ b/tests/components/test_weblink.py
@@ -11,7 +11,7 @@ class TestComponentWeblink(unittest.TestCase):
"""Test the Weblink component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/components/tts/test_google.py b/tests/components/tts/test_google.py
index cf9a7b2db29..f328e3e9f16 100644
--- a/tests/components/tts/test_google.py
+++ b/tests/components/tts/test_google.py
@@ -19,7 +19,7 @@ class TestTTSGooglePlatform:
"""Test the Google speech component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.url = "https://translate.google.com/translate_tts"
diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py
index e8746ee762f..719fe2716e7 100644
--- a/tests/components/tts/test_init.py
+++ b/tests/components/tts/test_init.py
@@ -33,7 +33,7 @@ class TestTTS:
"""Test the Google speech component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.demo_provider = DemoProvider('en')
self.default_tts_cache = self.hass.config.path(tts.DEFAULT_CACHE_DIR)
@@ -44,13 +44,13 @@ class TestTTS:
def teardown_method(self):
"""Stop everything that was started."""
+ self.hass.stop()
+
if os.path.isdir(self.default_tts_cache):
shutil.rmtree(self.default_tts_cache)
- self.hass.stop()
-
def test_setup_component_demo(self):
- """Setup the demo platform with defaults."""
+ """Set up the demo platform with defaults."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -65,7 +65,7 @@ class TestTTS:
@patch('os.mkdir', side_effect=OSError(2, "No access"))
def test_setup_component_demo_no_access_cache_folder(self, mock_mkdir):
- """Setup the demo platform with defaults."""
+ """Set up the demo platform with defaults."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -78,7 +78,7 @@ class TestTTS:
assert not self.hass.services.has_service(tts.DOMAIN, 'clear_cache')
def test_setup_component_and_test_service(self):
- """Setup the demo platform and call service."""
+ """Set up the demo platform and call service."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -106,7 +106,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"))
def test_setup_component_and_test_service_with_config_language(self):
- """Setup the demo platform and call service."""
+ """Set up the demo platform and call service."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -135,7 +135,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3"))
def test_setup_component_and_test_service_with_wrong_conf_language(self):
- """Setup the demo platform and call service with wrong config."""
+ """Set up the demo platform and call service with wrong config."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -147,7 +147,7 @@ class TestTTS:
setup_component(self.hass, tts.DOMAIN, config)
def test_setup_component_and_test_service_with_service_language(self):
- """Setup the demo platform and call service."""
+ """Set up the demo platform and call service."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -176,7 +176,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3"))
def test_setup_component_test_service_with_wrong_service_language(self):
- """Setup the demo platform and call service."""
+ """Set up the demo platform and call service."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -200,7 +200,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_lang_-_demo.mp3"))
def test_setup_component_and_test_service_with_service_options(self):
- """Setup the demo platform and call service with options."""
+ """Set up the demo platform and call service with options."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -237,7 +237,7 @@ class TestTTS:
@patch('homeassistant.components.tts.demo.DemoProvider.default_options',
new_callable=PropertyMock(return_value={'voice': 'alex'}))
def test_setup_component_and_test_with_service_options_def(self, def_mock):
- """Setup the demo platform and call service with default options."""
+ """Set up the demo platform and call service with default options."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -269,7 +269,7 @@ class TestTTS:
opt_hash)))
def test_setup_component_and_test_service_with_service_options_wrong(self):
- """Setup the demo platform and call service with wrong options."""
+ """Set up the demo platform and call service with wrong options."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -299,7 +299,7 @@ class TestTTS:
opt_hash)))
def test_setup_component_and_test_service_clear_cache(self):
- """Setup the demo platform and call service clear cache."""
+ """Set up the demo platform and call service clear cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -329,7 +329,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"))
def test_setup_component_and_test_service_with_receive_voice(self):
- """Setup the demo platform and call service and receive voice."""
+ """Set up the demo platform and call service and receive voice."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -359,7 +359,7 @@ class TestTTS:
assert req.content == demo_data
def test_setup_component_and_test_service_with_receive_voice_german(self):
- """Setup the demo platform and call service and receive voice."""
+ """Set up the demo platform and call service and receive voice."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -390,7 +390,7 @@ class TestTTS:
assert req.content == demo_data
def test_setup_component_and_web_view_wrong_file(self):
- """Setup the demo platform and receive wrong file from web."""
+ """Set up the demo platform and receive wrong file from web."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -409,7 +409,7 @@ class TestTTS:
assert req.status_code == 404
def test_setup_component_and_web_view_wrong_filename(self):
- """Setup the demo platform and receive wrong filename from web."""
+ """Set up the demo platform and receive wrong filename from web."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -428,7 +428,7 @@ class TestTTS:
assert req.status_code == 404
def test_setup_component_test_without_cache(self):
- """Setup demo platform without cache."""
+ """Set up demo platform without cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -452,7 +452,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"))
def test_setup_component_test_with_cache_call_service_without_cache(self):
- """Setup demo platform with cache and call service without cache."""
+ """Set up demo platform with cache and call service without cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -477,7 +477,7 @@ class TestTTS:
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"))
def test_setup_component_test_with_cache_dir(self):
- """Setup demo platform with cache and call service without cache."""
+ """Set up demo platform with cache and call service without cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
_, demo_data = self.demo_provider.get_tts_audio("bla", 'en')
@@ -515,7 +515,7 @@ class TestTTS:
@patch('homeassistant.components.tts.demo.DemoProvider.get_tts_audio',
return_value=(None, None))
def test_setup_component_test_with_error_on_get_tts(self, tts_mock):
- """Setup demo platform with wrong get_tts_audio."""
+ """Set up demo platform with wrong get_tts_audio."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
@@ -535,7 +535,7 @@ class TestTTS:
assert len(calls) == 0
def test_setup_component_load_cache_retrieve_without_mem_cache(self):
- """Setup component and load cache and get without mem cache."""
+ """Set up component and load cache and get without mem cache."""
_, demo_data = self.demo_provider.get_tts_audio("bla", 'en')
cache_file = os.path.join(
self.default_tts_cache,
@@ -565,7 +565,7 @@ class TestTTS:
assert req.content == demo_data
def test_setup_component_and_web_get_url(self):
- """Setup the demo platform and receive wrong file from web."""
+ """Set up the demo platform and receive wrong file from web."""
config = {
tts.DOMAIN: {
'platform': 'demo',
@@ -589,7 +589,7 @@ class TestTTS:
.format(self.hass.config.api.base_url))
def test_setup_component_and_web_get_url_bad_config(self):
- """Setup the demo platform and receive wrong file from web."""
+ """Set up the demo platform and receive wrong file from web."""
config = {
tts.DOMAIN: {
'platform': 'demo',
diff --git a/tests/components/tts/test_marytts.py b/tests/components/tts/test_marytts.py
index 7ec2ae39cd6..110473d75a8 100644
--- a/tests/components/tts/test_marytts.py
+++ b/tests/components/tts/test_marytts.py
@@ -18,7 +18,7 @@ class TestTTSMaryTTSPlatform:
"""Test the speech component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.url = "http://localhost:59125/process?"
diff --git a/tests/components/tts/test_voicerss.py b/tests/components/tts/test_voicerss.py
index 365cf1ff73b..334e35a9386 100644
--- a/tests/components/tts/test_voicerss.py
+++ b/tests/components/tts/test_voicerss.py
@@ -18,7 +18,7 @@ class TestTTSVoiceRSSPlatform:
"""Test the voicerss speech component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.url = "https://api.voicerss.org/"
diff --git a/tests/components/tts/test_yandextts.py b/tests/components/tts/test_yandextts.py
index 82d20318928..2675e322507 100644
--- a/tests/components/tts/test_yandextts.py
+++ b/tests/components/tts/test_yandextts.py
@@ -17,7 +17,7 @@ class TestTTSYandexPlatform:
"""Test the speech component."""
def setup_method(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self._base_url = "https://tts.voicetech.yandex.net/generate?"
diff --git a/tests/components/vacuum/test_demo.py b/tests/components/vacuum/test_demo.py
index bd6f2ae543c..1fc8f8cd5c1 100644
--- a/tests/components/vacuum/test_demo.py
+++ b/tests/components/vacuum/test_demo.py
@@ -30,7 +30,7 @@ class TestVacuumDemo(unittest.TestCase):
"""Test the Demo vacuum."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(
self.hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: 'demo'}}))
diff --git a/tests/components/vacuum/test_dyson.py b/tests/components/vacuum/test_dyson.py
index 8a4e6d57b91..5e32fc9daef 100644
--- a/tests/components/vacuum/test_dyson.py
+++ b/tests/components/vacuum/test_dyson.py
@@ -67,7 +67,7 @@ class DysonTest(unittest.TestCase):
"""Dyson 360 eye robot vacuum component test class."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/components/weather/test_darksky.py b/tests/components/weather/test_darksky.py
index 41687451cd6..5423943e6fd 100644
--- a/tests/components/weather/test_darksky.py
+++ b/tests/components/weather/test_darksky.py
@@ -17,7 +17,7 @@ class TestDarkSky(unittest.TestCase):
"""Test the Dark Sky weather component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
self.lat = self.hass.config.latitude = 37.8267
diff --git a/tests/components/weather/test_ipma.py b/tests/components/weather/test_ipma.py
index 7df6166a2b6..d438e118573 100644
--- a/tests/components/weather/test_ipma.py
+++ b/tests/components/weather/test_ipma.py
@@ -52,7 +52,7 @@ class TestIPMA(unittest.TestCase):
"""Test the IPMA weather component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
self.lat = self.hass.config.latitude = 40.00
diff --git a/tests/components/weather/test_weather.py b/tests/components/weather/test_weather.py
index a88e9979551..42b1dacc5f8 100644
--- a/tests/components/weather/test_weather.py
+++ b/tests/components/weather/test_weather.py
@@ -17,7 +17,7 @@ class TestWeather(unittest.TestCase):
"""Test the Weather component."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
self.assertTrue(setup_component(self.hass, weather.DOMAIN, {
diff --git a/tests/components/weather/test_yweather.py b/tests/components/weather/test_yweather.py
index 3e5eff9dae7..c36b4454c93 100644
--- a/tests/components/weather/test_yweather.py
+++ b/tests/components/weather/test_yweather.py
@@ -41,31 +41,31 @@ class YahooWeatherMock():
@property
def RawData(self): # pylint: disable=invalid-name
- """Raw Data."""
+ """Return raw Data."""
if self.woeid == '12345':
return json.loads('[]')
return self._data
@property
def Now(self): # pylint: disable=invalid-name
- """Current weather data."""
+ """Return current weather data."""
if self.woeid == '111':
raise ValueError
return self._data['query']['results']['channel']['item']['condition']
@property
def Atmosphere(self): # pylint: disable=invalid-name
- """Atmosphere weather data."""
+ """Return atmosphere weather data."""
return self._data['query']['results']['channel']['atmosphere']
@property
def Wind(self): # pylint: disable=invalid-name
- """Wind weather data."""
+ """Return wind weather data."""
return self._data['query']['results']['channel']['wind']
@property
def Forecast(self): # pylint: disable=invalid-name
- """Forecast data 0-5 Days."""
+ """Return forecast data 0-5 Days."""
if self.woeid == '123123':
raise ValueError
return self._data['query']['results']['channel']['item']['forecast']
@@ -83,7 +83,7 @@ class TestWeather(unittest.TestCase):
self.DEVICES.append(device)
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py
index 92dee05818d..ba98915e777 100644
--- a/tests/components/zone/test_init.py
+++ b/tests/components/zone/test_init.py
@@ -42,7 +42,7 @@ class TestComponentZone(unittest.TestCase):
"""Test the zone component."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
diff --git a/tests/conftest.py b/tests/conftest.py
index 28c47948666..61c5c1c7dd5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,4 @@
-"""Setup some common test helper things."""
+"""Set up some common test helper things."""
import asyncio
import functools
import logging
diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py
index ccfe1b1aff9..699342381f9 100644
--- a/tests/helpers/test_aiohttp_client.py
+++ b/tests/helpers/test_aiohttp_client.py
@@ -30,7 +30,7 @@ class TestHelpersAiohttpClient(unittest.TestCase):
"""Test homeassistant.helpers.aiohttp_client module."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py
index aa7b5170648..69fab77715c 100644
--- a/tests/helpers/test_condition.py
+++ b/tests/helpers/test_condition.py
@@ -11,7 +11,7 @@ class TestConditionHelper:
"""Test condition helpers."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py
index 9eede7dff9b..a9b4dc158e0 100644
--- a/tests/helpers/test_config_entry_flow.py
+++ b/tests/helpers/test_config_entry_flow.py
@@ -49,7 +49,7 @@ async def test_user_no_devices_found(hass, flow_conf):
async def test_user_no_confirmation(hass, flow_conf):
- """Test user requires no confirmation to setup."""
+ """Test user requires no confirmation to set up."""
flow = config_entries.HANDLERS['test']()
flow.hass = hass
flow_conf['discovered'] = True
@@ -118,7 +118,7 @@ async def test_user_init_trumps_discovery(hass, flow_conf):
async def test_import_no_confirmation(hass, flow_conf):
- """Test import requires no confirmation to setup."""
+ """Test import requires no confirmation to set up."""
flow = config_entries.HANDLERS['test']()
flow.hass = hass
flow_conf['discovered'] = True
diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py
index 28efcb3e868..ab575c61789 100644
--- a/tests/helpers/test_config_validation.py
+++ b/tests/helpers/test_config_validation.py
@@ -523,21 +523,6 @@ def test_has_at_least_one_key():
schema(value)
-def test_has_at_least_one_key_value():
- """Test has_at_least_one_key_value validator."""
- schema = vol.Schema(cv.has_at_least_one_key_value(('drink', 'beer'),
- ('drink', 'soda'),
- ('food', 'maultaschen')))
-
- for value in (None, [], {}, {'wine': None}, {'drink': 'water'}):
- with pytest.raises(vol.MultipleInvalid):
- schema(value)
-
- for value in ({'drink': 'beer'}, {'food': 'maultaschen'},
- {'drink': 'soda', 'food': 'maultaschen'}):
- schema(value)
-
-
def test_enum():
"""Test enum validator."""
class TestEnum(enum.Enum):
diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py
new file mode 100644
index 00000000000..41e7d39e977
--- /dev/null
+++ b/tests/helpers/test_device_registry.py
@@ -0,0 +1,78 @@
+"""Tests for the Device Registry."""
+import pytest
+
+from homeassistant.helpers import device_registry
+
+
+def mock_registry(hass, mock_entries=None):
+ """Mock the Device Registry."""
+ registry = device_registry.DeviceRegistry(hass)
+ registry.devices = mock_entries or []
+
+ async def _get_reg():
+ return registry
+
+ hass.data[device_registry.DATA_REGISTRY] = \
+ hass.loop.create_task(_get_reg())
+ return registry
+
+
+@pytest.fixture
+def registry(hass):
+ """Return an empty, loaded, registry."""
+ return mock_registry(hass)
+
+
+async def test_get_or_create_returns_same_entry(registry):
+ """Make sure we do not duplicate entries."""
+ entry = registry.async_get_or_create(
+ [['bridgeid', '0123']], 'manufacturer', 'model',
+ [['ethernet', '12:34:56:78:90:AB:CD:EF']])
+ entry2 = registry.async_get_or_create(
+ [['bridgeid', '0123']], 'manufacturer', 'model',
+ [['ethernet', '11:22:33:44:55:66:77:88']])
+ entry3 = registry.async_get_or_create(
+ [['bridgeid', '1234']], 'manufacturer', 'model',
+ [['ethernet', '12:34:56:78:90:AB:CD:EF']])
+
+ assert len(registry.devices) == 1
+ assert entry is entry2
+ assert entry is entry3
+ assert entry.identifiers == [['bridgeid', '0123']]
+
+
+async def test_loading_from_storage(hass, hass_storage):
+ """Test loading stored devices on start."""
+ hass_storage[device_registry.STORAGE_KEY] = {
+ 'version': device_registry.STORAGE_VERSION,
+ 'data': {
+ 'devices': [
+ {
+ 'connection': [
+ [
+ 'Zigbee',
+ '01.23.45.67.89'
+ ]
+ ],
+ 'id': 'abcdefghijklm',
+ 'identifiers': [
+ [
+ 'serial',
+ '12:34:56:78:90:AB:CD:EF'
+ ]
+ ],
+ 'manufacturer': 'manufacturer',
+ 'model': 'model',
+ 'name': 'name',
+ 'sw_version': 'version'
+ }
+ ]
+ }
+ }
+
+ registry = await device_registry.async_get_registry(hass)
+
+ entry = registry.async_get_or_create(
+ [['serial', '12:34:56:78:90:AB:CD:EF']], 'manufacturer',
+ 'model', [['Zigbee', '01.23.45.67.89']])
+ assert entry.id == 'abcdefghijklm'
diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py
index c7b39954d85..8ee8d4596fe 100644
--- a/tests/helpers/test_discovery.py
+++ b/tests/helpers/test_discovery.py
@@ -16,7 +16,7 @@ class TestHelpersDiscovery:
"""Tests for discovery helper methods."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
@@ -117,7 +117,7 @@ class TestHelpersDiscovery:
platform_calls = []
def component_setup(hass, config):
- """Setup mock component."""
+ """Set up mock component."""
discovery.load_platform(hass, 'switch', 'test_circular', 'disc',
config)
component_calls.append(1)
@@ -125,7 +125,7 @@ class TestHelpersDiscovery:
def setup_platform(hass, config, add_devices_callback,
discovery_info=None):
- """Setup mock platform."""
+ """Set up mock platform."""
platform_calls.append('disc' if discovery_info else 'component')
loader.set_component(
@@ -158,21 +158,21 @@ class TestHelpersDiscovery:
def test_1st_discovers_2nd_component(self, mock_signal):
"""Test that we don't break if one component discovers the other.
- If the first component fires a discovery event to setup the
- second component while the second component is about to be setup,
- it should not setup the second component twice.
+ If the first component fires a discovery event to set up the
+ second component while the second component is about to be set up,
+ it should not set up the second component twice.
"""
component_calls = []
def component1_setup(hass, config):
- """Setup mock component."""
+ """Set up mock component."""
print('component1 setup')
discovery.discover(hass, 'test_component2',
component='test_component2')
return True
def component2_setup(hass, config):
- """Setup mock component."""
+ """Set up mock component."""
component_calls.append(1)
return True
@@ -186,7 +186,7 @@ class TestHelpersDiscovery:
@callback
def do_setup():
- """Setup 2 components."""
+ """Set up 2 components."""
self.hass.async_add_job(setup.async_setup_component(
self.hass, 'test_component1', {}))
self.hass.async_add_job(setup.async_setup_component(
diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py
index 55e67def2bc..ef1ad2336eb 100644
--- a/tests/helpers/test_dispatcher.py
+++ b/tests/helpers/test_dispatcher.py
@@ -12,7 +12,7 @@ class TestHelpersDispatcher:
"""Tests for discovery helper methods."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py
index e24bec489f4..a51787225ca 100644
--- a/tests/helpers/test_entity.py
+++ b/tests/helpers/test_entity.py
@@ -1,11 +1,13 @@
"""Test the entity helper."""
# pylint: disable=protected-access
import asyncio
-from unittest.mock import MagicMock, patch
+from datetime import timedelta
+from unittest.mock import MagicMock, patch, PropertyMock
import pytest
import homeassistant.helpers.entity as entity
+from homeassistant.core import Context
from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS
from homeassistant.config import DATA_CUSTOMIZE
from homeassistant.helpers.entity_values import EntityValues
@@ -75,7 +77,7 @@ class TestHelpersEntity:
"""Test homeassistant.helpers.entity module."""
def setup_method(self, method):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.entity = entity.Entity()
self.entity.entity_id = 'test.overwrite_hidden_true'
self.hass = self.entity.hass = get_test_home_assistant()
@@ -412,3 +414,32 @@ async def test_async_remove_runs_callbacks(hass):
ent.async_on_remove(lambda: result.append(1))
await ent.async_remove()
assert len(result) == 1
+
+
+async def test_set_context(hass):
+ """Test setting context."""
+ context = Context()
+ ent = entity.Entity()
+ ent.hass = hass
+ ent.entity_id = 'hello.world'
+ ent.async_set_context(context)
+ await ent.async_update_ha_state()
+ assert hass.states.get('hello.world').context == context
+
+
+async def test_set_context_expired(hass):
+ """Test setting context."""
+ context = Context()
+
+ with patch.object(entity.Entity, 'context_recent_time',
+ new_callable=PropertyMock) as recent:
+ recent.return_value = timedelta(seconds=-5)
+ ent = entity.Entity()
+ ent.hass = hass
+ ent.entity_id = 'hello.world'
+ ent.async_set_context(context)
+ await ent.async_update_ha_state()
+
+ assert hass.states.get('hello.world').context != context
+ assert ent._context is None
+ assert ent._context_set is None
diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py
index b4910723c8d..1ce12e0b9ad 100644
--- a/tests/helpers/test_entity_component.py
+++ b/tests/helpers/test_entity_component.py
@@ -39,7 +39,7 @@ class TestHelpersEntityComponent(unittest.TestCase):
self.hass.stop()
def test_setting_up_group(self):
- """Setup the setting of a group."""
+ """Set up the setting of a group."""
setup_component(self.hass, 'group', {'group': {}})
component = EntityComponent(_LOGGER, DOMAIN, self.hass,
group_name='everyone')
diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py
index b52405aa8be..07901f7aad4 100644
--- a/tests/helpers/test_entity_platform.py
+++ b/tests/helpers/test_entity_platform.py
@@ -336,7 +336,7 @@ def test_raise_error_on_update(hass):
entity2 = MockEntity(name='test_2')
def _raise():
- """Helper to raise an exception."""
+ """Raise an exception."""
raise AssertionError
entity1.update = _raise
diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py
index 5a9efd5c041..d0c088a6f69 100644
--- a/tests/helpers/test_entity_registry.py
+++ b/tests/helpers/test_entity_registry.py
@@ -1,6 +1,6 @@
"""Tests for the Entity Registry."""
import asyncio
-from unittest.mock import patch, mock_open
+from unittest.mock import patch
import pytest
@@ -61,29 +61,13 @@ def test_get_or_create_suggested_object_id_conflict_existing(hass, registry):
@asyncio.coroutine
def test_create_triggers_save(hass, registry):
"""Test that registering entry triggers a save."""
- with patch.object(hass.loop, 'call_later') as mock_call_later:
+ with patch.object(registry, 'async_schedule_save') as mock_schedule_save:
registry.async_get_or_create('light', 'hue', '1234')
- assert len(mock_call_later.mock_calls) == 1
+ assert len(mock_schedule_save.mock_calls) == 1
-@asyncio.coroutine
-def test_save_timer_reset_on_subsequent_save(hass, registry):
- """Test we reset the save timer on a new create."""
- with patch.object(hass.loop, 'call_later') as mock_call_later:
- registry.async_get_or_create('light', 'hue', '1234')
-
- assert len(mock_call_later.mock_calls) == 1
-
- with patch.object(hass.loop, 'call_later') as mock_call_later_2:
- registry.async_get_or_create('light', 'hue', '5678')
-
- assert len(mock_call_later().cancel.mock_calls) == 1
- assert len(mock_call_later_2.mock_calls) == 1
-
-
-@asyncio.coroutine
-def test_loading_saving_data(hass, registry):
+async def test_loading_saving_data(hass, registry):
"""Test that we load/save data correctly."""
orig_entry1 = registry.async_get_or_create('light', 'hue', '1234')
orig_entry2 = registry.async_get_or_create(
@@ -91,18 +75,11 @@ def test_loading_saving_data(hass, registry):
assert len(registry.entities) == 2
- with patch(YAML__OPEN_PATH, mock_open(), create=True) as mock_write:
- yield from registry._async_save()
-
- # Mock open calls are: open file, context enter, write, context leave
- written = mock_write.mock_calls[2][1][0]
-
# Now load written data in new registry
registry2 = entity_registry.EntityRegistry(hass)
+ registry2._store = registry._store
- with patch('os.path.isfile', return_value=True), \
- patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True):
- yield from registry2._async_load()
+ await registry2.async_load()
# Ensure same order
assert list(registry.entities) == list(registry2.entities)
@@ -139,32 +116,37 @@ def test_is_registered(registry):
assert not registry.async_is_registered('light.non_existing')
-@asyncio.coroutine
-def test_loading_extra_values(hass):
+async def test_loading_extra_values(hass, hass_storage):
"""Test we load extra data from the registry."""
- written = """
-test.named:
- platform: super_platform
- unique_id: with-name
- name: registry override
-test.no_name:
- platform: super_platform
- unique_id: without-name
-test.disabled_user:
- platform: super_platform
- unique_id: disabled-user
- disabled_by: user
-test.disabled_hass:
- platform: super_platform
- unique_id: disabled-hass
- disabled_by: hass
-"""
+ hass_storage[entity_registry.STORAGE_KEY] = {
+ 'version': entity_registry.STORAGE_VERSION,
+ 'data': {
+ 'entities': [
+ {
+ 'entity_id': 'test.named',
+ 'platform': 'super_platform',
+ 'unique_id': 'with-name',
+ 'name': 'registry override',
+ }, {
+ 'entity_id': 'test.no_name',
+ 'platform': 'super_platform',
+ 'unique_id': 'without-name',
+ }, {
+ 'entity_id': 'test.disabled_user',
+ 'platform': 'super_platform',
+ 'unique_id': 'disabled-user',
+ 'disabled_by': 'user',
+ }, {
+ 'entity_id': 'test.disabled_hass',
+ 'platform': 'super_platform',
+ 'unique_id': 'disabled-hass',
+ 'disabled_by': 'hass',
+ }
+ ]
+ }
+ }
- registry = entity_registry.EntityRegistry(hass)
-
- with patch('os.path.isfile', return_value=True), \
- patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True):
- yield from registry._async_load()
+ registry = await entity_registry.async_get_registry(hass)
entry_with_name = registry.async_get_or_create(
'test', 'super_platform', 'with-name')
@@ -202,3 +184,31 @@ async def test_updating_config_entry_id(registry):
'light', 'hue', '5678', config_entry_id='mock-id-2')
assert entry.entity_id == entry2.entity_id
assert entry2.config_entry_id == 'mock-id-2'
+
+
+async def test_migration(hass):
+ """Test migration from old data to new."""
+ old_conf = {
+ 'light.kitchen': {
+ 'config_entry_id': 'test-config-id',
+ 'unique_id': 'test-unique',
+ 'platform': 'test-platform',
+ 'name': 'Test Name',
+ 'disabled_by': 'hass',
+ }
+ }
+ with patch('os.path.isfile', return_value=True), patch('os.remove'), \
+ patch('homeassistant.helpers.entity_registry.load_yaml',
+ return_value=old_conf):
+ registry = await entity_registry.async_get_registry(hass)
+
+ assert registry.async_is_registered('light.kitchen')
+ entry = registry.async_get_or_create(
+ domain='light',
+ platform='test-platform',
+ unique_id='test-unique',
+ config_entry_id='test-config-id',
+ )
+ assert entry.name == 'Test Name'
+ assert entry.disabled_by == 'hass'
+ assert entry.config_entry_id == 'test-config-id'
diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py
index 73f2b9ff5a4..deefcec773a 100644
--- a/tests/helpers/test_event.py
+++ b/tests/helpers/test_event.py
@@ -37,7 +37,7 @@ class TestEventHelpers(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py
new file mode 100644
index 00000000000..1d6e7eb6ede
--- /dev/null
+++ b/tests/helpers/test_json.py
@@ -0,0 +1,21 @@
+"""Test Home Assistant remote methods and classes."""
+import pytest
+
+from homeassistant import core
+from homeassistant.helpers.json import JSONEncoder
+from homeassistant.util import dt as dt_util
+
+
+def test_json_encoder(hass):
+ """Test the JSON Encoder."""
+ ha_json_enc = JSONEncoder()
+ state = core.State('test.test', 'hello')
+
+ assert ha_json_enc.default(state) == state.as_dict()
+
+ # Default method raises TypeError if non HA object
+ with pytest.raises(TypeError):
+ ha_json_enc.default(1)
+
+ now = dt_util.utcnow()
+ assert ha_json_enc.default(now) == now.isoformat()
diff --git a/tests/helpers/test_location.py b/tests/helpers/test_location.py
index 068e1a58ac2..22f69c18326 100644
--- a/tests/helpers/test_location.py
+++ b/tests/helpers/test_location.py
@@ -7,15 +7,15 @@ from homeassistant.helpers import location
class TestHelpersLocation(unittest.TestCase):
- """Setup the tests."""
+ """Set up the tests."""
def test_has_location_with_invalid_states(self):
- """Setup the tests."""
+ """Set up the tests."""
for state in (None, 1, "hello", object):
self.assertFalse(location.has_location(state))
def test_has_location_with_states_with_invalid_locations(self):
- """Setup the tests."""
+ """Set up the tests."""
state = State('hello.world', 'invalid', {
ATTR_LATITUDE: 'no number',
ATTR_LONGITUDE: 123.12
@@ -23,7 +23,7 @@ class TestHelpersLocation(unittest.TestCase):
self.assertFalse(location.has_location(state))
def test_has_location_with_states_with_valid_location(self):
- """Setup the tests."""
+ """Set up the tests."""
state = State('hello.world', 'invalid', {
ATTR_LATITUDE: 123.12,
ATTR_LONGITUDE: 123.12
@@ -31,7 +31,7 @@ class TestHelpersLocation(unittest.TestCase):
self.assertTrue(location.has_location(state))
def test_closest_with_no_states_with_location(self):
- """Setup the tests."""
+ """Set up the tests."""
state = State('light.test', 'on')
state2 = State('light.test', 'on', {
ATTR_LATITUDE: 'invalid',
diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py
index 7e60cc796cc..ba5bdbcdc6e 100644
--- a/tests/helpers/test_script.py
+++ b/tests/helpers/test_script.py
@@ -21,7 +21,7 @@ class TestScriptHelper(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py
index 79054726c03..529804bd307 100644
--- a/tests/helpers/test_service.py
+++ b/tests/helpers/test_service.py
@@ -19,7 +19,7 @@ class TestServiceHelpers(unittest.TestCase):
"""Test the Home Assistant service helpers."""
def setUp(self): # pylint: disable=invalid-name
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test_domain', 'test_service')
diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py
index f414eaec97c..6cb75899d35 100644
--- a/tests/helpers/test_storage.py
+++ b/tests/helpers/test_storage.py
@@ -56,7 +56,7 @@ async def test_loading_parallel(hass, store, hass_storage, caplog):
async def test_saving_with_delay(hass, store, hass_storage):
"""Test saving data after a delay."""
- await store.async_save(MOCK_DATA, delay=1)
+ store.async_delay_save(lambda: MOCK_DATA, 1)
assert store.key not in hass_storage
async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1))
@@ -71,7 +71,7 @@ async def test_saving_with_delay(hass, store, hass_storage):
async def test_saving_on_stop(hass, hass_storage):
"""Test delayed saves trigger when we quit Home Assistant."""
store = storage.Store(hass, MOCK_VERSION, MOCK_KEY)
- await store.async_save(MOCK_DATA, delay=1)
+ store.async_delay_save(lambda: MOCK_DATA, 1)
assert store.key not in hass_storage
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
@@ -92,7 +92,7 @@ async def test_loading_while_delay(hass, store, hass_storage):
'data': {'delay': 'no'},
}
- await store.async_save({'delay': 'yes'}, delay=1)
+ store.async_delay_save(lambda: {'delay': 'yes'}, 1)
assert hass_storage[store.key] == {
'version': MOCK_VERSION,
'key': MOCK_KEY,
@@ -105,7 +105,7 @@ async def test_loading_while_delay(hass, store, hass_storage):
async def test_writing_while_writing_delay(hass, store, hass_storage):
"""Test a write while a write with delay is active."""
- await store.async_save({'delay': 'yes'}, delay=1)
+ store.async_delay_save(lambda: {'delay': 'yes'}, 1)
assert store.key not in hass_storage
await store.async_save({'delay': 'no'})
assert hass_storage[store.key] == {
@@ -141,11 +141,10 @@ async def test_migrator_no_existing_config(hass, store, hass_storage):
async def test_migrator_existing_config(hass, store, hass_storage):
"""Test migrating existing config."""
with patch('os.path.isfile', return_value=True), \
- patch('os.remove') as mock_remove, \
- patch('homeassistant.util.json.load_json',
- return_value={'old': 'config'}):
+ patch('os.remove') as mock_remove:
data = await storage.async_migrator(
- hass, 'old-path', store)
+ hass, 'old-path', store,
+ old_conf_load_func=lambda _: {'old': 'config'})
assert len(mock_remove.mock_calls) == 1
assert data == {'old': 'config'}
@@ -163,12 +162,11 @@ async def test_migrator_transforming_config(hass, store, hass_storage):
return {'new': old_config['old']}
with patch('os.path.isfile', return_value=True), \
- patch('os.remove') as mock_remove, \
- patch('homeassistant.util.json.load_json',
- return_value={'old': 'config'}):
+ patch('os.remove') as mock_remove:
data = await storage.async_migrator(
hass, 'old-path', store,
- old_conf_migrate_func=old_conf_migrate_func)
+ old_conf_migrate_func=old_conf_migrate_func,
+ old_conf_load_func=lambda _: {'old': 'config'})
assert len(mock_remove.mock_calls) == 1
assert data == {'new': 'config'}
diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py
index 2cfe28e5178..b1c7f62e776 100644
--- a/tests/helpers/test_sun.py
+++ b/tests/helpers/test_sun.py
@@ -15,7 +15,7 @@ class TestSun(unittest.TestCase):
"""Test the sun helpers."""
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self):
diff --git a/tests/helpers/test_temperature.py b/tests/helpers/test_temperature.py
index 96e7bd6c74f..e2366d8866a 100644
--- a/tests/helpers/test_temperature.py
+++ b/tests/helpers/test_temperature.py
@@ -13,10 +13,10 @@ TEMP = 24.636626
class TestHelpersTemperature(unittest.TestCase):
- """Setup the temperature tests."""
+ """Set up the temperature tests."""
def setUp(self):
- """Setup the tests."""
+ """Set up the tests."""
self.hass = get_test_home_assistant()
self.hass.config.unit_system = METRIC_SYSTEM
diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py
index 2dfcb2a58e5..6f426c290c5 100644
--- a/tests/helpers/test_template.py
+++ b/tests/helpers/test_template.py
@@ -27,7 +27,7 @@ class TestHelpersTemplate(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup the tests."""
+ """Set up the tests."""
self.hass = get_test_home_assistant()
self.hass.config.units = UnitSystem('custom', TEMP_CELSIUS,
LENGTH_METERS, VOLUME_LITERS,
@@ -511,8 +511,8 @@ class TestHelpersTemplate(unittest.TestCase):
def test_regex_match(self):
"""Test regex_match method."""
- tpl = template.Template("""
-{{ '123-456-7890' | regex_match('(\d{3})-(\d{3})-(\d{4})') }}
+ tpl = template.Template(r"""
+{{ '123-456-7890' | regex_match('(\\d{3})-(\\d{3})-(\\d{4})') }}
""", self.hass)
self.assertEqual('True', tpl.render())
@@ -528,8 +528,8 @@ class TestHelpersTemplate(unittest.TestCase):
def test_regex_search(self):
"""Test regex_search method."""
- tpl = template.Template("""
-{{ '123-456-7890' | regex_search('(\d{3})-(\d{3})-(\d{4})') }}
+ tpl = template.Template(r"""
+{{ '123-456-7890' | regex_search('(\\d{3})-(\\d{3})-(\\d{4})') }}
""", self.hass)
self.assertEqual('True', tpl.render())
@@ -545,8 +545,8 @@ class TestHelpersTemplate(unittest.TestCase):
def test_regex_replace(self):
"""Test regex_replace method."""
- tpl = template.Template("""
-{{ 'Hello World' | regex_replace('(Hello\s)',) }}
+ tpl = template.Template(r"""
+{{ 'Hello World' | regex_replace('(Hello\\s)',) }}
""", self.hass)
self.assertEqual('World', tpl.render())
diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py
index 532197b4072..28438a5e4b3 100644
--- a/tests/scripts/test_check_config.py
+++ b/tests/scripts/test_check_config.py
@@ -22,6 +22,12 @@ BASE_CONFIG = (
'\n\n'
)
+BAD_CORE_CONFIG = (
+ 'homeassistant:\n'
+ ' unit_system: bad\n'
+ '\n\n'
+)
+
def normalize_yaml_files(check_dict):
"""Remove configuration path from ['yaml_files']."""
@@ -47,6 +53,17 @@ class TestCheckConfig(unittest.TestCase):
self.maxDiff = None # pylint: disable=invalid-name
# pylint: disable=no-self-use,invalid-name
+ @patch('os.path.isfile', return_value=True)
+ def test_bad_core_config(self, isfile_patch):
+ """Test a bad core config setup."""
+ files = {
+ YAML_CONFIG_FILE: BAD_CORE_CONFIG,
+ }
+ with patch_yaml_files(files):
+ res = check_config.check(get_test_config_dir())
+ assert res['except'].keys() == {'homeassistant'}
+ assert res['except']['homeassistant'][1] == {'unit_system': 'bad'}
+
@patch('os.path.isfile', return_value=True)
def test_config_platform_valid(self, isfile_patch):
"""Test a valid platform setup."""
diff --git a/tests/test_config.py b/tests/test_config.py
index 435d3a00ec2..77a30fd771b 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -812,6 +812,47 @@ async def test_auth_provider_config(hass):
await config_util.async_process_ha_core_config(hass, core_config)
assert len(hass.auth.auth_providers) == 2
+ assert hass.auth.auth_providers[0].type == 'homeassistant'
+ assert hass.auth.auth_providers[1].type == 'legacy_api_password'
+ assert hass.auth.active is True
+
+
+async def test_auth_provider_config_default(hass):
+ """Test loading default auth provider config."""
+ core_config = {
+ 'latitude': 60,
+ 'longitude': 50,
+ 'elevation': 25,
+ 'name': 'Huis',
+ CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
+ 'time_zone': 'GMT',
+ }
+ if hasattr(hass, 'auth'):
+ del hass.auth
+ await config_util.async_process_ha_core_config(hass, core_config)
+
+ assert len(hass.auth.auth_providers) == 1
+ assert hass.auth.auth_providers[0].type == 'homeassistant'
+ assert hass.auth.active is True
+
+
+async def test_auth_provider_config_default_api_password(hass):
+ """Test loading default auth provider config with api password."""
+ core_config = {
+ 'latitude': 60,
+ 'longitude': 50,
+ 'elevation': 25,
+ 'name': 'Huis',
+ CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
+ 'time_zone': 'GMT',
+ }
+ if hasattr(hass, 'auth'):
+ del hass.auth
+ await config_util.async_process_ha_core_config(hass, core_config, True)
+
+ assert len(hass.auth.auth_providers) == 2
+ assert hass.auth.auth_providers[0].type == 'homeassistant'
+ assert hass.auth.auth_providers[1].type == 'legacy_api_password'
assert hass.auth.active is True
diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py
index 1f6fd8756e6..d8756d87a19 100644
--- a/tests/test_config_entries.py
+++ b/tests/test_config_entries.py
@@ -231,7 +231,7 @@ async def test_forward_entry_sets_up_component(hass):
async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
- """Test we do not setup entry if component setup fails."""
+ """Test we do not set up entry if component setup fails."""
entry = MockConfigEntry(domain='original')
mock_setup = MagicMock(return_value=mock_coro(False))
diff --git a/tests/test_core.py b/tests/test_core.py
index f23bed6bc8a..ce066135709 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -130,7 +130,7 @@ class TestHomeAssistant(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
@@ -291,7 +291,7 @@ class TestEventBus(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.bus = self.hass.bus
@@ -495,7 +495,7 @@ class TestStateMachine(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.states = self.hass.states
self.states.set("light.Bowl", "on")
@@ -620,7 +620,7 @@ class TestServiceRegistry(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.services = self.hass.services
@@ -775,7 +775,7 @@ class TestConfig(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup things to be run when tests are started."""
+ """Set up things to be run when tests are started."""
self.config = ha.Config()
self.assertIsNone(self.config.config_dir)
diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py
index c5d5bbb50bf..aa8240ff567 100644
--- a/tests/test_data_entry_flow.py
+++ b/tests/test_data_entry_flow.py
@@ -25,11 +25,12 @@ def manager():
if context is not None else 'user_input'
return flow
- async def async_add_entry(context, result):
- if (result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY):
- result['source'] = context.get('source') \
- if context is not None else 'user'
+ async def async_add_entry(flow, result):
+ if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
+ result['source'] = flow.context.get('source') \
+ if flow.context is not None else 'user'
entries.append(result)
+ return result
manager = data_entry_flow.FlowManager(
None, async_create_flow, async_add_entry)
@@ -198,3 +199,49 @@ async def test_discovery_init_flow(manager):
assert entry['title'] == 'hello'
assert entry['data'] == data
assert entry['source'] == 'discovery'
+
+
+async def test_finish_callback_change_result_type(hass):
+ """Test finish callback can change result type."""
+ class TestFlow(data_entry_flow.FlowHandler):
+ VERSION = 1
+
+ async def async_step_init(self, input):
+ """Return init form with one input field 'count'."""
+ if input is not None:
+ return self.async_create_entry(title='init', data=input)
+ return self.async_show_form(
+ step_id='init',
+ data_schema=vol.Schema({'count': int}))
+
+ async def async_create_flow(handler_name, *, context, data):
+ """Create a test flow."""
+ return TestFlow()
+
+ async def async_finish_flow(flow, result):
+ """Redirect to init form if count <= 1."""
+ if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
+ if (result['data'] is None or
+ result['data'].get('count', 0) <= 1):
+ return flow.async_show_form(
+ step_id='init',
+ data_schema=vol.Schema({'count': int}))
+ else:
+ result['result'] = result['data']['count']
+ return result
+
+ manager = data_entry_flow.FlowManager(
+ hass, async_create_flow, async_finish_flow)
+
+ result = await manager.async_init('test')
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'init'
+
+ result = await manager.async_configure(result['flow_id'], {'count': 0})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'init'
+ assert 'result' not in result
+
+ result = await manager.async_configure(result['flow_id'], {'count': 2})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['result'] == 2
diff --git a/tests/test_loader.py b/tests/test_loader.py
index d87201fb61b..4beb7db570e 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -17,7 +17,7 @@ class TestLoader(unittest.TestCase):
# pylint: disable=invalid-name
def setUp(self):
- """Setup tests."""
+ """Set up tests."""
self.hass = get_test_home_assistant()
# pylint: disable=invalid-name
diff --git a/tests/test_remote.py b/tests/test_remote.py
deleted file mode 100644
index 9aa730d6eb6..00000000000
--- a/tests/test_remote.py
+++ /dev/null
@@ -1,205 +0,0 @@
-"""Test Home Assistant remote methods and classes."""
-# pylint: disable=protected-access
-import unittest
-
-from homeassistant import remote, setup, core as ha
-import homeassistant.components.http as http
-from homeassistant.const import HTTP_HEADER_HA_AUTH, EVENT_STATE_CHANGED
-import homeassistant.util.dt as dt_util
-
-from tests.common import (
- get_test_instance_port, get_test_home_assistant)
-
-API_PASSWORD = 'test1234'
-MASTER_PORT = get_test_instance_port()
-BROKEN_PORT = get_test_instance_port()
-HTTP_BASE_URL = 'http://127.0.0.1:{}'.format(MASTER_PORT)
-
-HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD}
-
-broken_api = remote.API('127.0.0.1', "bladybla", port=get_test_instance_port())
-hass, master_api = None, None
-
-
-def _url(path=''):
- """Helper method to generate URLs."""
- return HTTP_BASE_URL + path
-
-
-# pylint: disable=invalid-name
-def setUpModule():
- """Initialization of a Home Assistant server instance."""
- global hass, master_api
-
- hass = get_test_home_assistant()
-
- hass.bus.listen('test_event', lambda _: _)
- hass.states.set('test.test', 'a_state')
-
- setup.setup_component(
- hass, http.DOMAIN,
- {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
- http.CONF_SERVER_PORT: MASTER_PORT}})
-
- setup.setup_component(hass, 'api')
-
- hass.start()
-
- master_api = remote.API('127.0.0.1', API_PASSWORD, MASTER_PORT)
-
-
-# pylint: disable=invalid-name
-def tearDownModule():
- """Stop the Home Assistant server."""
- hass.stop()
-
-
-class TestRemoteMethods(unittest.TestCase):
- """Test the homeassistant.remote module."""
-
- def tearDown(self):
- """Stop everything that was started."""
- hass.block_till_done()
-
- def test_validate_api(self):
- """Test Python API validate_api."""
- self.assertEqual(remote.APIStatus.OK, remote.validate_api(master_api))
-
- self.assertEqual(
- remote.APIStatus.INVALID_PASSWORD,
- remote.validate_api(
- remote.API('127.0.0.1', API_PASSWORD + 'A', MASTER_PORT)))
-
- self.assertEqual(
- remote.APIStatus.CANNOT_CONNECT, remote.validate_api(broken_api))
-
- def test_get_event_listeners(self):
- """Test Python API get_event_listeners."""
- local_data = hass.bus.listeners
- remote_data = remote.get_event_listeners(master_api)
-
- for event in remote_data:
- self.assertEqual(local_data.pop(event["event"]),
- event["listener_count"])
-
- self.assertEqual(len(local_data), 0)
-
- self.assertEqual({}, remote.get_event_listeners(broken_api))
-
- def test_fire_event(self):
- """Test Python API fire_event."""
- test_value = []
-
- @ha.callback
- def listener(event):
- """Helper method that will verify our event got called."""
- test_value.append(1)
-
- hass.bus.listen("test.event_no_data", listener)
- remote.fire_event(master_api, "test.event_no_data")
- hass.block_till_done()
- self.assertEqual(1, len(test_value))
-
- # Should not trigger any exception
- remote.fire_event(broken_api, "test.event_no_data")
-
- def test_get_state(self):
- """Test Python API get_state."""
- self.assertEqual(
- hass.states.get('test.test'),
- remote.get_state(master_api, 'test.test'))
-
- self.assertEqual(None, remote.get_state(broken_api, 'test.test'))
-
- def test_get_states(self):
- """Test Python API get_state_entity_ids."""
- self.assertEqual(hass.states.all(), remote.get_states(master_api))
- self.assertEqual([], remote.get_states(broken_api))
-
- def test_remove_state(self):
- """Test Python API set_state."""
- hass.states.set('test.remove_state', 'set_test')
-
- self.assertIn('test.remove_state', hass.states.entity_ids())
- remote.remove_state(master_api, 'test.remove_state')
- self.assertNotIn('test.remove_state', hass.states.entity_ids())
-
- def test_set_state(self):
- """Test Python API set_state."""
- remote.set_state(master_api, 'test.test', 'set_test')
-
- state = hass.states.get('test.test')
-
- self.assertIsNotNone(state)
- self.assertEqual('set_test', state.state)
-
- self.assertFalse(remote.set_state(broken_api, 'test.test', 'set_test'))
-
- def test_set_state_with_push(self):
- """Test Python API set_state with push option."""
- events = []
- hass.bus.listen(EVENT_STATE_CHANGED, lambda ev: events.append(ev))
-
- remote.set_state(master_api, 'test.test', 'set_test_2')
- remote.set_state(master_api, 'test.test', 'set_test_2')
- hass.block_till_done()
- self.assertEqual(1, len(events))
-
- remote.set_state(
- master_api, 'test.test', 'set_test_2', force_update=True)
- hass.block_till_done()
- self.assertEqual(2, len(events))
-
- def test_is_state(self):
- """Test Python API is_state."""
- self.assertTrue(
- remote.is_state(master_api, 'test.test',
- hass.states.get('test.test').state))
-
- self.assertFalse(
- remote.is_state(broken_api, 'test.test',
- hass.states.get('test.test').state))
-
- def test_get_services(self):
- """Test Python API get_services."""
- local_services = hass.services.services
-
- for serv_domain in remote.get_services(master_api):
- local = local_services.pop(serv_domain["domain"])
-
- self.assertEqual(local, serv_domain["services"])
-
- self.assertEqual({}, remote.get_services(broken_api))
-
- def test_call_service(self):
- """Test Python API services.call."""
- test_value = []
-
- @ha.callback
- def listener(service_call):
- """Helper method that will verify that our service got called."""
- test_value.append(1)
-
- hass.services.register("test_domain", "test_service", listener)
-
- remote.call_service(master_api, "test_domain", "test_service")
-
- hass.block_till_done()
-
- self.assertEqual(1, len(test_value))
-
- # Should not raise an exception
- remote.call_service(broken_api, "test_domain", "test_service")
-
- def test_json_encoder(self):
- """Test the JSON Encoder."""
- ha_json_enc = remote.JSONEncoder()
- state = hass.states.get('test.test')
-
- self.assertEqual(state.as_dict(), ha_json_enc.default(state))
-
- # Default method raises TypeError if non HA object
- self.assertRaises(TypeError, ha_json_enc.default, 1)
-
- now = dt_util.utcnow()
- self.assertEqual(now.isoformat(), ha_json_enc.default(now))
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index 8ae0f6c11de..e3ef797df4d 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -16,7 +16,7 @@ class TestRequirements:
# pylint: disable=invalid-name, no-self-use
def setup_method(self, method):
- """Setup the test."""
+ """Set up the test."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
diff --git a/tests/test_setup.py b/tests/test_setup.py
index 6f0c282e016..29712f40ebc 100644
--- a/tests/test_setup.py
+++ b/tests/test_setup.py
@@ -34,7 +34,7 @@ class TestSetup:
# pylint: disable=invalid-name, no-self-use
def setup_method(self, method):
- """Setup the test."""
+ """Set up the test."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
@@ -179,7 +179,7 @@ class TestSetup:
assert not setup.setup_component(self.hass, 'non_existing')
def test_component_not_double_initialized(self):
- """Test we do not setup a component twice."""
+ """Test we do not set up a component twice."""
mock_setup = mock.MagicMock(return_value=True)
loader.set_component(
@@ -206,7 +206,7 @@ class TestSetup:
assert 'comp' not in self.hass.config.components
def test_component_not_setup_twice_if_loaded_during_other_setup(self):
- """Test component setup while waiting for lock is not setup twice."""
+ """Test component setup while waiting for lock is not set up twice."""
result = []
@asyncio.coroutine
@@ -219,7 +219,7 @@ class TestSetup:
'comp', MockModule('comp', async_setup=async_setup))
def setup_component():
- """Setup the component."""
+ """Set up the component."""
setup.setup_component(self.hass, 'comp')
thread = threading.Thread(target=setup_component)
@@ -231,7 +231,7 @@ class TestSetup:
assert len(result) == 1
def test_component_not_setup_missing_dependencies(self):
- """Test we do not setup a component if not all dependencies loaded."""
+ """Test we do not set up a component if not all dependencies loaded."""
deps = ['non_existing']
loader.set_component(
self.hass, 'comp', MockModule('comp', dependencies=deps))
@@ -257,7 +257,7 @@ class TestSetup:
def test_component_exception_setup(self):
"""Test component that raises exception during setup."""
def exception_setup(hass, config):
- """Setup that raises exception."""
+ """Raise exception."""
raise Exception('fail!')
loader.set_component(
@@ -269,7 +269,7 @@ class TestSetup:
def test_component_setup_with_validation_and_dependency(self):
"""Test all config is passed to dependencies."""
def config_check_setup(hass, config):
- """Setup method that tests config is passed in."""
+ """Test that config is passed in."""
if config.get('comp_a', {}).get('valid', False):
return True
raise Exception('Config not passed in: {}'.format(config))
@@ -377,7 +377,7 @@ class TestSetup:
call_order = []
def component1_setup(hass, config):
- """Setup mock component."""
+ """Set up mock component."""
discovery.discover(hass, 'test_component2',
component='test_component2')
discovery.discover(hass, 'test_component3',
@@ -385,7 +385,7 @@ class TestSetup:
return True
def component_track_setup(hass, config):
- """Setup mock component."""
+ """Set up mock component."""
call_order.append(1)
return True
diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py
index 813eb84707c..d662f3b1955 100644
--- a/tests/test_util/aiohttp.py
+++ b/tests/test_util/aiohttp.py
@@ -12,6 +12,8 @@ from yarl import URL
from aiohttp.client_exceptions import ClientResponseError
+from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
+
retype = type(re.compile(''))
@@ -216,7 +218,18 @@ def mock_aiohttp_client():
"""Context manager to mock aiohttp client."""
mocker = AiohttpClientMocker()
+ def create_session(hass, *args):
+ session = mocker.create_session(hass.loop)
+
+ async def close_session(event):
+ """Close session."""
+ await session.close()
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, close_session)
+
+ return session
+
with mock.patch(
'homeassistant.helpers.aiohttp_client.async_create_clientsession',
- side_effect=lambda hass, *args: mocker.create_session(hass.loop)):
+ side_effect=create_session):
yield mocker
diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py
index feee69ad3c8..d670917c055 100644
--- a/tests/util/test_dt.py
+++ b/tests/util/test_dt.py
@@ -11,7 +11,7 @@ class TestDateUtil(unittest.TestCase):
"""Test util date methods."""
def setUp(self):
- """Setup the tests."""
+ """Set up the tests."""
self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE
def tearDown(self):
diff --git a/tests/util/test_package.py b/tests/util/test_package.py
index ab9f9f0ad2c..19e85a094ee 100644
--- a/tests/util/test_package.py
+++ b/tests/util/test_package.py
@@ -30,8 +30,8 @@ def mock_sys():
@pytest.fixture
def mock_exists():
- """Mock check_package_exists."""
- with patch('homeassistant.util.package.check_package_exists') as mock:
+ """Mock package_loadable."""
+ with patch('homeassistant.util.package.package_loadable') as mock:
mock.return_value = False
yield mock
@@ -193,12 +193,12 @@ def test_install_constraint(
def test_check_package_global():
"""Test for an installed package."""
installed_package = list(pkg_resources.working_set)[0].project_name
- assert package.check_package_exists(installed_package)
+ assert package.package_loadable(installed_package)
def test_check_package_zip():
"""Test for an installed zip package."""
- assert not package.check_package_exists(TEST_ZIP_REQ)
+ assert not package.package_loadable(TEST_ZIP_REQ)
@asyncio.coroutine
@@ -217,3 +217,25 @@ def test_async_get_user_site(mock_env_copy):
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL,
env=env)
assert ret == os.path.join(deps_dir, 'lib_dir')
+
+
+def test_package_loadable_installed_twice():
+ """Test that a package is loadable when installed twice.
+
+ If a package is installed twice, only the first version will be imported.
+ Test that package_loadable will only compare with the first package.
+ """
+ v1 = pkg_resources.Distribution(project_name='hello', version='1.0.0')
+ v2 = pkg_resources.Distribution(project_name='hello', version='2.0.0')
+
+ with patch('pkg_resources.find_distributions', side_effect=[[v1]]):
+ assert not package.package_loadable('hello==2.0.0')
+
+ with patch('pkg_resources.find_distributions', side_effect=[[v1], [v2]]):
+ assert not package.package_loadable('hello==2.0.0')
+
+ with patch('pkg_resources.find_distributions', side_effect=[[v2], [v1]]):
+ assert package.package_loadable('hello==2.0.0')
+
+ with patch('pkg_resources.find_distributions', side_effect=[[v2]]):
+ assert package.package_loadable('hello==2.0.0')
diff --git a/tox.ini b/tox.ini
index d6ef1981bef..e1261457c47 100644
--- a/tox.ini
+++ b/tox.ini
@@ -58,4 +58,4 @@ whitelist_externals=/bin/bash
deps =
-r{toxinidir}/requirements_test.txt
commands =
- /bin/bash -c 'mypy homeassistant/*.py homeassistant/util/'
+ /bin/bash -c 'mypy homeassistant/*.py homeassistant/auth/ homeassistant/util/'