diff --git a/.coveragerc b/.coveragerc
index bb0be2d9433..ca36f4a8dbb 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -116,11 +116,14 @@ omit =
homeassistant/components/google.py
homeassistant/components/*/google.py
+ homeassistant/components/habitica/*
+ homeassistant/components/*/habitica.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/*/hangouts.py
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
@@ -142,12 +145,12 @@ omit =
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
-
+
homeassistant/components/insteon/*
homeassistant/components/*/insteon.py
homeassistant/components/insteon_local.py
-
+
homeassistant/components/insteon_plm.py
homeassistant/components/ios.py
@@ -225,7 +228,7 @@ omit =
homeassistant/components/opencv.py
homeassistant/components/*/opencv.py
- homeassistant/components/openuv.py
+ homeassistant/components/openuv/__init__.py
homeassistant/components/*/openuv.py
homeassistant/components/pilight.py
@@ -374,6 +377,7 @@ omit =
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
+ homeassistant/components/alarm_control_panel/yale_smart_alarm.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
@@ -411,6 +415,7 @@ omit =
homeassistant/components/climate/honeywell.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/oem.py
+ homeassistant/components/climate/opentherm_gw.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/climate/sensibo.py
@@ -759,6 +764,7 @@ omit =
homeassistant/components/sensor/uscis.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/viaggiatreno.py
+ homeassistant/components/sensor/volkszaehler.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/waze_travel_time.py
homeassistant/components/sensor/whois.py
@@ -789,6 +795,8 @@ omit =
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
+ homeassistant/components/switch/switchbot.py
+ homeassistant/components/switch/switchmate.py
homeassistant/components/switch/telnet.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
diff --git a/LICENSE.md b/LICENSE.md
index b62a9b5ff78..261eeb9e9f8 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,194 +1,201 @@
-Apache License
-==============
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
-_Version 2.0, January 2004_
-_<>_
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-### Terms and Conditions for use, reproduction, and distribution
+ 1. Definitions.
-#### 1. Definitions
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
-“License” shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
-“Licensor” shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
-“Legal Entity” shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, “control” means **(i)** the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
-outstanding shares, or **(iii)** beneficial ownership of such entity.
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
-“You” (or “Your”) shall mean an individual or Legal Entity exercising
-permissions granted by this License.
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
-“Source” form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
-“Object” form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
-“Work” shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
-“Derivative Works” shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
-“Contribution” shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-“submitted” means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as “Not a Contribution.”
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
-“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
-#### 2. Grant of Copyright License
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
-#### 3. Grant of Patent License
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
-#### 4. Redistribution
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
-* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-* **(b)** You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
-#### 5. Submission of Contributions
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
-#### 6. Trademarks
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
-#### 7. Disclaimer of Warranty
+ END OF TERMS AND CONDITIONS
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
+ APPENDIX: How to apply the Apache License to your work.
-#### 8. Limitation of Liability
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
+ Copyright [yyyy] [name of copyright owner]
-#### 9. Accepting Warranty or Additional Liability
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
+ http://www.apache.org/licenses/LICENSE-2.0
-_END OF TERMS AND CONDITIONS_
-
-### APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets `[]` replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same “printed page” as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py
index 4ef8440de62..c6f978640f6 100644
--- a/homeassistant/auth/__init__.py
+++ b/homeassistant/auth/__init__.py
@@ -2,11 +2,13 @@
import asyncio
import logging
from collections import OrderedDict
+from datetime import timedelta
from typing import Any, Dict, List, Optional, Tuple, cast
import jwt
from homeassistant import data_entry_flow
+from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import callback, HomeAssistant
from homeassistant.util import dt as dt_util
@@ -242,8 +244,12 @@ class AuthManager:
modules[module_id] = module.name
return modules
- async def async_create_refresh_token(self, user: models.User,
- client_id: Optional[str] = None) \
+ async def async_create_refresh_token(
+ self, user: models.User, client_id: Optional[str] = None,
+ client_name: Optional[str] = None,
+ client_icon: Optional[str] = None,
+ token_type: Optional[str] = None,
+ access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \
-> models.RefreshToken:
"""Create a new refresh token for a user."""
if not user.is_active:
@@ -254,10 +260,36 @@ class AuthManager:
'System generated users cannot have refresh tokens connected '
'to a client.')
- if not user.system_generated and client_id is None:
+ if token_type is None:
+ if user.system_generated:
+ token_type = models.TOKEN_TYPE_SYSTEM
+ else:
+ token_type = models.TOKEN_TYPE_NORMAL
+
+ if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM):
+ raise ValueError(
+ 'System generated users can only have system type '
+ 'refresh tokens')
+
+ if token_type == models.TOKEN_TYPE_NORMAL and client_id is None:
raise ValueError('Client is required to generate a refresh token.')
- return await self._store.async_create_refresh_token(user, client_id)
+ if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and
+ client_name is None):
+ raise ValueError('Client_name is required for long-lived access '
+ 'token')
+
+ if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN:
+ for token in user.refresh_tokens.values():
+ if (token.client_name == client_name and token.token_type ==
+ models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN):
+ # Each client_name can only have one
+ # long_lived_access_token type of refresh token
+ raise ValueError('{} already exists'.format(client_name))
+
+ return await self._store.async_create_refresh_token(
+ user, client_id, client_name, client_icon,
+ token_type, access_token_expiration)
async def async_get_refresh_token(
self, token_id: str) -> Optional[models.RefreshToken]:
@@ -277,13 +309,17 @@ class AuthManager:
@callback
def async_create_access_token(self,
- refresh_token: models.RefreshToken) -> str:
+ refresh_token: models.RefreshToken,
+ remote_ip: Optional[str] = None) -> str:
"""Create a new access token."""
+ self._store.async_log_refresh_token_usage(refresh_token, remote_ip)
+
# pylint: disable=no-self-use
+ now = dt_util.utcnow()
return jwt.encode({
'iss': refresh_token.id,
- 'iat': dt_util.utcnow(),
- 'exp': dt_util.utcnow() + refresh_token.access_token_expiration,
+ 'iat': now,
+ 'exp': now + refresh_token.access_token_expiration,
}, refresh_token.jwt_key, algorithm='HS256').decode()
async def async_validate_access_token(
diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py
index 0f12d69211c..fb4700c806f 100644
--- a/homeassistant/auth/auth_store.py
+++ b/homeassistant/auth/auth_store.py
@@ -5,6 +5,7 @@ from logging import getLogger
from typing import Any, Dict, List, Optional # noqa: F401
import hmac
+from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util
@@ -128,11 +129,27 @@ class AuthStore:
self._async_schedule_save()
async def async_create_refresh_token(
- self, user: models.User, client_id: Optional[str] = None) \
+ self, user: models.User, client_id: Optional[str] = None,
+ client_name: Optional[str] = None,
+ client_icon: Optional[str] = None,
+ token_type: str = models.TOKEN_TYPE_NORMAL,
+ access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \
-> models.RefreshToken:
"""Create a new token for a user."""
- refresh_token = models.RefreshToken(user=user, client_id=client_id)
+ kwargs = {
+ 'user': user,
+ 'client_id': client_id,
+ 'token_type': token_type,
+ 'access_token_expiration': access_token_expiration
+ } # type: Dict[str, Any]
+ if client_name:
+ kwargs['client_name'] = client_name
+ if client_icon:
+ kwargs['client_icon'] = client_icon
+
+ refresh_token = models.RefreshToken(**kwargs)
user.refresh_tokens[refresh_token.id] = refresh_token
+
self._async_schedule_save()
return refresh_token
@@ -178,6 +195,15 @@ class AuthStore:
return found
+ @callback
+ def async_log_refresh_token_usage(
+ self, refresh_token: models.RefreshToken,
+ remote_ip: Optional[str] = None) -> None:
+ """Update refresh token last used information."""
+ refresh_token.last_used_at = dt_util.utcnow()
+ refresh_token.last_used_ip = remote_ip
+ self._async_schedule_save()
+
async def _async_load(self) -> None:
"""Load the users."""
data = await self._store.async_load()
@@ -216,15 +242,36 @@ class AuthStore:
'Ignoring refresh token %(id)s with invalid created_at '
'%(created_at)s for user_id %(user_id)s', rt_dict)
continue
+
+ token_type = rt_dict.get('token_type')
+ if token_type is None:
+ if rt_dict['client_id'] is None:
+ token_type = models.TOKEN_TYPE_SYSTEM
+ else:
+ token_type = models.TOKEN_TYPE_NORMAL
+
+ # old refresh_token don't have last_used_at (pre-0.78)
+ last_used_at_str = rt_dict.get('last_used_at')
+ if last_used_at_str:
+ last_used_at = dt_util.parse_datetime(last_used_at_str)
+ else:
+ last_used_at = None
+
token = models.RefreshToken(
id=rt_dict['id'],
user=users[rt_dict['user_id']],
client_id=rt_dict['client_id'],
+ # use dict.get to keep backward compatibility
+ client_name=rt_dict.get('client_name'),
+ client_icon=rt_dict.get('client_icon'),
+ token_type=token_type,
created_at=created_at,
access_token_expiration=timedelta(
seconds=rt_dict['access_token_expiration']),
token=rt_dict['token'],
- jwt_key=rt_dict['jwt_key']
+ jwt_key=rt_dict['jwt_key'],
+ last_used_at=last_used_at,
+ last_used_ip=rt_dict.get('last_used_ip'),
)
users[rt_dict['user_id']].refresh_tokens[token.id] = token
@@ -271,11 +318,18 @@ class AuthStore:
'id': refresh_token.id,
'user_id': user.id,
'client_id': refresh_token.client_id,
+ 'client_name': refresh_token.client_name,
+ 'client_icon': refresh_token.client_icon,
+ 'token_type': refresh_token.token_type,
'created_at': refresh_token.created_at.isoformat(),
'access_token_expiration':
refresh_token.access_token_expiration.total_seconds(),
'token': refresh_token.token,
'jwt_key': refresh_token.jwt_key,
+ 'last_used_at':
+ refresh_token.last_used_at.isoformat()
+ if refresh_token.last_used_at else None,
+ 'last_used_ip': refresh_token.last_used_ip,
}
for user in self._users.values()
for refresh_token in user.refresh_tokens.values()
diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py
index a6500510e0d..b0f4024c3ab 100644
--- a/homeassistant/auth/models.py
+++ b/homeassistant/auth/models.py
@@ -7,9 +7,12 @@ import attr
from homeassistant.util import dt as dt_util
-from .const import ACCESS_TOKEN_EXPIRATION
from .util import generate_secret
+TOKEN_TYPE_NORMAL = 'normal'
+TOKEN_TYPE_SYSTEM = 'system'
+TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = 'long_lived_access_token'
+
@attr.s(slots=True)
class User:
@@ -37,23 +40,31 @@ class RefreshToken:
"""RefreshToken for a user to grant new access tokens."""
user = attr.ib(type=User)
- client_id = attr.ib(type=str) # type: Optional[str]
+ client_id = attr.ib(type=Optional[str])
+ access_token_expiration = attr.ib(type=timedelta)
+ client_name = attr.ib(type=Optional[str], default=None)
+ client_icon = attr.ib(type=Optional[str], default=None)
+ token_type = attr.ib(type=str, default=TOKEN_TYPE_NORMAL,
+ validator=attr.validators.in_((
+ TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM,
+ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)))
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,
- default=ACCESS_TOKEN_EXPIRATION)
token = attr.ib(type=str,
default=attr.Factory(lambda: generate_secret(64)))
jwt_key = attr.ib(type=str,
default=attr.Factory(lambda: generate_secret(64)))
+ last_used_at = attr.ib(type=Optional[datetime], default=None)
+ last_used_ip = attr.ib(type=Optional[str], default=None)
+
@attr.s(slots=True)
class Credentials:
"""Credentials for a user on an auth provider."""
auth_provider_type = attr.ib(type=str)
- auth_provider_id = attr.ib(type=str) # type: Optional[str]
+ auth_provider_id = attr.ib(type=Optional[str])
# Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict)
diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py
index f631f8e73cf..111b9e7d39f 100644
--- a/homeassistant/auth/providers/legacy_api_password.py
+++ b/homeassistant/auth/providers/legacy_api_password.py
@@ -24,7 +24,7 @@ USER_SCHEMA = vol.Schema({
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
-LEGACY_USER = 'homeassistant'
+LEGACY_USER_NAME = 'Legacy API password user'
class InvalidAuthError(HomeAssistantError):
@@ -52,23 +52,21 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
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:
- return credential
+ """Return credentials for this login."""
+ credentials = await self.async_credentials()
+ if credentials:
+ return credentials[0]
- return self.async_create_credentials({
- 'username': LEGACY_USER
- })
+ return self.async_create_credentials({})
async def async_user_meta_for_credentials(
self, credentials: Credentials) -> UserMeta:
"""
- Set name as LEGACY_USER always.
+ Return info for the user.
Will be used to populate info when creating a new user.
"""
- return UserMeta(name=LEGACY_USER, is_active=True)
+ return UserMeta(name=LEGACY_USER_NAME, is_active=True)
class LegacyLoginFlow(LoginFlow):
diff --git a/homeassistant/components/alarm_control_panel/yale_smart_alarm.py b/homeassistant/components/alarm_control_panel/yale_smart_alarm.py
new file mode 100755
index 00000000000..e512d15fcdd
--- /dev/null
+++ b/homeassistant/components/alarm_control_panel/yale_smart_alarm.py
@@ -0,0 +1,98 @@
+"""
+Yale Smart Alarm client for interacting with the Yale Smart Alarm System API.
+
+For more details about this platform, please refer to the documentation at
+https://www.home-assistant.io/components/alarm_control_panel.yale_smart_alarm
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.alarm_control_panel import (
+ AlarmControlPanel, PLATFORM_SCHEMA)
+from homeassistant.const import (
+ CONF_PASSWORD, CONF_USERNAME, CONF_NAME,
+ STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
+import homeassistant.helpers.config_validation as cv
+
+REQUIREMENTS = ['yalesmartalarmclient==0.1.4']
+
+CONF_AREA_ID = 'area_id'
+
+DEFAULT_NAME = 'Yale Smart Alarm'
+
+DEFAULT_AREA_ID = '1'
+
+_LOGGER = logging.getLogger(__name__)
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_AREA_ID, default=DEFAULT_AREA_ID): cv.string,
+})
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the alarm platform."""
+ name = config[CONF_NAME]
+ username = config[CONF_USERNAME]
+ password = config[CONF_PASSWORD]
+ area_id = config[CONF_AREA_ID]
+
+ from yalesmartalarmclient.client import (
+ YaleSmartAlarmClient, AuthenticationError)
+ try:
+ client = YaleSmartAlarmClient(username, password, area_id)
+ except AuthenticationError:
+ _LOGGER.error("Authentication failed. Check credentials")
+ return
+
+ add_entities([YaleAlarmDevice(name, client)], True)
+
+
+class YaleAlarmDevice(AlarmControlPanel):
+ """Represent a Yale Smart Alarm."""
+
+ def __init__(self, name, client):
+ """Initialize the Yale Alarm Device."""
+ self._name = name
+ self._client = client
+ self._state = None
+
+ from yalesmartalarmclient.client import (YALE_STATE_DISARM,
+ YALE_STATE_ARM_PARTIAL,
+ YALE_STATE_ARM_FULL)
+ self._state_map = {
+ YALE_STATE_DISARM: STATE_ALARM_DISARMED,
+ YALE_STATE_ARM_PARTIAL: STATE_ALARM_ARMED_HOME,
+ YALE_STATE_ARM_FULL: STATE_ALARM_ARMED_AWAY
+ }
+
+ @property
+ def name(self):
+ """Return the name of the device."""
+ return self._name
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ return self._state
+
+ def update(self):
+ """Return the state of the device."""
+ armed_status = self._client.get_armed_status()
+
+ self._state = self._state_map.get(armed_status)
+
+ def alarm_disarm(self, code=None):
+ """Send disarm command."""
+ self._client.disarm()
+
+ def alarm_arm_home(self, code=None):
+ """Send arm home command."""
+ self._client.arm_partial()
+
+ def alarm_arm_away(self, code=None):
+ """Send arm away command."""
+ self._client.arm_full()
diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py
index c6a414b9d91..015e1e0d1fc 100644
--- a/homeassistant/components/arlo.py
+++ b/homeassistant/components/arlo.py
@@ -61,10 +61,12 @@ def setup(hass, config):
arlo_base_station = next((
station for station in arlo.base_stations), None)
- if arlo_base_station is None:
+ if arlo_base_station is not None:
+ arlo_base_station.refresh_rate = scan_interval.total_seconds()
+ elif not arlo.cameras:
+ _LOGGER.error("No Arlo camera or base station available.")
return False
- arlo_base_station.refresh_rate = scan_interval.total_seconds()
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py
index e273d7d6f6a..0d6d811db70 100644
--- a/homeassistant/components/asterisk_mbox.py
+++ b/homeassistant/components/asterisk_mbox.py
@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
-REQUIREMENTS = ['asterisk_mbox==0.4.0']
+REQUIREMENTS = ['asterisk_mbox==0.5.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/auth/.translations/fr.json b/homeassistant/components/auth/.translations/fr.json
index e8a8037c39a..b8d10dc89d0 100644
--- a/homeassistant/components/auth/.translations/fr.json
+++ b/homeassistant/components/auth/.translations/fr.json
@@ -2,7 +2,7 @@
"mfa_setup": {
"totp": {
"error": {
- "invalid_code": "Code invalide. S'il vous pla\u00eet essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
+ "invalid_code": "Code invalide. Veuillez essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
},
"step": {
"init": {
diff --git a/homeassistant/components/auth/.translations/sv.json b/homeassistant/components/auth/.translations/sv.json
new file mode 100644
index 00000000000..cf8227c09a3
--- /dev/null
+++ b/homeassistant/components/auth/.translations/sv.json
@@ -0,0 +1,16 @@
+{
+ "mfa_setup": {
+ "totp": {
+ "error": {
+ "invalid_code": "Ogiltig kod, f\u00f6rs\u00f6k igen. Om du flera g\u00e5nger i rad f\u00e5r detta fel, se till att klockan i din Home Assistant \u00e4r korrekt inst\u00e4lld."
+ },
+ "step": {
+ "init": {
+ "description": "F\u00f6r att aktivera tv\u00e5faktorsautentisering som anv\u00e4nder tidsbaserade eng\u00e5ngsl\u00f6senord, skanna QR-koden med din autentiseringsapp. Om du inte har en, rekommenderar vi antingen [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n{qr_code} \n\nN\u00e4r du har skannat koden anger du den sexsiffriga koden fr\u00e5n din app f\u00f6r att verifiera inst\u00e4llningen. Om du har problem med att skanna QR-koden, g\u00f6r en manuell inst\u00e4llning med kod ** ` {code} ` **.",
+ "title": "St\u00e4ll in tv\u00e5faktorsautentisering med TOTP"
+ }
+ },
+ "title": "TOTP"
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py
index a87e646761c..bee72d8e4fc 100644
--- a/homeassistant/components/auth/__init__.py
+++ b/homeassistant/components/auth/__init__.py
@@ -12,6 +12,7 @@ be in JSON as it's more readable.
Exchange the authorization code retrieved from the login flow for tokens.
{
+ "client_id": "https://hassbian.local:8123/",
"grant_type": "authorization_code",
"code": "411ee2f916e648d691e937ae9344681e"
}
@@ -32,6 +33,7 @@ token.
Request a new access token using a refresh token.
{
+ "client_id": "https://hassbian.local:8123/",
"grant_type": "refresh_token",
"refresh_token": "IJKLMNOPQRST"
}
@@ -55,6 +57,67 @@ ever been granted by that refresh token. Response code will ALWAYS be 200.
"action": "revoke"
}
+# Websocket API
+
+## Get current user
+
+Send websocket command `auth/current_user` will return current user of the
+active websocket connection.
+
+{
+ "id": 10,
+ "type": "auth/current_user",
+}
+
+The result payload likes
+
+{
+ "id": 10,
+ "type": "result",
+ "success": true,
+ "result": {
+ "id": "USER_ID",
+ "name": "John Doe",
+ "is_owner': true,
+ "credentials": [
+ {
+ "auth_provider_type": "homeassistant",
+ "auth_provider_id": null
+ }
+ ],
+ "mfa_modules": [
+ {
+ "id": "totp",
+ "name": "TOTP",
+ "enabled": true,
+ }
+ ]
+ }
+}
+
+## Create a long-lived access token
+
+Send websocket command `auth/long_lived_access_token` will create
+a long-lived access token for current user. Access token will not be saved in
+Home Assistant. User need to record the token in secure place.
+
+{
+ "id": 11,
+ "type": "auth/long_lived_access_token",
+ "client_name": "GPS Logger",
+ "client_icon": null,
+ "lifespan": 365
+}
+
+Result will be a long-lived access token:
+
+{
+ "id": 11,
+ "type": "result",
+ "success": true,
+ "result": "ABCDEFGH"
+}
+
"""
import logging
import uuid
@@ -63,8 +126,10 @@ from datetime import timedelta
from aiohttp import web
import voluptuous as vol
-from homeassistant.auth.models import User, Credentials
+from homeassistant.auth.models import User, Credentials, \
+ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
from homeassistant.components import websocket_api
+from homeassistant.components.http import KEY_REAL_IP
from homeassistant.components.http.ban import log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.http.view import HomeAssistantView
@@ -83,6 +148,28 @@ SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_CURRENT_USER,
})
+WS_TYPE_LONG_LIVED_ACCESS_TOKEN = 'auth/long_lived_access_token'
+SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN = \
+ websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): WS_TYPE_LONG_LIVED_ACCESS_TOKEN,
+ vol.Required('lifespan'): int, # days
+ vol.Required('client_name'): str,
+ vol.Optional('client_icon'): str,
+ })
+
+WS_TYPE_REFRESH_TOKENS = 'auth/refresh_tokens'
+SCHEMA_WS_REFRESH_TOKENS = \
+ websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): WS_TYPE_REFRESH_TOKENS,
+ })
+
+WS_TYPE_DELETE_REFRESH_TOKEN = 'auth/delete_refresh_token'
+SCHEMA_WS_DELETE_REFRESH_TOKEN = \
+ websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): WS_TYPE_DELETE_REFRESH_TOKEN,
+ vol.Required('refresh_token_id'): str,
+ })
+
RESULT_TYPE_CREDENTIALS = 'credentials'
RESULT_TYPE_USER = 'user'
@@ -100,6 +187,21 @@ async def async_setup(hass, config):
WS_TYPE_CURRENT_USER, websocket_current_user,
SCHEMA_WS_CURRENT_USER
)
+ hass.components.websocket_api.async_register_command(
+ WS_TYPE_LONG_LIVED_ACCESS_TOKEN,
+ websocket_create_long_lived_access_token,
+ SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN
+ )
+ hass.components.websocket_api.async_register_command(
+ WS_TYPE_REFRESH_TOKENS,
+ websocket_refresh_tokens,
+ SCHEMA_WS_REFRESH_TOKENS
+ )
+ hass.components.websocket_api.async_register_command(
+ WS_TYPE_DELETE_REFRESH_TOKEN,
+ websocket_delete_refresh_token,
+ SCHEMA_WS_DELETE_REFRESH_TOKEN
+ )
await login_flow.async_setup(hass, store_result)
await mfa_setup_flow.async_setup(hass)
@@ -135,10 +237,12 @@ class TokenView(HomeAssistantView):
return await self._async_handle_revoke_token(hass, data)
if grant_type == 'authorization_code':
- return await self._async_handle_auth_code(hass, data)
+ return await self._async_handle_auth_code(
+ hass, data, str(request[KEY_REAL_IP]))
if grant_type == 'refresh_token':
- return await self._async_handle_refresh_token(hass, data)
+ return await self._async_handle_refresh_token(
+ hass, data, str(request[KEY_REAL_IP]))
return self.json({
'error': 'unsupported_grant_type',
@@ -163,7 +267,7 @@ class TokenView(HomeAssistantView):
await hass.auth.async_remove_refresh_token(refresh_token)
return web.Response(status=200)
- async def _async_handle_auth_code(self, hass, data):
+ async def _async_handle_auth_code(self, hass, data, remote_addr):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is None or not indieauth.verify_client_id(client_id):
@@ -199,7 +303,8 @@ class TokenView(HomeAssistantView):
refresh_token = await hass.auth.async_create_refresh_token(user,
client_id)
- access_token = hass.auth.async_create_access_token(refresh_token)
+ access_token = hass.auth.async_create_access_token(
+ refresh_token, remote_addr)
return self.json({
'access_token': access_token,
@@ -209,7 +314,7 @@ class TokenView(HomeAssistantView):
int(refresh_token.access_token_expiration.total_seconds()),
})
- async def _async_handle_refresh_token(self, hass, data):
+ async def _async_handle_refresh_token(self, hass, data, remote_addr):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is not None and not indieauth.verify_client_id(client_id):
@@ -237,7 +342,8 @@ class TokenView(HomeAssistantView):
'error': 'invalid_request',
}, status_code=400)
- access_token = hass.auth.async_create_access_token(refresh_token)
+ access_token = hass.auth.async_create_access_token(
+ refresh_token, remote_addr)
return self.json({
'access_token': access_token,
@@ -343,3 +449,68 @@ def websocket_current_user(
}))
hass.async_create_task(async_get_current_user(connection.user))
+
+
+@websocket_api.ws_require_user()
+@callback
+def websocket_create_long_lived_access_token(
+ hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
+ """Create or a long-lived access token."""
+ async def async_create_long_lived_access_token(user):
+ """Create or a long-lived access token."""
+ refresh_token = await hass.auth.async_create_refresh_token(
+ user,
+ client_name=msg['client_name'],
+ client_icon=msg.get('client_icon'),
+ token_type=TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN,
+ access_token_expiration=timedelta(days=msg['lifespan']))
+
+ access_token = hass.auth.async_create_access_token(
+ refresh_token)
+
+ connection.send_message_outside(
+ websocket_api.result_message(msg['id'], access_token))
+
+ hass.async_create_task(
+ async_create_long_lived_access_token(connection.user))
+
+
+@websocket_api.ws_require_user()
+@callback
+def websocket_refresh_tokens(
+ hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
+ """Return metadata of users refresh tokens."""
+ current_id = connection.request.get('refresh_token_id')
+ connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{
+ 'id': refresh.id,
+ 'client_id': refresh.client_id,
+ 'client_name': refresh.client_name,
+ 'client_icon': refresh.client_icon,
+ 'type': refresh.token_type,
+ 'created_at': refresh.created_at,
+ 'is_current': refresh.id == current_id,
+ 'last_used_at': refresh.last_used_at,
+ 'last_used_ip': refresh.last_used_ip,
+ } for refresh in connection.user.refresh_tokens.values()]))
+
+
+@websocket_api.ws_require_user()
+@callback
+def websocket_delete_refresh_token(
+ hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
+ """Handle a delete refresh token request."""
+ async def async_delete_refresh_token(user, refresh_token_id):
+ """Delete a refresh token."""
+ refresh_token = connection.user.refresh_tokens.get(refresh_token_id)
+
+ if refresh_token is None:
+ return websocket_api.error_message(
+ msg['id'], 'invalid_token_id', 'Received invalid token')
+
+ await hass.auth.async_remove_refresh_token(refresh_token)
+
+ connection.send_message_outside(
+ websocket_api.result_message(msg['id'], {}))
+
+ hass.async_create_task(
+ async_delete_refresh_token(connection.user, msg['refresh_token_id']))
diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py
index a518bdde415..73a739c2960 100644
--- a/homeassistant/components/auth/login_flow.py
+++ b/homeassistant/components/auth/login_flow.py
@@ -66,7 +66,7 @@ associate with an credential if "type" set to "link_user" in
"version": 1
}
"""
-import aiohttp.web
+from aiohttp import web
import voluptuous as vol
from homeassistant import data_entry_flow
@@ -95,11 +95,20 @@ class AuthProvidersView(HomeAssistantView):
async def get(self, request):
"""Get available auth providers."""
+ hass = request.app['hass']
+
+ if not hass.components.onboarding.async_is_onboarded():
+ return self.json_message(
+ message='Onboarding not finished',
+ status_code=400,
+ message_code='onboarding_required'
+ )
+
return self.json([{
'name': provider.name,
'id': provider.id,
'type': provider.type,
- } for provider in request.app['hass'].auth.auth_providers])
+ } for provider in hass.auth.auth_providers])
def _prepare_result_json(result):
@@ -139,7 +148,7 @@ class LoginFlowIndexView(HomeAssistantView):
async def get(self, request):
"""Do not allow index of flows in progress."""
- return aiohttp.web.Response(status=405)
+ return web.Response(status=405)
@RequestDataValidator(vol.Schema({
vol.Required('client_id'): str,
diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py
index c6c0af90d15..43fd4cedb88 100644
--- a/homeassistant/components/automation/__init__.py
+++ b/homeassistant/components/automation/__init__.py
@@ -158,27 +158,26 @@ def async_reload(hass):
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
- yield from _async_process_config(hass, config, component)
+ await _async_process_config(hass, config, component)
- @asyncio.coroutine
- def trigger_service_handler(service_call):
+ async def trigger_service_handler(service_call):
"""Handle automation triggers."""
tasks = []
for entity in component.async_extract_from_service(service_call):
tasks.append(entity.async_trigger(
- service_call.data.get(ATTR_VARIABLES), True))
+ service_call.data.get(ATTR_VARIABLES),
+ skip_condition=True,
+ context=service_call.context))
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
- @asyncio.coroutine
- def turn_onoff_service_handler(service_call):
+ async def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
tasks = []
method = 'async_{}'.format(service_call.service)
@@ -186,10 +185,9 @@ def async_setup(hass, config):
tasks.append(getattr(entity, method)())
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
- @asyncio.coroutine
- def toggle_service_handler(service_call):
+ async def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
tasks = []
for entity in component.async_extract_from_service(service_call):
@@ -199,15 +197,14 @@ def async_setup(hass, config):
tasks.append(entity.async_turn_on())
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
- @asyncio.coroutine
- def reload_service_handler(service_call):
+ async def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
- conf = yield from component.async_prepare_reload()
+ conf = await component.async_prepare_reload()
if conf is None:
return
- yield from _async_process_config(hass, conf, component)
+ await _async_process_config(hass, conf, component)
hass.services.async_register(
DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
@@ -272,15 +269,14 @@ class AutomationEntity(ToggleEntity):
"""Return True if entity is on."""
return self._async_detach_triggers is not None
- @asyncio.coroutine
- def async_added_to_hass(self) -> None:
+ async def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
if self._initial_state is not None:
enable_automation = self._initial_state
_LOGGER.debug("Automation %s initial state %s from config "
"initial_state", self.entity_id, enable_automation)
else:
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
@@ -298,54 +294,50 @@ class AutomationEntity(ToggleEntity):
# HomeAssistant is starting up
if self.hass.state == CoreState.not_running:
- @asyncio.coroutine
- def async_enable_automation(event):
+ async def async_enable_automation(event):
"""Start automation on startup."""
- yield from self.async_enable()
+ await self.async_enable()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_enable_automation)
# HomeAssistant is running
else:
- yield from self.async_enable()
+ await self.async_enable()
- @asyncio.coroutine
- def async_turn_on(self, **kwargs) -> None:
+ async def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self.is_on:
return
- yield from self.async_enable()
+ await self.async_enable()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self.is_on:
return
self._async_detach_triggers()
self._async_detach_triggers = None
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_trigger(self, variables, skip_condition=False):
+ async def async_trigger(self, variables, skip_condition=False,
+ context=None):
"""Trigger automation.
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
- yield from self._async_action(self.entity_id, variables)
+ self.async_set_context(context)
+ await self._async_action(self.entity_id, variables, context)
self._last_triggered = utcnow()
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_will_remove_from_hass(self):
+ async def async_will_remove_from_hass(self):
"""Remove listeners when removing automation from HASS."""
- yield from self.async_turn_off()
+ await self.async_turn_off()
- @asyncio.coroutine
- def async_enable(self):
+ async def async_enable(self):
"""Enable this automation entity.
This method is a coroutine.
@@ -353,9 +345,9 @@ class AutomationEntity(ToggleEntity):
if self.is_on:
return
- self._async_detach_triggers = yield from self._async_attach_triggers(
+ self._async_detach_triggers = await self._async_attach_triggers(
self.async_trigger)
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
@property
def device_state_attributes(self):
@@ -368,8 +360,7 @@ class AutomationEntity(ToggleEntity):
}
-@asyncio.coroutine
-def _async_process_config(hass, config, component):
+async def _async_process_config(hass, config, component):
"""Process config and add automations.
This method is a coroutine.
@@ -411,20 +402,19 @@ def _async_process_config(hass, config, component):
entities.append(entity)
if entities:
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
- @asyncio.coroutine
- def action(entity_id, variables):
+ async def action(entity_id, variables, context):
"""Execute an action."""
_LOGGER.info('Executing %s', name)
logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id)
- yield from script_obj.async_run(variables)
+ await script_obj.async_run(variables, context)
return action
@@ -448,8 +438,7 @@ def _async_process_if(hass, config, p_config):
return if_action
-@asyncio.coroutine
-def _async_process_trigger(hass, config, trigger_configs, name, action):
+async def _async_process_trigger(hass, config, trigger_configs, name, action):
"""Set up the triggers.
This method is a coroutine.
@@ -457,13 +446,13 @@ def _async_process_trigger(hass, config, trigger_configs, name, action):
removes = []
for conf in trigger_configs:
- platform = yield from async_prepare_setup_platform(
+ platform = await async_prepare_setup_platform(
hass, config, DOMAIN, conf.get(CONF_PLATFORM))
if platform is None:
return None
- remove = yield from platform.async_trigger(hass, conf, action)
+ remove = await platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up trigger %s", name)
diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py
index 7c035d7d1a5..e19a85edae6 100644
--- a/homeassistant/components/automation/event.py
+++ b/homeassistant/components/automation/event.py
@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
# If event data doesn't match requested schema, skip event
return
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'event',
'event': event,
},
- })
+ }, context=event.context))
return hass.bus.async_listen(event_type, handle_event)
diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py
index 74cf195bc61..b55d99f706a 100644
--- a/homeassistant/components/automation/homeassistant.py
+++ b/homeassistant/components/automation/homeassistant.py
@@ -32,12 +32,12 @@ def async_trigger(hass, config, action):
@callback
def hass_shutdown(event):
"""Execute when Home Assistant is shutting down."""
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'homeassistant',
'event': event,
},
- })
+ }, context=event.context))
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
hass_shutdown)
@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
if hass.state == CoreState.starting:
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'homeassistant',
'event': event,
},
- })
+ }))
return lambda: None
diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py
index b59271f25e5..f0dcbf0be57 100644
--- a/homeassistant/components/automation/numeric_state.py
+++ b/homeassistant/components/automation/numeric_state.py
@@ -66,7 +66,7 @@ def async_trigger(hass, config, action):
@callback
def call_action():
"""Call action with right context."""
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
@@ -75,7 +75,7 @@ def async_trigger(hass, config, action):
'from_state': from_s,
'to_state': to_s,
}
- })
+ }, context=to_s.context))
matching = check_numeric_state(entity, from_s, to_s)
diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py
index 9243f960850..263d4158e25 100644
--- a/homeassistant/components/automation/state.py
+++ b/homeassistant/components/automation/state.py
@@ -43,7 +43,7 @@ def async_trigger(hass, config, action):
@callback
def call_action():
"""Call action with right context."""
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'state',
'entity_id': entity,
@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
'to_state': to_s,
'for': time_delta,
}
- })
+ }, context=to_s.context))
# Ignore changes to state attributes if from/to is in use
if (not match_all and from_s is not None and to_s is not None and
diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py
index 0fcdeaae5e0..67a44f1a347 100644
--- a/homeassistant/components/automation/template.py
+++ b/homeassistant/components/automation/template.py
@@ -32,13 +32,13 @@ def async_trigger(hass, config, action):
@callback
def template_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
- })
+ }, context=to_s.context))
return async_track_template(hass, value_template, template_listener)
diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py
index 61d846582cb..f30dfe753cb 100644
--- a/homeassistant/components/automation/zone.py
+++ b/homeassistant/components/automation/zone.py
@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
- hass.async_run_job(action, {
+ hass.async_run_job(action({
'trigger': {
'platform': 'zone',
'entity_id': entity,
@@ -60,7 +60,7 @@ def async_trigger(hass, config, action):
'zone': zone_state,
'event': event,
},
- })
+ }, context=to_s.context))
return async_track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)
diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py
index 1fb62124407..d2ca9e7c5e8 100644
--- a/homeassistant/components/binary_sensor/deconz.py
+++ b/homeassistant/components/binary_sensor/deconz.py
@@ -54,6 +54,11 @@ class DeconzBinarySensor(BinarySensorDevice):
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
+ async def async_will_remove_from_hass(self) -> None:
+ """Disconnect sensor object when removed."""
+ self._sensor.remove_callback(self.async_update_callback)
+ self._sensor = None
+
@callback
def async_update_callback(self, reason):
"""Update the sensor's state.
diff --git a/homeassistant/components/binary_sensor/openuv.py b/homeassistant/components/binary_sensor/openuv.py
index 0b299529a46..c7c27d73ee4 100644
--- a/homeassistant/components/binary_sensor/openuv.py
+++ b/homeassistant/components/binary_sensor/openuv.py
@@ -7,12 +7,11 @@ https://home-assistant.io/components/binary_sensor.openuv/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
-from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.openuv import (
- BINARY_SENSORS, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE,
- TYPE_PROTECTION_WINDOW, OpenUvEntity)
+ BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN,
+ TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity)
from homeassistant.util.dt import as_local, parse_datetime, utcnow
DEPENDENCIES = ['openuv']
@@ -26,17 +25,20 @@ ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
- """Set up the OpenUV binary sensor platform."""
- if discovery_info is None:
- return
+ """Set up an OpenUV sensor based on existing config."""
+ pass
- openuv = hass.data[DOMAIN]
+
+async def async_setup_entry(hass, entry, async_add_entities):
+ """Set up an OpenUV sensor based on a config entry."""
+ openuv = hass.data[DOMAIN][DATA_OPENUV_CLIENT][entry.entry_id]
binary_sensors = []
- for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
+ for sensor_type in openuv.binary_sensor_conditions:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
- OpenUvBinarySensor(openuv, sensor_type, name, icon))
+ OpenUvBinarySensor(
+ openuv, sensor_type, name, icon, entry.entry_id))
async_add_entities(binary_sensors, True)
@@ -44,14 +46,16 @@ async def async_setup_platform(
class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
"""Define a binary sensor for OpenUV."""
- def __init__(self, openuv, sensor_type, name, icon):
+ def __init__(self, openuv, sensor_type, name, icon, entry_id):
"""Initialize the sensor."""
super().__init__(openuv)
+ self._entry_id = entry_id
self._icon = icon
self._latitude = openuv.client.latitude
self._longitude = openuv.client.longitude
self._name = name
+ self._dispatch_remove = None
self._sensor_type = sensor_type
self._state = None
@@ -83,8 +87,9 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
async def async_added_to_hass(self):
"""Register callbacks."""
- async_dispatcher_connect(
+ self._dispatch_remove = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, self._update_data)
+ self.async_on_remove(self._dispatch_remove)
async def async_update(self):
"""Update the state."""
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index 76860702165..a8a486013d4 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -142,6 +142,68 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass
async def async_get_image(hass, entity_id, timeout=10):
"""Fetch an image from a camera entity."""
+ camera = _get_camera_from_entity_id(hass, entity_id)
+
+ with suppress(asyncio.CancelledError, asyncio.TimeoutError):
+ with async_timeout.timeout(timeout, loop=hass.loop):
+ image = await camera.async_camera_image()
+
+ if image:
+ return Image(camera.content_type, image)
+
+ raise HomeAssistantError('Unable to get image')
+
+
+@bind_hass
+async def async_get_mjpeg_stream(hass, request, entity_id):
+ """Fetch an mjpeg stream from a camera entity."""
+ camera = _get_camera_from_entity_id(hass, entity_id)
+
+ return await camera.handle_async_mjpeg_stream(request)
+
+
+async def async_get_still_stream(request, image_cb, content_type, interval):
+ """Generate an HTTP MJPEG stream from camera images.
+
+ This method must be run in the event loop.
+ """
+ response = web.StreamResponse()
+ response.content_type = ('multipart/x-mixed-replace; '
+ 'boundary=--frameboundary')
+ await response.prepare(request)
+
+ async def write_to_mjpeg_stream(img_bytes):
+ """Write image to stream."""
+ await response.write(bytes(
+ '--frameboundary\r\n'
+ 'Content-Type: {}\r\n'
+ 'Content-Length: {}\r\n\r\n'.format(
+ content_type, len(img_bytes)),
+ 'utf-8') + img_bytes + b'\r\n')
+
+ last_image = None
+
+ while True:
+ img_bytes = await image_cb()
+ if not img_bytes:
+ break
+
+ if img_bytes != last_image:
+ await write_to_mjpeg_stream(img_bytes)
+
+ # Chrome seems to always ignore first picture,
+ # print it twice.
+ if last_image is None:
+ await write_to_mjpeg_stream(img_bytes)
+ last_image = img_bytes
+
+ await asyncio.sleep(interval)
+
+ return response
+
+
+def _get_camera_from_entity_id(hass, entity_id):
+ """Get camera component from entity_id."""
component = hass.data.get(DOMAIN)
if component is None:
@@ -155,14 +217,7 @@ async def async_get_image(hass, entity_id, timeout=10):
if not camera.is_on:
raise HomeAssistantError('Camera is off')
- with suppress(asyncio.CancelledError, asyncio.TimeoutError):
- with async_timeout.timeout(timeout, loop=hass.loop):
- image = await camera.async_camera_image()
-
- if image:
- return Image(camera.content_type, image)
-
- raise HomeAssistantError('Unable to get image')
+ return camera
async def async_setup(hass, config):
@@ -290,39 +345,8 @@ class Camera(Entity):
This method must be run in the event loop.
"""
- response = web.StreamResponse()
- response.content_type = ('multipart/x-mixed-replace; '
- 'boundary=--frameboundary')
- await response.prepare(request)
-
- async def write_to_mjpeg_stream(img_bytes):
- """Write image to stream."""
- await response.write(bytes(
- '--frameboundary\r\n'
- 'Content-Type: {}\r\n'
- 'Content-Length: {}\r\n\r\n'.format(
- self.content_type, len(img_bytes)),
- 'utf-8') + img_bytes + b'\r\n')
-
- last_image = None
-
- while True:
- img_bytes = await self.async_camera_image()
- if not img_bytes:
- break
-
- if img_bytes and img_bytes != last_image:
- await write_to_mjpeg_stream(img_bytes)
-
- # Chrome seems to always ignore first picture,
- # print it twice.
- if last_image is None:
- await write_to_mjpeg_stream(img_bytes)
- last_image = img_bytes
-
- await asyncio.sleep(interval)
-
- return response
+ return await async_get_still_stream(request, self.async_camera_image,
+ self.content_type, interval)
async def handle_async_mjpeg_stream(self, request):
"""Serve an HTTP MJPEG stream from the camera.
diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py
index 6c245ffdf43..83d87311646 100644
--- a/homeassistant/components/camera/proxy.py
+++ b/homeassistant/components/camera/proxy.py
@@ -7,17 +7,15 @@ https://www.home-assistant.io/components/camera.proxy/
import asyncio
import logging
-import aiohttp
-import async_timeout
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
+from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers.aiohttp_client import (
- async_aiohttp_proxy_web, async_get_clientsession)
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
+from . import async_get_still_stream
REQUIREMENTS = ['pillow==5.2.0']
@@ -158,22 +156,14 @@ class ProxyCamera(Camera):
return self._last_image
self._last_image_time = now
- url = "{}/api/camera_proxy/{}".format(
- self.hass.config.api.base_url, self._proxied_camera)
- try:
- websession = async_get_clientsession(self.hass)
- with async_timeout.timeout(10, loop=self.hass.loop):
- response = await websession.get(url, headers=self._headers)
- image = await response.read()
- except asyncio.TimeoutError:
- _LOGGER.error("Timeout getting camera image")
- return self._last_image
- except aiohttp.ClientError as err:
- _LOGGER.error("Error getting new camera image: %s", err)
+ image = await self.hass.components.camera.async_get_image(
+ self._proxied_camera)
+ if not image:
+ _LOGGER.error("Error getting original camera image")
return self._last_image
image = await self.hass.async_add_job(
- _resize_image, image, self._image_opts)
+ _resize_image, image.content, self._image_opts)
if self._cache_images:
self._last_image = image
@@ -181,56 +171,28 @@ class ProxyCamera(Camera):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from camera images."""
- websession = async_get_clientsession(self.hass)
- url = "{}/api/camera_proxy_stream/{}".format(
- self.hass.config.api.base_url, self._proxied_camera)
- stream_coro = websession.get(url, headers=self._headers)
-
if not self._stream_opts:
- return await async_aiohttp_proxy_web(
- self.hass, request, stream_coro)
+ return await self.hass.components.camera.async_get_mjpeg_stream(
+ request, self._proxied_camera)
- response = aiohttp.web.StreamResponse()
- response.content_type = (
- 'multipart/x-mixed-replace; boundary=--frameboundary')
- await response.prepare(request)
-
- async def write(img_bytes):
- """Write image to stream."""
- await response.write(bytes(
- '--frameboundary\r\n'
- 'Content-Type: {}\r\n'
- 'Content-Length: {}\r\n\r\n'.format(
- self.content_type, len(img_bytes)),
- 'utf-8') + img_bytes + b'\r\n')
-
- with async_timeout.timeout(10, loop=self.hass.loop):
- req = await stream_coro
-
- try:
- # This would be nicer as an async generator
- # But that would only be supported for python >=3.6
- data = b''
- stream = req.content
- while True:
- chunk = await stream.read(102400)
- if not chunk:
- break
- data += chunk
- jpg_start = data.find(b'\xff\xd8')
- jpg_end = data.find(b'\xff\xd9')
- if jpg_start != -1 and jpg_end != -1:
- image = data[jpg_start:jpg_end + 2]
- image = await self.hass.async_add_job(
- _resize_image, image, self._stream_opts)
- await write(image)
- data = data[jpg_end + 2:]
- finally:
- req.close()
-
- return response
+ return await async_get_still_stream(
+ request, self._async_stream_image,
+ self.content_type, self.frame_interval)
@property
def name(self):
"""Return the name of this camera."""
return self._name
+
+ async def _async_stream_image(self):
+ """Return a still image response from the camera."""
+ try:
+ image = await self.hass.components.camera.async_get_image(
+ self._proxied_camera)
+ if not image:
+ return None
+ except HomeAssistantError:
+ raise asyncio.CancelledError
+
+ return await self.hass.async_add_job(
+ _resize_image, image.content, self._stream_opts)
diff --git a/homeassistant/components/camera/push.py b/homeassistant/components/camera/push.py
index 305e29d62d3..c9deca1309d 100644
--- a/homeassistant/components/camera/push.py
+++ b/homeassistant/components/camera/push.py
@@ -13,8 +13,10 @@ import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
STATE_IDLE, STATE_RECORDING
from homeassistant.core import callback
-from homeassistant.components.http.view import HomeAssistantView
-from homeassistant.const import CONF_NAME, CONF_TIMEOUT, HTTP_BAD_REQUEST
+from homeassistant.components.http.view import KEY_AUTHENTICATED,\
+ HomeAssistantView
+from homeassistant.const import CONF_NAME, CONF_TIMEOUT,\
+ HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
import homeassistant.util.dt as dt_util
@@ -25,11 +27,13 @@ DEPENDENCIES = ['http']
CONF_BUFFER_SIZE = 'buffer'
CONF_IMAGE_FIELD = 'field'
+CONF_TOKEN = 'token'
DEFAULT_NAME = "Push Camera"
ATTR_FILENAME = 'filename'
ATTR_LAST_TRIP = 'last_trip'
+ATTR_TOKEN = 'token'
PUSH_CAMERA_DATA = 'push_camera'
@@ -39,6 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TIMEOUT, default=timedelta(seconds=5)): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_IMAGE_FIELD, default='image'): cv.string,
+ vol.Optional(CONF_TOKEN): vol.All(cv.string, vol.Length(min=8)),
})
@@ -50,7 +55,8 @@ async def async_setup_platform(hass, config, async_add_entities,
cameras = [PushCamera(config[CONF_NAME],
config[CONF_BUFFER_SIZE],
- config[CONF_TIMEOUT])]
+ config[CONF_TIMEOUT],
+ config.get(CONF_TOKEN))]
hass.http.register_view(CameraPushReceiver(hass,
config[CONF_IMAGE_FIELD]))
@@ -63,6 +69,7 @@ class CameraPushReceiver(HomeAssistantView):
url = "/api/camera_push/{entity_id}"
name = 'api:camera_push:camera_entity'
+ requires_auth = False
def __init__(self, hass, image_field):
"""Initialize CameraPushReceiver with camera entity."""
@@ -75,8 +82,21 @@ class CameraPushReceiver(HomeAssistantView):
if _camera is None:
_LOGGER.error("Unknown %s", entity_id)
+ status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED]\
+ else HTTP_UNAUTHORIZED
return self.json_message('Unknown {}'.format(entity_id),
- HTTP_BAD_REQUEST)
+ status)
+
+ # Supports HA authentication and token based
+ # when token has been configured
+ authenticated = (request[KEY_AUTHENTICATED] or
+ (_camera.token is not None and
+ request.query.get('token') == _camera.token))
+
+ if not authenticated:
+ return self.json_message(
+ 'Invalid authorization credentials for {}'.format(entity_id),
+ HTTP_UNAUTHORIZED)
try:
data = await request.post()
@@ -95,7 +115,7 @@ class CameraPushReceiver(HomeAssistantView):
class PushCamera(Camera):
"""The representation of a Push camera."""
- def __init__(self, name, buffer_size, timeout):
+ def __init__(self, name, buffer_size, timeout, token):
"""Initialize push camera component."""
super().__init__()
self._name = name
@@ -106,6 +126,7 @@ class PushCamera(Camera):
self._timeout = timeout
self.queue = deque([], buffer_size)
self._current_image = None
+ self.token = token
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
@@ -168,5 +189,6 @@ class PushCamera(Camera):
name: value for name, value in (
(ATTR_LAST_TRIP, self._last_trip),
(ATTR_FILENAME, self._filename),
+ (ATTR_TOKEN, self.token),
) if value is not None
}
diff --git a/homeassistant/components/cast/.translations/ca.json b/homeassistant/components/cast/.translations/ca.json
index e65e00f8624..570cc7fdc00 100644
--- a/homeassistant/components/cast/.translations/ca.json
+++ b/homeassistant/components/cast/.translations/ca.json
@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.",
- "single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de Google Cast."
+ "single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de Google Cast."
},
"step": {
"confirm": {
diff --git a/homeassistant/components/cast/.translations/fr.json b/homeassistant/components/cast/.translations/fr.json
index d3b95121de6..99feeb3c898 100644
--- a/homeassistant/components/cast/.translations/fr.json
+++ b/homeassistant/components/cast/.translations/fr.json
@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.",
- "single_instance_allowed": "Seulement une seule configuration de Google Cast est n\u00e9cessaire."
+ "single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire."
},
"step": {
"confirm": {
diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py
index fec18329878..85879b8122a 100644
--- a/homeassistant/components/climate/generic_thermostat.py
+++ b/homeassistant/components/climate/generic_thermostat.py
@@ -251,6 +251,14 @@ class GenericThermostat(ClimateDevice):
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
+ async def async_turn_on(self):
+ """Turn thermostat on."""
+ await self.async_set_operation_mode(self.operation_list[0])
+
+ async def async_turn_off(self):
+ """Turn thermostat off."""
+ await self.async_set_operation_mode(STATE_OFF)
+
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py
index 321559f10ee..bc63512fcf3 100644
--- a/homeassistant/components/climate/nest.py
+++ b/homeassistant/components/climate/nest.py
@@ -8,7 +8,8 @@ import logging
import voluptuous as vol
-from homeassistant.components.nest import DATA_NEST, SIGNAL_NEST_UPDATE
+from homeassistant.components.nest import (
+ DATA_NEST, SIGNAL_NEST_UPDATE, DOMAIN as NEST_DOMAIN)
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
@@ -127,6 +128,19 @@ class NestThermostat(ClimateDevice):
"""Return unique ID for this device."""
return self.device.serial
+ @property
+ def device_info(self):
+ """Return information about the device."""
+ return {
+ 'identifiers': {
+ (NEST_DOMAIN, self.device.device_id),
+ },
+ 'name': self.device.name_long,
+ 'manufacturer': 'Nest Labs',
+ 'model': "Thermostat",
+ 'sw_version': self.device.software_version,
+ }
+
@property
def name(self):
"""Return the name of the nest, if any."""
diff --git a/homeassistant/components/climate/opentherm_gw.py b/homeassistant/components/climate/opentherm_gw.py
new file mode 100644
index 00000000000..c1f7afa61b0
--- /dev/null
+++ b/homeassistant/components/climate/opentherm_gw.py
@@ -0,0 +1,189 @@
+"""
+Support for OpenTherm Gateway devices.
+
+For more details about this component, please refer to the documentation at
+http://home-assistant.io/components/climate.opentherm_gw/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
+ STATE_IDLE, STATE_HEAT,
+ STATE_COOL,
+ SUPPORT_TARGET_TEMPERATURE)
+from homeassistant.const import (ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME,
+ PRECISION_HALVES, PRECISION_TENTHS,
+ TEMP_CELSIUS, PRECISION_WHOLE)
+import homeassistant.helpers.config_validation as cv
+
+REQUIREMENTS = ['pyotgw==0.1b0']
+
+CONF_FLOOR_TEMP = "floor_temperature"
+CONF_PRECISION = 'precision'
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_DEVICE): cv.string,
+ vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string,
+ vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES,
+ PRECISION_WHOLE]),
+ vol.Optional(CONF_FLOOR_TEMP, default=False): cv.boolean,
+})
+
+SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
+ """Set up the opentherm_gw device."""
+ gateway = OpenThermGateway(config)
+ async_add_entities([gateway])
+
+
+class OpenThermGateway(ClimateDevice):
+ """Representation of a climate device."""
+
+ def __init__(self, config):
+ """Initialize the sensor."""
+ import pyotgw
+ self.pyotgw = pyotgw
+ self.gateway = self.pyotgw.pyotgw()
+ self._device = config[CONF_DEVICE]
+ self.friendly_name = config.get(CONF_NAME)
+ self.floor_temp = config.get(CONF_FLOOR_TEMP)
+ self.temp_precision = config.get(CONF_PRECISION)
+ self._current_operation = STATE_IDLE
+ self._current_temperature = 0.0
+ self._target_temperature = 0.0
+ self._away_mode_a = None
+ self._away_mode_b = None
+ self._away_state_a = False
+ self._away_state_b = False
+
+ async def async_added_to_hass(self):
+ """Connect to the OpenTherm Gateway device."""
+ await self.gateway.connect(self.hass.loop, self._device)
+ self.gateway.subscribe(self.receive_report)
+ _LOGGER.debug("Connected to %s on %s", self.friendly_name,
+ self._device)
+
+ async def receive_report(self, status):
+ """Receive and handle a new report from the Gateway."""
+ _LOGGER.debug("Received report: %s", status)
+ ch_active = status.get(self.pyotgw.DATA_SLAVE_CH_ACTIVE)
+ cooling_active = status.get(self.pyotgw.DATA_SLAVE_COOLING_ACTIVE)
+ if ch_active:
+ self._current_operation = STATE_HEAT
+ elif cooling_active:
+ self._current_operation = STATE_COOL
+ else:
+ self._current_operation = STATE_IDLE
+ self._current_temperature = status.get(self.pyotgw.DATA_ROOM_TEMP)
+
+ temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT_OVRD)
+ if temp is None:
+ temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT)
+ self._target_temperature = temp
+
+ # GPIO mode 5: 0 == Away
+ # GPIO mode 6: 1 == Away
+ gpio_a_state = status.get(self.pyotgw.OTGW_GPIO_A)
+ if gpio_a_state == 5:
+ self._away_mode_a = 0
+ elif gpio_a_state == 6:
+ self._away_mode_a = 1
+ else:
+ self._away_mode_a = None
+ gpio_b_state = status.get(self.pyotgw.OTGW_GPIO_B)
+ if gpio_b_state == 5:
+ self._away_mode_b = 0
+ elif gpio_b_state == 6:
+ self._away_mode_b = 1
+ else:
+ self._away_mode_b = None
+ if self._away_mode_a is not None:
+ self._away_state_a = (status.get(self.pyotgw.OTGW_GPIO_A_STATE) ==
+ self._away_mode_a)
+ if self._away_mode_b is not None:
+ self._away_state_b = (status.get(self.pyotgw.OTGW_GPIO_B_STATE) ==
+ self._away_mode_b)
+ self.async_schedule_update_ha_state()
+
+ @property
+ def name(self):
+ """Return the friendly name."""
+ return self.friendly_name
+
+ @property
+ def precision(self):
+ """Return the precision of the system."""
+ if self.temp_precision is not None:
+ return self.temp_precision
+ if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
+ return PRECISION_HALVES
+ return PRECISION_WHOLE
+
+ @property
+ def should_poll(self):
+ """Disable polling for this entity."""
+ return False
+
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement used by the platform."""
+ return TEMP_CELSIUS
+
+ @property
+ def current_operation(self):
+ """Return current operation ie. heat, cool, idle."""
+ return self._current_operation
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ if self.floor_temp is True:
+ if self.temp_precision == PRECISION_HALVES:
+ return int(2 * self._current_temperature) / 2
+ if self.temp_precision == PRECISION_TENTHS:
+ return int(10 * self._current_temperature) / 10
+ return int(self._current_temperature)
+ return self._current_temperature
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ return self._target_temperature
+
+ @property
+ def target_temperature_step(self):
+ """Return the supported step of target temperature."""
+ return self.temp_precision
+
+ @property
+ def is_away_mode_on(self):
+ """Return true if away mode is on."""
+ return self._away_state_a or self._away_state_b
+
+ async def async_set_temperature(self, **kwargs):
+ """Set new target temperature."""
+ if ATTR_TEMPERATURE in kwargs:
+ temp = float(kwargs[ATTR_TEMPERATURE])
+ self._target_temperature = await self.gateway.set_target_temp(
+ temp)
+ self.async_schedule_update_ha_state()
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ return SUPPORT_FLAGS
+
+ @property
+ def min_temp(self):
+ """Return the minimum temperature."""
+ return 1
+
+ @property
+ def max_temp(self):
+ """Return the maximum temperature."""
+ return 30
diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py
index 429b544aefc..14cd2a0f02e 100644
--- a/homeassistant/components/climate/radiotherm.py
+++ b/homeassistant/components/climate/radiotherm.py
@@ -174,8 +174,8 @@ class RadioThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
- ATTR_FAN: self._fmode,
- ATTR_MODE: self._tmode,
+ ATTR_FAN: self._fstate,
+ ATTR_MODE: self._tstate,
}
@property
diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py
index 04d2c713cdc..e0c0e7daaf4 100644
--- a/homeassistant/components/config/config_entries.py
+++ b/homeassistant/components/config/config_entries.py
@@ -7,9 +7,6 @@ from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
-REQUIREMENTS = ['voluptuous-serialize==2.0.0']
-
-
@asyncio.coroutine
def async_setup(hass):
"""Enable the Home Assistant views."""
diff --git a/homeassistant/components/cover/insteon.py b/homeassistant/components/cover/insteon.py
new file mode 100644
index 00000000000..f0cf93c13e9
--- /dev/null
+++ b/homeassistant/components/cover/insteon.py
@@ -0,0 +1,73 @@
+"""
+Support for Insteon covers via PowerLinc Modem.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/cover.insteon/
+"""
+import logging
+import math
+
+from homeassistant.components.insteon import InsteonEntity
+from homeassistant.components.cover import (CoverDevice, ATTR_POSITION,
+ SUPPORT_OPEN, SUPPORT_CLOSE,
+ SUPPORT_SET_POSITION)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['insteon']
+SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
+
+
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
+ """Set up the Insteon platform."""
+ if not discovery_info:
+ return
+
+ insteon_modem = hass.data['insteon'].get('modem')
+
+ address = discovery_info['address']
+ device = insteon_modem.devices[address]
+ state_key = discovery_info['state_key']
+
+ _LOGGER.debug('Adding device %s entity %s to Cover platform',
+ device.address.hex, device.states[state_key].name)
+
+ new_entity = InsteonCoverDevice(device, state_key)
+
+ async_add_entities([new_entity])
+
+
+class InsteonCoverDevice(InsteonEntity, CoverDevice):
+ """A Class for an Insteon device."""
+
+ @property
+ def current_cover_position(self):
+ """Return the current cover position."""
+ return int(math.ceil(self._insteon_device_state.value*100/255))
+
+ @property
+ def supported_features(self):
+ """Return the supported features for this entity."""
+ return SUPPORTED_FEATURES
+
+ @property
+ def is_closed(self):
+ """Return the boolean response if the node is on."""
+ return bool(self.current_cover_position)
+
+ async def async_open_cover(self, **kwargs):
+ """Open device."""
+ self._insteon_device_state.open()
+
+ async def async_close_cover(self, **kwargs):
+ """Close device."""
+ self._insteon_device_state.close()
+
+ async def async_set_cover_position(self, **kwargs):
+ """Set the cover position."""
+ position = int(kwargs[ATTR_POSITION]*255/100)
+ if position == 0:
+ self._insteon_device_state.close()
+ else:
+ self._insteon_device_state.set_position(position)
diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py
index bedc041fccc..413794505db 100644
--- a/homeassistant/components/cover/myq.py
+++ b/homeassistant/components/cover/myq.py
@@ -8,17 +8,25 @@ import logging
import voluptuous as vol
-from homeassistant.components.cover import CoverDevice
+from homeassistant.components.cover import (
+ CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
from homeassistant.const import (
- CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
+ CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
+ STATE_OPENING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pymyq==0.0.11']
+REQUIREMENTS = ['pymyq==0.0.15']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'myq'
+MYQ_TO_HASS = {
+ 'closed': STATE_CLOSED,
+ 'closing': STATE_CLOSING,
+ 'opening': STATE_OPENING
+}
+
NOTIFICATION_ID = 'myq_notification'
NOTIFICATION_TITLE = 'MyQ Cover Setup'
@@ -87,7 +95,17 @@ class MyQDevice(CoverDevice):
@property
def is_closed(self):
"""Return true if cover is closed, else False."""
- return self._status == STATE_CLOSED
+ return MYQ_TO_HASS[self._status] == STATE_CLOSED
+
+ @property
+ def is_closing(self):
+ """Return if the cover is closing or not."""
+ return MYQ_TO_HASS[self._status] == STATE_CLOSING
+
+ @property
+ def is_opening(self):
+ """Return if the cover is opening or not."""
+ return MYQ_TO_HASS[self._status] == STATE_OPENING
def close_cover(self, **kwargs):
"""Issue close command to cover."""
@@ -97,6 +115,16 @@ class MyQDevice(CoverDevice):
"""Issue open command to cover."""
self.myq.open_device(self.device_id)
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ return SUPPORT_OPEN | SUPPORT_CLOSE
+
+ @property
+ def unique_id(self):
+ """Return a unique, HASS-friendly identifier for this entity."""
+ return self.device_id
+
def update(self):
"""Update status of cover."""
self._status = self.myq.get_status(self.device_id)
diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py
index e50fa488b92..41a4c2af045 100644
--- a/homeassistant/components/cover/rflink.py
+++ b/homeassistant/components/cover/rflink.py
@@ -92,9 +92,9 @@ class RflinkCover(RflinkCommand, CoverDevice):
self.cancel_queued_send_commands()
command = event['command']
- if command in ['on', 'allon']:
+ if command in ['on', 'allon', 'up']:
self._state = True
- elif command in ['off', 'alloff']:
+ elif command in ['off', 'alloff', 'down']:
self._state = False
@property
@@ -105,7 +105,12 @@ class RflinkCover(RflinkCommand, CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
- return None
+ return not self._state
+
+ @property
+ def assumed_state(self):
+ """Return True because covers can be stopped midway."""
+ return True
def async_close_cover(self, **kwargs):
"""Turn the device close."""
diff --git a/homeassistant/components/deconz/.translations/fr.json b/homeassistant/components/deconz/.translations/fr.json
index 02f174cd59f..56399a3c6fd 100644
--- a/homeassistant/components/deconz/.translations/fr.json
+++ b/homeassistant/components/deconz/.translations/fr.json
@@ -22,7 +22,8 @@
},
"options": {
"data": {
- "allow_clip_sensor": "Autoriser l'importation de capteurs virtuels"
+ "allow_clip_sensor": "Autoriser l'importation de capteurs virtuels",
+ "allow_deconz_groups": "Autoriser l'importation des groupes deCONZ"
},
"title": "Options de configuration suppl\u00e9mentaires pour deCONZ"
}
diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py
index a4edc009ea1..6ed0a6e2c11 100644
--- a/homeassistant/components/deconz/__init__.py
+++ b/homeassistant/components/deconz/__init__.py
@@ -24,7 +24,7 @@ from .const import (
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
-REQUIREMENTS = ['pydeconz==44']
+REQUIREMENTS = ['pydeconz==47']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -46,6 +46,8 @@ SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_DATA): dict,
})
+SERVICE_DEVICE_REFRESH = 'device_refresh'
+
async def async_setup(hass, config):
"""Load configuration for deCONZ component.
@@ -84,15 +86,17 @@ async def async_setup_entry(hass, config_entry):
@callback
def async_add_device_callback(device_type, device):
"""Handle event of new device creation in deCONZ."""
+ if not isinstance(device, list):
+ device = [device]
async_dispatcher_send(
- hass, 'deconz_new_{}'.format(device_type), [device])
+ hass, 'deconz_new_{}'.format(device_type), device)
session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config_entry.data,
async_add_device=async_add_device_callback)
result = await deconz.async_load_parameters()
+
if result is False:
- _LOGGER.error("Failed to communicate with deCONZ")
return False
hass.data[DOMAIN] = deconz
@@ -149,16 +153,60 @@ async def async_setup_entry(hass, config_entry):
data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
if entity_id:
+
entities = hass.data.get(DATA_DECONZ_ID)
+
if entities:
field = entities.get(entity_id)
+
if field is None:
_LOGGER.error('Could not find the entity %s', entity_id)
return
+
await deconz.async_put_state(field, data)
+
hass.services.async_register(
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)
+ async def async_refresh_devices(call):
+ """Refresh available devices from deCONZ."""
+ deconz = hass.data[DOMAIN]
+
+ groups = list(deconz.groups.keys())
+ lights = list(deconz.lights.keys())
+ scenes = list(deconz.scenes.keys())
+ sensors = list(deconz.sensors.keys())
+
+ if not await deconz.async_load_parameters():
+ return
+
+ async_add_device_callback(
+ 'group', [group
+ for group_id, group in deconz.groups.items()
+ if group_id not in groups]
+ )
+
+ async_add_device_callback(
+ 'light', [light
+ for light_id, light in deconz.lights.items()
+ if light_id not in lights]
+ )
+
+ async_add_device_callback(
+ 'scene', [scene
+ for scene_id, scene in deconz.scenes.items()
+ if scene_id not in scenes]
+ )
+
+ async_add_device_callback(
+ 'sensor', [sensor
+ for sensor_id, sensor in deconz.sensors.items()
+ if sensor_id not in sensors]
+ )
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
+
@callback
def deconz_shutdown(event):
"""
@@ -179,15 +227,22 @@ async def async_unload_entry(hass, config_entry):
deconz = hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
deconz.close()
- for component in ['binary_sensor', 'light', 'scene', 'sensor']:
+
+ for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
await hass.config_entries.async_forward_entry_unload(
config_entry, component)
+
dispatchers = hass.data[DATA_DECONZ_UNSUB]
for unsub_dispatcher in dispatchers:
unsub_dispatcher()
hass.data[DATA_DECONZ_UNSUB] = []
- hass.data[DATA_DECONZ_EVENT] = []
+
+ for event in hass.data[DATA_DECONZ_EVENT]:
+ event.async_will_remove_from_hass()
+ hass.data[DATA_DECONZ_EVENT].remove(event)
+
hass.data[DATA_DECONZ_ID] = []
+
return True
@@ -206,6 +261,12 @@ class DeconzEvent:
self._event = 'deconz_{}'.format(CONF_EVENT)
self._id = slugify(self._device.name)
+ @callback
+ def async_will_remove_from_hass(self) -> None:
+ """Disconnect event object when removed."""
+ self._device.remove_callback(self.async_update_callback)
+ self._device = None
+
@callback
def async_update_callback(self, reason):
"""Fire the event if reason is that state is updated."""
diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml
index 78bf7041a93..fa0fb8e14a4 100644
--- a/homeassistant/components/deconz/services.yaml
+++ b/homeassistant/components/deconz/services.yaml
@@ -1,4 +1,3 @@
-
configure:
description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details.
fields:
@@ -11,3 +10,6 @@ configure:
data:
description: Data is a json object with what data you want to alter.
example: '{"on": true}'
+
+device_refresh:
+ description: Refresh device lists from deCONZ.
\ No newline at end of file
diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py
index d9cda24b699..47b86ab9ab2 100644
--- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py
+++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py
@@ -6,35 +6,25 @@ https://home-assistant.io/components/device_tracker.bluetooth_le_tracker/
"""
import logging
-import voluptuous as vol
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
- PLATFORM_SCHEMA, load_config, SOURCE_TYPE_BLUETOOTH_LE
+ load_config, SOURCE_TYPE_BLUETOOTH_LE
)
import homeassistant.util.dt as dt_util
-import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['gattlib==0.20150805']
+REQUIREMENTS = ['pygatt==3.2.0']
BLE_PREFIX = 'BLE_'
MIN_SEEN_NEW = 5
-CONF_SCAN_DURATION = 'scan_duration'
-CONF_BLUETOOTH_DEVICE = 'device_id'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int,
- vol.Optional(CONF_BLUETOOTH_DEVICE, default='hci0'): cv.string
-})
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the Bluetooth LE Scanner."""
# pylint: disable=import-error
- from gattlib import DiscoveryService
-
+ import pygatt
new_devices = {}
def see_device(address, name, new_device=False):
@@ -61,17 +51,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Discover Bluetooth LE devices."""
_LOGGER.debug("Discovering Bluetooth LE devices")
try:
- service = DiscoveryService(ble_dev_id)
- devices = service.discover(duration)
+ adapter = pygatt.GATTToolBackend()
+ devs = adapter.scan()
+
+ devices = {x['address']: x['name'] for x in devs}
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
except RuntimeError as error:
_LOGGER.error("Error during Bluetooth LE scan: %s", error)
- devices = []
+ return {}
return devices
yaml_path = hass.config.path(YAML_DEVICES)
- duration = config.get(CONF_SCAN_DURATION)
- ble_dev_id = config.get(CONF_BLUETOOTH_DEVICE)
devs_to_track = []
devs_donot_track = []
@@ -102,11 +92,11 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Lookup Bluetooth LE devices and update status."""
devs = discover_ble_devices()
for mac in devs_to_track:
- _LOGGER.debug("Checking %s", mac)
- result = mac in devs
- if not result:
- # Could not lookup device name
+ if mac not in devs:
continue
+
+ if devs[mac] is None:
+ devs[mac] = mac
see_device(mac, devs[mac])
if track_new:
@@ -119,5 +109,4 @@ def setup_scanner(hass, config, see, discovery_info=None):
track_point_in_utc_time(hass, update_ble, dt_util.utcnow() + interval)
update_ble(dt_util.utcnow())
-
return True
diff --git a/homeassistant/components/device_tracker/bluetooth_tracker.py b/homeassistant/components/device_tracker/bluetooth_tracker.py
index 2ca519d225c..d22a1ba7c1f 100644
--- a/homeassistant/components/device_tracker/bluetooth_tracker.py
+++ b/homeassistant/components/device_tracker/bluetooth_tracker.py
@@ -12,7 +12,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
- load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH)
+ load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH,
+ DOMAIN)
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -79,7 +80,13 @@ def setup_scanner(hass, config, see, discovery_info=None):
request_rssi = config.get(CONF_REQUEST_RSSI, False)
- def update_bluetooth(now):
+ def update_bluetooth(_):
+ """Update Bluetooth and set timer for the next update."""
+ update_bluetooth_once()
+ track_point_in_utc_time(
+ hass, update_bluetooth, dt_util.utcnow() + interval)
+
+ def update_bluetooth_once():
"""Lookup Bluetooth device and update status."""
try:
if track_new:
@@ -99,9 +106,14 @@ def setup_scanner(hass, config, see, discovery_info=None):
see_device(mac, result, rssi)
except bluetooth.BluetoothError:
_LOGGER.exception("Error looking up Bluetooth device")
- track_point_in_utc_time(
- hass, update_bluetooth, dt_util.utcnow() + interval)
+
+ def handle_update_bluetooth(call):
+ """Update bluetooth devices on demand."""
+ update_bluetooth_once()
update_bluetooth(dt_util.utcnow())
+ hass.services.register(
+ DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth)
+
return True
diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py
index 8c21e71bd30..170d3de6800 100644
--- a/homeassistant/components/device_tracker/google_maps.py
+++ b/homeassistant/components/device_tracker/google_maps.py
@@ -15,7 +15,7 @@ from homeassistant.const import ATTR_ID, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
-from homeassistant.util import slugify
+from homeassistant.util import slugify, dt as dt_util
REQUIREMENTS = ['locationsharinglib==2.0.11']
@@ -92,7 +92,7 @@ class GoogleMapsScanner:
ATTR_ADDRESS: person.address,
ATTR_FULL_NAME: person.full_name,
ATTR_ID: person.id,
- ATTR_LAST_SEEN: person.datetime,
+ ATTR_LAST_SEEN: dt_util.as_utc(person.datetime),
ATTR_NICKNAME: person.nickname,
}
self.see(
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index 72f61ddf1eb..6dd4be7ecec 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==20180903.0']
+REQUIREMENTS = ['home-assistant-frontend==20180916.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py
new file mode 100644
index 00000000000..67ed9520fa4
--- /dev/null
+++ b/homeassistant/components/geo_location/__init__.py
@@ -0,0 +1,68 @@
+"""
+Geo Location component.
+
+This component covers platforms that deal with external events that contain
+a geo location related to the installed HA instance.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/geo_location/
+"""
+import logging
+from datetime import timedelta
+from typing import Optional
+
+from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.entity_component import EntityComponent
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_DISTANCE = 'distance'
+DOMAIN = 'geo_location'
+ENTITY_ID_FORMAT = DOMAIN + '.{}'
+GROUP_NAME_ALL_EVENTS = 'All Geo Location Events'
+SCAN_INTERVAL = timedelta(seconds=60)
+
+
+async def async_setup(hass, config):
+ """Set up this component."""
+ component = EntityComponent(
+ _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_EVENTS)
+ await component.async_setup(config)
+ return True
+
+
+class GeoLocationEvent(Entity):
+ """This represents an external event with an associated geo location."""
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ if self.distance is not None:
+ return round(self.distance, 1)
+ return None
+
+ @property
+ def distance(self) -> Optional[float]:
+ """Return distance value of this external event."""
+ return None
+
+ @property
+ def latitude(self) -> Optional[float]:
+ """Return latitude value of this external event."""
+ return None
+
+ @property
+ def longitude(self) -> Optional[float]:
+ """Return longitude value of this external event."""
+ return None
+
+ @property
+ def state_attributes(self):
+ """Return the state attributes of this external event."""
+ data = {}
+ if self.latitude is not None:
+ data[ATTR_LATITUDE] = round(self.latitude, 5)
+ if self.longitude is not None:
+ data[ATTR_LONGITUDE] = round(self.longitude, 5)
+ return data
diff --git a/homeassistant/components/geo_location/demo.py b/homeassistant/components/geo_location/demo.py
new file mode 100644
index 00000000000..8e8d8211086
--- /dev/null
+++ b/homeassistant/components/geo_location/demo.py
@@ -0,0 +1,132 @@
+"""
+Demo platform for the geo location component.
+
+For more details about this platform, please refer to the documentation
+https://home-assistant.io/components/demo/
+"""
+import logging
+import random
+from datetime import timedelta
+from math import pi, cos, sin, radians
+
+from typing import Optional
+
+from homeassistant.components.geo_location import GeoLocationEvent
+from homeassistant.helpers.event import track_time_interval
+
+_LOGGER = logging.getLogger(__name__)
+
+AVG_KM_PER_DEGREE = 111.0
+DEFAULT_UNIT_OF_MEASUREMENT = "km"
+DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
+MAX_RADIUS_IN_KM = 50
+NUMBER_OF_DEMO_DEVICES = 5
+
+EVENT_NAMES = ["Bushfire", "Hazard Reduction", "Grass Fire", "Burn off",
+ "Structure Fire", "Fire Alarm", "Thunderstorm", "Tornado",
+ "Cyclone", "Waterspout", "Dust Storm", "Blizzard", "Ice Storm",
+ "Earthquake", "Tsunami"]
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the Demo geo locations."""
+ DemoManager(hass, add_entities)
+
+
+class DemoManager:
+ """Device manager for demo geo location events."""
+
+ def __init__(self, hass, add_entities):
+ """Initialise the demo geo location event manager."""
+ self._hass = hass
+ self._add_entities = add_entities
+ self._managed_devices = []
+ self._update(count=NUMBER_OF_DEMO_DEVICES)
+ self._init_regular_updates()
+
+ def _generate_random_event(self):
+ """Generate a random event in vicinity of this HA instance."""
+ home_latitude = self._hass.config.latitude
+ home_longitude = self._hass.config.longitude
+
+ # Approx. 111km per degree (north-south).
+ radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / \
+ AVG_KM_PER_DEGREE
+ radius_in_km = radius_in_degrees * AVG_KM_PER_DEGREE
+ angle = random.random() * 2 * pi
+ # Compute coordinates based on radius and angle. Adjust longitude value
+ # based on HA's latitude.
+ latitude = home_latitude + radius_in_degrees * sin(angle)
+ longitude = home_longitude + radius_in_degrees * cos(angle) / \
+ cos(radians(home_latitude))
+
+ event_name = random.choice(EVENT_NAMES)
+ return DemoGeoLocationEvent(event_name, radius_in_km, latitude,
+ longitude, DEFAULT_UNIT_OF_MEASUREMENT)
+
+ def _init_regular_updates(self):
+ """Schedule regular updates based on configured time interval."""
+ track_time_interval(self._hass, lambda now: self._update(),
+ DEFAULT_UPDATE_INTERVAL)
+
+ def _update(self, count=1):
+ """Remove events and add new random events."""
+ # Remove devices.
+ for _ in range(1, count + 1):
+ if self._managed_devices:
+ device = random.choice(self._managed_devices)
+ if device:
+ _LOGGER.debug("Removing %s", device)
+ self._managed_devices.remove(device)
+ self._hass.add_job(device.async_remove())
+ # Generate new devices from events.
+ new_devices = []
+ for _ in range(1, count + 1):
+ new_device = self._generate_random_event()
+ _LOGGER.debug("Adding %s", new_device)
+ new_devices.append(new_device)
+ self._managed_devices.append(new_device)
+ self._add_entities(new_devices)
+
+
+class DemoGeoLocationEvent(GeoLocationEvent):
+ """This represents a demo geo location event."""
+
+ def __init__(self, name, distance, latitude, longitude,
+ unit_of_measurement):
+ """Initialize entity with data provided."""
+ self._name = name
+ self._distance = distance
+ self._latitude = latitude
+ self._longitude = longitude
+ self._unit_of_measurement = unit_of_measurement
+
+ @property
+ def name(self) -> Optional[str]:
+ """Return the name of the event."""
+ return self._name
+
+ @property
+ def should_poll(self):
+ """No polling needed for a demo geo location event."""
+ return False
+
+ @property
+ def distance(self) -> Optional[float]:
+ """Return distance value of this external event."""
+ return self._distance
+
+ @property
+ def latitude(self) -> Optional[float]:
+ """Return latitude value of this external event."""
+ return self._latitude
+
+ @property
+ def longitude(self) -> Optional[float]:
+ """Return longitude value of this external event."""
+ return self._longitude
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement."""
+ return self._unit_of_measurement
diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py
new file mode 100644
index 00000000000..44b9e392157
--- /dev/null
+++ b/homeassistant/components/habitica/__init__.py
@@ -0,0 +1,158 @@
+"""
+The Habitica API component.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/habitica/
+"""
+
+import logging
+from collections import namedtuple
+
+import voluptuous as vol
+from homeassistant.const import \
+ CONF_NAME, CONF_URL, CONF_SENSORS, CONF_PATH, CONF_API_KEY
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers import \
+ config_validation as cv, discovery
+
+REQUIREMENTS = ['habitipy==0.2.0']
+_LOGGER = logging.getLogger(__name__)
+DOMAIN = "habitica"
+
+CONF_API_USER = "api_user"
+
+ST = SensorType = namedtuple('SensorType', [
+ "name", "icon", "unit", "path"
+])
+
+SENSORS_TYPES = {
+ 'name': ST('Name', None, '', ["profile", "name"]),
+ 'hp': ST('HP', 'mdi:heart', 'HP', ["stats", "hp"]),
+ 'maxHealth': ST('max HP', 'mdi:heart', 'HP', ["stats", "maxHealth"]),
+ 'mp': ST('Mana', 'mdi:auto-fix', 'MP', ["stats", "mp"]),
+ 'maxMP': ST('max Mana', 'mdi:auto-fix', 'MP', ["stats", "maxMP"]),
+ 'exp': ST('EXP', 'mdi:star', 'EXP', ["stats", "exp"]),
+ 'toNextLevel': ST(
+ 'Next Lvl', 'mdi:star', 'EXP', ["stats", "toNextLevel"]),
+ 'lvl': ST(
+ 'Lvl', 'mdi:arrow-up-bold-circle-outline', 'Lvl', ["stats", "lvl"]),
+ 'gp': ST('Gold', 'mdi:coin', 'Gold', ["stats", "gp"]),
+ 'class': ST('Class', 'mdi:sword', '', ["stats", "class"])
+}
+
+INSTANCE_SCHEMA = vol.Schema({
+ vol.Optional(CONF_URL, default='https://habitica.com'): cv.url,
+ vol.Optional(CONF_NAME): cv.string,
+ vol.Required(CONF_API_USER): cv.string,
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)):
+ vol.All(
+ cv.ensure_list,
+ vol.Unique(),
+ [vol.In(list(SENSORS_TYPES))])
+})
+
+has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name
+# because we want a handy alias
+
+
+def has_all_unique_users(value):
+ """Validate that all `api_user`s are unique."""
+ api_users = [user[CONF_API_USER] for user in value]
+ has_unique_values(api_users)
+ return value
+
+
+def has_all_unique_users_names(value):
+ """Validate that all user's names are unique and set if any is set."""
+ names = [user.get(CONF_NAME) for user in value]
+ if None in names and any(name is not None for name in names):
+ raise vol.Invalid(
+ 'user names of all users must be set if any is set')
+ if not all(name is None for name in names):
+ has_unique_values(names)
+ return value
+
+
+INSTANCE_LIST_SCHEMA = vol.All(
+ cv.ensure_list,
+ has_all_unique_users,
+ has_all_unique_users_names,
+ [INSTANCE_SCHEMA])
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: INSTANCE_LIST_SCHEMA
+}, extra=vol.ALLOW_EXTRA)
+
+SERVICE_API_CALL = 'api_call'
+ATTR_NAME = CONF_NAME
+ATTR_PATH = CONF_PATH
+ATTR_ARGS = "args"
+EVENT_API_CALL_SUCCESS = "{0}_{1}_{2}".format(
+ DOMAIN, SERVICE_API_CALL, "success")
+
+SERVICE_API_CALL_SCHEMA = vol.Schema({
+ vol.Required(ATTR_NAME): str,
+ vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
+ vol.Optional(ATTR_ARGS): dict
+})
+
+
+async def async_setup(hass, config):
+ """Set up the habitica service."""
+ conf = config[DOMAIN]
+ data = hass.data[DOMAIN] = {}
+ websession = async_get_clientsession(hass)
+ from habitipy.aio import HabitipyAsync
+
+ class HAHabitipyAsync(HabitipyAsync):
+ """Closure API class to hold session."""
+
+ def __call__(self, **kwargs):
+ return super().__call__(websession, **kwargs)
+
+ for instance in conf:
+ url = instance[CONF_URL]
+ username = instance[CONF_API_USER]
+ password = instance[CONF_API_KEY]
+ name = instance.get(CONF_NAME)
+ config_dict = {"url": url, "login": username, "password": password}
+ api = HAHabitipyAsync(config_dict)
+ user = await api.user.get()
+ if name is None:
+ name = user['profile']['name']
+ data[name] = api
+ if CONF_SENSORS in instance:
+ hass.async_create_task(
+ discovery.async_load_platform(
+ hass, "sensor", DOMAIN,
+ {"name": name, "sensors": instance[CONF_SENSORS]},
+ config))
+
+ async def handle_api_call(call):
+ name = call.data[ATTR_NAME]
+ path = call.data[ATTR_PATH]
+ api = hass.data[DOMAIN].get(name)
+ if api is None:
+ _LOGGER.error(
+ "API_CALL: User '%s' not configured", name)
+ return
+ try:
+ for element in path:
+ api = api[element]
+ except KeyError:
+ _LOGGER.error(
+ "API_CALL: Path %s is invalid"
+ " for api on '{%s}' element", path, element)
+ return
+ kwargs = call.data.get(ATTR_ARGS, {})
+ data = await api(**kwargs)
+ hass.bus.async_fire(EVENT_API_CALL_SUCCESS, {
+ "name": name, "path": path, "data": data
+ })
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_API_CALL,
+ handle_api_call,
+ schema=SERVICE_API_CALL_SCHEMA)
+ return True
diff --git a/homeassistant/components/habitica/services.yaml b/homeassistant/components/habitica/services.yaml
new file mode 100644
index 00000000000..a063b1577f5
--- /dev/null
+++ b/homeassistant/components/habitica/services.yaml
@@ -0,0 +1,15 @@
+# Describes the format for Habitica service
+
+---
+api_call:
+ description: Call Habitica api
+ fields:
+ name:
+ description: Habitica's username to call for
+ example: 'xxxNotAValidNickxxx'
+ path:
+ description: "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks"
+ example: '["tasks", "user", "post"]'
+ args:
+ description: Any additional json or url parameter arguments. See apidoc mentioned for path. Example uses same api endpoint
+ example: '{"text": "Use API from Home Assistant", "type": "todo"}'
diff --git a/homeassistant/components/hangouts/.translations/fr.json b/homeassistant/components/hangouts/.translations/fr.json
index 53759f9b534..00a7d5fd80d 100644
--- a/homeassistant/components/hangouts/.translations/fr.json
+++ b/homeassistant/components/hangouts/.translations/fr.json
@@ -5,10 +5,15 @@
"unknown": "Une erreur inconnue s'est produite"
},
"error": {
+ "invalid_2fa": "Authentification \u00e0 2 facteurs invalide, veuillez r\u00e9essayer.",
+ "invalid_2fa_method": "M\u00e9thode 2FA non valide (v\u00e9rifiez sur le t\u00e9l\u00e9phone).",
"invalid_login": "Login invalide, veuillez r\u00e9essayer."
},
"step": {
"2fa": {
+ "data": {
+ "2fa": "Code PIN d'authentification \u00e0 2 facteurs"
+ },
"title": "Authentification \u00e0 2 facteurs"
},
"user": {
diff --git a/homeassistant/components/hangouts/.translations/sv.json b/homeassistant/components/hangouts/.translations/sv.json
new file mode 100644
index 00000000000..90bf4e97712
--- /dev/null
+++ b/homeassistant/components/hangouts/.translations/sv.json
@@ -0,0 +1,29 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Google Hangouts \u00e4r redan inst\u00e4llt",
+ "unknown": "Ett ok\u00e4nt fel intr\u00e4ffade"
+ },
+ "error": {
+ "invalid_2fa": "Ogiltig 2FA autentisering, f\u00f6rs\u00f6k igen.",
+ "invalid_2fa_method": "Ogiltig 2FA-metod (Verifiera med telefon).",
+ "invalid_login": "Ogiltig inloggning, f\u00f6rs\u00f6k igen."
+ },
+ "step": {
+ "2fa": {
+ "data": {
+ "2fa": "2FA Pinkod"
+ },
+ "title": "Tv\u00e5faktorsautentisering"
+ },
+ "user": {
+ "data": {
+ "email": "E-postadress",
+ "password": "L\u00f6senord"
+ },
+ "title": "Google Hangouts-inloggning"
+ }
+ },
+ "title": "Google Hangouts"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json
index 7e54586b810..dd421fee57a 100644
--- a/homeassistant/components/hangouts/strings.json
+++ b/homeassistant/components/hangouts/strings.json
@@ -6,7 +6,7 @@
},
"error": {
"invalid_login": "Invalid Login, please try again.",
- "invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
+ "invalid_2fa": "Invalid 2 Factor Authentication, please try again.",
"invalid_2fa_method": "Invalid 2FA Method (Verify on Phone)."
},
"step": {
@@ -23,7 +23,7 @@
"2fa": "2FA Pin"
},
"description": "",
- "title": "2-Factor-Authorization"
+ "title": "2-Factor-Authentication"
}
},
"title": "Google Hangouts"
diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py
index 53c8e267016..2b517652ad7 100644
--- a/homeassistant/components/homematic/__init__.py
+++ b/homeassistant/components/homematic/__init__.py
@@ -77,7 +77,7 @@ HM_DEVICE_TYPES = {
'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall',
'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat',
'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor',
- 'IPKeySwitchPowermeter'],
+ 'IPKeySwitchPowermeter', 'IPThermostatWall230V'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
diff --git a/homeassistant/components/homematicip_cloud/.translations/da.json b/homeassistant/components/homematicip_cloud/.translations/da.json
index b617130945a..7473b4a7b86 100644
--- a/homeassistant/components/homematicip_cloud/.translations/da.json
+++ b/homeassistant/components/homematicip_cloud/.translations/da.json
@@ -2,6 +2,13 @@
"config": {
"error": {
"invalid_pin": "Ugyldig PIN, pr\u00f8v igen."
+ },
+ "step": {
+ "init": {
+ "data": {
+ "pin": "Pin kode (valgfri)"
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/homeassistant/components/homematicip_cloud/.translations/fr.json b/homeassistant/components/homematicip_cloud/.translations/fr.json
index c10cb519133..6cab0993c01 100644
--- a/homeassistant/components/homematicip_cloud/.translations/fr.json
+++ b/homeassistant/components/homematicip_cloud/.translations/fr.json
@@ -1,12 +1,16 @@
{
"config": {
"abort": {
- "unknown": "Une erreur inconnue s'est produite"
+ "already_configured": "Le point d'acc\u00e8s est d\u00e9j\u00e0 configur\u00e9",
+ "conection_aborted": "Impossible de se connecter au serveur HMIP",
+ "connection_aborted": "Impossible de se connecter au serveur HMIP",
+ "unknown": "Une erreur inconnue s'est produite."
},
"error": {
"invalid_pin": "Code PIN invalide, veuillez r\u00e9essayer.",
"press_the_button": "Veuillez appuyer sur le bouton bleu.",
- "register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer."
+ "register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer.",
+ "timeout_button": "D\u00e9lai d'attente expir\u00e9, veuillez r\u00e9\u00e9ssayer."
},
"step": {
"init": {
@@ -14,8 +18,14 @@
"hapid": "ID du point d'acc\u00e8s (SGTIN)",
"name": "Nom (facultatif, utilis\u00e9 comme pr\u00e9fixe de nom pour tous les p\u00e9riph\u00e9riques)",
"pin": "Code PIN (facultatif)"
- }
+ },
+ "title": "Choisissez le point d'acc\u00e8s HomematicIP"
+ },
+ "link": {
+ "description": "Appuyez sur le bouton bleu du point d'acc\u00e8s et sur le bouton Envoyer pour enregistrer HomematicIP avec Home Assistant. \n\n ",
+ "title": "Lier le point d'acc\u00e8s"
}
- }
+ },
+ "title": "HomematicIP Cloud"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/homematicip_cloud/.translations/it.json b/homeassistant/components/homematicip_cloud/.translations/it.json
index 95e600e6d03..9ef1abd500c 100644
--- a/homeassistant/components/homematicip_cloud/.translations/it.json
+++ b/homeassistant/components/homematicip_cloud/.translations/it.json
@@ -5,6 +5,7 @@
"connection_aborted": "Impossibile connettersi al server HMIP"
},
"error": {
+ "invalid_pin": "PIN non valido, riprova.",
"press_the_button": "Si prega di premere il pulsante blu.",
"register_failed": "Registrazione fallita, si prega di riprovare."
},
diff --git a/homeassistant/components/homematicip_cloud/.translations/sv.json b/homeassistant/components/homematicip_cloud/.translations/sv.json
index 945dca8a277..4e8aac999de 100644
--- a/homeassistant/components/homematicip_cloud/.translations/sv.json
+++ b/homeassistant/components/homematicip_cloud/.translations/sv.json
@@ -3,6 +3,7 @@
"abort": {
"already_configured": "Accesspunkten \u00e4r redan konfigurerad",
"conection_aborted": "Kunde inte ansluta till HMIP server",
+ "connection_aborted": "Det gick inte att ansluta till HMIP-servern",
"unknown": "Ett ok\u00e4nt fel har intr\u00e4ffat"
},
"error": {
diff --git a/homeassistant/components/hue/.translations/fr.json b/homeassistant/components/hue/.translations/fr.json
index 73613f237da..5414bf01ea7 100644
--- a/homeassistant/components/hue/.translations/fr.json
+++ b/homeassistant/components/hue/.translations/fr.json
@@ -24,6 +24,6 @@
"title": "Hub de liaison"
}
},
- "title": "Pont Philips Hue"
+ "title": "Philips Hue"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py
index c04380e1303..38b521078f4 100644
--- a/homeassistant/components/hue/__init__.py
+++ b/homeassistant/components/hue/__init__.py
@@ -11,7 +11,8 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_FILENAME, CONF_HOST
-from homeassistant.helpers import aiohttp_client, config_validation as cv
+from homeassistant.helpers import (
+ aiohttp_client, config_validation as cv, device_registry as dr)
from .const import DOMAIN, API_NUPNP
from .bridge import HueBridge
@@ -132,7 +133,28 @@ async def async_setup_entry(hass, entry):
bridge = HueBridge(hass, entry, allow_unreachable, allow_groups)
hass.data[DOMAIN][host] = bridge
- return await bridge.async_setup()
+
+ if not await bridge.async_setup():
+ return False
+
+ config = bridge.api.config
+ device_registry = await dr.async_get_registry(hass)
+ device_registry.async_get_or_create(
+ config_entry=entry.entry_id,
+ connections={
+ (dr.CONNECTION_NETWORK_MAC, config.mac)
+ },
+ identifiers={
+ (DOMAIN, config.bridgeid)
+ },
+ manufacturer='Signify',
+ name=config.name,
+ # Not yet exposed as properties in aiohue
+ model=config.raw['modelid'],
+ sw_version=config.raw['swversion'],
+ )
+
+ return True
async def async_unload_entry(hass, entry):
diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py
index 212cdbac3b8..749d167e6de 100644
--- a/homeassistant/components/insteon/__init__.py
+++ b/homeassistant/components/insteon/__init__.py
@@ -7,6 +7,8 @@ https://home-assistant.io/components/insteon/
import asyncio
import collections
import logging
+from typing import Dict
+
import voluptuous as vol
from homeassistant.core import callback
@@ -18,7 +20,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['insteonplm==0.12.3']
+REQUIREMENTS = ['insteonplm==0.14.2']
_LOGGER = logging.getLogger(__name__)
@@ -27,9 +29,9 @@ DOMAIN = 'insteon'
CONF_IP_PORT = 'ip_port'
CONF_HUB_USERNAME = 'username'
CONF_HUB_PASSWORD = 'password'
+CONF_HUB_VERSION = 'hub_version'
CONF_OVERRIDE = 'device_override'
-CONF_PLM_HUB_MSG = ('Must configure either a PLM port or a Hub host, username '
- 'and password')
+CONF_PLM_HUB_MSG = 'Must configure either a PLM port or a Hub host'
CONF_ADDRESS = 'address'
CONF_CAT = 'cat'
CONF_SUBCAT = 'subcat'
@@ -66,6 +68,22 @@ EVENT_BUTTON_ON = 'insteon.button_on'
EVENT_BUTTON_OFF = 'insteon.button_off'
EVENT_CONF_BUTTON = 'button'
+
+def set_default_port(schema: Dict) -> Dict:
+ """Set the default port based on the Hub version."""
+ # If the ip_port is found do nothing
+ # If it is not found the set the default
+ ip_port = schema.get(CONF_IP_PORT)
+ if not ip_port:
+ hub_version = schema.get(CONF_HUB_VERSION)
+ # Found hub_version but not ip_port
+ if hub_version == 1:
+ schema[CONF_IP_PORT] = 9761
+ else:
+ schema[CONF_IP_PORT] = 25105
+ return schema
+
+
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
cv.deprecated(CONF_PLATFORM), vol.Schema({
vol.Required(CONF_ADDRESS): cv.string,
@@ -88,12 +106,13 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(
vol.Schema(
{vol.Exclusive(CONF_PORT, 'plm_or_hub',
- msg=CONF_PLM_HUB_MSG): cv.isdevice,
+ msg=CONF_PLM_HUB_MSG): cv.string,
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_IP_PORT): cv.port,
vol.Optional(CONF_HUB_USERNAME): cv.string,
vol.Optional(CONF_HUB_PASSWORD): cv.string,
+ vol.Optional(CONF_HUB_VERSION, default=2): vol.In([1, 2]),
vol.Optional(CONF_OVERRIDE): vol.All(
cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA]),
vol.Optional(CONF_X10_ALL_UNITS_OFF): vol.In(HOUSECODES),
@@ -103,14 +122,7 @@ CONFIG_SCHEMA = vol.Schema({
[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))
+ set_default_port)
}, extra=vol.ALLOW_EXTRA)
@@ -151,6 +163,7 @@ def async_setup(hass, config):
ip_port = conf.get(CONF_IP_PORT)
username = conf.get(CONF_HUB_USERNAME)
password = conf.get(CONF_HUB_PASSWORD)
+ hub_version = conf.get(CONF_HUB_VERSION)
overrides = conf.get(CONF_OVERRIDE, [])
x10_devices = conf.get(CONF_X10, [])
x10_all_units_off_housecode = conf.get(CONF_X10_ALL_UNITS_OFF)
@@ -284,6 +297,7 @@ def async_setup(hass, config):
port=ip_port,
username=username,
password=password,
+ hub_version=hub_version,
loop=hass.loop,
workdir=hass.config.config_dir)
else:
@@ -358,6 +372,8 @@ class IPDB:
def __init__(self):
"""Create the INSTEON Product Database (IPDB)."""
+ from insteonplm.states.cover import Cover
+
from insteonplm.states.onOff import (OnOffSwitch,
OnOffSwitch_OutletTop,
OnOffSwitch_OutletBottom,
@@ -383,7 +399,9 @@ class IPDB:
X10AllLightsOnSensor,
X10AllLightsOffSensor)
- self.states = [State(OnOffSwitch_OutletTop, 'switch'),
+ self.states = [State(Cover, 'cover'),
+
+ State(OnOffSwitch_OutletTop, 'switch'),
State(OnOffSwitch_OutletBottom, 'switch'),
State(OpenClosedRelay, 'switch'),
State(OnOffSwitch, 'switch'),
@@ -470,11 +488,10 @@ class InsteonEntity(Entity):
return attributes
@callback
- def async_entity_update(self, deviceid, statename, val):
+ def async_entity_update(self, deviceid, group, 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)
+ _LOGGER.debug('Received update for device %s group %d value %s',
+ deviceid.human, group, val)
self.async_schedule_update_ha_state()
@asyncio.coroutine
diff --git a/homeassistant/components/ios/.translations/ca.json b/homeassistant/components/ios/.translations/ca.json
new file mode 100644
index 00000000000..1b1ed732ab3
--- /dev/null
+++ b/homeassistant/components/ios/.translations/ca.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de Home Assistant iOS."
+ },
+ "step": {
+ "confirm": {
+ "description": "Voleu configurar el component Home Assistant iOS?",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/cs.json b/homeassistant/components/ios/.translations/cs.json
new file mode 100644
index 00000000000..95d675076da
--- /dev/null
+++ b/homeassistant/components/ios/.translations/cs.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/fr.json b/homeassistant/components/ios/.translations/fr.json
new file mode 100644
index 00000000000..934849549e7
--- /dev/null
+++ b/homeassistant/components/ios/.translations/fr.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Seule une configuration de Home Assistant iOS est n\u00e9cessaire."
+ },
+ "step": {
+ "confirm": {
+ "description": "Voulez-vous configurer le composant Home Assistant iOS?",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/ko.json b/homeassistant/components/ios/.translations/ko.json
new file mode 100644
index 00000000000..6d69ea3126c
--- /dev/null
+++ b/homeassistant/components/ios/.translations/ko.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "\ud558\ub098\uc758 Home Assistant iOS \uad6c\uc131\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
+ },
+ "step": {
+ "confirm": {
+ "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/nl.json b/homeassistant/components/ios/.translations/nl.json
new file mode 100644
index 00000000000..8e5c46692a0
--- /dev/null
+++ b/homeassistant/components/ios/.translations/nl.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Home Assistant iOS nodig."
+ },
+ "step": {
+ "confirm": {
+ "description": "Wilt u het Home Assistant iOS component instellen?",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/pl.json b/homeassistant/components/ios/.translations/pl.json
new file mode 100644
index 00000000000..6240f074cfc
--- /dev/null
+++ b/homeassistant/components/ios/.translations/pl.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Wymagana jest tylko jedna konfiguracja Home Assistant iOS."
+ },
+ "step": {
+ "confirm": {
+ "description": "Czy chcesz skonfigurowa\u0107 Home Assistant iOS?",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/zh-Hans.json b/homeassistant/components/ios/.translations/zh-Hans.json
new file mode 100644
index 00000000000..0de30f0f3da
--- /dev/null
+++ b/homeassistant/components/ios/.translations/zh-Hans.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Home Assistant iOS \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002"
+ },
+ "step": {
+ "confirm": {
+ "description": "\u662f\u5426\u8981\u8bbe\u7f6e Home Assistant iOS \u7ec4\u4ef6\uff1f",
+ "title": "Home Assistant iOS"
+ }
+ },
+ "title": "Home Assistant iOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/light/deconz.py b/homeassistant/components/light/deconz.py
index 412cf8693e5..ff3fe609924 100644
--- a/homeassistant/components/light/deconz.py
+++ b/homeassistant/components/light/deconz.py
@@ -82,6 +82,11 @@ class DeconzLight(Light):
self._light.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id
+ async def async_will_remove_from_hass(self) -> None:
+ """Disconnect light object when removed."""
+ self._light.remove_callback(self.async_update_callback)
+ self._light = None
+
@callback
def async_update_callback(self, reason):
"""Update the light's state."""
diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py
index 2a51423a7a8..6f6e0ed617e 100644
--- a/homeassistant/components/light/hue.py
+++ b/homeassistant/components/light/hue.py
@@ -285,6 +285,25 @@ class HueLight(Light):
"""Return the list of supported effects."""
return [EFFECT_COLORLOOP, EFFECT_RANDOM]
+ @property
+ def device_info(self):
+ """Return the device info."""
+ if self.light.type in ('LightGroup', 'Room'):
+ return None
+
+ return {
+ 'identifiers': {
+ (hue.DOMAIN, self.unique_id)
+ },
+ 'name': self.name,
+ 'manufacturer': self.light.manufacturername,
+ # productname added in Hue Bridge API 1.24
+ # (published 03/05/2018)
+ 'model': self.light.productname or self.light.modelid,
+ # Not yet exposed as properties in aiohue
+ 'sw_version': self.light.raw['swversion'],
+ }
+
async def async_turn_on(self, **kwargs):
"""Turn the specified or all lights on."""
command = {'on': True}
diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py
index 225f0f510ad..64331411f7f 100644
--- a/homeassistant/components/light/mqtt.py
+++ b/homeassistant/components/light/mqtt.py
@@ -54,6 +54,7 @@ CONF_WHITE_VALUE_SCALE = 'white_value_scale'
CONF_WHITE_VALUE_STATE_TOPIC = 'white_value_state_topic'
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
CONF_ON_COMMAND_TYPE = 'on_command_type'
+CONF_UNIQUE_ID = 'unique_id'
DEFAULT_BRIGHTNESS_SCALE = 255
DEFAULT_NAME = 'MQTT Light'
@@ -79,6 +80,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
@@ -111,6 +113,7 @@ async def async_setup_platform(hass, config, async_add_entities,
async_add_entities([MqttLight(
config.get(CONF_NAME),
+ config.get(CONF_UNIQUE_ID),
config.get(CONF_EFFECT_LIST),
{
key: config.get(key) for key in (
@@ -159,14 +162,15 @@ async def async_setup_platform(hass, config, async_add_entities,
class MqttLight(MqttAvailability, Light):
"""Representation of a MQTT light."""
- def __init__(self, name, effect_list, topic, templates, qos,
- retain, payload, optimistic, brightness_scale,
+ def __init__(self, name, unique_id, effect_list, topic, templates,
+ qos, retain, payload, optimistic, brightness_scale,
white_value_scale, on_command_type, availability_topic,
payload_available, payload_not_available):
"""Initialize MQTT light."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
+ self._unique_id = unique_id
self._effect_list = effect_list
self._topic = topic
self._qos = qos
@@ -392,6 +396,11 @@ class MqttLight(MqttAvailability, Light):
"""Return the name of the device if any."""
return self._name
+ @property
+ def unique_id(self):
+ """Return a unique ID."""
+ return self._unique_id
+
@property
def is_on(self):
"""Return true if device is on."""
diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py
index a1e46c07d7d..0cc02e82b65 100644
--- a/homeassistant/components/light/tplink.py
+++ b/homeassistant/components/light/tplink.py
@@ -19,7 +19,7 @@ from homeassistant.util.color import \
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired)
-REQUIREMENTS = ['pyHS100==0.3.2']
+REQUIREMENTS = ['pyHS100==0.3.3']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py
index 6a648e4dc47..0c5dabb6eeb 100644
--- a/homeassistant/components/mailbox/__init__.py
+++ b/homeassistant/components/mailbox/__init__.py
@@ -4,31 +4,33 @@ Provides functionality for mailboxes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mailbox/
"""
-
import asyncio
-import logging
from contextlib import suppress
from datetime import timedelta
-
-import async_timeout
+import logging
from aiohttp import web
from aiohttp.web_exceptions import HTTPNotFound
+import async_timeout
-from homeassistant.core import callback
-from homeassistant.helpers import config_per_platform, discovery
-from homeassistant.helpers.entity_component import EntityComponent
-from homeassistant.helpers.entity import Entity
from homeassistant.components.http import HomeAssistantView
+from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
+from homeassistant.helpers import config_per_platform, discovery
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.setup import async_prepare_setup_platform
+_LOGGER = logging.getLogger(__name__)
+
+CONTENT_TYPE_MPEG = 'audio/mpeg'
+
DEPENDENCIES = ['http']
DOMAIN = 'mailbox'
+
EVENT = 'mailbox_updated'
-CONTENT_TYPE_MPEG = 'audio/mpeg'
+
SCAN_INTERVAL = timedelta(seconds=30)
-_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
diff --git a/homeassistant/components/mailbox/asterisk_mbox.py b/homeassistant/components/mailbox/asterisk_mbox.py
index 47d59234d7d..29b34f3e512 100644
--- a/homeassistant/components/mailbox/asterisk_mbox.py
+++ b/homeassistant/components/mailbox/asterisk_mbox.py
@@ -7,17 +7,18 @@ https://home-assistant.io/components/mailbox.asteriskvm/
import asyncio
import logging
-from homeassistant.core import callback
from homeassistant.components.asterisk_mbox import DOMAIN
-from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
- StreamError)
+from homeassistant.components.mailbox import (
+ CONTENT_TYPE_MPEG, Mailbox, StreamError)
+from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-DEPENDENCIES = ['asterisk_mbox']
_LOGGER = logging.getLogger(__name__)
-SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
+DEPENDENCIES = ['asterisk_mbox']
+
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
+SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
@asyncio.coroutine
diff --git a/homeassistant/components/mailbox/demo.py b/homeassistant/components/mailbox/demo.py
index 8096a4fabb7..e0d2618ac4e 100644
--- a/homeassistant/components/mailbox/demo.py
+++ b/homeassistant/components/mailbox/demo.py
@@ -5,16 +5,16 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/mailbox.asteriskvm/
"""
import asyncio
+from hashlib import sha1
import logging
import os
-from hashlib import sha1
+from homeassistant.components.mailbox import (
+ CONTENT_TYPE_MPEG, Mailbox, StreamError)
from homeassistant.util import dt
-from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
- StreamError)
-
_LOGGER = logging.getLogger(__name__)
+
DOMAIN = "DemoMailbox"
@@ -38,11 +38,15 @@ class DemoMailbox(Mailbox):
msgtxt = "Message {}. {}".format(
idx + 1, txt * (1 + idx * (idx % 2)))
msgsha = sha1(msgtxt.encode('utf-8')).hexdigest()
- msg = {"info": {"origtime": msgtime,
- "callerid": "John Doe <212-555-1212>",
- "duration": "10"},
- "text": msgtxt,
- "sha": msgsha}
+ msg = {
+ 'info': {
+ 'origtime': msgtime,
+ 'callerid': 'John Doe <212-555-1212>',
+ 'duration': '10',
+ },
+ 'text': msgtxt,
+ 'sha': msgsha,
+ }
self._messages[msgsha] = msg
@property
diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py
index 8f2abb9be19..0d7d76c4447 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.22']
+REQUIREMENTS = ['youtube_dl==2018.09.10']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index 7c49b095c66..831009ed8bf 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -15,33 +15,32 @@ from random import SystemRandom
from urllib.parse import urlparse
from aiohttp import web
-from aiohttp.hdrs import CONTENT_TYPE, CACHE_CONTROL
+from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
import async_timeout
import voluptuous as vol
-from homeassistant.core import callback
+from homeassistant.components import websocket_api
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
from homeassistant.const import (
- STATE_OFF, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, ATTR_ENTITY_ID,
- SERVICE_TOGGLE, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
- SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP,
- SERVICE_VOLUME_SET, SERVICE_MEDIA_PAUSE, SERVICE_SHUFFLE_SET,
- SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_MEDIA_NEXT_TRACK,
- SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK)
+ ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
+ SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK,
+ SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, SERVICE_SHUFFLE_SET,
+ SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN,
+ SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE,
+ STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
+from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import bind_hass
-from homeassistant.components import websocket_api
_LOGGER = logging.getLogger(__name__)
_RND = SystemRandom()
DOMAIN = 'media_player'
DEPENDENCIES = ['http']
-SCAN_INTERVAL = timedelta(seconds=10)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -97,6 +96,8 @@ MEDIA_TYPE_CHANNEL = 'channel'
MEDIA_TYPE_PLAYLIST = 'playlist'
MEDIA_TYPE_URL = 'url'
+SCAN_INTERVAL = timedelta(seconds=10)
+
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
SUPPORT_VOLUME_SET = 4
diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py
index 359ee0a9254..33b6e28a890 100644
--- a/homeassistant/components/media_player/anthemav.py
+++ b/homeassistant/components/media_player/anthemav.py
@@ -4,17 +4,17 @@ Support for Anthem Network Receivers and Processors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.anthemav/
"""
-import logging
import asyncio
+import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE,
+ PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, STATE_UNKNOWN,
- EVENT_HOMEASSISTANT_STOP)
+ CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF,
+ STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['anthemav==1.1.8']
@@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
- })
+})
@asyncio.coroutine
diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py
index 360ccd0f522..399e59ae9f5 100644
--- a/homeassistant/components/media_player/apple_tv.py
+++ b/homeassistant/components/media_player/apple_tv.py
@@ -7,20 +7,19 @@ https://home-assistant.io/components/media_player.apple_tv/
import asyncio
import logging
-from homeassistant.core import callback
from homeassistant.components.apple_tv import (
ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES)
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
- SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_TURN_ON,
- SUPPORT_TURN_OFF, MediaPlayerDevice, MEDIA_TYPE_MUSIC,
- MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
+ MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ MediaPlayerDevice)
from homeassistant.const import (
- STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, CONF_HOST,
- STATE_OFF, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
+ CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
+ STATE_PAUSED, STATE_PLAYING, STATE_STANDBY)
+from homeassistant.core import callback
import homeassistant.util.dt as dt_util
-
DEPENDENCIES = ['apple_tv']
_LOGGER = logging.getLogger(__name__)
@@ -31,8 +30,8 @@ SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \
@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Apple TV platform."""
if not discovery_info:
return
diff --git a/homeassistant/components/media_player/aquostv.py b/homeassistant/components/media_player/aquostv.py
index 6e8cc727121..ac399307126 100644
--- a/homeassistant/components/media_player/aquostv.py
+++ b/homeassistant/components/media_player/aquostv.py
@@ -9,16 +9,13 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_SELECT_SOURCE,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
- SUPPORT_VOLUME_SET, MediaPlayerDevice, PLATFORM_SCHEMA)
-
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN,
- CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT)
-
-
+ CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT,
+ CONF_USERNAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['sharp_aquos_rc==0.3.2']
diff --git a/homeassistant/components/media_player/blackbird.py b/homeassistant/components/media_player/blackbird.py
index 7869093138c..2c78bb24bbd 100644
--- a/homeassistant/components/media_player/blackbird.py
+++ b/homeassistant/components/media_player/blackbird.py
@@ -13,15 +13,15 @@ from homeassistant.components.media_player import (
DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice)
from homeassistant.const import (
- ATTR_ENTITY_ID, CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON)
+ ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_OFF,
+ STATE_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyblackbird==0.5']
_LOGGER = logging.getLogger(__name__)
-SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
- SUPPORT_SELECT_SOURCE
+SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
@@ -33,7 +33,6 @@ SOURCE_SCHEMA = vol.Schema({
CONF_ZONES = 'zones'
CONF_SOURCES = 'sources'
-CONF_TYPE = 'type'
DATA_BLACKBIRD = 'blackbird'
diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py
index 1fe939b34ef..ab012402636 100644
--- a/homeassistant/components/media_player/bluesound.py
+++ b/homeassistant/components/media_player/bluesound.py
@@ -24,8 +24,8 @@ from homeassistant.components.media_player import (
MediaPlayerDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT,
- EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE,
- STATE_OFF, STATE_PAUSED, STATE_PLAYING)
+ EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
+ STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py
index 9f4496582ad..04dc013108f 100644
--- a/homeassistant/components/media_player/braviatv.py
+++ b/homeassistant/components/media_player/braviatv.py
@@ -10,11 +10,11 @@ import re
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
- SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
- SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
- PLATFORM_SCHEMA)
-from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
+from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py
index ae9589c7886..088aef82373 100644
--- a/homeassistant/components/media_player/cast.py
+++ b/homeassistant/components/media_player/cast.py
@@ -7,26 +7,27 @@ https://home-assistant.io/components/media_player.cast/
import asyncio
import logging
import threading
+
from typing import Optional, Tuple
-import voluptuous as vol
import attr
+import voluptuous as vol
-from homeassistant.exceptions import PlatformNotReady
-from homeassistant.helpers.typing import HomeAssistantType, ConfigType
-from homeassistant.core import callback
-from homeassistant.helpers.dispatcher import (dispatcher_send,
- async_dispatcher_connect)
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
- SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA)
+ MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
- EVENT_HOMEASSISTANT_STOP)
+ CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING)
+from homeassistant.core import callback
+from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import (
+ async_dispatcher_connect, dispatcher_send)
+from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.dt as dt_util
DEPENDENCIES = ('cast',)
@@ -57,10 +58,14 @@ SIGNAL_CAST_DISCOVERED = 'cast_discovered'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
- vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list,
- [cv.string])
+ vol.Optional(CONF_IGNORE_CEC, default=[]):
+ vol.All(cv.ensure_list, [cv.string]),
})
+CONNECTION_RETRY = 3
+CONNECTION_RETRY_WAIT = 2
+CONNECTION_TIMEOUT = 10
+
@attr.s(slots=True, frozen=True)
class ChromecastInfo:
@@ -73,7 +78,8 @@ class ChromecastInfo:
port = attr.ib(type=int)
uuid = attr.ib(type=Optional[str], converter=attr.converters.optional(str),
default=None) # always convert UUID to string if not None
- model_name = attr.ib(type=str, default='') # needed for cast type
+ manufacturer = attr.ib(type=str, default='')
+ model_name = attr.ib(type=str, default='')
friendly_name = attr.ib(type=Optional[str], default=None)
@property
@@ -111,6 +117,7 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo:
host=info.host, port=info.port,
uuid=(info.uuid or http_device_status.uuid),
friendly_name=(info.friendly_name or http_device_status.friendly_name),
+ manufacturer=(info.manufacturer or http_device_status.manufacturer),
model_name=(info.model_name or http_device_status.model_name)
)
@@ -148,7 +155,13 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None:
def internal_callback(name):
"""Handle zeroconf discovery of a new chromecast."""
mdns = listener.services[name]
- _discover_chromecast(hass, ChromecastInfo(*mdns))
+ _discover_chromecast(hass, ChromecastInfo(
+ host=mdns[0],
+ port=mdns[1],
+ uuid=mdns[2],
+ model_name=mdns[3],
+ friendly_name=mdns[4],
+ ))
_LOGGER.debug("Starting internal pychromecast discovery.")
listener, browser = pychromecast.start_discovery(internal_callback)
@@ -360,12 +373,13 @@ class CastDevice(MediaPlayerDevice):
return
await self._async_disconnect()
- # Failed connection will unfortunately never raise an exception, it
- # will instead just try connecting indefinitely.
# pylint: disable=protected-access
_LOGGER.debug("Connecting to cast device %s", cast_info)
chromecast = await self.hass.async_add_job(
- pychromecast._get_chromecast_from_host, attr.astuple(cast_info))
+ pychromecast._get_chromecast_from_host, (
+ cast_info.host, cast_info.port, cast_info.uuid,
+ cast_info.model_name, cast_info.friendly_name
+ ), CONNECTION_RETRY, CONNECTION_RETRY_WAIT, CONNECTION_TIMEOUT)
self._chromecast = chromecast
self._status_listener = CastStatusListener(self, chromecast)
# Initialise connection status as connected because we can only
@@ -494,6 +508,23 @@ class CastDevice(MediaPlayerDevice):
"""Return the name of the device."""
return self._cast_info.friendly_name
+ @property
+ def device_info(self):
+ """Return information about the device."""
+ cast_info = self._cast_info
+
+ if cast_info.model_name == "Google Cast Group":
+ return None
+
+ return {
+ 'name': cast_info.friendly_name,
+ 'identifiers': {
+ (CAST_DOMAIN, cast_info.uuid.replace('-', ''))
+ },
+ 'model': cast_info.model_name,
+ 'manufacturer': cast_info.manufacturer,
+ }
+
@property
def state(self):
"""Return the state of the player."""
diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/media_player/channels.py
index fcfa16b33ac..43259c40f65 100644
--- a/homeassistant/components/media_player/channels.py
+++ b/homeassistant/components/media_player/channels.py
@@ -9,18 +9,18 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_CHANNEL, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_EPISODE,
- MEDIA_TYPE_MOVIE, SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP,
- SUPPORT_VOLUME_MUTE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, DOMAIN, PLATFORM_SCHEMA,
+ DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE,
+ MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE,
MediaPlayerDevice)
-
from homeassistant.const import (
- CONF_HOST, CONF_PORT, CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING,
- ATTR_ENTITY_ID)
-
+ ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED,
+ STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
+REQUIREMENTS = ['pychannels==1.0.0']
+
_LOGGER = logging.getLogger(__name__)
DATA_CHANNELS = 'channels'
@@ -52,16 +52,11 @@ CHANNELS_SEEK_BY_SCHEMA = CHANNELS_SCHEMA.extend({
vol.Required(ATTR_SECONDS): vol.Coerce(int),
})
-REQUIREMENTS = ['pychannels==1.0.0']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Channels platform."""
device = ChannelsPlayer(
- config.get('name'),
- config.get(CONF_HOST),
- config.get(CONF_PORT)
- )
+ config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT))
if DATA_CHANNELS not in hass.data:
hass.data[DATA_CHANNELS] = []
@@ -77,8 +72,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.entity_id == entity_id), None)
if device is None:
- _LOGGER.warning("Unable to find Channels with entity_id: %s",
- entity_id)
+ _LOGGER.warning(
+ "Unable to find Channels with entity_id: %s", entity_id)
return
if service.service == SERVICE_SEEK_FORWARD:
@@ -90,12 +85,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.seek_by(seconds)
hass.services.register(
- DOMAIN, SERVICE_SEEK_FORWARD, service_handler,
- schema=CHANNELS_SCHEMA)
+ DOMAIN, SERVICE_SEEK_FORWARD, service_handler, schema=CHANNELS_SCHEMA)
hass.services.register(
- DOMAIN, SERVICE_SEEK_BACKWARD, service_handler,
- schema=CHANNELS_SCHEMA)
+ DOMAIN, SERVICE_SEEK_BACKWARD, service_handler, schema=CHANNELS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_SEEK_BY, service_handler,
diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/media_player/clementine.py
index fab2bef73f0..e38c44b8d27 100644
--- a/homeassistant/components/media_player/clementine.py
+++ b/homeassistant/components/media_player/clementine.py
@@ -4,7 +4,6 @@ Support for Clementine Music Player as media player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.clementine/
"""
-
import asyncio
from datetime import timedelta
import logging
@@ -12,24 +11,24 @@ import time
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
- SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_MUSIC,
- SUPPORT_VOLUME_SET, MediaPlayerDevice)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
+ SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_PORT, CONF_ACCESS_TOKEN,
- STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
+ CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF,
+ STATE_PAUSED, STATE_PLAYING)
+import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-clementine-remote==1.0.1']
-SCAN_INTERVAL = timedelta(seconds=5)
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Clementine Remote'
DEFAULT_PORT = 5500
+SCAN_INTERVAL = timedelta(seconds=5)
+
SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \
SUPPORT_NEXT_TRACK | \
@@ -69,7 +68,7 @@ class ClementineDevice(MediaPlayerDevice):
self._track_name = ''
self._track_artist = ''
self._track_album_name = ''
- self._state = STATE_UNKNOWN
+ self._state = None
def update(self):
"""Retrieve the latest data from the Clementine Player."""
diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/media_player/cmus.py
index 6f579fd9791..2711ac1ff11 100644
--- a/homeassistant/components/media_player/cmus.py
+++ b/homeassistant/components/media_player/cmus.py
@@ -8,15 +8,14 @@ import logging
import voluptuous as vol
-
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PLAY,
- SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK, PLATFORM_SCHEMA,
+ MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import (
- STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME, CONF_PORT,
- CONF_PASSWORD)
+ CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pycmus==0.1.1']
diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py
index 06f2a3f1155..c2a736f531e 100644
--- a/homeassistant/components/media_player/demo.py
+++ b/homeassistant/components/media_player/demo.py
@@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
- SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE, SUPPORT_CLEAR_PLAYLIST,
- SUPPORT_PLAY, SUPPORT_SHUFFLE_SET, MediaPlayerDevice)
+ MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
+ SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE,
+ SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
import homeassistant.util.dt as dt_util
@@ -20,8 +21,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
DemoYoutubePlayer(
'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)', 300),
- DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours',
- 360000),
+ DemoYoutubePlayer(
+ 'Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours', 360000),
DemoMusicPlayer(), DemoTVShowPlayer(),
])
diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/media_player/denon.py
index 320211e700f..c0f296c2fb8 100644
--- a/homeassistant/components/media_player/denon.py
+++ b/homeassistant/components/media_player/denon.py
@@ -10,10 +10,10 @@ import telnetlib
import voluptuous as vol
from homeassistant.components.media_player import (
- PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_SELECT_SOURCE,
- SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
- SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py
index 14839590bee..296548dd3c2 100644
--- a/homeassistant/components/media_player/denonavr.py
+++ b/homeassistant/components/media_player/denonavr.py
@@ -5,35 +5,37 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.denon/
"""
-import logging
from collections import namedtuple
+import logging
+
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_PAUSE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
- SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE,
- SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, MediaPlayerDevice,
- PLATFORM_SCHEMA, SUPPORT_TURN_ON, MEDIA_TYPE_MUSIC,
- SUPPORT_VOLUME_SET, SUPPORT_PLAY)
+ MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
- CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT)
+ CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, STATE_OFF, STATE_ON,
+ STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['denonavr==0.7.5']
_LOGGER = logging.getLogger(__name__)
+ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
+
+CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
+CONF_SHOW_ALL_SOURCES = 'show_all_sources'
+CONF_VALID_ZONES = ['Zone2', 'Zone3']
+CONF_ZONES = 'zones'
+
DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 2
-CONF_SHOW_ALL_SOURCES = 'show_all_sources'
-CONF_ZONES = 'zones'
-CONF_VALID_ZONES = ['Zone2', 'Zone3']
-CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
-KEY_DENON_CACHE = 'denonavr_hosts'
-ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
+KEY_DENON_CACHE = 'denonavr_hosts'
SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py
index e03474cdb38..42293ba25fe 100644
--- a/homeassistant/components/media_player/directv.py
+++ b/homeassistant/components/media_player/directv.py
@@ -4,29 +4,28 @@ Support for the DirecTV receivers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.directv/
"""
-import voluptuous as vol
import requests
+import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_STOP, PLATFORM_SCHEMA,
- SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_PLAY,
- MediaPlayerDevice)
+ MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice)
from homeassistant.const import (
- CONF_DEVICE, CONF_HOST, CONF_NAME, STATE_OFF, STATE_PLAYING, CONF_PORT)
+ CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['directpy==0.5']
DEFAULT_DEVICE = '0'
-DEFAULT_NAME = 'DirecTV Receiver'
+DEFAULT_NAME = "DirecTV Receiver"
DEFAULT_PORT = 8080
SUPPORT_DTV = SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY
-DATA_DIRECTV = "data_directv"
+DATA_DIRECTV = 'data_directv'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
@@ -51,9 +50,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
elif discovery_info:
host = discovery_info.get('host')
- name = 'DirecTV_' + discovery_info.get('serial', '')
+ name = 'DirecTV_{}'.format(discovery_info.get('serial', ''))
- # attempt to discover additional RVU units
+ # Attempt to discover additional RVU units
try:
resp = requests.get(
'http://%s:%d/info/getLocations' % (host, DEFAULT_PORT)).json()
@@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
DEFAULT_PORT, loc["clientAddr"]])
except requests.exceptions.RequestException:
- # bail out and just go forward with uPnP data
+ # Bail out and just go forward with uPnP data
if DEFAULT_DEVICE not in known_devices:
hosts.append([name, host, DEFAULT_PORT, DEFAULT_DEVICE])
@@ -78,8 +77,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(dtvs)
hass.data[DATA_DIRECTV] = known_devices
- return True
-
class DirecTvDevice(MediaPlayerDevice):
"""Representation of a DirecTV receiver on the network."""
diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/media_player/dlna_dmr.py
index 6c970ec197e..8aab4bfa43a 100644
--- a/homeassistant/components/media_player/dlna_dmr.py
+++ b/homeassistant/components/media_player/dlna_dmr.py
@@ -1,43 +1,36 @@
-# -*- coding: utf-8 -*-
"""
Support for DLNA DMR (Device Media Renderer).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.dlna_dmr/
"""
-
import asyncio
+from datetime import datetime
import functools
import logging
-from datetime import datetime
import aiohttp
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
- SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_PLAY_MEDIA,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
- MediaPlayerDevice,
- PLATFORM_SCHEMA)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- EVENT_HOMEASSISTANT_STOP,
- CONF_URL, CONF_NAME,
- STATE_OFF, STATE_ON, STATE_IDLE, STATE_PLAYING, STATE_PAUSED)
+ CONF_NAME, CONF_URL, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
+ STATE_ON, STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
from homeassistant.util import get_local_ip
+REQUIREMENTS = ['async-upnp-client==0.12.4']
+
+_LOGGER = logging.getLogger(__name__)
DLNA_DMR_DATA = 'dlna_dmr'
-REQUIREMENTS = [
- 'async-upnp-client==0.12.4',
-]
-
DEFAULT_NAME = 'DLNA Digital Media Renderer'
DEFAULT_LISTEN_PORT = 8301
@@ -68,8 +61,6 @@ HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING = {
'playlist': 'playlist/*',
}
-_LOGGER = logging.getLogger(__name__)
-
def catch_request_errors():
"""Catch asyncio.TimeoutError, aiohttp.ClientError errors."""
@@ -96,13 +87,11 @@ async def async_start_event_handler(hass, server_host, server_port, requester):
# start event handler
from async_upnp_client.aiohttp import AiohttpNotifyServer
- server = AiohttpNotifyServer(requester,
- server_port,
- server_host,
- hass.loop)
+ server = AiohttpNotifyServer(
+ requester, server_port, server_host, hass.loop)
await server.start_server()
- _LOGGER.info('UPNP/DLNA event handler listening on: %s',
- server.callback_url)
+ _LOGGER.info(
+ 'UPNP/DLNA event handler listening on: %s', server.callback_url)
hass_data['notify_server'] = server
hass_data['event_handler'] = server.event_handler
@@ -116,10 +105,8 @@ async def async_start_event_handler(hass, server_host, server_port, requester):
return hass_data['event_handler']
-async def async_setup_platform(hass: HomeAssistant,
- config,
- async_add_entities,
- discovery_info=None):
+async def async_setup_platform(
+ hass: HomeAssistant, config, async_add_entities, discovery_info=None):
"""Set up DLNA DMR platform."""
if config.get(CONF_URL) is not None:
url = config[CONF_URL]
@@ -145,10 +132,8 @@ async def async_setup_platform(hass: HomeAssistant,
if server_host is None:
server_host = get_local_ip()
server_port = config.get(CONF_LISTEN_PORT, DEFAULT_LISTEN_PORT)
- event_handler = await async_start_event_handler(hass,
- server_host,
- server_port,
- requester)
+ event_handler = await async_start_event_handler(
+ hass, server_host, server_port, requester)
# create upnp device
from async_upnp_client import UpnpFactory
@@ -183,10 +168,10 @@ class DlnaDmrDevice(MediaPlayerDevice):
"""Handle addition."""
self._device.on_event = self._on_event
- # register unsubscribe on stop
+ # Register unsubscribe on stop
bus = self.hass.bus
- bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
- self._async_on_hass_stop)
+ bus.async_listen_once(
+ EVENT_HOMEASSISTANT_STOP, self._async_on_hass_stop)
@property
def available(self):
@@ -306,23 +291,21 @@ class DlnaDmrDevice(MediaPlayerDevice):
mime_type = HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING[media_type]
upnp_class = HOME_ASSISTANT_UPNP_CLASS_MAPPING[media_type]
- # stop current playing media
+ # Stop current playing media
if self._device.can_stop:
await self.async_media_stop()
- # queue media
- await self._device.async_set_transport_uri(media_id,
- title,
- mime_type,
- upnp_class)
+ # +ueue media
+ await self._device.async_set_transport_uri(
+ media_id, title, mime_type, upnp_class)
await self._device.async_wait_for_can_play()
- # if already playing, no need to call Play
+ # If already playing, no need to call Play
from async_upnp_client import dlna
if self._device.state == dlna.STATE_PLAYING:
return
- # play it
+ # Play it
await self.async_media_play()
@catch_request_errors()
diff --git a/homeassistant/components/media_player/dunehd.py b/homeassistant/components/media_player/dunehd.py
index f582ceefe5f..00c8ff3f4df 100644
--- a/homeassistant/components/media_player/dunehd.py
+++ b/homeassistant/components/media_player/dunehd.py
@@ -6,13 +6,13 @@ https://home-assistant.io/components/media_player.dunehd/
"""
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
- SUPPORT_PAUSE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_NEXT_TRACK,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, PLATFORM_SCHEMA,
- SUPPORT_PLAY, MediaPlayerDevice)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, STATE_OFF, STATE_PAUSED, STATE_ON, STATE_PLAYING)
+ CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
+import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pdunehd==1.3']
diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py
index 809db228d02..2bf3a1b803f 100644
--- a/homeassistant/components/media_player/emby.py
+++ b/homeassistant/components/media_player/emby.py
@@ -10,13 +10,13 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK,
- SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_PREVIOUS_TRACK,
- MediaPlayerDevice, SUPPORT_PLAY, PLATFORM_SCHEMA)
+ MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SEEK, SUPPORT_STOP, MediaPlayerDevice)
from homeassistant.const import (
- STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
- CONF_HOST, CONF_PORT, CONF_SSL, CONF_API_KEY, DEVICE_DEFAULT_NAME,
- EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
+ CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_SSL, DEVICE_DEFAULT_NAME,
+ EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
+ STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
@@ -42,11 +42,11 @@ SUPPORT_EMBY = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_STOP | SUPPORT_SEEK | SUPPORT_PLAY
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
- vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Required(CONF_API_KEY): cv.string,
- vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_AUTO_HIDE, default=DEFAULT_AUTO_HIDE): cv.boolean,
+ vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
+ vol.Optional(CONF_PORT): cv.port,
+ vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
})
@@ -95,7 +95,7 @@ def async_setup_platform(hass, config, async_add_entities,
if new_devices:
_LOGGER.debug("Adding new devices: %s", new_devices)
- async_add_entities(new_devices, update_before_add=True)
+ async_add_entities(new_devices, True)
@callback
def device_removal_callback(data):
diff --git a/homeassistant/components/media_player/epson.py b/homeassistant/components/media_player/epson.py
index 23bbf685004..46beb4487fd 100644
--- a/homeassistant/components/media_player/epson.py
+++ b/homeassistant/components/media_player/epson.py
@@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/media_player.epson/
"""
import logging
+
import voluptuous as vol
from homeassistant.components.media_player import (
@@ -20,37 +21,45 @@ import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['epson-projector==0.1.3']
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_CMODE = 'cmode'
+
DATA_EPSON = 'epson'
DEFAULT_NAME = 'EPSON Projector'
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Required(CONF_HOST): cv.string,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PORT, default=80): cv.port,
- vol.Optional(CONF_SSL, default=False): cv.boolean
-})
-
SERVICE_SELECT_CMODE = 'epson_select_cmode'
-ATTR_CMODE = 'cmode'
SUPPORT_CMODE = 33001
SUPPORT_EPSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE |\
SUPPORT_CMODE | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | \
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK
-_LOGGER = logging.getLogger(__name__)
-async def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_PORT, default=80): cv.port,
+ vol.Optional(CONF_SSL, default=False): cv.boolean,
+})
+
+
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Epson media player platform."""
+ from epson_projector.const import (CMODE_LIST_SET)
+
if DATA_EPSON not in hass.data:
hass.data[DATA_EPSON] = []
+
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
+ port = config.get(CONF_PORT)
+ ssl = config.get(CONF_SSL)
+
+ epson = EpsonProjector(async_get_clientsession(
+ hass, verify_ssl=False), name, host, port, ssl)
- epson = EpsonProjector(async_get_clientsession(hass, verify_ssl=False),
- name, host,
- config.get(CONF_PORT), config.get(CONF_SSL))
hass.data[DATA_EPSON].append(epson)
async_add_entities([epson], update_before_add=True)
@@ -67,7 +76,7 @@ async def async_setup_platform(hass, config, async_add_entities,
cmode = service.data.get(ATTR_CMODE)
await device.select_cmode(cmode)
await device.update()
- from epson_projector.const import (CMODE_LIST_SET)
+
epson_schema = MEDIA_PLAYER_SCHEMA.extend({
vol.Required(ATTR_CMODE): vol.All(cv.string, vol.Any(*CMODE_LIST_SET))
})
@@ -81,13 +90,12 @@ class EpsonProjector(MediaPlayerDevice):
def __init__(self, websession, name, host, port, encryption):
"""Initialize entity to control Epson projector."""
- self._name = name
import epson_projector as epson
from epson_projector.const import DEFAULT_SOURCES
+
+ self._name = name
self._projector = epson.Projector(
- host,
- websession=websession,
- port=port)
+ host, websession=websession, port=port)
self._cmode = None
self._source_list = list(DEFAULT_SOURCES.values())
self._source = None
@@ -97,9 +105,8 @@ class EpsonProjector(MediaPlayerDevice):
async def update(self):
"""Update state of device."""
from epson_projector.const import (
- EPSON_CODES, POWER,
- CMODE, CMODE_LIST, SOURCE, VOLUME,
- BUSY, SOURCE_LIST)
+ EPSON_CODES, POWER, CMODE, CMODE_LIST, SOURCE, VOLUME, BUSY,
+ SOURCE_LIST)
is_turned_on = await self._projector.get_property(POWER)
_LOGGER.debug("Project turn on/off status: %s", is_turned_on)
if is_turned_on and is_turned_on == EPSON_CODES[POWER]:
diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py
index 0594b603a0c..3914d2381b2 100644
--- a/homeassistant/components/media_player/firetv.py
+++ b/homeassistant/components/media_player/firetv.py
@@ -10,13 +10,13 @@ import requests
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
- SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
- SUPPORT_VOLUME_SET, SUPPORT_PLAY, MediaPlayerDevice)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
+ CONF_DEVICE, CONF_DEVICES, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL,
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY,
- STATE_UNKNOWN, CONF_HOST, CONF_PORT, CONF_SSL, CONF_NAME, CONF_DEVICE,
- CONF_DEVICES)
+ STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/frontier_silicon.py b/homeassistant/components/media_player/frontier_silicon.py
index 6dc4e73b1c0..aebdb676859 100644
--- a/homeassistant/components/media_player/frontier_silicon.py
+++ b/homeassistant/components/media_player/frontier_silicon.py
@@ -10,14 +10,14 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
- SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_STEP, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
- SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA,
- MEDIA_TYPE_MUSIC)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
+ SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
from homeassistant.const import (
- STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN,
- CONF_HOST, CONF_PORT, CONF_PASSWORD)
+ CONF_HOST, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['afsapi==0.0.4']
@@ -42,16 +42,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Frontier Silicon platform."""
import requests
if discovery_info is not None:
async_add_entities(
- [AFSAPIDevice(discovery_info['ssdp_description'],
- DEFAULT_PASSWORD)],
- update_before_add=True)
+ [AFSAPIDevice(
+ discovery_info['ssdp_description'], DEFAULT_PASSWORD)], True)
return True
host = config.get(CONF_HOST)
@@ -60,8 +59,7 @@ def async_setup_platform(hass, config, async_add_entities,
try:
async_add_entities(
- [AFSAPIDevice(DEVICE_URL.format(host, port), password)],
- update_before_add=True)
+ [AFSAPIDevice(DEVICE_URL.format(host, port), password)], True)
_LOGGER.debug("FSAPI device %s:%s -> %s", host, port, password)
return True
except requests.exceptions.RequestException:
@@ -78,7 +76,7 @@ class AFSAPIDevice(MediaPlayerDevice):
"""Initialize the Frontier Silicon API device."""
self._device_url = device_url
self._password = password
- self._state = STATE_UNKNOWN
+ self._state = None
self._name = None
self._title = None
diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/media_player/gpmdp.py
index 200191ad77a..b16eb8d417a 100644
--- a/homeassistant/components/media_player/gpmdp.py
+++ b/homeassistant/components/media_player/gpmdp.py
@@ -4,19 +4,19 @@ Support for Google Play Music Desktop Player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.gpmdp/
"""
-import logging
import json
+import logging
import socket
import time
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_SEEK, SUPPORT_PLAY,
- MediaPlayerDevice, PLATFORM_SCHEMA)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import (
- STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME)
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
@@ -55,9 +55,11 @@ def request_configuration(hass, config, url, add_entities_callback):
return
from websocket import create_connection
websocket = create_connection((url), timeout=1)
- websocket.send(json.dumps({'namespace': 'connect',
- 'method': 'connect',
- 'arguments': ['Home Assistant']}))
+ websocket.send(json.dumps({
+ 'namespace': 'connect',
+ 'method': 'connect',
+ 'arguments': ['Home Assistant']
+ }))
def gpmdp_configuration_callback(callback_data):
"""Handle configuration changes."""
diff --git a/homeassistant/components/media_player/gstreamer.py b/homeassistant/components/media_player/gstreamer.py
index e2477f0a4cd..e520fcb1033 100644
--- a/homeassistant/components/media_player/gstreamer.py
+++ b/homeassistant/components/media_player/gstreamer.py
@@ -9,21 +9,18 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, SUPPORT_VOLUME_SET, SUPPORT_PAUSE,
- SUPPORT_PLAY_MEDIA, SUPPORT_PLAY, SUPPORT_NEXT_TRACK,
- PLATFORM_SCHEMA, MediaPlayerDevice)
-from homeassistant.const import (
- STATE_IDLE, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET, MediaPlayerDevice)
+from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE
import homeassistant.helpers.config_validation as cv
+REQUIREMENTS = ['gstreamer-player==1.1.0']
_LOGGER = logging.getLogger(__name__)
-
-REQUIREMENTS = ['gstreamer-player==1.1.0']
-DOMAIN = 'gstreamer'
CONF_PIPELINE = 'pipeline'
+DOMAIN = 'gstreamer'
SUPPORT_GSTREAMER = SUPPORT_VOLUME_SET | SUPPORT_PLAY | SUPPORT_PAUSE |\
SUPPORT_PLAY_MEDIA | SUPPORT_NEXT_TRACK
diff --git a/homeassistant/components/media_player/hdmi_cec.py b/homeassistant/components/media_player/hdmi_cec.py
index 9198e4dec88..cb4afadd058 100644
--- a/homeassistant/components/media_player/hdmi_cec.py
+++ b/homeassistant/components/media_player/hdmi_cec.py
@@ -7,12 +7,12 @@ https://home-assistant.io/components/hdmi_cec/
import logging
from homeassistant.components.hdmi_cec import ATTR_NEW, CecDevice
-from homeassistant.components.media_player import MediaPlayerDevice, DOMAIN, \
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA, SUPPORT_PAUSE, \
- SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_STOP, \
- SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_MUTE
-from homeassistant.const import STATE_ON, STATE_OFF, STATE_PLAYING, \
- STATE_IDLE, STATE_PAUSED
+from homeassistant.components.media_player import (
+ DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
+from homeassistant.const import (
+ STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import HomeAssistant
DEPENDENCIES = ['hdmi_cec']
diff --git a/homeassistant/components/media_player/horizon.py b/homeassistant/components/media_player/horizon.py
index 4fa97cb5537..04471c69b9c 100644
--- a/homeassistant/components/media_player/horizon.py
+++ b/homeassistant/components/media_player/horizon.py
@@ -4,27 +4,26 @@ Support for the Unitymedia Horizon HD Recorder.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/media_player.horizon/
"""
-
from datetime import timedelta
import logging
import voluptuous as vol
+from homeassistant import util
from homeassistant.components.media_player import (
- MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_CHANNEL,
- SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PAUSE,
- SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK)
-from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF,
- STATE_PAUSED, STATE_PLAYING)
+ MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, MediaPlayerDevice)
+from homeassistant.const import (
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-from homeassistant import util
REQUIREMENTS = ['einder==0.3.1']
_LOGGER = logging.getLogger(__name__)
-DEFAULT_NAME = "Horizon"
+DEFAULT_NAME = 'Horizon'
DEFAULT_PORT = 5900
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py
index 31df74dbeaf..e2ae179676b 100644
--- a/homeassistant/components/media_player/itunes.py
+++ b/homeassistant/components/media_player/itunes.py
@@ -10,21 +10,21 @@ import requests
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
- SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF,
- SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, PLATFORM_SCHEMA,
- SUPPORT_PLAY, MediaPlayerDevice)
+ MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, CONF_NAME,
- CONF_HOST, CONF_PORT, CONF_SSL)
+ CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL, STATE_IDLE, STATE_OFF, STATE_ON,
+ STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'iTunes'
DEFAULT_PORT = 8181
-DEFAULT_TIMEOUT = 10
DEFAULT_SSL = False
+DEFAULT_TIMEOUT = 10
DOMAIN = 'itunes'
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@@ -52,7 +52,7 @@ class Itunes:
@property
def _base_url(self):
- """Return the base url for endpoints."""
+ """Return the base URL for endpoints."""
if self.use_ssl:
uri_scheme = 'https://'
else:
diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py
index b36512e7c65..c98dc5c56fe 100644
--- a/homeassistant/components/media_player/kodi.py
+++ b/homeassistant/components/media_player/kodi.py
@@ -8,27 +8,29 @@ import asyncio
from collections import OrderedDict
from functools import wraps
import logging
+import re
import socket
import urllib
-import re
import aiohttp
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
- SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
- SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, SUPPORT_SHUFFLE_SET,
- MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
- MEDIA_TYPE_MOVIE, MEDIA_TYPE_VIDEO, MEDIA_TYPE_CHANNEL,
- MEDIA_TYPE_PLAYLIST, MEDIA_PLAYER_SCHEMA, DOMAIN, SUPPORT_TURN_ON)
+ DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE,
+ MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO,
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
+ SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
from homeassistant.const import (
- STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
- CONF_PORT, CONF_PROXY_SSL, CONF_USERNAME, CONF_PASSWORD,
- CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP)
+ CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_PROXY_SSL,
+ CONF_TIMEOUT, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE,
+ STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import callback
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers import script
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-from homeassistant.helpers import script, config_validation as cv
from homeassistant.helpers.template import Template
from homeassistant.util.yaml import dump
@@ -310,6 +312,8 @@ class KodiDevice(MediaPlayerDevice):
# Register notification listeners
self._ws_server.Player.OnPause = self.async_on_speed_event
self._ws_server.Player.OnPlay = self.async_on_speed_event
+ self._ws_server.Player.OnAVStart = self.async_on_speed_event
+ self._ws_server.Player.OnAVChange = self.async_on_speed_event
self._ws_server.Player.OnResume = self.async_on_speed_event
self._ws_server.Player.OnSpeedChanged = self.async_on_speed_event
self._ws_server.Player.OnStop = self.async_on_stop
diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py
index cbc15af91b6..92f48411401 100644
--- a/homeassistant/components/media_player/lg_netcast.py
+++ b/homeassistant/components/media_player/lg_netcast.py
@@ -10,15 +10,16 @@ import logging
from requests import RequestException
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
-from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
- SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
- SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_CHANNEL, MediaPlayerDevice)
-from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN,
- STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
from homeassistant import util
+from homeassistant.components.media_player import (
+ MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
+ SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
+from homeassistant.const import (
+ CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING, STATE_UNKNOWN)
+import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0']
@@ -35,16 +36,16 @@ SUPPORT_LGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
- vol.Optional(CONF_ACCESS_TOKEN):
- vol.All(cv.string, vol.Length(max=6)),
+ vol.Optional(CONF_ACCESS_TOKEN): vol.All(cv.string, vol.Length(max=6)),
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the LG TV platform."""
from pylgnetcast import LgNetCastClient
+
host = config.get(CONF_HOST)
access_token = config.get(CONF_ACCESS_TOKEN)
name = config.get(CONF_NAME)
diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py
index d5b1bcd78fe..9a08ceeac93 100644
--- a/homeassistant/components/media_player/liveboxplaytv.py
+++ b/homeassistant/components/media_player/liveboxplaytv.py
@@ -5,20 +5,20 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.liveboxplaytv/
"""
import asyncio
-import logging
from datetime import timedelta
+import logging
import requests
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY,
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_MUTE, SUPPORT_SELECT_SOURCE,
- MEDIA_TYPE_CHANNEL, MediaPlayerDevice, PLATFORM_SCHEMA)
+ MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_PORT, STATE_ON, STATE_OFF, STATE_PLAYING,
- STATE_PAUSED, CONF_NAME)
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON, STATE_PAUSED,
+ STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
@@ -40,7 +40,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
diff --git a/homeassistant/components/media_player/mediaroom.py b/homeassistant/components/media_player/mediaroom.py
index f0b5365cf83..345b58cbbe4 100644
--- a/homeassistant/components/media_player/mediaroom.py
+++ b/homeassistant/components/media_player/mediaroom.py
@@ -8,45 +8,41 @@ import logging
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components.media_player import (
- MEDIA_TYPE_CHANNEL, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_TURN_OFF,
- SUPPORT_TURN_ON, SUPPORT_STOP, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_PLAY, SUPPORT_VOLUME_STEP,
- SUPPORT_VOLUME_MUTE, MediaPlayerDevice,
-)
-from homeassistant.helpers.dispatcher import (
- dispatcher_send, async_dispatcher_connect
-)
+ MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_OPTIMISTIC, STATE_OFF,
- CONF_TIMEOUT, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY,
- STATE_UNAVAILABLE, EVENT_HOMEASSISTANT_STOP
-)
+ CONF_HOST, CONF_NAME, CONF_OPTIMISTIC, CONF_TIMEOUT,
+ EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
+ STATE_STANDBY, STATE_UNAVAILABLE)
+from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import (
+ async_dispatcher_connect, dispatcher_send)
REQUIREMENTS = ['pymediaroom==0.6.4']
_LOGGER = logging.getLogger(__name__)
-DEFAULT_NAME = 'Mediaroom STB'
+DATA_MEDIAROOM = 'mediaroom_known_stb'
+DEFAULT_NAME = "Mediaroom STB"
DEFAULT_TIMEOUT = 9
-DATA_MEDIAROOM = "mediaroom_known_stb"
-DISCOVERY_MEDIAROOM = "mediaroom_discovery_installed"
+DISCOVERY_MEDIAROOM = 'mediaroom_discovery_installed'
+
SIGNAL_STB_NOTIFY = 'mediaroom_stb_discovered'
SUPPORT_MEDIAROOM = SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF \
| SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_PLAY_MEDIA \
| SUPPORT_STOP | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK \
| SUPPORT_PLAY
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
- {
- vol.Optional(CONF_HOST): cv.string,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
- vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
- }
-)
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_HOST): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
+ vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
+})
async def async_setup_platform(hass, config, async_add_entities,
@@ -57,10 +53,9 @@ async def async_setup_platform(hass, config, async_add_entities,
known_hosts = hass.data[DATA_MEDIAROOM] = []
host = config.get(CONF_HOST, None)
if host:
- async_add_entities([MediaroomDevice(host=host,
- device_id=None,
- optimistic=config[CONF_OPTIMISTIC],
- timeout=config[CONF_TIMEOUT])])
+ async_add_entities([MediaroomDevice(
+ host=host, device_id=None, optimistic=config[CONF_OPTIMISTIC],
+ timeout=config[CONF_TIMEOUT])])
hass.data[DATA_MEDIAROOM].append(host)
_LOGGER.debug("Trying to discover Mediaroom STB")
@@ -75,8 +70,7 @@ async def async_setup_platform(hass, config, async_add_entities,
hass.data[DATA_MEDIAROOM].append(notify.ip_address)
new_stb = MediaroomDevice(
host=notify.ip_address, device_id=notify.device_uuid,
- optimistic=False
- )
+ optimistic=False)
async_add_entities([new_stb])
if not config[CONF_OPTIMISTIC]:
@@ -90,11 +84,11 @@ async def async_setup_platform(hass, config, async_add_entities,
@callback
def stop_discovery(event):
"""Stop discovery of new mediaroom STB's."""
- _LOGGER.debug("Stopping internal pymediaroom discovery.")
+ _LOGGER.debug("Stopping internal pymediaroom discovery")
hass.data[DISCOVERY_MEDIAROOM].close()
- hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
- stop_discovery)
+ hass.bus.async_listen_once(
+ EVENT_HOMEASSISTANT_STOP, stop_discovery)
_LOGGER.debug("Auto discovery installed")
@@ -118,8 +112,8 @@ class MediaroomDevice(MediaPlayerDevice):
self._state = state_map[mediaroom_state]
- def __init__(self, host, device_id, optimistic=False,
- timeout=DEFAULT_TIMEOUT):
+ def __init__(
+ self, host, device_id, optimistic=False, timeout=DEFAULT_TIMEOUT):
"""Initialize the device."""
from pymediaroom import Remote
@@ -160,8 +154,8 @@ class MediaroomDevice(MediaPlayerDevice):
self._available = True
self.async_schedule_update_ha_state()
- async_dispatcher_connect(self.hass, SIGNAL_STB_NOTIFY,
- async_notify_received)
+ async_dispatcher_connect(
+ self.hass, SIGNAL_STB_NOTIFY, async_notify_received)
async def async_play_media(self, media_type, media_id, **kwargs):
"""Play media."""
diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/media_player/mpchc.py
index 840429ef3d4..e6bc1f2699d 100644
--- a/homeassistant/components/media_player/mpchc.py
+++ b/homeassistant/components/media_player/mpchc.py
@@ -11,12 +11,12 @@ import requests
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_VOLUME_MUTE, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_NEXT_TRACK,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
- MediaPlayerDevice, PLATFORM_SCHEMA)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- STATE_OFF, STATE_IDLE, STATE_PAUSED, STATE_PLAYING, CONF_NAME, CONF_HOST,
- CONF_PORT)
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py
index d30c5815d3f..5fff8831617 100644
--- a/homeassistant/components/media_player/nad.py
+++ b/homeassistant/components/media_player/nad.py
@@ -9,12 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_MUTE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
- SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
- PLATFORM_SCHEMA)
-from homeassistant.const import (
- CONF_NAME, STATE_OFF, STATE_ON)
+ PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
+from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['nad_receiver==0.0.9']
diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py
index af9a6ef54ce..0ba098d85f5 100644
--- a/homeassistant/components/media_player/onkyo.py
+++ b/homeassistant/components/media_player/onkyo.py
@@ -12,10 +12,10 @@ from typing import List # noqa: F401
import voluptuous as vol
from homeassistant.components.media_player import (
+ PLATFORM_SCHEMA, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY,
- MediaPlayerDevice, PLATFORM_SCHEMA)
-from homeassistant.const import (STATE_OFF, STATE_ON, CONF_HOST, CONF_NAME)
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
+from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['onkyo-eiscp==1.2.4']
@@ -30,17 +30,19 @@ SUPPORTED_MAX_VOLUME = 80
SUPPORT_ONKYO = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
- SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
+ SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
SUPPORT_ONKYO_WO_VOLUME = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
- SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
+ SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
KNOWN_HOSTS = [] # type: List[str]
DEFAULT_SOURCES = {'tv': 'TV', 'bd': 'Bluray', 'game': 'Game', 'aux1': 'Aux1',
'video1': 'Video 1', 'video2': 'Video 2',
'video3': 'Video 3', 'video4': 'Video 4',
'video5': 'Video 5', 'video6': 'Video 6',
- 'video7': 'Video 7'}
+ 'video7': 'Video 7', 'fm': 'Radio'}
+
+DEFAULT_PLAYABLE_SOURCES = ("fm", "am", "tuner")
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
@@ -266,6 +268,13 @@ class OnkyoDevice(MediaPlayerDevice):
source = self._reverse_mapping[source]
self.command('input-selector {}'.format(source))
+ def play_media(self, media_type, media_id, **kwargs):
+ """Play radio station by preset number."""
+ source = self._reverse_mapping[self._current_source]
+ if (media_type.lower() == 'radio' and
+ source in DEFAULT_PLAYABLE_SOURCES):
+ self.command('preset {}'.format(media_id))
+
class OnkyoDeviceZone(OnkyoDevice):
"""Representation of an Onkyo device's extra zone."""
diff --git a/homeassistant/components/media_player/openhome.py b/homeassistant/components/media_player/openhome.py
index 1ffe0ef82df..ab23f8a7f9a 100644
--- a/homeassistant/components/media_player/openhome.py
+++ b/homeassistant/components/media_player/openhome.py
@@ -7,12 +7,12 @@ https://home-assistant.io/components/media_player.openhome/
import logging
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
- SUPPORT_TURN_OFF, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
- SUPPORT_VOLUME_STEP, SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
MediaPlayerDevice)
from homeassistant.const import (
- STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_OFF)
+ STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
REQUIREMENTS = ['openhomedevice==0.4.2']
diff --git a/homeassistant/components/media_player/panasonic_viera.py b/homeassistant/components/media_player/panasonic_viera.py
index 937a72c80ff..efe04c7005b 100644
--- a/homeassistant/components/media_player/panasonic_viera.py
+++ b/homeassistant/components/media_player/panasonic_viera.py
@@ -9,22 +9,19 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MEDIA_TYPE_URL,
- SUPPORT_PLAY_MEDIA, SUPPORT_STOP,
- SUPPORT_VOLUME_STEP, MediaPlayerDevice, PLATFORM_SCHEMA)
+ MEDIA_TYPE_URL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT)
+ CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON,
+ STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['panasonic_viera==0.3.1',
- 'wakeonlan==1.0.0']
+REQUIREMENTS = ['panasonic_viera==0.3.1', 'wakeonlan==1.0.0']
_LOGGER = logging.getLogger(__name__)
-CONF_MAC = 'mac'
-
DEFAULT_NAME = 'Panasonic Viera TV'
DEFAULT_PORT = 55000
diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py
index 5295bfc40eb..231ea5302ae 100644
--- a/homeassistant/components/media_player/pandora.py
+++ b/homeassistant/components/media_player/pandora.py
@@ -4,23 +4,22 @@ Component for controlling Pandora stations through the pianobar client.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/media_player.pandora/
"""
-import logging
-import re
-import os
-import signal
from datetime import timedelta
+import logging
+import os
+import re
import shutil
+import signal
-from homeassistant.const import EVENT_HOMEASSISTANT_STOP
-from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_MUSIC,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PLAY,
- SUPPORT_SELECT_SOURCE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
- SERVICE_MEDIA_PLAY, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
- MediaPlayerDevice)
-from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
- STATE_IDLE)
from homeassistant import util
+from homeassistant.components.media_player import (
+ MEDIA_TYPE_MUSIC, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY,
+ SERVICE_MEDIA_PLAY_PAUSE, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice)
+from homeassistant.const import (
+ EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING)
REQUIREMENTS = ['pexpect==4.6.0']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/philips_js.py b/homeassistant/components/media_player/philips_js.py
index 9c7418d4c80..7d434ab480e 100644
--- a/homeassistant/components/media_player/philips_js.py
+++ b/homeassistant/components/media_player/philips_js.py
@@ -4,19 +4,19 @@ Media Player component to integrate TVs exposing the Joint Space API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.philips_js/
"""
-import logging
from datetime import timedelta
+import logging
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
- PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
- SUPPORT_PLAY, MediaPlayerDevice)
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_API_VERSION, STATE_OFF, STATE_ON, STATE_UNKNOWN)
+ CONF_API_VERSION, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
+import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
from homeassistant.util import Throttle
@@ -37,7 +37,7 @@ CONF_ON_ACTION = 'turn_on_action'
DEFAULT_DEVICE = 'default'
DEFAULT_HOST = '127.0.0.1'
-DEFAULT_NAME = 'Philips TV'
+DEFAULT_NAME = "Philips TV"
DEFAULT_API_VERSION = '1'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/media_player/pioneer.py b/homeassistant/components/media_player/pioneer.py
index 71ccf9a460d..29e4068f1d4 100644
--- a/homeassistant/components/media_player/pioneer.py
+++ b/homeassistant/components/media_player/pioneer.py
@@ -10,12 +10,12 @@ import telnetlib
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_PAUSE, SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA,
+ PLATFORM_SCHEMA, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_PLAY)
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_NAME, CONF_PORT,
- CONF_TIMEOUT)
+ CONF_HOST, CONF_NAME, CONF_PORT, CONF_TIMEOUT, STATE_OFF, STATE_ON,
+ STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/pjlink.py b/homeassistant/components/media_player/pjlink.py
index 42884cdce09..168cde4a792 100644
--- a/homeassistant/components/media_player/pjlink.py
+++ b/homeassistant/components/media_player/pjlink.py
@@ -9,11 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
- SUPPORT_SELECT_SOURCE, PLATFORM_SCHEMA, MediaPlayerDevice)
+ PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, MediaPlayerDevice)
from homeassistant.const import (
- STATE_OFF, STATE_ON, CONF_HOST,
- CONF_NAME, CONF_PASSWORD, CONF_PORT)
+ CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pypjlink2==1.2.0']
@@ -87,8 +86,8 @@ class PjLinkDevice(MediaPlayerDevice):
def projector(self):
"""Create PJLink Projector instance."""
from pypjlink import Projector
- projector = Projector.from_address(self._host, self._port,
- self._encoding)
+ projector = Projector.from_address(
+ self._host, self._port, self._encoding)
projector.authenticate(self._password)
return projector
diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py
index 46dacd98aad..0b4069ed664 100644
--- a/homeassistant/components/media_player/plex.py
+++ b/homeassistant/components/media_player/plex.py
@@ -4,17 +4,16 @@ Support to interface with the Plex API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.plex/
"""
+from datetime import timedelta
import json
import logging
-from datetime import timedelta
-
import requests
import voluptuous as vol
from homeassistant import util
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, PLATFORM_SCHEMA,
+ MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA,
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
@@ -22,9 +21,8 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
-from homeassistant.util.json import load_json, save_json
from homeassistant.util import dt as dt_util
-
+from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['plexapi==3.0.6']
@@ -35,6 +33,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PLEX_CONFIG_FILE = 'plex.conf'
+PLEX_DATA = 'plex'
CONF_INCLUDE_NON_CLIENTS = 'include_non_clients'
CONF_USE_EPISODE_ART = 'use_episode_art'
@@ -44,20 +43,14 @@ CONF_REMOVE_UNAVAILABLE_CLIENTS = 'remove_unavailable_clients'
CONF_CLIENT_REMOVE_INTERVAL = 'client_remove_interval'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_INCLUDE_NON_CLIENTS, default=False):
- cv.boolean,
- vol.Optional(CONF_USE_EPISODE_ART, default=False):
- cv.boolean,
- vol.Optional(CONF_USE_CUSTOM_ENTITY_IDS, default=False):
- cv.boolean,
- vol.Optional(CONF_REMOVE_UNAVAILABLE_CLIENTS, default=True):
- cv.boolean,
+ vol.Optional(CONF_INCLUDE_NON_CLIENTS, default=False): cv.boolean,
+ vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean,
+ vol.Optional(CONF_USE_CUSTOM_ENTITY_IDS, default=False): cv.boolean,
+ vol.Optional(CONF_REMOVE_UNAVAILABLE_CLIENTS, default=True): cv.boolean,
vol.Optional(CONF_CLIENT_REMOVE_INTERVAL, default=timedelta(seconds=600)):
vol.All(cv.time_period, cv.positive_timedelta),
})
-PLEX_DATA = "plex"
-
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
"""Set up the Plex platform."""
@@ -157,8 +150,8 @@ def setup_plexserver(
_LOGGER.exception("Error listing plex devices")
return
except requests.exceptions.RequestException as ex:
- _LOGGER.error("Could not connect to plex server at http://%s (%s)",
- host, ex)
+ _LOGGER.error(
+ "Could not connect to plex server at http://%s (%s)", host, ex)
return
new_plex_clients = []
@@ -171,9 +164,9 @@ def setup_plexserver(
available_client_ids.append(device.machineIdentifier)
if device.machineIdentifier not in plex_clients:
- new_client = PlexClient(config, device, None,
- plex_sessions, update_devices,
- update_sessions)
+ new_client = PlexClient(
+ config, device, None, plex_sessions, update_devices,
+ update_sessions)
plex_clients[device.machineIdentifier] = new_client
new_plex_clients.append(new_client)
else:
@@ -184,9 +177,9 @@ def setup_plexserver(
for machine_identifier, session in plex_sessions.items():
if (machine_identifier not in plex_clients
and machine_identifier is not None):
- new_client = PlexClient(config, None, session,
- plex_sessions, update_devices,
- update_sessions)
+ new_client = PlexClient(
+ config, None, session, plex_sessions, update_devices,
+ update_sessions)
plex_clients[machine_identifier] = new_client
new_plex_clients.append(new_client)
else:
@@ -225,8 +218,8 @@ def setup_plexserver(
_LOGGER.exception("Error listing plex sessions")
return
except requests.exceptions.RequestException as ex:
- _LOGGER.error("Could not connect to plex server at http://%s (%s)",
- host, ex)
+ _LOGGER.error(
+ "Could not connect to plex server at http://%s (%s)", host, ex)
return
plex_sessions.clear()
diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py
index fca7b29d2ec..fccca235193 100644
--- a/homeassistant/components/media_player/roku.py
+++ b/homeassistant/components/media_player/roku.py
@@ -9,11 +9,11 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA)
+ MEDIA_TYPE_MOVIE, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PLAY,
+ SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, STATE_HOME)
+ CONF_HOST, STATE_HOME, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-roku==3.1.5']
@@ -40,13 +40,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
hosts = []
if discovery_info:
- host = discovery_info.get("host")
+ host = discovery_info.get('host')
if host in KNOWN_HOSTS:
return
_LOGGER.debug("Discovered Roku: %s", host)
- hosts.append(discovery_info.get("host"))
+ hosts.append(discovery_info.get('host'))
elif CONF_HOST in config:
hosts.append(config.get(CONF_HOST))
diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/media_player/russound_rio.py
index cf946945cf2..74f6bfb35ab 100644
--- a/homeassistant/components/media_player/russound_rio.py
+++ b/homeassistant/components/media_player/russound_rio.py
@@ -4,20 +4,19 @@ Support for Russound multizone controllers using RIO Protocol.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.russound_rio/
"""
-
import asyncio
import logging
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components.media_player import (
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA,
- MEDIA_TYPE_MUSIC)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON,
- CONF_NAME, EVENT_HOMEASSISTANT_STOP)
+ CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF,
+ STATE_ON)
+from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['russound_rio==0.1.4']
@@ -31,26 +30,24 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_PORT, default=9621): cv.port,
- })
+})
@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Russound RIO platform."""
+ from russound_rio import Russound
+
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
- from russound_rio import Russound
-
russ = Russound(hass.loop, host, port)
yield from russ.connect()
- # Discover sources
+ # Discover sources and zones
sources = yield from russ.enumerate_sources()
-
- # Discover zones
valid_zones = yield from russ.enumerate_zones()
devices = []
@@ -81,9 +78,8 @@ class RussoundZoneDevice(MediaPlayerDevice):
self._sources = sources
def _zone_var(self, name, default=None):
- return self._russ.get_cached_zone_variable(self._zone_id,
- name,
- default)
+ return self._russ.get_cached_zone_variable(
+ self._zone_id, name, default)
def _source_var(self, name, default=None):
current = int(self._zone_var('currentsource', 0))
@@ -188,21 +184,17 @@ class RussoundZoneDevice(MediaPlayerDevice):
def async_turn_off(self):
"""Turn off the zone."""
- return self._russ.send_zone_event(self._zone_id,
- "ZoneOff")
+ return self._russ.send_zone_event(self._zone_id, 'ZoneOff')
def async_turn_on(self):
"""Turn on the zone."""
- return self._russ.send_zone_event(self._zone_id,
- "ZoneOn")
+ return self._russ.send_zone_event(self._zone_id, 'ZoneOn')
def async_set_volume_level(self, volume):
"""Set the volume level."""
rvol = int(volume * 50.0)
- return self._russ.send_zone_event(self._zone_id,
- "KeyPress",
- "Volume",
- rvol)
+ return self._russ.send_zone_event(
+ self._zone_id, 'KeyPress', 'Volume', rvol)
def async_select_source(self, source):
"""Select the source input for this zone."""
@@ -210,4 +202,4 @@ class RussoundZoneDevice(MediaPlayerDevice):
if name.lower() != source.lower():
continue
return self._russ.send_zone_event(
- self._zone_id, "SelectSource", source_id)
+ self._zone_id, 'SelectSource', source_id)
diff --git a/homeassistant/components/media_player/russound_rnet.py b/homeassistant/components/media_player/russound_rnet.py
index 8aef15e02af..7f4d04eb634 100644
--- a/homeassistant/components/media_player/russound_rnet.py
+++ b/homeassistant/components/media_player/russound_rnet.py
@@ -9,10 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA)
+ PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, CONF_NAME)
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['russound==0.1.9']
diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py
index 72c3ab2c621..cc966c0d263 100644
--- a/homeassistant/components/media_player/samsungtv.py
+++ b/homeassistant/components/media_player/samsungtv.py
@@ -5,23 +5,22 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""
import asyncio
+from datetime import timedelta
import logging
import socket
-from datetime import timedelta
-
+import subprocess
import sys
-import subprocess
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PLAY,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY_MEDIA,
- MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_CHANNEL)
+ MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT,
- CONF_MAC)
+ CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_TIMEOUT, STATE_OFF,
+ STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util
@@ -29,13 +28,11 @@ REQUIREMENTS = ['samsungctl[websocket]==0.7.1', 'wakeonlan==1.0.0']
_LOGGER = logging.getLogger(__name__)
-CONF_TIMEOUT = 'timeout'
-
DEFAULT_NAME = 'Samsung TV Remote'
DEFAULT_PORT = 55000
DEFAULT_TIMEOUT = 0
-KEY_PRESS_TIMEOUT = 1.2
+KEY_PRESS_TIMEOUT = 1.2
KNOWN_DEVICES_KEY = 'samsungtv_known_devices'
SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py
index 36f28769b12..ef6b02514f0 100644
--- a/homeassistant/components/media_player/sisyphus.py
+++ b/homeassistant/components/media_player/sisyphus.py
@@ -7,25 +7,18 @@ https://home-assistant.io/components/media_player.sisyphus/
import logging
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK,
- SUPPORT_PAUSE,
- SUPPORT_PLAY,
- SUPPORT_PREVIOUS_TRACK,
- SUPPORT_SHUFFLE_SET,
- SUPPORT_TURN_OFF,
- SUPPORT_TURN_ON,
- SUPPORT_VOLUME_MUTE,
- SUPPORT_VOLUME_SET,
- MediaPlayerDevice)
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.components.sisyphus import DATA_SISYPHUS
-from homeassistant.const import CONF_HOST, CONF_NAME, STATE_PLAYING, \
- STATE_PAUSED, STATE_IDLE, STATE_OFF
+from homeassistant.const import (
+ CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['sisyphus']
-MEDIA_TYPE_TRACK = "sisyphus_track"
+MEDIA_TYPE_TRACK = 'sisyphus_track'
SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE \
| SUPPORT_VOLUME_SET \
@@ -44,21 +37,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
name = discovery_info[CONF_NAME]
host = discovery_info[CONF_HOST]
add_entities(
- [SisyphusPlayer(name, host, hass.data[DATA_SISYPHUS][name])],
- update_before_add=True)
+ [SisyphusPlayer(name, host, hass.data[DATA_SISYPHUS][name])], True)
class SisyphusPlayer(MediaPlayerDevice):
- """Represents a single Sisyphus table as a media player device."""
+ """Representation of a Sisyphus table as a media player device."""
def __init__(self, name, host, table):
- """
- Constructor.
-
- :param name: name of the table
- :param host: hostname or ip address
- :param table: sisyphus-control Table object
- """
+ """Initialize the Sisyphus media device."""
self._name = name
self._host = host
self._table = table
@@ -99,11 +85,7 @@ class SisyphusPlayer(MediaPlayerDevice):
return self._table.is_shuffle
async def async_set_shuffle(self, shuffle):
- """
- Change the shuffle mode of the current playlist.
-
- :param shuffle: True to shuffle, False not to
- """
+ """Change the shuffle mode of the current playlist."""
await self._table.set_shuffle(shuffle)
@property
diff --git a/homeassistant/components/media_player/songpal.py b/homeassistant/components/media_player/songpal.py
index c1bfbbe59cd..83b10997c31 100644
--- a/homeassistant/components/media_player/songpal.py
+++ b/homeassistant/components/media_player/songpal.py
@@ -9,32 +9,30 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET,
- SUPPORT_TURN_ON, MediaPlayerDevice, DOMAIN)
-from homeassistant.const import (
- CONF_NAME, STATE_ON, STATE_OFF, ATTR_ENTITY_ID)
+ DOMAIN, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
+from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-songpal==0.0.7']
+REQUIREMENTS = ['python-songpal==0.0.8']
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_ENDPOINT = 'endpoint'
+
+PARAM_NAME = 'name'
+PARAM_VALUE = 'value'
+
+PLATFORM = 'songpal'
+
+SET_SOUND_SETTING = 'songpal_set_sound_setting'
SUPPORT_SONGPAL = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
-_LOGGER = logging.getLogger(__name__)
-
-
-PLATFORM = "songpal"
-
-SET_SOUND_SETTING = "songpal_set_sound_setting"
-
-PARAM_NAME = "name"
-PARAM_VALUE = "value"
-
-CONF_ENDPOINT = "endpoint"
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_ENDPOINT): cv.string,
@@ -43,13 +41,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
SET_SOUND_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(PARAM_NAME): cv.string,
- vol.Required(PARAM_VALUE): cv.string})
+ vol.Required(PARAM_VALUE): cv.string,
+})
-async def async_setup_platform(hass, config,
- async_add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Songpal platform."""
from songpal import SongpalException
+
if PLATFORM not in hass.data:
hass.data[PLATFORM] = {}
@@ -85,8 +85,8 @@ async def async_setup_platform(hass, config,
_LOGGER.debug("Calling %s (entity: %s) with params %s",
service, entity_id, params)
- await device.async_set_sound_setting(params[PARAM_NAME],
- params[PARAM_VALUE])
+ await device.async_set_sound_setting(
+ params[PARAM_NAME], params[PARAM_VALUE])
hass.services.async_register(
DOMAIN, SET_SOUND_SETTING, async_service_handler,
@@ -151,8 +151,8 @@ class SongpalDevice(MediaPlayerDevice):
return
if len(volumes) > 1:
- _LOGGER.debug("Got %s volume controls, using the first one",
- volumes)
+ _LOGGER.debug(
+ "Got %s volume controls, using the first one", volumes)
volume = volumes[0]
_LOGGER.debug("Current volume: %s", volume)
diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py
index c4309519e36..72ac0a046a3 100644
--- a/homeassistant/components/media_player/sonos.py
+++ b/homeassistant/components/media_player/sonos.py
@@ -9,8 +9,8 @@ import datetime
import functools as ft
import logging
import socket
-import urllib
import threading
+import urllib
import voluptuous as vol
@@ -388,6 +388,18 @@ class SonosDevice(MediaPlayerDevice):
"""Return the name of the device."""
return self._name
+ @property
+ def device_info(self):
+ """Return information about the device."""
+ return {
+ 'identifiers': {
+ (SONOS_DOMAIN, self._unique_id)
+ },
+ 'name': self._name,
+ 'model': self._model.replace("Sonos ", ""),
+ 'manufacturer': 'Sonos',
+ }
+
@property
@soco_coordinator
def state(self):
@@ -872,6 +884,8 @@ class SonosDevice(MediaPlayerDevice):
sources += [SOURCE_LINEIN]
elif 'PLAYBAR' in model:
sources += [SOURCE_LINEIN, SOURCE_TV]
+ elif 'BEAM' in model:
+ sources += [SOURCE_TV]
return sources
diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py
index 4e26af9dcc2..a16658501cb 100644
--- a/homeassistant/components/media_player/soundtouch.py
+++ b/homeassistant/components/media_player/soundtouch.py
@@ -5,19 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.soundtouch/
"""
import logging
-
import re
+
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
- SUPPORT_VOLUME_SET, SUPPORT_TURN_ON, SUPPORT_PLAY, MediaPlayerDevice,
- DOMAIN, PLATFORM_SCHEMA)
-from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, CONF_PORT,
- STATE_PAUSED, STATE_PLAYING,
- STATE_UNAVAILABLE)
+ DOMAIN, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
+from homeassistant.const import (
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
+ STATE_UNAVAILABLE)
+import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['libsoundtouch==0.7.2']
@@ -43,17 +43,17 @@ SOUNDTOUCH_PLAY_EVERYWHERE = vol.Schema({
SOUNDTOUCH_CREATE_ZONE_SCHEMA = vol.Schema({
vol.Required('master'): cv.entity_id,
- vol.Required('slaves'): cv.entity_ids
+ vol.Required('slaves'): cv.entity_ids,
})
SOUNDTOUCH_ADD_ZONE_SCHEMA = vol.Schema({
vol.Required('master'): cv.entity_id,
- vol.Required('slaves'): cv.entity_ids
+ vol.Required('slaves'): cv.entity_ids,
})
SOUNDTOUCH_REMOVE_ZONE_SCHEMA = vol.Schema({
vol.Required('master'): cv.entity_id,
- vol.Required('slaves'): cv.entity_ids
+ vol.Required('slaves'): cv.entity_ids,
})
DEFAULT_NAME = 'Bose Soundtouch'
@@ -67,7 +67,7 @@ SUPPORT_SOUNDTOUCH = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port
+ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py
index 9fc200c67fd..8a4ffeeb157 100644
--- a/homeassistant/components/media_player/spotify.py
+++ b/homeassistant/components/media_player/spotify.py
@@ -4,48 +4,54 @@ Support for interacting with Spotify Connect.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.spotify/
"""
-import logging
from datetime import timedelta
+import logging
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
- SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET,
- PLATFORM_SCHEMA, MediaPlayerDevice)
+ MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
+ SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN)
+ CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
+from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['spotipy-homeassistant==2.4.4.dev1']
-DEPENDENCIES = ['http']
-
_LOGGER = logging.getLogger(__name__)
+AUTH_CALLBACK_NAME = 'api:spotify'
+AUTH_CALLBACK_PATH = '/api/spotify'
+
+CONF_ALIASES = 'aliases'
+CONF_CACHE_PATH = 'cache_path'
+CONF_CLIENT_ID = 'client_id'
+CONF_CLIENT_SECRET = 'client_secret'
+
+CONFIGURATOR_DESCRIPTION = 'To link your Spotify account, ' \
+ 'click the link, login, and authorize:'
+CONFIGURATOR_LINK_NAME = 'Link Spotify account'
+CONFIGURATOR_SUBMIT_CAPTION = 'I authorized successfully'
+
+DEFAULT_CACHE_PATH = '.spotify-token-cache'
+DEFAULT_NAME = 'Spotify'
+DEPENDENCIES = ['http']
+DOMAIN = 'spotify'
+
+ICON = 'mdi:spotify'
+
+SCAN_INTERVAL = timedelta(seconds=30)
+
+SCOPE = 'user-read-playback-state user-modify-playback-state user-read-private'
+
SUPPORT_SPOTIFY = SUPPORT_VOLUME_SET | SUPPORT_PAUSE | SUPPORT_PLAY |\
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\
SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET
-SCOPE = 'user-read-playback-state user-modify-playback-state user-read-private'
-DEFAULT_CACHE_PATH = '.spotify-token-cache'
-AUTH_CALLBACK_PATH = '/api/spotify'
-AUTH_CALLBACK_NAME = 'api:spotify'
-ICON = 'mdi:spotify'
-DEFAULT_NAME = 'Spotify'
-DOMAIN = 'spotify'
-CONF_ALIASES = 'aliases'
-CONF_CLIENT_ID = 'client_id'
-CONF_CLIENT_SECRET = 'client_secret'
-CONF_CACHE_PATH = 'cache_path'
-CONFIGURATOR_LINK_NAME = 'Link Spotify account'
-CONFIGURATOR_SUBMIT_CAPTION = 'I authorized successfully'
-CONFIGURATOR_DESCRIPTION = 'To link your Spotify account, ' \
- 'click the link, login, and authorize:'
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string,
@@ -54,8 +60,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ALIASES, default={}): {cv.string: cv.string}
})
-SCAN_INTERVAL = timedelta(seconds=30)
-
def request_configuration(hass, config, add_entities, oauth):
"""Request Spotify authorization."""
@@ -71,6 +75,7 @@ def request_configuration(hass, config, add_entities, oauth):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Spotify platform."""
import spotipy.oauth2
+
callback_url = '{}{}'.format(hass.config.api.base_url, AUTH_CALLBACK_PATH)
cache = config.get(CONF_CACHE_PATH, hass.config.path(DEFAULT_CACHE_PATH))
oauth = spotipy.oauth2.SpotifyOAuth(
@@ -88,8 +93,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
configurator = hass.components.configurator
configurator.request_done(hass.data.get(DOMAIN))
del hass.data[DOMAIN]
- player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME),
- config[CONF_ALIASES])
+ player = SpotifyMediaPlayer(
+ oauth, config.get(CONF_NAME, DEFAULT_NAME), config[CONF_ALIASES])
add_entities([player], True)
diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py
index a2732b5f849..2d6a849aecb 100644
--- a/homeassistant/components/media_player/squeezebox.py
+++ b/homeassistant/components/media_player/squeezebox.py
@@ -4,26 +4,26 @@ Support for interfacing to the Logitech SqueezeBox API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.squeezebox/
"""
-import logging
import asyncio
-import urllib.parse
import json
+import logging
+import urllib.parse
+
import aiohttp
import async_timeout
-
import voluptuous as vol
from homeassistant.components.media_player import (
- ATTR_MEDIA_ENQUEUE, SUPPORT_PLAY_MEDIA,
- MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, PLATFORM_SCHEMA,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
- SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_PLAY, MediaPlayerDevice,
- MEDIA_PLAYER_SCHEMA, DOMAIN, SUPPORT_SHUFFLE_SET, SUPPORT_CLEAR_PLAYLIST)
+ ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_MUSIC,
+ PLATFORM_SCHEMA, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
+ SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
- CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_IDLE, STATE_OFF,
- STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, CONF_PORT, ATTR_COMMAND)
-import homeassistant.helpers.config_validation as cv
+ ATTR_COMMAND, CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
+ STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/media_player/ue_smart_radio.py b/homeassistant/components/media_player/ue_smart_radio.py
index ae7617ead24..066972aaa25 100644
--- a/homeassistant/components/media_player/ue_smart_radio.py
+++ b/homeassistant/components/media_player/ue_smart_radio.py
@@ -6,31 +6,34 @@ https://home-assistant.io/components/media_player.ue_smart_radio/
"""
import logging
-import voluptuous as vol
+
import requests
+import voluptuous as vol
from homeassistant.components.media_player import (
- MediaPlayerDevice, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA,
- SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_MUTE)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
from homeassistant.const import (
- CONF_USERNAME, CONF_PASSWORD, STATE_OFF, STATE_IDLE, STATE_PLAYING,
- STATE_PAUSED)
+ CONF_PASSWORD, CONF_USERNAME, STATE_IDLE, STATE_OFF, STATE_PAUSED,
+ STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-ICON = "mdi:radio"
-URL = "http://decibel.logitechmusic.com/jsonrpc.js"
+ICON = 'mdi:radio'
+URL = 'http://decibel.logitechmusic.com/jsonrpc.js'
SUPPORT_UE_SMART_RADIO = SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_TURN_ON | \
SUPPORT_TURN_OFF | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE
-PLAYBACK_DICT = {"play": STATE_PLAYING,
- "pause": STATE_PAUSED,
- "stop": STATE_IDLE}
+PLAYBACK_DICT = {
+ 'play': STATE_PLAYING,
+ 'pause': STATE_PAUSED,
+ 'stop': STATE_IDLE,
+}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
@@ -41,10 +44,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def send_request(payload, session):
"""Send request to radio."""
try:
- request = requests.post(URL,
- cookies={"sdi_squeezenetwork_session":
- session},
- json=payload, timeout=5)
+ request = requests.post(
+ URL, cookies={"sdi_squeezenetwork_session": session},
+ json=payload, timeout=5)
except requests.exceptions.Timeout:
_LOGGER.error("Timed out when sending request")
except requests.exceptions.ConnectionError:
@@ -58,9 +60,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
email = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
- session_request = requests.post("https://www.uesmartradio.com/user/login",
- data={"email": email, "password":
- password})
+ session_request = requests.post(
+ "https://www.uesmartradio.com/user/login",
+ data={"email": email, "password": password}, timeout=5)
session = session_request.cookies["sdi_squeezenetwork_session"]
player_request = send_request({"params": ["", ["serverstatus"]]}, session)
diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py
index 1572e2df89b..47eaf599929 100644
--- a/homeassistant/components/media_player/universal.py
+++ b/homeassistant/components/media_player/universal.py
@@ -4,12 +4,11 @@ Combination of multiple media players into one for a universal controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.universal/
"""
-import logging
from copy import copy
+import logging
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components.media_player import (
ATTR_APP_ID, ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST,
ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_ARTIST,
@@ -18,36 +17,35 @@ from homeassistant.components.media_player import (
ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, ATTR_MEDIA_SEASON,
ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_SHUFFLE,
ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK, ATTR_MEDIA_VOLUME_LEVEL,
- ATTR_MEDIA_VOLUME_MUTED, DOMAIN, MediaPlayerDevice, PLATFORM_SCHEMA,
- SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE,
- SUPPORT_CLEAR_PLAYLIST, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_STEP)
+ ATTR_MEDIA_VOLUME_MUTED, DOMAIN, PLATFORM_SCHEMA, SERVICE_CLEAR_PLAYLIST,
+ SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
+ SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, CONF_NAME,
- CONF_STATE_TEMPLATE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
- SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK,
- SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN,
- SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP,
- SERVICE_SHUFFLE_SET, STATE_IDLE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE,
- SERVICE_MEDIA_STOP)
+ CONF_STATE, CONF_STATE_TEMPLATE, SERVICE_MEDIA_NEXT_TRACK,
+ SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
+ SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP,
+ SERVICE_SHUFFLE_SET, SERVICE_TURN_OFF, SERVICE_TURN_ON,
+ SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET,
+ SERVICE_VOLUME_UP, STATE_IDLE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE)
+from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_call_from_config
+_LOGGER = logging.getLogger(__name__)
+
ATTR_ACTIVE_CHILD = 'active_child'
+ATTR_DATA = 'data'
CONF_ATTRS = 'attributes'
CONF_CHILDREN = 'children'
CONF_COMMANDS = 'commands'
-CONF_PLATFORM = 'platform'
CONF_SERVICE = 'service'
CONF_SERVICE_DATA = 'service_data'
-ATTR_DATA = 'data'
-CONF_STATE = 'state'
OFF_STATES = [STATE_IDLE, STATE_OFF, STATE_UNAVAILABLE]
-REQUIREMENTS = []
-_LOGGER = logging.getLogger(__name__)
ATTRS_SCHEMA = vol.Schema({cv.slug: cv.string})
CMD_SCHEMA = vol.Schema({cv.slug: cv.SERVICE_SCHEMA})
diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py
index 673be3074de..9564a8d3df0 100644
--- a/homeassistant/components/media_player/vizio.py
+++ b/homeassistant/components/media_player/vizio.py
@@ -9,6 +9,7 @@ import logging
import voluptuous as vol
+from homeassistant import util
from homeassistant.components.media_player import (
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
@@ -18,7 +19,6 @@ from homeassistant.const import (
CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON,
STATE_UNKNOWN)
from homeassistant.helpers import config_validation as cv
-from homeassistant import util
REQUIREMENTS = ['pyvizio==0.0.3']
diff --git a/homeassistant/components/media_player/vlc.py b/homeassistant/components/media_player/vlc.py
index 075a533e372..5cc4196d4e1 100644
--- a/homeassistant/components/media_player/vlc.py
+++ b/homeassistant/components/media_player/vlc.py
@@ -9,12 +9,11 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_VOLUME_MUTE,
- SUPPORT_VOLUME_SET, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA,
- MEDIA_TYPE_MUSIC)
-
-from homeassistant.const import (CONF_NAME, STATE_IDLE, STATE_PAUSED,
- STATE_PLAYING)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
+from homeassistant.const import (
+ CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
@@ -29,8 +28,8 @@ SUPPORT_VLC = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_STOP
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_ARGUMENTS, default=''): cv.string,
+ vol.Optional(CONF_NAME): cv.string,
})
diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py
index 00f5d25362f..743f19cb259 100644
--- a/homeassistant/components/media_player/volumio.py
+++ b/homeassistant/components/media_player/volumio.py
@@ -6,23 +6,24 @@ https://home-assistant.io/components/media_player.volumio/
Volumio rest API: https://volumio.github.io/docs/API/REST_API.html
"""
+import asyncio
from datetime import timedelta
import logging
import socket
-import asyncio
-import aiohttp
+import aiohttp
import voluptuous as vol
from homeassistant.components.media_player import (
- SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
- SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
- SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC,
- SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST)
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_CLEAR_PLAYLIST,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
from homeassistant.const import (
- STATE_PLAYING, STATE_PAUSED, STATE_IDLE, CONF_HOST, CONF_PORT, CONF_NAME)
-import homeassistant.helpers.config_validation as cv
+ CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED, STATE_PLAYING)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
_CONFIGURING = {}
diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py
index fd6b1c6d96e..d78619a8279 100644
--- a/homeassistant/components/media_player/webostv.py
+++ b/homeassistant/components/media_player/webostv.py
@@ -14,6 +14,7 @@ from typing import Dict # noqa: F401
import voluptuous as vol
+from homeassistant import util
from homeassistant.components.media_player import (
MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
@@ -24,9 +25,8 @@ from homeassistant.const import (
STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
-from homeassistant import util
-REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==3.2']
+REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==6.0']
_CONFIGURING = {} # type: Dict[str, str]
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_SOURCES = 'sources'
CONF_ON_ACTION = 'turn_on_action'
-DEFAULT_NAME = 'LG webOS Smart TV'
+DEFAULT_NAME = "LG webOS Smart TV"
LIVETV_APP_ID = 'com.webos.app.livetv'
WEBOSTV_CONFIG_FILE = 'webostv.conf'
diff --git a/homeassistant/components/media_player/xiaomi_tv.py b/homeassistant/components/media_player/xiaomi_tv.py
index ad66ae855bd..93c4bb72b17 100644
--- a/homeassistant/components/media_player/xiaomi_tv.py
+++ b/homeassistant/components/media_player/xiaomi_tv.py
@@ -4,14 +4,15 @@ Add support for the Xiaomi TVs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/xiaomi_tv/
"""
-
import logging
+
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
-from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
+
from homeassistant.components.media_player import (
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, MediaPlayerDevice, PLATFORM_SCHEMA,
- SUPPORT_VOLUME_STEP)
+ PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_STEP,
+ MediaPlayerDevice)
+from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
+import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymitv==1.4.0']
@@ -41,8 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
# Check if there's a valid TV at the IP address.
if not Discover().check_ip(host):
_LOGGER.error(
- "Could not find Xiaomi TV with specified IP: %s", host
- )
+ "Could not find Xiaomi TV with specified IP: %s", host)
else:
# Register TV with Home Assistant.
add_entities([XiaomiTV(host, name)])
diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py
index 2ffe58b02af..be61560d52b 100644
--- a/homeassistant/components/media_player/yamaha.py
+++ b/homeassistant/components/media_player/yamaha.py
@@ -12,9 +12,9 @@ import voluptuous as vol
from homeassistant.components.media_player import (
DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA,
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
- SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
- MediaPlayerDevice)
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE,
+ SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
+ SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_ON,
STATE_PLAYING)
@@ -43,7 +43,8 @@ ENABLE_OUTPUT_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
SERVICE_ENABLE_OUTPUT = 'yamaha_enable_output'
SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
- SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
+ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE | SUPPORT_PLAY \
+ | SUPPORT_SELECT_SOUND_MODE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -140,6 +141,8 @@ class YamahaDevice(MediaPlayerDevice):
self._volume = 0
self._pwstate = STATE_OFF
self._current_source = None
+ self._sound_mode = None
+ self._sound_mode_list = None
self._source_list = None
self._source_ignore = source_ignore or []
self._source_names = source_names or {}
@@ -181,6 +184,8 @@ class YamahaDevice(MediaPlayerDevice):
self._playback_support = self.receiver.get_playback_support()
self._is_playback_supported = self.receiver.is_playback_supported(
self._current_source)
+ self._sound_mode = self.receiver.surround_program
+ self._sound_mode_list = self.receiver.surround_programs()
def build_source_list(self):
"""Build the source list."""
@@ -222,6 +227,16 @@ class YamahaDevice(MediaPlayerDevice):
"""Return the current input source."""
return self._current_source
+ @property
+ def sound_mode(self):
+ """Return the current sound mode."""
+ return self._sound_mode
+
+ @property
+ def sound_mode_list(self):
+ """Return the current sound mode."""
+ return self._sound_mode_list
+
@property
def source_list(self):
"""List of available input sources."""
@@ -330,6 +345,10 @@ class YamahaDevice(MediaPlayerDevice):
"""Enable or disable an output port.."""
self.receiver.enable_output(port, enabled)
+ def select_sound_mode(self, sound_mode):
+ """Set Sound Mode for Receiver.."""
+ self.receiver.surround_program = sound_mode
+
@property
def media_artist(self):
"""Artist of current playing media."""
diff --git a/homeassistant/components/media_player/yamaha_musiccast.py b/homeassistant/components/media_player/yamaha_musiccast.py
index 135bf4d0aef..bf21a3f5028 100644
--- a/homeassistant/components/media_player/yamaha_musiccast.py
+++ b/homeassistant/components/media_player/yamaha_musiccast.py
@@ -1,28 +1,26 @@
-"""Example for configuration.yaml.
-
-media_player:
- - platform: yamaha_musiccast
- host: 192.168.xxx.xx
- port: 5005
-
"""
+Support for Yamaha MusicCast Receivers.
+For more details about this platform, please refer to the documentation at
+https://www.home-assistant.io/components/media_player.yamaha_musiccast/
+"""
import logging
+
import voluptuous as vol
+
+from homeassistant.components.media_player import (
+ MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+ SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ MediaPlayerDevice)
+from homeassistant.const import (
+ CONF_HOST, CONF_PORT, STATE_IDLE, STATE_ON, STATE_PAUSED, STATE_PLAYING,
+ STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-from homeassistant.const import (
- CONF_HOST, CONF_PORT,
- STATE_UNKNOWN, STATE_ON, STATE_PLAYING, STATE_PAUSED, STATE_IDLE
-)
-from homeassistant.components.media_player import (
- MediaPlayerDevice, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA,
- SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY,
- SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
- SUPPORT_SELECT_SOURCE, SUPPORT_STOP
-)
+REQUIREMENTS = ['pymusiccast==0.1.6']
+
_LOGGER = logging.getLogger(__name__)
SUPPORTED_FEATURES = (
@@ -36,8 +34,6 @@ SUPPORTED_FEATURES = (
KNOWN_HOSTS_KEY = 'data_yamaha_musiccast'
INTERVAL_SECONDS = 'interval_seconds'
-REQUIREMENTS = ['pymusiccast==0.1.6']
-
DEFAULT_PORT = 5005
DEFAULT_INTERVAL = 480
@@ -71,11 +67,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return
if [item for item in known_hosts if item[0] == ipaddr]:
- _LOGGER.warning("Host %s:%d already registered.", host, port)
+ _LOGGER.warning("Host %s:%d already registered", host, port)
return
if [item for item in known_hosts if item[1] == port]:
- _LOGGER.warning("Port %s:%d already registered.", host, port)
+ _LOGGER.warning("Port %s:%d already registered", host, port)
return
reg_host = (ipaddr, port)
@@ -91,11 +87,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if receiver:
for zone in receiver.zones:
_LOGGER.debug(
- "receiver: %s / Port: %d / Zone: %s",
- receiver, port, zone)
+ "Receiver: %s / Port: %d / Zone: %s", receiver, port, zone)
add_entities(
- [YamahaDevice(receiver, receiver.zones[zone])],
- True)
+ [YamahaDevice(receiver, receiver.zones[zone])], True)
else:
known_hosts.remove(reg_host)
diff --git a/homeassistant/components/media_player/ziggo_mediabox_xl.py b/homeassistant/components/media_player/ziggo_mediabox_xl.py
index 376b9e7c426..555042bee5c 100644
--- a/homeassistant/components/media_player/ziggo_mediabox_xl.py
+++ b/homeassistant/components/media_player/ziggo_mediabox_xl.py
@@ -10,10 +10,9 @@ import socket
import voluptuous as vol
from homeassistant.components.media_player import (
- PLATFORM_SCHEMA, MediaPlayerDevice,
- SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
- SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
- SUPPORT_PLAY, SUPPORT_PAUSE)
+ PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
@@ -169,6 +168,5 @@ class ZiggoMediaboxXLDevice(MediaPlayerDevice):
if digits is None:
return
- self.send_keys(['NUM_{}'.format(digit)
- for digit in str(digits)])
+ self.send_keys(['NUM_{}'.format(digit) for digit in str(digits)])
self._state = STATE_PLAYING
diff --git a/homeassistant/components/mqtt/.translations/ca.json b/homeassistant/components/mqtt/.translations/ca.json
new file mode 100644
index 00000000000..57e9a83d201
--- /dev/null
+++ b/homeassistant/components/mqtt/.translations/ca.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 de MQTT."
+ },
+ "error": {
+ "cannot_connect": "No es pot connectar amb el broker."
+ },
+ "step": {
+ "broker": {
+ "data": {
+ "broker": "Broker",
+ "password": "Contrasenya",
+ "port": "Port",
+ "username": "Nom d'usuari"
+ },
+ "description": "Introdu\u00efu la informaci\u00f3 de connexi\u00f3 del vostre broker MQTT.",
+ "title": "MQTT"
+ }
+ },
+ "title": "MQTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/.translations/fr.json b/homeassistant/components/mqtt/.translations/fr.json
new file mode 100644
index 00000000000..1870c598e3b
--- /dev/null
+++ b/homeassistant/components/mqtt/.translations/fr.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "Une seule configuration de MQTT est autoris\u00e9e."
+ },
+ "error": {
+ "cannot_connect": "Impossible de se connecter au broker."
+ },
+ "step": {
+ "broker": {
+ "data": {
+ "broker": "Broker",
+ "password": "Mot de passe",
+ "port": "Port",
+ "username": "Nom d'utilisateur"
+ },
+ "description": "Veuillez entrer les informations de connexion de votre broker MQTT.",
+ "title": "MQTT"
+ }
+ },
+ "title": "MQTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/.translations/ko.json b/homeassistant/components/mqtt/.translations/ko.json
new file mode 100644
index 00000000000..a38b00fd68d
--- /dev/null
+++ b/homeassistant/components/mqtt/.translations/ko.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "single_instance_allowed": "\ud558\ub098\uc758 MQTT \ube0c\ub85c\ucee4\ub9cc \uad6c\uc131\uc774 \uac00\ub2a5\ud569\ub2c8\ub2e4."
+ },
+ "error": {
+ "cannot_connect": "MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc74c"
+ },
+ "step": {
+ "broker": {
+ "data": {
+ "broker": "\ube0c\ub85c\ucee4",
+ "password": "\ube44\ubc00\ubc88\ud638",
+ "port": "\ud3ec\ud2b8",
+ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984"
+ },
+ "description": "MQTT \ube0c\ub85c\ucee4\uc640\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud558\uc138\uc694.",
+ "title": "MQTT"
+ }
+ },
+ "title": "MQTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py
index 19bacbc8d4c..6bb08d7e8e5 100644
--- a/homeassistant/components/mqtt/__init__.py
+++ b/homeassistant/components/mqtt/__init__.py
@@ -13,7 +13,6 @@ import os
import socket
import time
import ssl
-import re
import requests.certs
import attr
@@ -550,6 +549,7 @@ class MQTT:
This method must be run in the event loop and returns a coroutine.
"""
async with self._paho_lock:
+ _LOGGER.debug("Transmitting message on %s: %s", topic, payload)
await self.hass.async_add_job(
self._mqttc.publish, topic, payload, qos, retain)
@@ -726,23 +726,14 @@ def _raise_on_error(result_code: int) -> None:
def _match_topic(subscription: str, topic: str) -> bool:
"""Test if topic matches subscription."""
- reg_ex_parts = [] # type: List[str]
- suffix = ""
- if subscription.endswith('#'):
- subscription = subscription[:-2]
- suffix = "(.*)"
- sub_parts = subscription.split('/')
- for sub_part in sub_parts:
- if sub_part == "+":
- reg_ex_parts.append(r"([^\/]+)")
- else:
- reg_ex_parts.append(re.escape(sub_part))
-
- reg_ex = "^" + (r'\/'.join(reg_ex_parts)) + suffix + "$"
-
- reg = re.compile(reg_ex)
-
- return reg.match(topic) is not None
+ from paho.mqtt.matcher import MQTTMatcher
+ matcher = MQTTMatcher()
+ matcher[subscription] = True
+ try:
+ next(matcher.iter_match(topic))
+ return True
+ except StopIteration:
+ return False
class MqttAvailability(Entity):
diff --git a/homeassistant/components/mqtt/server.py b/homeassistant/components/mqtt/server.py
index 5fc365342ae..45529411ed5 100644
--- a/homeassistant/components/mqtt/server.py
+++ b/homeassistant/components/mqtt/server.py
@@ -13,7 +13,7 @@ import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['hbmqtt==0.9.2']
+REQUIREMENTS = ['hbmqtt==0.9.4']
DEPENDENCIES = ['http']
# None allows custom config to be created through generate_config
@@ -85,6 +85,10 @@ def generate_config(hass, passwd, password):
'allow-anonymous': password is None
},
'plugins': ['auth_anonymous'],
+ 'topic-check': {
+ 'enabled': True,
+ 'plugins': ['topic_taboo'],
+ },
}
if password:
diff --git a/homeassistant/components/nest/.translations/da.json b/homeassistant/components/nest/.translations/da.json
index 4410f83d2ca..5edf3a00af4 100644
--- a/homeassistant/components/nest/.translations/da.json
+++ b/homeassistant/components/nest/.translations/da.json
@@ -16,8 +16,10 @@
"link": {
"data": {
"code": "PIN-kode"
- }
+ },
+ "title": "Link Nest-konto"
}
- }
+ },
+ "title": "Nest"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/nest/.translations/fr.json b/homeassistant/components/nest/.translations/fr.json
index 734e82dbcd0..822ec6ae836 100644
--- a/homeassistant/components/nest/.translations/fr.json
+++ b/homeassistant/components/nest/.translations/fr.json
@@ -1,20 +1,30 @@
{
"config": {
"abort": {
- "already_setup": "Vous ne pouvez configurer qu'un seul compte Nest."
+ "already_setup": "Vous ne pouvez configurer qu'un seul compte Nest.",
+ "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.",
+ "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.",
+ "no_flows": "Vous devez configurer Nest avant de pouvoir vous authentifier avec celui-ci. [Veuillez lire les instructions] (https://www.home-assistant.io/components/nest/)."
},
"error": {
"internal_error": "Erreur interne lors de la validation du code",
- "invalid_code": "Code invalide"
+ "invalid_code": "Code invalide",
+ "timeout": "D\u00e9lai de la validation du code expir\u00e9",
+ "unknown": "Erreur inconnue lors de la validation du code"
},
"step": {
"init": {
+ "data": {
+ "flow_impl": "Fournisseur"
+ },
+ "description": "S\u00e9lectionnez via quel fournisseur d'authentification vous souhaitez vous authentifier avec Nest.",
"title": "Fournisseur d'authentification"
},
"link": {
"data": {
"code": "Code PIN"
},
+ "description": "Pour associer votre compte Nest, [autorisez votre compte] ( {url} ). \n\n Apr\u00e8s autorisation, copiez-collez le code PIN fourni ci-dessous.",
"title": "Lier un compte Nest"
}
},
diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py
index c25b57fbd62..59b0a64f6e9 100644
--- a/homeassistant/components/netatmo.py
+++ b/homeassistant/components/netatmo.py
@@ -101,10 +101,10 @@ class CameraData:
return self.module_names
def get_camera_type(self, camera=None, home=None, cid=None):
- """Return all module available on the API as a list."""
- for camera_name in self.camera_names:
- self.camera_type = self.camera_data.cameraType(camera_name)
- return self.camera_type
+ """Return camera type for a camera, cid has preference over camera."""
+ self.camera_type = self.camera_data.cameraType(camera=camera,
+ home=home, cid=cid)
+ return self.camera_type
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
diff --git a/homeassistant/components/notify/discord.py b/homeassistant/components/notify/discord.py
index dca47a46dbf..0cf4bced360 100644
--- a/homeassistant/components/notify/discord.py
+++ b/homeassistant/components/notify/discord.py
@@ -11,7 +11,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
- PLATFORM_SCHEMA, BaseNotificationService, ATTR_TARGET)
+ PLATFORM_SCHEMA, BaseNotificationService, ATTR_TARGET, ATTR_DATA)
from homeassistant.const import CONF_TOKEN
_LOGGER = logging.getLogger(__name__)
@@ -22,6 +22,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): cv.string
})
+ATTR_IMAGES = 'images'
+
def get_service(hass, config, discovery_info=None):
"""Get the Discord notification service."""
@@ -53,9 +55,15 @@ class DiscordNotificationService(BaseNotificationService):
def on_ready():
"""Send the messages when the bot is ready."""
try:
+ data = kwargs.get(ATTR_DATA)
+ if data:
+ images = data.get(ATTR_IMAGES)
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
yield from discord_bot.send_message(channel, message)
+ if images:
+ for anum, f_name in enumerate(images):
+ yield from discord_bot.send_file(channel, f_name)
except (discord.errors.HTTPException,
discord.errors.NotFound) as error:
_LOGGER.warning("Communication error: %s", error)
diff --git a/homeassistant/components/openuv/.translations/ar.json b/homeassistant/components/openuv/.translations/ar.json
new file mode 100644
index 00000000000..288fae919dc
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/ar.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/ca.json b/homeassistant/components/openuv/.translations/ca.json
new file mode 100644
index 00000000000..4a6cf526921
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/ca.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Les coordenades ja estan registrades",
+ "invalid_api_key": "Contrasenya API no v\u00e0lida"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "Contrasenya API d'OpenUV",
+ "elevation": "Elevaci\u00f3",
+ "latitude": "Latitud",
+ "longitude": "Longitud"
+ },
+ "title": "Introdu\u00efu la vostra informaci\u00f3"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/da.json b/homeassistant/components/openuv/.translations/da.json
new file mode 100644
index 00000000000..5cda5c6e663
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/da.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API N\u00f8gle",
+ "latitude": "Breddegrad",
+ "longitude": "L\u00e6ngdegrad"
+ },
+ "title": "Udfyld dine oplysninger"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/de.json b/homeassistant/components/openuv/.translations/de.json
new file mode 100644
index 00000000000..1f81ac30f53
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/de.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Koordinaten existieren bereits",
+ "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API-Schl\u00fcssel",
+ "elevation": "H\u00f6he",
+ "latitude": "Breitengrad",
+ "longitude": "L\u00e4ngengrad"
+ }
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/en.json b/homeassistant/components/openuv/.translations/en.json
new file mode 100644
index 00000000000..df0232d01fc
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/en.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Coordinates already registered",
+ "invalid_api_key": "Invalid API key"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API Key",
+ "elevation": "Elevation",
+ "latitude": "Latitude",
+ "longitude": "Longitude"
+ },
+ "title": "Fill in your information"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/es-419.json b/homeassistant/components/openuv/.translations/es-419.json
new file mode 100644
index 00000000000..6b391c20a0a
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/es-419.json
@@ -0,0 +1,17 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Coordenadas ya registradas",
+ "invalid_api_key": "Clave de API inv\u00e1lida"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "elevation": "Elevaci\u00f3n",
+ "latitude": "Latitud",
+ "longitude": "Longitud"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/fa.json b/homeassistant/components/openuv/.translations/fa.json
new file mode 100644
index 00000000000..288fae919dc
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/fa.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/fr.json b/homeassistant/components/openuv/.translations/fr.json
new file mode 100644
index 00000000000..2f83fa30085
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/fr.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Coordonn\u00e9es d\u00e9j\u00e0 enregistr\u00e9es",
+ "invalid_api_key": "Cl\u00e9 d'API invalide"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "Cl\u00e9 d'API OpenUV",
+ "elevation": "Altitude",
+ "latitude": "Latitude",
+ "longitude": "Longitude"
+ },
+ "title": "Veuillez saisir vos informations"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/hu.json b/homeassistant/components/openuv/.translations/hu.json
new file mode 100644
index 00000000000..fd30f83c5f8
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/hu.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "A koordin\u00e1t\u00e1k m\u00e1r regisztr\u00e1lva vannak",
+ "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API kulcs",
+ "elevation": "Magass\u00e1g",
+ "latitude": "Sz\u00e9less\u00e9g",
+ "longitude": "Hossz\u00fas\u00e1g"
+ },
+ "title": "T\u00f6ltsd ki az adataid"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/it.json b/homeassistant/components/openuv/.translations/it.json
new file mode 100644
index 00000000000..a18d36693d5
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/it.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "error": {
+ "invalid_api_key": "Chiave API non valida"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Latitudine",
+ "longitude": "Logitudine"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/ko.json b/homeassistant/components/openuv/.translations/ko.json
new file mode 100644
index 00000000000..bb054f0b3a6
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/ko.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "invalid_api_key": "\uc798\ubabb\ub41c API \ud0a4"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API \ud0a4",
+ "elevation": "\uace0\ub3c4",
+ "latitude": "\uc704\ub3c4",
+ "longitude": "\uacbd\ub3c4"
+ },
+ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/nl.json b/homeassistant/components/openuv/.translations/nl.json
new file mode 100644
index 00000000000..e2b264182d0
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/nl.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Co\u00f6rdinaten al geregistreerd",
+ "invalid_api_key": "Ongeldige API-sleutel"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API-Sleutel",
+ "elevation": "Hoogte",
+ "latitude": "Breedtegraad",
+ "longitude": "Lengtegraad"
+ },
+ "title": "Vul uw gegevens in"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/no.json b/homeassistant/components/openuv/.translations/no.json
new file mode 100644
index 00000000000..2ffd5e7fb41
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/no.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Koordinatene er allerede registrert",
+ "invalid_api_key": "Ugyldig API-n\u00f8kkel"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API-n\u00f8kkel",
+ "elevation": "Elevasjon",
+ "latitude": "Breddegrad",
+ "longitude": "Lengdegrad"
+ },
+ "title": "Fyll ut informasjonen din"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/pl.json b/homeassistant/components/openuv/.translations/pl.json
new file mode 100644
index 00000000000..f6c52ffd04e
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/pl.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane",
+ "invalid_api_key": "Nieprawid\u0142owy klucz API"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "Klucz API OpenUV",
+ "elevation": "Wysoko\u015b\u0107",
+ "latitude": "Szeroko\u015b\u0107 geograficzna",
+ "longitude": "D\u0142ugo\u015b\u0107 geograficzna"
+ },
+ "title": "Wpisz swoje informacje"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/ru.json b/homeassistant/components/openuv/.translations/ru.json
new file mode 100644
index 00000000000..bd7fc3f8191
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/ru.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b",
+ "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "\u041a\u043b\u044e\u0447 API OpenUV",
+ "elevation": "\u0412\u044b\u0441\u043e\u0442\u0430",
+ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
+ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430"
+ },
+ "title": "\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u0432\u043e\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/sl.json b/homeassistant/components/openuv/.translations/sl.json
new file mode 100644
index 00000000000..6d8c537d6aa
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/sl.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Koordinate \u017ee registrirane",
+ "invalid_api_key": "Neveljaven API klju\u010d"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API Klju\u010d",
+ "elevation": "Nadmorska vi\u0161ina",
+ "latitude": "Zemljepisna \u0161irina",
+ "longitude": "Zemljepisna dol\u017eina"
+ },
+ "title": "Izpolnite svoje podatke"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/sv.json b/homeassistant/components/openuv/.translations/sv.json
new file mode 100644
index 00000000000..d9de0f7c0a6
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/sv.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "Koordinater \u00e4r redan registrerade",
+ "invalid_api_key": "Ogiltigt API-l\u00f6senord"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API-nyckel",
+ "elevation": "H\u00f6jd",
+ "latitude": "Latitud",
+ "longitude": "Longitud"
+ },
+ "title": "Fyll i dina uppgifter"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/zh-Hans.json b/homeassistant/components/openuv/.translations/zh-Hans.json
new file mode 100644
index 00000000000..d8f46d6afe4
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/zh-Hans.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "\u5750\u6807\u5df2\u7ecf\u6ce8\u518c",
+ "invalid_api_key": "\u65e0\u6548\u7684 API \u5bc6\u94a5"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API \u5bc6\u94a5",
+ "elevation": "\u6d77\u62d4",
+ "latitude": "\u7eac\u5ea6",
+ "longitude": "\u7ecf\u5ea6"
+ },
+ "title": "\u586b\u5199\u60a8\u7684\u4fe1\u606f"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv/.translations/zh-Hant.json b/homeassistant/components/openuv/.translations/zh-Hant.json
new file mode 100644
index 00000000000..2310af22fa2
--- /dev/null
+++ b/homeassistant/components/openuv/.translations/zh-Hant.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "identifier_exists": "\u8a72\u5ea7\u6a19\u5df2\u8a3b\u518a",
+ "invalid_api_key": "API \u5bc6\u78bc\u7121\u6548"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "api_key": "OpenUV API \u5bc6\u9470",
+ "elevation": "\u6d77\u62d4",
+ "latitude": "\u7def\u5ea6",
+ "longitude": "\u7d93\u5ea6"
+ },
+ "title": "\u586b\u5beb\u8cc7\u8a0a"
+ }
+ },
+ "title": "OpenUV"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/openuv.py b/homeassistant/components/openuv/__init__.py
similarity index 57%
rename from homeassistant/components/openuv.py
rename to homeassistant/components/openuv/__init__.py
index d696f0e5100..bfd90b4a574 100644
--- a/homeassistant/components/openuv.py
+++ b/homeassistant/components/openuv/__init__.py
@@ -1,5 +1,5 @@
"""
-Support for data from openuv.io.
+Support for UV data from openuv.io.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/openuv/
@@ -9,21 +9,24 @@ from datetime import timedelta
import voluptuous as vol
+from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_API_KEY, CONF_BINARY_SENSORS, CONF_ELEVATION,
CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS,
CONF_SCAN_INTERVAL, CONF_SENSORS)
-from homeassistant.helpers import (
- aiohttp_client, config_validation as cv, discovery)
+from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['pyopenuv==1.0.1']
+from .config_flow import configured_instances
+from .const import DOMAIN
+
+REQUIREMENTS = ['pyopenuv==1.0.4']
_LOGGER = logging.getLogger(__name__)
-DOMAIN = 'openuv'
-
+DATA_OPENUV_CLIENT = 'data_client'
+DATA_OPENUV_LISTENER = 'data_listener'
DATA_PROTECTION_WINDOW = 'protection_window'
DATA_UV = 'uv'
@@ -82,39 +85,77 @@ SENSOR_SCHEMA = vol.Schema({
})
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_API_KEY): cv.string,
- vol.Optional(CONF_ELEVATION): float,
- vol.Optional(CONF_LATITUDE): cv.latitude,
- vol.Optional(CONF_LONGITUDE): cv.longitude,
- vol.Optional(CONF_BINARY_SENSORS, default={}): BINARY_SENSOR_SCHEMA,
- vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
- cv.time_period,
- })
+ DOMAIN:
+ vol.Schema({
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Optional(CONF_ELEVATION): float,
+ vol.Optional(CONF_LATITUDE): cv.latitude,
+ vol.Optional(CONF_LONGITUDE): cv.longitude,
+ vol.Optional(CONF_BINARY_SENSORS, default={}):
+ BINARY_SENSOR_SCHEMA,
+ vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
+ cv.time_period,
+ })
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the OpenUV component."""
- from pyopenuv import Client
- from pyopenuv.errors import OpenUvError
+ hass.data[DOMAIN] = {}
+ hass.data[DOMAIN][DATA_OPENUV_CLIENT] = {}
+ hass.data[DOMAIN][DATA_OPENUV_LISTENER] = {}
+
+ if DOMAIN not in config:
+ return True
conf = config[DOMAIN]
- api_key = conf[CONF_API_KEY]
- elevation = conf.get(CONF_ELEVATION, hass.config.elevation)
latitude = conf.get(CONF_LATITUDE, hass.config.latitude)
longitude = conf.get(CONF_LONGITUDE, hass.config.longitude)
+ elevation = conf.get(CONF_ELEVATION, hass.config.elevation)
+
+ identifier = '{0}, {1}'.format(latitude, longitude)
+
+ if identifier not in configured_instances(hass):
+ hass.async_add_job(
+ hass.config_entries.flow.async_init(
+ DOMAIN,
+ context={'source': SOURCE_IMPORT},
+ data={
+ CONF_API_KEY: conf[CONF_API_KEY],
+ CONF_LATITUDE: latitude,
+ CONF_LONGITUDE: longitude,
+ CONF_ELEVATION: elevation,
+ CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS],
+ CONF_SENSORS: conf[CONF_SENSORS],
+ }))
+
+ hass.data[DOMAIN][CONF_SCAN_INTERVAL] = conf[CONF_SCAN_INTERVAL]
+
+ return True
+
+
+async def async_setup_entry(hass, config_entry):
+ """Set up OpenUV as config entry."""
+ from pyopenuv import Client
+ from pyopenuv.errors import OpenUvError
try:
websession = aiohttp_client.async_get_clientsession(hass)
openuv = OpenUV(
Client(
- api_key, latitude, longitude, websession, altitude=elevation),
- conf[CONF_BINARY_SENSORS][CONF_MONITORED_CONDITIONS] +
- conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS])
+ config_entry.data[CONF_API_KEY],
+ config_entry.data.get(CONF_LATITUDE, hass.config.latitude),
+ config_entry.data.get(CONF_LONGITUDE, hass.config.longitude),
+ websession,
+ altitude=config_entry.data.get(
+ CONF_ELEVATION, hass.config.elevation)),
+ config_entry.data.get(CONF_BINARY_SENSORS, {}).get(
+ CONF_MONITORED_CONDITIONS, list(BINARY_SENSORS)),
+ config_entry.data.get(CONF_SENSORS, {}).get(
+ CONF_MONITORED_CONDITIONS, list(SENSORS)))
await openuv.async_update()
- hass.data[DOMAIN] = openuv
+ hass.data[DOMAIN][DATA_OPENUV_CLIENT][config_entry.entry_id] = openuv
except OpenUvError as err:
_LOGGER.error('An error occurred: %s', str(err))
hass.components.persistent_notification.create(
@@ -125,13 +166,9 @@ async def async_setup(hass, config):
notification_id=NOTIFICATION_ID)
return False
- for component, schema in [
- ('binary_sensor', conf[CONF_BINARY_SENSORS]),
- ('sensor', conf[CONF_SENSORS]),
- ]:
- hass.async_create_task(
- discovery.async_load_platform(
- hass, component, DOMAIN, schema, config))
+ for component in ('binary_sensor', 'sensor'):
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ config_entry, component))
async def refresh_sensors(event_time):
"""Refresh OpenUV data."""
@@ -139,7 +176,25 @@ async def async_setup(hass, config):
await openuv.async_update()
async_dispatcher_send(hass, TOPIC_UPDATE)
- async_track_time_interval(hass, refresh_sensors, conf[CONF_SCAN_INTERVAL])
+ hass.data[DOMAIN][DATA_OPENUV_LISTENER][
+ config_entry.entry_id] = async_track_time_interval(
+ hass, refresh_sensors,
+ hass.data[DOMAIN][CONF_SCAN_INTERVAL])
+
+ return True
+
+
+async def async_unload_entry(hass, config_entry):
+ """Unload an OpenUV config entry."""
+ for component in ('binary_sensor', 'sensor'):
+ await hass.config_entries.async_forward_entry_unload(
+ config_entry, component)
+
+ hass.data[DOMAIN][DATA_OPENUV_CLIENT].pop(config_entry.entry_id)
+
+ remove_listener = hass.data[DOMAIN][DATA_OPENUV_LISTENER].pop(
+ config_entry.entry_id)
+ remove_listener()
return True
@@ -147,19 +202,20 @@ async def async_setup(hass, config):
class OpenUV:
"""Define a generic OpenUV object."""
- def __init__(self, client, monitored_conditions):
+ def __init__(self, client, binary_sensor_conditions, sensor_conditions):
"""Initialize."""
- self._monitored_conditions = monitored_conditions
+ self.binary_sensor_conditions = binary_sensor_conditions
self.client = client
self.data = {}
+ self.sensor_conditions = sensor_conditions
async def async_update(self):
"""Update sensor/binary sensor data."""
- if TYPE_PROTECTION_WINDOW in self._monitored_conditions:
+ if TYPE_PROTECTION_WINDOW in self.binary_sensor_conditions:
data = await self.client.uv_protection_window()
self.data[DATA_PROTECTION_WINDOW] = data
- if any(c in self._monitored_conditions for c in SENSORS):
+ if any(c in self.sensor_conditions for c in SENSORS):
data = await self.client.uv_index()
self.data[DATA_UV] = data
diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py
new file mode 100644
index 00000000000..55ee566268e
--- /dev/null
+++ b/homeassistant/components/openuv/config_flow.py
@@ -0,0 +1,73 @@
+"""Config flow to configure the OpenUV component."""
+
+from collections import OrderedDict
+
+import voluptuous as vol
+
+from homeassistant import config_entries, data_entry_flow
+from homeassistant.core import callback
+from homeassistant.const import (
+ CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE)
+from homeassistant.helpers import aiohttp_client, config_validation as cv
+
+from .const import DOMAIN
+
+
+@callback
+def configured_instances(hass):
+ """Return a set of configured OpenUV instances."""
+ return set(
+ '{0}, {1}'.format(
+ entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE])
+ for entry in hass.config_entries.async_entries(DOMAIN))
+
+
+@config_entries.HANDLERS.register(DOMAIN)
+class OpenUvFlowHandler(data_entry_flow.FlowHandler):
+ """Handle an OpenUV config flow."""
+
+ VERSION = 1
+
+ def __init__(self):
+ """Initialize the config flow."""
+ pass
+
+ async def async_step_import(self, import_config):
+ """Import a config entry from configuration.yaml."""
+ return await self.async_step_user(import_config)
+
+ async def async_step_user(self, user_input=None):
+ """Handle the start of the config flow."""
+ from pyopenuv.util import validate_api_key
+
+ errors = {}
+
+ if user_input is not None:
+ identifier = '{0}, {1}'.format(
+ user_input.get(CONF_LATITUDE, self.hass.config.latitude),
+ user_input.get(CONF_LONGITUDE, self.hass.config.longitude))
+
+ if identifier in configured_instances(self.hass):
+ errors['base'] = 'identifier_exists'
+ else:
+ websession = aiohttp_client.async_get_clientsession(self.hass)
+ api_key_validation = await validate_api_key(
+ user_input[CONF_API_KEY], websession)
+ if api_key_validation:
+ return self.async_create_entry(
+ title=identifier,
+ data=user_input,
+ )
+ errors['base'] = 'invalid_api_key'
+
+ data_schema = OrderedDict()
+ data_schema[vol.Required(CONF_API_KEY)] = str
+ data_schema[vol.Optional(CONF_LATITUDE)] = cv.latitude
+ data_schema[vol.Optional(CONF_LONGITUDE)] = cv.longitude
+ data_schema[vol.Optional(CONF_ELEVATION)] = vol.Coerce(float)
+
+ return self.async_show_form(
+ step_id='user',
+ data_schema=vol.Schema(data_schema),
+ errors=errors,
+ )
diff --git a/homeassistant/components/openuv/const.py b/homeassistant/components/openuv/const.py
new file mode 100644
index 00000000000..1aa3d2abcaa
--- /dev/null
+++ b/homeassistant/components/openuv/const.py
@@ -0,0 +1,3 @@
+"""Define constants for the OpenUV component."""
+
+DOMAIN = 'openuv'
diff --git a/homeassistant/components/openuv/strings.json b/homeassistant/components/openuv/strings.json
new file mode 100644
index 00000000000..9c5af45619e
--- /dev/null
+++ b/homeassistant/components/openuv/strings.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "title": "OpenUV",
+ "step": {
+ "user": {
+ "title": "Fill in your information",
+ "data": {
+ "api_key": "OpenUV API Key",
+ "elevation": "Elevation",
+ "latitude": "Latitude",
+ "longitude": "Longitude"
+ }
+ }
+ },
+ "error": {
+ "identifier_exists": "Coordinates already registered",
+ "invalid_api_key": "Invalid API key"
+ }
+ }
+}
diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py
index f3d8e269a42..47d6e181c8f 100644
--- a/homeassistant/components/recorder/__init__.py
+++ b/homeassistant/components/recorder/__init__.py
@@ -35,7 +35,7 @@ from . import migration, purge
from .const import DATA_INSTANCE
from .util import session_scope
-REQUIREMENTS = ['sqlalchemy==1.2.10']
+REQUIREMENTS = ['sqlalchemy==1.2.11']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py
index 207f2f53a7f..7b257e223db 100644
--- a/homeassistant/components/recorder/migration.py
+++ b/homeassistant/components/recorder/migration.py
@@ -169,7 +169,7 @@ def _add_columns(engine, table_name, columns_def):
if 'duplicate' not in str(err).lower():
raise
- _LOGGER.warning('Column %s already exists on %s, continueing',
+ _LOGGER.warning('Column %s already exists on %s, continuing',
column_def.split(' ')[1], table_name)
diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py
index 60dbb209039..b5bc97b7ffa 100644
--- a/homeassistant/components/rfxtrx.py
+++ b/homeassistant/components/rfxtrx.py
@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
-REQUIREMENTS = ['pyRFXtrx==0.22.1']
+REQUIREMENTS = ['pyRFXtrx==0.23']
DOMAIN = 'rfxtrx'
diff --git a/homeassistant/components/scene/deconz.py b/homeassistant/components/scene/deconz.py
index dde78dadc49..b8fca6d8630 100644
--- a/homeassistant/components/scene/deconz.py
+++ b/homeassistant/components/scene/deconz.py
@@ -5,8 +5,10 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.deconz/
"""
from homeassistant.components.deconz import (
- DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
+ DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
from homeassistant.components.scene import Scene
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['deconz']
@@ -19,12 +21,17 @@ async def async_setup_platform(hass, config, async_add_entities,
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up scenes for deCONZ component."""
- scenes = hass.data[DATA_DECONZ].scenes
- entities = []
+ @callback
+ def async_add_scene(scenes):
+ """Add scene from deCONZ."""
+ entities = []
+ for scene in scenes:
+ entities.append(DeconzScene(scene))
+ async_add_entities(entities)
+ hass.data[DATA_DECONZ_UNSUB].append(
+ async_dispatcher_connect(hass, 'deconz_new_scene', async_add_scene))
- for scene in scenes.values():
- entities.append(DeconzScene(scene))
- async_add_entities(entities)
+ async_add_scene(hass.data[DATA_DECONZ].scenes.values())
class DeconzScene(Scene):
@@ -38,6 +45,10 @@ class DeconzScene(Scene):
"""Subscribe to sensors events."""
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._scene.deconz_id
+ async def async_will_remove_from_hass(self) -> None:
+ """Disconnect scene object when removed."""
+ self._scene = None
+
async def async_activate(self):
"""Activate the scene."""
await self._scene.async_set_state({})
diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py
index a45f8ba8930..247ac07283e 100644
--- a/homeassistant/components/script.py
+++ b/homeassistant/components/script.py
@@ -63,11 +63,11 @@ def is_on(hass, entity_id):
@bind_hass
-def turn_on(hass, entity_id, variables=None):
+def turn_on(hass, entity_id, variables=None, context=None):
"""Turn script on."""
_, object_id = split_entity_id(entity_id)
- hass.services.call(DOMAIN, object_id, variables)
+ hass.services.call(DOMAIN, object_id, variables, context=context)
@bind_hass
@@ -97,45 +97,41 @@ def async_reload(hass):
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Load the scripts from the configuration."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_SCRIPTS)
- yield from _async_process_config(hass, config, component)
+ await _async_process_config(hass, config, component)
- @asyncio.coroutine
- def reload_service(service):
+ async def reload_service(service):
"""Call a service to reload scripts."""
- conf = yield from component.async_prepare_reload()
+ conf = await component.async_prepare_reload()
if conf is None:
return
- yield from _async_process_config(hass, conf, component)
+ await _async_process_config(hass, conf, component)
- @asyncio.coroutine
- def turn_on_service(service):
+ async def turn_on_service(service):
"""Call a service to turn script on."""
# We could turn on script directly here, but we only want to offer
# one way to do it. Otherwise no easy way to detect invocations.
var = service.data.get(ATTR_VARIABLES)
for script in component.async_extract_from_service(service):
- yield from hass.services.async_call(DOMAIN, script.object_id, var)
+ await hass.services.async_call(DOMAIN, script.object_id, var,
+ context=service.context)
- @asyncio.coroutine
- def turn_off_service(service):
+ async def turn_off_service(service):
"""Cancel a script."""
# Stopping a script is ok to be done in parallel
- yield from asyncio.wait(
+ await asyncio.wait(
[script.async_turn_off() for script
in component.async_extract_from_service(service)], loop=hass.loop)
- @asyncio.coroutine
- def toggle_service(service):
+ async def toggle_service(service):
"""Toggle a script."""
for script in component.async_extract_from_service(service):
- yield from script.async_toggle()
+ await script.async_toggle(context=service.context)
hass.services.async_register(DOMAIN, SERVICE_RELOAD, reload_service,
schema=RELOAD_SERVICE_SCHEMA)
@@ -149,18 +145,17 @@ def async_setup(hass, config):
return True
-@asyncio.coroutine
-def _async_process_config(hass, config, component):
- """Process group configuration."""
- @asyncio.coroutine
- def service_handler(service):
+async def _async_process_config(hass, config, component):
+ """Process script configuration."""
+ async def service_handler(service):
"""Execute a service call to script.